Skip to content

Commit 17d2516

Browse files
authored
Merge pull request #20581 from maribu/timer_get_closest_freq
drivers/periph_timer: add `timer_get_closest_freq()`
2 parents b1932dd + 8b77df4 commit 17d2516

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

drivers/include/periph/timer.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ uword_t timer_query_channel_numof(tim_t dev);
267267
/**
268268
* @brief Iterate over supported frequencies
269269
*
270-
* @param dev Timer to get the next supported frequency of
270+
* @param dev The timer to get the next supported frequency of
271271
* @param index Index of the frequency to get
272272
* @return The @p index highest frequency supported by the timer
273273
* @retval 0 @p index is too high
@@ -296,6 +296,24 @@ uword_t timer_query_channel_numof(tim_t dev);
296296
*/
297297
uint32_t timer_query_freqs(tim_t dev, uword_t index);
298298

299+
/**
300+
* @brief Search the frequency supported by the timer that is closest to
301+
* a given target frequency, efficiently
302+
*
303+
* @param dev The timer to get the closest supported frequency for
304+
* @param target Ideal frequency to match
305+
* @return The frequency supported by the timer @p dev that is
306+
* closest to @p target
307+
*
308+
* @details This will use binary search internally to have an O(log(n))
309+
* runtime. This can be relevant on hardware with 16 bit or 32 bit
310+
* prescaler registers.
311+
*
312+
* @note Add `FEATURES_REQUIRED += periph_timer_query_freqs` to your
313+
* `Makefile`.
314+
*/
315+
uint32_t timer_get_closest_freq(tim_t dev, uint32_t target);
316+
299317
#if defined(DOXYGEN)
300318
/**
301319
* @brief Check whether a compare channel has matched

drivers/periph_common/timer.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,53 @@ uword_t timer_query_channel_numof(tim_t dev)
3838
(void)dev;
3939
return TIMER_CHANNEL_NUMOF;
4040
}
41+
42+
uint32_t timer_get_closest_freq(tim_t dev, uint32_t target)
43+
{
44+
uint32_t freq;
45+
uword_t idx_max = timer_query_freqs_numof(dev) - 1;
46+
47+
/* use binary search to find one of the three possibilities:
48+
* 1. If an exact match is possible: The exact match is found
49+
* 2. Otherwise the closest frequency is found, which might be either
50+
* a) the highest frequency below the target, or
51+
* b) the lowest frequency above the target
52+
*/
53+
uword_t idx_lower = 0;
54+
uword_t idx_upper = idx_max;
55+
while (idx_lower != idx_upper) {
56+
uword_t idx_mid = (idx_lower + idx_upper) >> 1;
57+
freq = timer_query_freqs(dev, idx_mid);
58+
if (freq > target) {
59+
idx_lower = idx_mid + 1;
60+
}
61+
else {
62+
idx_upper = idx_mid;
63+
}
64+
}
65+
66+
freq = timer_query_freqs(dev, idx_lower);
67+
if ((freq < target) && (idx_lower > 0)) {
68+
/* binary search yielded the closest frequency below the target. But
69+
* maybe the closest frequency above the target is actually better. */
70+
uint32_t diff = target - freq;
71+
uint32_t alternative = timer_query_freqs(dev, idx_lower - 1);
72+
if (target + diff > alternative) {
73+
/* the alternative is better */
74+
return alternative;
75+
}
76+
}
77+
else if ((freq > target) && (idx_lower < idx_max)) {
78+
/* Got a frequency above the target, maybe the one below would be
79+
* a closer match */
80+
uint32_t diff = freq - target;
81+
uint32_t alternative = timer_query_freqs(dev, idx_lower + 1);
82+
if (target < alternative + diff) {
83+
return alternative;
84+
}
85+
}
86+
87+
/* either we got an exact match, or the other candidate was no closer */
88+
return freq;
89+
}
4190
#endif

tests/periph/timer/main.c

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ static int test_timer(unsigned num, uint32_t timer_freq)
202202
return 0;
203203
}
204204

205-
printf(" OK (no spurious IRQs)\n");
205+
printf(" [OK] (no spurious IRQs)\n");
206206

207207
return 1;
208208
}
@@ -249,8 +249,47 @@ static void print_supported_frequencies(tim_t dev)
249249
" %u: %" PRIu32 "\n",
250250
(unsigned)(end - 1), timer_query_freqs(dev, end - 1));
251251
}
252+
}
253+
254+
static void test_querying(tim_t dev)
255+
{
256+
uword_t last = query_freq_numof(dev) - 1;
257+
258+
puts("Testing timer_get_closest_freq()...");
259+
for (uword_t i = 0; i <= MIN(last, 255); i++) {
260+
uint32_t closest;
261+
uint32_t freq = timer_query_freqs(dev, i);
262+
263+
/* Searching for a supported freq should yield the supported freq: */
264+
expect(freq == timer_get_closest_freq(dev, freq));
265+
266+
/* Assuming that no other frequency close to `freq` are supported,
267+
* we would assume that asking for `freq - 1` and for `freq + 1` would
268+
* also return `freq`. There are some corner cases, though. Let's look
269+
* at asking for `freq - 1` here first:
270+
*
271+
* - `freq - 1` is actually also supported. In that case
272+
* `timer_get_closest_freq(dev, freq - 1)` should actually return
273+
* `freq - 1`
274+
* - `freq - 2` is also supported (but not `freq - 1`). In this case
275+
* returning either `freq - 2` or `freq` would be valid for
276+
* `timer_get_closest_freq(dev, freq - 1)`.
277+
*
278+
* Therefore, we just except `freq - 2`, `freq - 1`, and `freq` as
279+
* results for `timer_get_closest_freq(dev, freq - 1)`. */
280+
closest = timer_get_closest_freq(dev, freq - 1);
281+
expect((freq >= closest) && closest >= freq - 2);
282+
283+
/* Now same with `freq + 1` as target. Due to the same corner cases,
284+
* we accept `freq`, `freq + 1`, and `freq + 2` here. */
285+
closest = timer_get_closest_freq(dev, freq + 1);
286+
expect((freq <= closest) && closest <= freq + 2);
287+
}
252288

253-
expect(timer_query_freqs(dev, end) == 0);
289+
puts("[OK]\n"
290+
"Testing timer_query_freqs() for out of bound index...");
291+
expect(timer_query_freqs(dev, last + 1) == 0);
292+
puts("[OK]");
254293
}
255294

256295
int main(void)
@@ -265,6 +304,12 @@ int main(void)
265304
printf("\nTIMER %u\n"
266305
"=======\n\n", i);
267306
print_supported_frequencies(TIMER_DEV(i));
307+
308+
/* test querying of frequencies, but only if supported by the driver */
309+
if (IS_USED(MODULE_PERIPH_TIMER_QUERY_FREQS)) {
310+
test_querying(TIMER_DEV(i));
311+
}
312+
268313
uword_t end = query_freq_numof(TIMER_DEV(i));
269314

270315
/* Test only up to three frequencies and only the fastest once.

0 commit comments

Comments
 (0)