|
| 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 | +``` |
0 commit comments