Skip to content

Commit fdd2a9c

Browse files
committed
[math] Add prescaler + counter algorithm
1 parent 534be22 commit fdd2a9c

17 files changed

+693
-70
lines changed

src/modm/math/algorithm/module.lb

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
def init(module):
1515
module.name = ":math:algorithm"
16-
module.description = "Math Algorithms"
16+
module.description = FileReader("module.md")
1717

1818
def prepare(module, options):
1919
module.depends(":math:units")

src/modm/math/algorithm/module.md

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Algorithms
2+
3+
A collection of useful and lightweight algorithms.
4+
5+
6+
## Convenience Iterators
7+
8+
Inspired by Python's built-in `range` and `enumerate` functions, you can use
9+
`modm::range` and `modm::enumerate` in for loops:
10+
11+
```cpp
12+
// Iterates over 0 .. 9
13+
for (auto i : modm::range(10)) {
14+
MODM_LOG_INFO << i << modm::endl;
15+
}
16+
// Iterates over 10 .. 19
17+
for (auto i : modm::range(10, 20)) {
18+
MODM_LOG_INFO << i << modm::endl;
19+
}
20+
// Iterates over 20, 22, 24, 26, 28
21+
for (auto i : modm::range(20, 30, 2)) {
22+
MODM_LOG_INFO << i << modm::endl;
23+
}
24+
25+
// Iterates over 0 .. N-1, where N = size of iterable
26+
for (auto [i, item] : modm::enumerate(iterable)) {
27+
MODM_LOG_INFO << i << item << modm::endl;
28+
}
29+
```
30+
31+
32+
## Prescaler Calculators
33+
34+
Peripheral output frequencies are usually generated by dividing an input clock
35+
with a prescaler in hardware. Finding the closest prescaler value for a desired
36+
output frequency can be unintuitive, therefore, these classes provide a simple
37+
interface for a constexpr calculator.
38+
39+
All calculators return a `Result` struct containing desired, input, and output
40+
frequencies, the relative error of the output vs desired frequency, and the
41+
prescaler and its index. The prescaler index is typically the value to write
42+
to the register directly:
43+
44+
```cpp
45+
// 16-bit linear prescaler [1, 2^16] mapped as [0, 0xffff].
46+
constexpr auto result = Prescaler::from_linear(10_MHz, 1_kHz, 1, 1ul << 16);
47+
static_assert(result.input_frequency == 10_MHz);
48+
static_assert(result.desired_frequency == 1_kHz);
49+
// Calculator finds an exact match without error
50+
static_assert(result.frequency == 1_kHz);
51+
static_assert(result.error == 0);
52+
// with prescaler 1e4 = 1e7 / 1e3.
53+
static_assert(result.prescaler == 10'000);
54+
static_assert(result.index == 9'999);
55+
PERIPHERAL->PRESCALER = result.index;
56+
```
57+
58+
The index is particularly useful for non-contiguous prescalers, the most common
59+
being power-of-two values:
60+
61+
```cpp
62+
// Power-of-two prescaler with 8 values between [16, 4096].
63+
constexpr auto result = Prescaler::from_power(32_kHz, 100_Hz, 1ul << 4, 1ul << 12);
64+
// Calculator cannot find an exact match! Closest has -25% error!
65+
static_assert(result.frequency == 125_Hz);
66+
static_assert(result.error == -0.25);
67+
// Ideal Prescaler is 320, clostest is 256
68+
static_assert(result.prescaler == 256);
69+
// Index is 256 = 1ul << (4 + 4)
70+
static_assert(result.index == 4);
71+
```
72+
73+
Non-contiguous prescalers can also be created with a modifier function:
74+
75+
```cpp
76+
// Only even prescalers from [2, 1024]
77+
constexpr auto result = Prescaler::from_function(
78+
110_MHz, 3.5_MHz, 1, 512, [](uint32_t i){ return i*2; });
79+
// Ideal prescaler is 31.4, closest is 32 with ~2% error.
80+
static_assert(result.frequency == 3.4375_MHz);
81+
static_assert(result.error == 0.02);
82+
static_assert(result.prescaler == 32);
83+
static_assert(result.index == 15); // 32/2 - 1
84+
```
85+
86+
For all other cases, prescalers can be passed as an initializer list or as any
87+
forward range. Note that the prescaler values *must* be sorted, otherwise
88+
the calculator will compute the wrong prescaler values!
89+
90+
```cpp
91+
constexpr auto result = Prescaler::from_list(1_MHz, 1_kHz, {2,4,16,256,1024});
92+
constexpr auto result = Prescaler::from_range(2_kHz, 1_kHz, std::array{1,2,3});
93+
```
94+
95+
A special case is made of two chained prescalers that are both linear
96+
powers-of-two. These are often called "fractional prescalers" and act as a
97+
single binary-scaled linear prescaler and can thus be modeled as such:
98+
99+
```cpp
100+
// A fractional 12.4-bit prescaler can be modeled as a single 16-bit prescaler.
101+
constexpr auto result = Prescaler::from_linear(SystemClock::Usart1, 115200, 16, 1ul << 16);
102+
// The resulting prescaler can be written directly to the register.
103+
USART1->BRR = result.prescaler;
104+
```
105+
106+
107+
### Prescalers with Counters
108+
109+
However, often chained prescalers cannot be converted to a linear prescaler, for
110+
example, a timer with a set of power-of-two prescalers and a 16 or 32-bit
111+
counter. These must be computed with a different class:
112+
113+
```cpp
114+
// A prescaler with power-of-two values [4, 256] going into a 12-bit down counter.
115+
constexpr auto result = PrescalerCounter::from_power(32_kHz, 1_Hz, 1ul << 12, 4, 256);
116+
// Calculator finds an exact match without error
117+
static_assert(result.frequency == 1_Hz);
118+
static_assert(result.error == 0);
119+
// with prescaler 8 and counter 4'000.
120+
static_assert(result.prescaler == 8);
121+
static_assert(result.counter == 4'000);
122+
static_assert(result.index == 1);
123+
```
124+
125+
The calculator only picks the first prescaler with the lowest error, however,
126+
in this example, there can be multiple exact solutions:
127+
128+
32000 = 8 × 4000 = 16 × 2000 = ... = 128 × 250 = 256 × 125
129+
130+
If the prescaler and counter is used to generate a waveform like PWM, then it is
131+
beneficial to pick the combination with the largest counter value. However, if
132+
the use case is to preserve power, then a slow running counter requires the
133+
highest prescaler. Therefore the order of prescalers can be reversed:
134+
135+
```cpp
136+
constexpr auto result = PrescalerCounter::from_power(32_kHz, 1_Hz, 1ul << 12, 256, 4);
137+
static_assert(result.prescaler == 256);
138+
static_assert(result.counter == 125);
139+
// Index is not the same!
140+
static_assert(result.index == 0);
141+
```
142+
143+
The same applies to the `PrescalerCounter::from_linear()` and
144+
`PrescalerCounter::from_function()` calculators, while the order for lists and
145+
forward ranges can be entirely arbitrary:
146+
147+
```cpp
148+
constexpr auto result = PrescalerCounter::from_list(32_kHz, 1_Hz, 1ul << 12, {128,16,256,4});
149+
static_assert(result.prescaler == 128);
150+
static_assert(result.counter == 250);
151+
// Index is relative to the list order now!
152+
static_assert(result.index == 0);
153+
```
154+
155+
!!! tip "Time Durations"
156+
While the calculator is designed for frequencies, time durations can also be
157+
computed by transforming the input as `frequency = 1.0 / duration` and then
158+
transforming the output back as `duration = 1.0 / frequency`.
159+
160+
!!! success "Floating-Point Frequencies"
161+
You can define the type used for frequency representation by using the
162+
`GenericPrescaler<double>` and `GenericPrescalerCounter<double>` classes.
163+
164+
165+
### Tolerance of Prescaler Error
166+
167+
Each `Result` has a signed(!), relative error attached, which can be used to
168+
assert on the quality of the calculation. Note that using `static_assert` on the
169+
error directly will only print the error values:
170+
171+
```cpp
172+
static_assert(std::abs(result.error) < 5_pct);
173+
```
174+
```
175+
error: static assertion failed
176+
| static_assert(std::abs(result.error) < tolerance);
177+
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
178+
note: the comparison reduces to '(0.10 < 0.05)'
179+
```
180+
181+
However, by using a helper method, the requested and closest available
182+
frequencies can be displayed to the developer:
183+
184+
```cpp
185+
// Accidentally used kBaud instead of Baud, which cannot be generated
186+
constexpr auto result = Prescaler::from_linear(SystemClock::Usart2, 115200_kBd, 16, 1ul << 16);
187+
modm::assertBaudrateInTolerance<result.frequency, result.desired_frequency, tolerance>();
188+
```
189+
```
190+
In instantiation of 'static void modm::PeripheralDriver::assertBaudrateInTolerance()
191+
[with long long unsigned int available = 3000000; long long unsigned int requested = 115200000; float tolerance = 0.01f]':
192+
error: static assertion failed: The closest available baudrate exceeds the tolerance of the requested baudrate!
193+
| static_assert(modm::isValueInTolerance(requested, available, tolerance),
194+
| ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195+
note: 'modm::isValueInTolerance<long long unsigned int>(115200000, 3000000, 0.01f)' evaluates to false
196+
```

