1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/tests/bench/timers/main.c

735 lines
24 KiB
C

/*
* Copyright (C) 2018 Eistec AB
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Another peripheral timer test application
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*
* @}
*/
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "bitarithm.h"
#include "fmt.h"
#include "mutex.h"
#include "random.h"
#include "div.h"
#include "matstat.h"
#include "thread.h"
#ifdef MODULE_PERIPH_RTT
#include "periph/rtt.h"
#endif
#if TEST_XTIMER
#include "xtimer.h"
#endif
#include "board.h"
#include "cpu.h"
#include "periph/timer.h"
#include "test_utils/expect.h"
#include "print_results.h"
#include "spin_random.h"
#include "bench_timers_config.h"
#ifndef TEST_TRACE
#define TEST_TRACE 0
#endif
/*
* All different variations will be mixed to provide the most varied input
* vector possible for the benchmark. A more varied input should yield a more
* correct estimate of the mean error and variance. Random CPU processing delays
* will be inserted between each step to avoid phase locking the benchmark to
* unobservable timer internals.
*/
#if TEST_XTIMER
enum test_variants {
TEST_XTIMER_SET = 0,
TEST_PARALLEL = 1,
TEST_XTIMER_PERIODIC_WAKEUP = 2,
TEST_XTIMER_SPIN = 4,
TEST_VARIANT_NUMOF = 6,
};
#else /* TEST_XTIMER */
/*
* Results will be grouped by function, rescheduling yes/no, start/stop.
* functions: timer_set, timer_set_absolute
* reschedule: yes/no, when yes: first set one target time, before that time has
* passed, set the real target time
* start/stop: if stop: call timer_stop before setting the target time, then call timer_start
*/
enum test_variants {
TEST_RESCHEDULE = 1,
TEST_STOPPED = 2,
TEST_ABSOLUTE = 4,
TEST_VARIANT_NUMOF = 8,
};
#endif /* else TEST_XTIMER */
/* Benchmark processing overhead, results will be compensated for this to make
* the results easier to understand */
static int32_t overhead_target;
static int32_t overhead_read;
/* Seed for initializing the random module */
static uint32_t seed = 123;
/* Mutex used for signalling between main thread and ISR callback */
static mutex_t mtx_cb = MUTEX_INIT_LOCKED;
/* Test state element */
typedef struct {
matstat_state_t *ref_state; /* timer_set error statistics state */
matstat_state_t *int_state; /* timer_read error statistics state */
unsigned int target_ref; /* Target time in reference timer */
unsigned int target_tut; /* Target time in timer under test */
} test_ctx_t;
static test_ctx_t test_context;
#if DETAILED_STATS
#if LOG2_STATS
/* Group test values by 2-logarithm to reduce memory requirements */
static matstat_state_t ref_states[TEST_VARIANT_NUMOF * TEST_LOG2NUM];
#else
/* State vector, first half will contain state for timer_set tests, second half
* will contain state for timer_set_absolute */
static matstat_state_t ref_states[TEST_VARIANT_NUMOF * TEST_NUM];
#endif
#else
/* Only keep stats per function variation */
static matstat_state_t ref_states[TEST_VARIANT_NUMOF];
#endif
/* timer_read error statistics states */
static matstat_state_t int_states[TEST_VARIANT_NUMOF];
/* Limits for the mean and variance, to compare the results against expectation */
static stat_limits_t ref_limits;
static stat_limits_t int_limits;
#if TEST_XTIMER
static const result_presentation_t presentation = {
.groups = (const result_group_t[1]) {
{
.label = "xtimer",
.sub_labels = (const char *[]){
[TEST_XTIMER_SET] = "_xt_set",
[TEST_XTIMER_SET | TEST_PARALLEL] = "_xt_set race",
[TEST_XTIMER_PERIODIC_WAKEUP] = "_xt_periodic",
[TEST_XTIMER_PERIODIC_WAKEUP | TEST_PARALLEL] = "_xt_periodic race",
[TEST_XTIMER_SPIN] = "_xt_spin",
[TEST_XTIMER_SPIN | TEST_PARALLEL] = "_xt_spin race",
},
.num_sub_labels = TEST_VARIANT_NUMOF,
},
},
.num_groups = 1,
.ref_limits = &ref_limits,
.int_limits = &int_limits,
.offsets = (const unsigned[]) {
[TEST_XTIMER_SET] = TEST_MIN_REL,
[TEST_XTIMER_SET | TEST_PARALLEL] = TEST_MIN_REL,
[TEST_XTIMER_PERIODIC_WAKEUP] = TEST_MIN_REL,
[TEST_XTIMER_PERIODIC_WAKEUP | TEST_PARALLEL] = TEST_MIN_REL,
[TEST_XTIMER_SPIN] = TEST_MIN_REL,
[TEST_XTIMER_SPIN | TEST_PARALLEL] = TEST_MIN_REL,
},
};
#else /* TEST_XTIMER */
/* Number of variant groups, used when printing results */
#define TEST_VARIANT_GROUPS 2
static const result_presentation_t presentation = {
.groups = (const result_group_t[TEST_VARIANT_GROUPS]) {
{
.label = "timer_set",
.sub_labels = (const char *[]){
[0] = "running",
[TEST_RESCHEDULE] = "resched",
[TEST_STOPPED] = "stopped",
[TEST_STOPPED | TEST_RESCHEDULE] = "stopped, resched",
},
.num_sub_labels = (TEST_VARIANT_NUMOF / TEST_VARIANT_GROUPS),
},
{
.label = "timer_set_absolute",
.sub_labels = (const char *[]){
[0] = "running",
[TEST_RESCHEDULE] = "resched",
[TEST_STOPPED] = "stopped",
[TEST_STOPPED | TEST_RESCHEDULE] = "stopped, resched",
},
.num_sub_labels = (TEST_VARIANT_NUMOF / TEST_VARIANT_GROUPS),
},
},
.num_groups = TEST_VARIANT_GROUPS,
.ref_limits = &ref_limits,
.int_limits = &int_limits,
.offsets = (const unsigned[]) {
[0] = TEST_MIN_REL,
[TEST_RESCHEDULE] = TEST_MIN_REL,
[TEST_STOPPED] = TEST_MIN_REL,
[TEST_STOPPED | TEST_RESCHEDULE] = TEST_MIN_REL,
[TEST_ABSOLUTE] = TEST_MIN,
[TEST_ABSOLUTE | TEST_RESCHEDULE] = TEST_MIN,
[TEST_ABSOLUTE | TEST_STOPPED] = TEST_MIN,
[TEST_ABSOLUTE | TEST_STOPPED | TEST_RESCHEDULE] = TEST_MIN,
},
};
#endif /* else TEST_XTIMER */
#ifdef MODULE_PERIPH_RTT
static uint32_t rtt_begin;
#endif
static unsigned int ref_begin;
static unsigned int tut_begin;
/**
* @brief Calculate the limits for mean and variance for this test
*/
static void set_limits(void)
{
ref_limits.mean_low = -(TEST_UNEXPECTED_MEAN);
ref_limits.mean_high = (TEST_UNEXPECTED_MEAN);
ref_limits.variance_low = 0;
ref_limits.variance_high = (TEST_UNEXPECTED_STDDEV) * (TEST_UNEXPECTED_STDDEV);
int_limits.mean_low = -(TEST_UNEXPECTED_MEAN);
int_limits.mean_high = TEST_UNEXPECTED_MEAN;
int_limits.variance_low = 0;
int_limits.variance_high = (TEST_UNEXPECTED_STDDEV) * (TEST_UNEXPECTED_STDDEV);
/* The quantization errors should be uniformly distributed within +/- 0.5
* test timer ticks of the reference time */
/* The formula for the variance of a rectangle distribution on [a, b] is
* Var = (b - a)^2 / 12 (taken directly from a statistics textbook)
* Using (b - a)^2 / 12 == (10b - 10a) * ((10b + 1) - (10a + 1)) / 1200
* gives a smaller truncation error when using integer operations for
* converting the ticks */
uint32_t conversion_variance = ((TIM_TEST_TO_REF(10) - TIM_TEST_TO_REF(0)) *
(TIM_TEST_TO_REF(11) - TIM_TEST_TO_REF(1))) / 1200;
if (TIM_REF_FREQ > TIM_TEST_FREQ) {
ref_limits.variance_low = ((TIM_TEST_TO_REF(10) - TIM_TEST_TO_REF(0) - 10 * (TEST_UNEXPECTED_STDDEV)) *
(TIM_TEST_TO_REF(11) - TIM_TEST_TO_REF(1) - 10 * (TEST_UNEXPECTED_STDDEV))) / 1200;
ref_limits.variance_high = ((TIM_TEST_TO_REF(10) - TIM_TEST_TO_REF(0) + 10 * (TEST_UNEXPECTED_STDDEV)) *
(TIM_TEST_TO_REF(11) - TIM_TEST_TO_REF(1) + 10 * (TEST_UNEXPECTED_STDDEV))) / 1200;
/* The limits of the mean should account for the conversion error as well */
/* rounded towards positive infinity */
int32_t mean_error = (TIM_TEST_TO_REF(128) - TIM_TEST_TO_REF(0) + 127) / 128;
ref_limits.mean_high += mean_error;
}
print_str("Expected error variance due to truncation in tick conversion: ");
print_u32_dec(conversion_variance);
print("\n", 1);
}
/* Callback for the timeout */
static void cb(void *arg)
{
unsigned int now_tut = READ_TUT();
unsigned int now_ref = timer_read(TIM_REF_DEV);
if (arg == NULL) {
print_str("cb: Warning! arg = NULL\n");
return;
}
test_ctx_t *ctx = arg;
if (ctx->ref_state == NULL) {
print_str("cb: Warning! ref_state = NULL\n");
return;
}
if (ctx->int_state == NULL) {
print_str("cb: Warning! int_state = NULL\n");
return;
}
/* Update running stats */
/* When setting a timer with a timeout of X ticks, we expect the
* duration between the set and the callback, dT, to be at least
* X * time_per_tick.
* In order to ensure that dT <= X * time_per_tick, the timer read value
* will actually have incremented (X + 1) times during that period,
* because the set can occur asynchrously anywhere between timer
* increments. Therefore, in this test, we consider (X + 1) to be the
* expected timer_read value at the point the callback is called.
*/
/* Check that reference timer did not overflow during the test */
if ((now_ref + 0x4000u) >= ctx->target_ref) {
int32_t diff = now_ref - ctx->target_ref - 1 - overhead_target;
matstat_add(ctx->ref_state, diff);
}
/* Update timer_read statistics only when timer_read has not overflowed
* since the timer was set */
if ((now_tut + 0x4000u) >= ctx->target_tut) {
int32_t diff = now_tut - ctx->target_tut - 1 - overhead_read;
matstat_add(ctx->int_state, diff);
}
mutex_unlock(&mtx_cb);
}
/* Wrapper for periph_timer callbacks */
static void cb_timer_periph(void *arg, int chan)
{
(void)chan;
cb(arg);
}
/**
* @brief Select the proper state for the given test number depending on the
* compile time configuration
*
* Depends on DETAILED_STATS, LOG2_STATS
*/
static void assign_state_ptr(test_ctx_t *ctx, unsigned int variant, uint32_t interval)
{
ctx->int_state = &int_states[variant];
if (DETAILED_STATS) {
if (LOG2_STATS) {
unsigned int log2num = bitarithm_msb(interval);
ctx->ref_state = &ref_states[variant * TEST_LOG2NUM + log2num];
}
else {
ctx->ref_state = &ref_states[variant * TEST_NUM + interval];
}
}
else {
ctx->ref_state = &ref_states[variant];
}
}
static uint32_t derive_interval(uint32_t num)
{
uint32_t interval;
if ((DETAILED_STATS) && (LOG2_STATS)) {
/* Use a logarithmic method to generate geometric variates in order to
* populate the result table evenly across all buckets */
/* Static exponent mask, picking the mask as tightly as possible reduces the
* probability of discarded values, which reduces the computing overhead
* between test iterations */
static uint32_t exp_mask = 0;
if (exp_mask == 0) {
/* non-constant initializer */
exp_mask = (2 << bitarithm_msb(TEST_LOG2NUM)) - 1;
print_str("exp_mask = ");
print_u32_hex(exp_mask);
print("\n", 1);
print_str("max interval = ");
print_u32_dec((2 << exp_mask) - 1);
print("\n", 1);
}
/* Pick an exponent based on the top bits of the number */
/* exponent will be a number in the interval [0, log2(TEST_NUM) + 1] */
unsigned int exponent = ((num >> (32 - 8)) & exp_mask);
if (exponent == 0) {
/* Special handling to avoid the situation where we never see a zero */
/* We could also have used an extra right shift in the else case,
* but the state grouping also groups 0 and 1 in the same bucket, which means that they are twice as likely */
interval = bitarithm_bits_set(num) & 1;
}
else {
interval = (1 << exponent);
interval |= (num & (interval - 1));
}
}
else {
static const uint32_t mask = (1 << TEST_LOG2NUM) - 1;
interval = num & mask;
}
return interval;
}
#if TEST_XTIMER
static void nop(void *arg)
{
(void)arg;
}
static void run_test(test_ctx_t *ctx, uint32_t interval, unsigned int variant)
{
interval += TEST_MIN;
unsigned int interval_ref = TIM_TEST_TO_REF(interval);
xtimer_t xt = {
.callback = cb,
.arg = ctx,
};
xtimer_t xt_parallel = {
.callback = nop,
.arg = NULL,
};
if (TEST_TRACE) {
switch (variant & ~TEST_PARALLEL) {
case TEST_XTIMER_SET:
print_str("rel ");
break;
case TEST_XTIMER_PERIODIC_WAKEUP:
print_str("per ");
break;
case TEST_XTIMER_SPIN:
print_str("spn ");
break;
default:
break;
}
if (variant & TEST_PARALLEL) {
print_str("= ");
}
else {
print_str("- ");
}
print_u32_dec(interval);
print("\n", 1);
}
spin_random_delay();
if (variant & TEST_PARALLEL) {
_xtimer_set64(&xt_parallel, interval, 0);
//~ interval += XTIMER_BACKOFF;
spin_random_delay();
}
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref;
uint32_t now = READ_TUT();
ctx->target_tut = now + interval;
switch (variant & ~TEST_PARALLEL) {
case TEST_XTIMER_SET:
_xtimer_set64(&xt, interval, 0);
break;
case TEST_XTIMER_PERIODIC_WAKEUP:
_xtimer_periodic_wakeup(&now, interval);
/* xtimer_periodic_wakeup sleeps the thread, no automatic callback */
cb(xt.arg);
break;
case TEST_XTIMER_SPIN:
_xtimer_spin(interval);
/* xtimer_spin sleeps the thread, no automatic callback */
cb(xt.arg);
break;
default:
break;
}
mutex_lock(&mtx_cb);
xtimer_remove(&xt_parallel);
xtimer_remove(&xt);
}
#else /* TEST_XTIMER */
static void run_test(test_ctx_t *ctx, uint32_t interval, unsigned int variant)
{
if (variant & TEST_ABSOLUTE) {
interval += TEST_MIN;
}
else {
interval += TEST_MIN_REL;
}
unsigned int interval_ref = TIM_TEST_TO_REF(interval);
if (TEST_TRACE) {
if (variant & TEST_ABSOLUTE) {
print_str("A");
}
else {
print_str("_");
}
if (variant & TEST_RESCHEDULE) {
print_str("R");
}
else {
print_str("_");
}
if (variant & TEST_STOPPED) {
print_str("S ");
}
else {
print_str("_ ");
}
print_u32_dec(interval);
print_str(" ");
print_u64_hex(READ_TUT());
print("\n", 1);
}
spin_random_delay();
if (variant & TEST_RESCHEDULE) {
timer_set(TIM_TEST_DEV, TIM_TEST_CHAN, interval + RESCHEDULE_MARGIN);
spin_random_delay();
}
if (variant & TEST_STOPPED) {
timer_stop(TIM_TEST_DEV);
spin_random_delay();
}
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref;
ctx->target_tut = READ_TUT() + interval;
if (variant & TEST_ABSOLUTE) {
timer_set_absolute(TIM_TEST_DEV, TIM_TEST_CHAN, ctx->target_tut);
}
else {
timer_set(TIM_TEST_DEV, TIM_TEST_CHAN, interval);
}
if (variant & TEST_STOPPED) {
spin_random_delay();
/* do not update ctx->target_tut, because TUT should have been stopped
* and not incremented during spin_random_delay */
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref;
timer_start(TIM_TEST_DEV);
}
mutex_lock(&mtx_cb);
}
#endif /* TEST_XTIMER */
static int test_timer(void)
{
uint32_t time_last = timer_read(TIM_REF_DEV);
uint32_t time_elapsed = 0;
do {
uint32_t num = random_uint32();
unsigned int variant = (num >> (32 - 3));
if (variant >= TEST_VARIANT_NUMOF) {
continue;
}
uint32_t interval = derive_interval(num);
if (interval >= TEST_NUM) {
/* Discard values outside our test range */
continue;
}
assign_state_ptr(&test_context, variant, interval);
run_test(&test_context, interval, variant);
uint32_t now = timer_read(TIM_REF_DEV);
if (now >= time_last) {
/* Account for reference timer possibly overflowing before 30 seconds have passed */
time_elapsed += now - time_last;
}
time_last = now;
//~ print_str("e: ");
//~ print_u32_dec(time_elapsed);
//~ print_str("\n");
} while(time_elapsed < TEST_PRINT_INTERVAL_TICKS);
uint32_t ref_now = timer_read(TIM_REF_DEV);
uint32_t tut_now = READ_TUT();
#ifdef MODULE_PERIPH_RTT
uint32_t rtt_now = rtt_get_counter();
#endif
print_str("Elapsed time:\n");
print_str(" Reference: ");
print_u32_dec((ref_now - ref_begin) / TIM_REF_FREQ);
print_str("\n");
print_str(" Timer under test: ");
print_u32_dec((tut_now - tut_begin) / TIM_TEST_FREQ);
print_str("\n");
#ifdef MODULE_PERIPH_RTT
print_str(" Wall clock (RTT): ");
print_u32_dec((rtt_now - rtt_begin) / RTT_FREQUENCY);
print_str("\n");
#endif
print_results(&presentation, &ref_states[0], &int_states[0]);
return 0;
}
static void estimate_cpu_overhead(void)
{
/* Try to estimate the amount of CPU overhead between test start to test
* finish to get a better reading */
print_str("Estimating benchmark overhead...\n");
uint32_t interval = 0;
overhead_target = 0;
overhead_read = 0;
test_ctx_t context;
test_ctx_t *ctx = &context;
matstat_state_t ref_state = MATSTAT_STATE_INIT;
matstat_state_t int_state = MATSTAT_STATE_INIT;
ctx->ref_state = &ref_state;
ctx->int_state = &int_state;
for (unsigned int k = 0; k < ESTIMATE_CPU_ITERATIONS; ++k) {
unsigned int interval_ref = TIM_TEST_TO_REF(interval);
spin_random_delay();
ctx->target_tut = READ_TUT() + interval - 1;
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref - 1;
/* call yield to simulate a context switch to isr and back */
thread_yield_higher();
cb_timer_periph(ctx, TIM_TEST_CHAN);
mutex_lock(&mtx_cb);
}
overhead_target = matstat_mean(&ref_state);
overhead_read = matstat_mean(&int_state);
print_str("overhead_target = ");
print_s32_dec(overhead_target);
print_str(" (s2 = ");
uint32_t var = matstat_variance(&ref_state);
print_u32_dec(var);
print_str(")\n");
if (var > 2) {
print_str("Warning: Variance in CPU estimation is too high\n");
#ifdef CPU_NATIVE
print_str("This is expected on native when other processes are running\n");
#endif
}
print_str("overhead_read = ");
print_s32_dec(overhead_read);
print_str(" (s2 = ");
var = matstat_variance(&int_state);
print_u32_dec(var);
print_str(")\n");
if (var > 2) {
print_str("Warning: Variance in CPU estimation is too high\n");
#ifdef CPU_NATIVE
print_str("This is expected on native when other processes are running\n");
#endif
}
}
int main(void)
{
print_str("\nStatistical benchmark for timers\n");
for (unsigned int k = 0; k < ARRAY_SIZE(ref_states); ++k) {
matstat_clear(&ref_states[k]);
}
for (unsigned int k = 0; k < ARRAY_SIZE(int_states); ++k) {
matstat_clear(&int_states[k]);
}
/* print test overview */
print_str("Running timer test with seed ");
print_u32_dec(seed);
print_str(" using ");
#if MODULE_PRNG_MERSENNE
print_str("Mersenne Twister PRNG.\n");
#elif MODULE_PRNG_MINSTD
print_str("Park & Miller Minimal Standard PRNG.\n");
#elif MODULE_PRNG_MUSL_LCG
print_str("Musl C PRNG.\n");
#elif MODULE_PRNG_TINYMT32
print_str("Tiny Mersenne Twister PRNG.\n");
#elif MODULE_PRNG_XORSHIFT
print_str("XOR Shift PRNG.\n");
#else
print_str("unknown PRNG.\n");
#endif
print_str("TEST_MIN = ");
print_u32_dec(TEST_MIN);
print("\n", 1);
print_str("TEST_MAX = ");
print_u32_dec(TEST_MAX);
print("\n", 1);
print_str("TEST_MIN_REL = ");
print_u32_dec(TEST_MIN_REL);
print("\n", 1);
print_str("TEST_MAX_REL = ");
print_u32_dec(TEST_MIN_REL + TEST_NUM - 1);
print("\n", 1);
print_str("TEST_NUM = ");
print_u32_dec(TEST_NUM);
print("\n", 1);
print_str("log2(TEST_NUM - 1) = ");
unsigned log2test = bitarithm_msb(TEST_NUM - 1);
print_u32_dec(log2test);
print("\n", 1);
print_str("state vector elements per variant = ");
print_u32_dec(ARRAY_SIZE(ref_states) / TEST_VARIANT_NUMOF);
print("\n", 1);
print_str("number of variants = ");
print_u32_dec(TEST_VARIANT_NUMOF);
print("\n", 1);
print_str("sizeof(state) = ");
print_u32_dec(sizeof(ref_states[0]));
print_str(" bytes\n");
print_str("state vector total memory usage = ");
print_u32_dec(sizeof(ref_states));
print_str(" bytes\n");
expect(log2test < TEST_LOG2NUM);
print_str("TIM_TEST_DEV = ");
print_u32_dec(TIM_TEST_DEV);
print_str(", TIM_TEST_FREQ = ");
print_u32_dec(TIM_TEST_FREQ);
print_str(", TIM_TEST_CHAN = ");
print_u32_dec(TIM_TEST_CHAN);
print("\n", 1);
print_str("TIM_REF_DEV = ");
print_u32_dec(TIM_REF_DEV);
print_str(", TIM_REF_FREQ = ");
print_u32_dec(TIM_REF_FREQ);
print("\n", 1);
#ifdef TIM_TEST_TO_REF_SHIFT
print_str("TIM_TEST_TO_REF_SHIFT = ");
print_u32_dec(TIM_TEST_TO_REF_SHIFT);
print("\n", 1);
#endif
print_str("USE_REFERENCE = ");
print_u32_dec(USE_REFERENCE);
print("\n", 1);
print_str("TEST_PRINT_INTERVAL_TICKS = ");
print_u32_dec(TEST_PRINT_INTERVAL_TICKS);
print("\n", 1);
if (TEST_MAX > 512) { /* Arbitrarily chosen limit */
print_str("Warning: Using long intervals for testing makes the result "
"more likely to be affected by clock drift between the "
"reference timer and the timer under test. This can be "
"detected as a skewness in the mean values between different "
"intervals in the results table.\n");
if (LOG2_STATS) {
print_str("The variance of the larger intervals may also be greater "
"than expected if there is significant clock drift across "
"the bucketed time frame\n");
}
}
int res = timer_init(TIM_REF_DEV, TIM_REF_FREQ, cb_timer_periph, NULL);
if (res < 0) {
print_str("Error ");
print_s32_dec(res);
print_str(" initializing reference timer\n");
return res;
}
random_init(seed);
#if !(TEST_XTIMER)
res = timer_init(TIM_TEST_DEV, TIM_TEST_FREQ, cb_timer_periph, &test_context);
if (res < 0) {
print_str("Error ");
print_s32_dec(res);
print_str(" initializing timer under test\n");
return res;
}
#endif
set_limits();
print_str("Calibrating spin delay...\n");
uint32_t spin_max = spin_random_calibrate(TIM_TEST_DEV, SPIN_MAX_TARGET);
print_str("spin_max = ");
print_u32_dec(spin_max);
print("\n", 1);
estimate_cpu_overhead();
#ifdef MODULE_PERIPH_RTT
rtt_begin = rtt_get_counter();
#endif
ref_begin = timer_read(TIM_REF_DEV);
tut_begin = READ_TUT();
while(1) {
test_timer();
}
return 0;
}