src/modm/math/algorithm/prescaler.hpp

+53-50
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
#include <modm/math/units.hpp>
1616

1717
#include <algorithm>
18-
#include <cmath>
19-
#include <concepts>
2018
#include <cstdint>
21-
#include <initializer_list>
19+
#include <ranges>
2220

2321
namespace modm
2422
{
@@ -29,50 +27,49 @@ namespace modm
2927
* @note For ranges the end is *inclusive*: [begin, end]!
3028
* @ingroup modm_math_algorithm
3129
*/
32-
template<std::unsigned_integral T>
30+
template<typename T>
3331
class
3432
GenericPrescaler
3533
{
3634
public:
3735
struct Result
3836
{
39-
constexpr Result(T input_frequency,
40-
T desired_frequency,
41-
uint32_t index, uint32_t prescaler) :
37+
constexpr Result(T input_frequency, T desired_frequency,
38+
uint32_t index, uint32_t prescaler) :
4239
index{index}, prescaler{prescaler},
4340
frequency{input_frequency / prescaler},
4441
input_frequency{input_frequency},
4542
desired_frequency{desired_frequency},
46-
error{1.f - float(input_frequency / prescaler) / desired_frequency}
43+
error{1.f - float(input_frequency) / float(prescaler * desired_frequency)}
4744
{}
4845
/// Zero-indexed prescaler result
49-
const uint32_t index;
46+
uint32_t index;
5047
/// Prescaler value
51-
const uint32_t prescaler;
48+
uint32_t prescaler;
5249
/// Generated frequency
53-
const T frequency;
50+
T frequency;
5451
/// Input frequency
55-
const T input_frequency;
52+
T input_frequency;
5653
/// Desired Frequency
57-
const T desired_frequency;
54+
T desired_frequency;
5855
/// Relative Frequency Error
59-
const float error;
56+
float error;
6057
};
6158

6259
public:
63-
/// From any iterable container.
64-
/// @note container must have `begin()`, `end()` and `size()` function!
65-
template<class Iterator>
60+
/// From any forward range.
61+
/// @pre The container must be sorted from low to high numbers!
6662
static constexpr Result
67-
from_iterator(T input_frequency,
68-
T desired_frequency,
69-
Iterator prescalers)
63+
from_range(T input_frequency, T desired_frequency,
64+
std::ranges::forward_range auto&& prescalers)
7065
{
7166
const double desired = double(input_frequency) / desired_frequency;
72-
uint32_t prescaler_floor{*prescalers.begin()};
73-
uint32_t prescaler_ceiling{*prescalers.begin()};
67+
const size_t prescaler_size = std::ranges::distance(
68+
std::ranges::begin(prescalers), std::ranges::end(prescalers));
69+
uint32_t prescaler_floor{*std::ranges::begin(prescalers)};
70+
uint32_t prescaler_ceiling{*std::ranges::begin(prescalers)};
7471
uint32_t ii_floor{0}, ii_ceiling{0};
75-
if (desired <= prescaler_floor or prescalers.size() == 1)
72+
if (desired <= prescaler_floor or prescaler_size == 1)
7673
return {input_frequency, desired_frequency, 0, prescaler_floor};
7774
for (uint32_t prescaler : prescalers)
7875
{
@@ -91,54 +88,62 @@ GenericPrescaler
9188
}
9289

9390
/// From a initializer list.
91+
/// @pre The list must be sorted from low to high numbers!
9492
static constexpr Result
95-
from_list(T input_frequency,
96-
T desired_frequency,
93+
from_list(T input_frequency, T desired_frequency,
9794
std::initializer_list<uint32_t> prescalers)
9895
{
99-
return from_iterator(input_frequency, desired_frequency, prescalers);
96+
return from_range(input_frequency, desired_frequency, prescalers);
10097
}
10198

10299
/// From any linear range via modifier function.
103100
/// @note the range end is *inclusive*: [begin, end].
101+
/// @pre begin must be smaller or equal than end!
104102
template< typename Function >
105103
static constexpr Result
106-
from_function(T input_frequency,
107-
T desired_frequency,
108-
uint32_t begin, uint32_t end,
109-
Function prescaler_modifier)
104+
from_function(T input_frequency, T desired_frequency,
105+
uint32_t begin, uint32_t end, Function prescaler_modifier)
110106
{
111107
struct linear_range_iterator
112108
{
109+
using iterator_category [[maybe_unused]] = std::forward_iterator_tag;
110+
using value_type [[maybe_unused]] = uint32_t;
111+
using difference_type [[maybe_unused]] = int32_t;
112+
using pointer [[maybe_unused]] = value_type*;
113+
using reference [[maybe_unused]] = value_type&;
114+
113115
uint32_t state;
114-
const Function modifier;
115-
constexpr uint32_t operator*() const { return modifier(state); }
116+
Function *modifier;
117+
118+
constexpr value_type operator*() const { return (*modifier)(state); }
116119
constexpr linear_range_iterator& operator++() { state++; return *this; }
117-
constexpr bool operator!=(const linear_range_iterator &o) const { return state != o.state; }
120+
constexpr linear_range_iterator operator++(int) { auto old(*this); ++*this; return old; }
121+
constexpr bool operator==(const linear_range_iterator &o) const { return state == o.state; }
122+
constexpr difference_type operator-(const linear_range_iterator &o) const { return state - o.state; }
118123
};
119124
struct linear_range
120125
{
121-
const uint32_t m_begin;
122-
const uint32_t m_end;
123-
const Function modifier;
124-
constexpr linear_range_iterator begin() const { return linear_range_iterator{m_begin, modifier}; }
125-
constexpr linear_range_iterator end() const { return linear_range_iterator{m_end, modifier}; }
126+
uint32_t m_begin;
127+
uint32_t m_end;
128+
Function *modifier;
129+
130+
constexpr linear_range_iterator begin() const { return {m_begin, modifier}; }
131+
constexpr linear_range_iterator end() const { return {m_end, modifier}; }
126132
constexpr size_t size() const { return m_end - m_begin; }
127133
};
128-
linear_range range{begin, end+1, prescaler_modifier};
129-
return from_iterator(input_frequency, desired_frequency, range);
134+
linear_range range{begin, end+1, &prescaler_modifier};
135+
return from_range(input_frequency, desired_frequency, range);
130136
}
131137

132138
/// From any linear range.
133139
/// @note the range end is *inclusive*: [begin, end].
140+
/// @pre begin must be smaller or equal than end!
134141
static constexpr Result
135-
from_range(T input_frequency,
136-
T desired_frequency,
137-
uint32_t begin, uint32_t end)
142+
from_linear(T input_frequency, T desired_frequency, uint32_t begin, uint32_t end)
138143
{
139144
const double desired = double(input_frequency) / desired_frequency;
140-
const uint32_t prescaler_floor = std::max(uint32_t(std::floor(desired)), begin);
141-
const uint32_t prescaler_ceiling = std::min(uint32_t(std::ceil(desired)), end);
145+
const uint32_t prescaler_floor = std::max(uint32_t(desired), begin);
146+
const uint32_t prescaler_ceiling = std::min(uint32_t(desired) + 1, end);
142147
const T baud_lower = input_frequency / prescaler_ceiling;
143148
const T baud_upper = input_frequency / prescaler_floor;
144149
const double baud_middle = (baud_upper + baud_lower) / 2.0;
@@ -149,12 +154,10 @@ GenericPrescaler
149154

150155
/// From any 2-logarithmic range.
151156
/// @note the range end is *inclusive*: [begin, end].
152-
/// @param begin must be a power-of-two!
153-
/// @param end must be a power-of-two!
157+
/// @pre `begin` and `end` must be powers of two!
158+
/// @pre `begin` must be smaller or equal than `end`!
154159
static constexpr Result
155-
from_power(T input_frequency,
156-
T desired_frequency,
157-
uint32_t begin, uint32_t end)
160+
from_power(T input_frequency, T desired_frequency, uint32_t begin, uint32_t end)
158161
{
159162
const double desired = double(input_frequency) / desired_frequency;
160163
uint32_t ii{0}; uint32_t power{begin};

0 commit comments

Comments
 (0)