tests: Added test for PTP clock
This commit is contained in:
parent
0da5d1607c
commit
2e529e92a4
16
tests/periph_ptp_clock/Makefile
Normal file
16
tests/periph_ptp_clock/Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
BOARD ?= nucleo-f767zi
|
||||
include ../Makefile.tests_common
|
||||
|
||||
FEATURES_REQUIRED += periph_ptp
|
||||
FEATURES_OPTIONAL += periph_ptp_speed_adjustment
|
||||
FEATURES_REQUIRED += periph_timer_periodic
|
||||
|
||||
USEMODULE += fmt
|
||||
|
||||
TIMER_FREQ ?=
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
||||
ifneq (,$(TIMER_FREQ))
|
||||
CFLAGS += -DTIM_FREQ=$(TIMER_FREQ)
|
||||
endif
|
||||
67
tests/periph_ptp_clock/README.md
Normal file
67
tests/periph_ptp_clock/README.md
Normal file
@ -0,0 +1,67 @@
|
||||
Peripheral PTP Clock Test Application
|
||||
=====================================
|
||||
|
||||
Hardware Requirements
|
||||
---------------------
|
||||
|
||||
In addition to the `periph_ptp` feature, this tests requires the peripheral
|
||||
timer to be run on a clock synchronous to the PTP clock. Otherwise clock
|
||||
drift between the two clocks might result in the test failing even though the
|
||||
driver works correctly.
|
||||
|
||||
Verifying Clock Speed Adjustment
|
||||
--------------------------------
|
||||
|
||||
In the first part of the test the speed of the PTP clock is adjusted to
|
||||
different values. The test sleeps for 0.5 seconds and verifies that the clock
|
||||
drift between the high level timer API and PTP clock matches the speed
|
||||
adjustment (± 0.01%).
|
||||
|
||||
Verifying Clock Adjustment
|
||||
--------------------------
|
||||
|
||||
The second part of the test verifies that adjusting the clock does not
|
||||
introduce noticeable errors. The clock speed is set back to nominal speed
|
||||
prior to this test part (so that it synchronous to the high level timer). For
|
||||
each of a set of predefined clock adjustment values the following is done:
|
||||
|
||||
- `xtimer_period_wakeup()` is used to get 50 wake ups after 10 ms each
|
||||
- right before sleeping, the PTP clock is adjusted
|
||||
- right after the wake up, the PTP clock is read
|
||||
- The difference between the two PTP timestamps should be the sum of the
|
||||
10 ms period length and the adjustment
|
||||
- The difference of the actual result and the expected result
|
||||
(10 ms + adjustment) is stored for each round
|
||||
- The average, minimum, maximum, and variance (σ²) is calculated from these
|
||||
differences between actual period length and expected period length
|
||||
|
||||
A test run is considered a pass, if the average error is less than ± 250 ns
|
||||
and minimum/maximum error are less than ± 500 ns.
|
||||
|
||||
Expected Output on Success
|
||||
--------------------------
|
||||
|
||||
main(): This is RIOT! (Version: <INSERT VERSION HERE>)
|
||||
Testing clock at speed 0 (~100.000 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed 32767 (~149.999 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed -32768 (~50.000 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed 1337 (~102.040 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed -1337 (~97.961 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed 42 (~100.064 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed -42 (~99.937 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed 665 (~101.015 % of nominal speed): SUCCEEDED
|
||||
Testing clock at speed -665 (~98.986 % of nominal speed): SUCCEEDED
|
||||
Testing clock adjustments for offset 0: SUCCEEDED
|
||||
Statistics: avg = 0, min = 0, max = 0, σ² = 0
|
||||
Testing clock adjustments for offset -1337: SUCCEEDED
|
||||
Statistics: avg = 0, min = 0, max = 0, σ² = 0
|
||||
Testing clock adjustments for offset 1337: SUCCEEDED
|
||||
Statistics: avg = 0, min = 0, max = 0, σ² = 0
|
||||
Testing clock adjustments for offset 2147483647: SUCCEEDED
|
||||
Statistics: avg = 0, min = 0, max = 0, σ² = 0
|
||||
TEST SUCCEEDED!
|
||||
|
||||
Note: If the values in the statistics of the clock adjustments differ to some
|
||||
degree, this might be due ISR overhead and jitter when reading the
|
||||
PTP timer from the periodic peripheral timer callback. The test will only
|
||||
fail if either the worst case offset or the variance are too big.
|
||||
261
tests/periph_ptp_clock/main.c
Normal file
261
tests/periph_ptp_clock/main.c
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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 Peripheral PTP clock test application
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fmt.h"
|
||||
#include "kernel_defines.h"
|
||||
#include "macros/units.h"
|
||||
#include "mutex.h"
|
||||
#include "periph/ptp.h"
|
||||
#include "periph/timer.h"
|
||||
|
||||
#define TEST_TIME_US (US_PER_SEC / 2)
|
||||
#define TEST_ROUNDS (50)
|
||||
#define PERIOD_US (TEST_TIME_US / TEST_ROUNDS)
|
||||
|
||||
#ifndef TIM
|
||||
#define TIM TIMER_DEV(0)
|
||||
#endif
|
||||
#ifndef TIM_FREQ
|
||||
#define TIM_FREQ MHZ(1)
|
||||
#endif
|
||||
|
||||
#define TIM_PERIOD (1ULL * PERIOD_US * TIM_FREQ / US_PER_SEC - 1)
|
||||
#define TIM_TIME (1ULL * TEST_TIME_US * TIM_FREQ / US_PER_SEC)
|
||||
|
||||
static mutex_t sync_mutex = MUTEX_INIT_LOCKED;
|
||||
static atomic_uint_least64_t timestamp;
|
||||
|
||||
static inline void print_s64_dec(int64_t _num)
|
||||
{
|
||||
uint64_t num = _num;
|
||||
if (_num < 0) {
|
||||
print_str("-");
|
||||
num = -_num;
|
||||
}
|
||||
print_u64_dec(num);
|
||||
}
|
||||
|
||||
static void speed_adj_cb(void *arg, int chan)
|
||||
{
|
||||
(void)arg;
|
||||
(void)chan;
|
||||
mutex_unlock(&sync_mutex);
|
||||
}
|
||||
|
||||
static int test_speed_adjustment(const int16_t speed)
|
||||
{
|
||||
|
||||
uint32_t expected_ns = TEST_TIME_US * 1000;
|
||||
expected_ns += ((int64_t)expected_ns * speed) >> 16;
|
||||
const uint32_t expected_ns_lower = expected_ns * 9999ULL / 10000ULL;
|
||||
const uint32_t expected_ns_upper = expected_ns * 10001ULL / 10000ULL;
|
||||
|
||||
ptp_clock_adjust_speed(speed);
|
||||
print_str("Testing clock at speed ");
|
||||
print_s32_dec(speed);
|
||||
print_str(" (~");
|
||||
{
|
||||
int64_t tmp = speed * 100000ULL;
|
||||
tmp = (tmp + UINT16_MAX / 2) / UINT16_MAX;
|
||||
tmp += 100000ULL;
|
||||
char output[16];
|
||||
print(output, fmt_s32_dfp(output, (int32_t)tmp, -3));
|
||||
}
|
||||
print_str(" % of nominal speed): ");
|
||||
|
||||
if (timer_init(TIM, TIM_FREQ, speed_adj_cb, NULL)) {
|
||||
print_str("FAILED.\nCouldn't set up periph_timer for comparison\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* be double sure mutex is indeed locked */
|
||||
mutex_trylock(&sync_mutex);
|
||||
|
||||
timer_stop(TIM);
|
||||
|
||||
if (timer_set(TIM, 0, TIM_TIME)) {
|
||||
print_str("FAILED.\nCouldn't set periph_timer for comparison\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t start_ns = ptp_clock_read_u64();
|
||||
timer_start(TIM);
|
||||
|
||||
mutex_lock(&sync_mutex);
|
||||
uint64_t got_ns = ptp_clock_read_u64() - start_ns;
|
||||
timer_stop(TIM);
|
||||
uint64_t diff_ns = 0;
|
||||
|
||||
int failed = 0;
|
||||
if (got_ns > expected_ns_upper) {
|
||||
failed = 1;
|
||||
diff_ns = got_ns - expected_ns;
|
||||
}
|
||||
else if (got_ns < expected_ns_lower) {
|
||||
failed = 1;
|
||||
diff_ns = expected_ns - got_ns;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
int64_t tmp = (int64_t)got_ns - (int64_t)expected_ns;
|
||||
tmp = (tmp * 100000LL + expected_ns / 2) / expected_ns;
|
||||
tmp += 100000LL;
|
||||
char percentage[16];
|
||||
print_str("FAILED\n");
|
||||
print_str("expected: ");
|
||||
print_u64_dec(expected_ns);
|
||||
print_str(", got: ");
|
||||
print_u64_dec(got_ns);
|
||||
print_str(", diff: ");
|
||||
print_u64_dec(diff_ns);
|
||||
print_str(" (");
|
||||
print(percentage, fmt_s32_dfp(percentage, (int32_t)tmp, -3));
|
||||
print_str("% offset)\n");
|
||||
}
|
||||
else {
|
||||
print_str("SUCCEEDED\n");
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
static void clock_adj_cb(void *arg, int chan)
|
||||
{
|
||||
int32_t offset = (uintptr_t)arg;
|
||||
(void)chan;
|
||||
uint64_t now = ptp_clock_read_u64();
|
||||
ptp_clock_adjust(offset);
|
||||
atomic_store(×tamp, now);
|
||||
mutex_unlock(&sync_mutex);
|
||||
}
|
||||
|
||||
|
||||
static int test_clock_adjustment(int32_t offset)
|
||||
{
|
||||
/* Record one extra sample, to throw away the first measurement */
|
||||
static int64_t diffs[TEST_ROUNDS + 1];
|
||||
int64_t period_ns = PERIOD_US * 1000ULL + offset;
|
||||
uint64_t last_ns;
|
||||
|
||||
print_str("Testing clock adjustments for offset ");
|
||||
print_s32_dec(offset);
|
||||
print_str(": ");
|
||||
|
||||
/* be double sure mutex is indeed locked */
|
||||
mutex_trylock(&sync_mutex);
|
||||
|
||||
if (timer_init(TIM, TIM_FREQ, clock_adj_cb, (void *)offset) ||
|
||||
timer_set_periodic(TIM, 0, TIM_PERIOD, TIM_FLAG_RESET_ON_MATCH)) {
|
||||
print_str("FAILED.\nCouldn't set up periph_timer for comparison\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* wait for periodic timer IRQ */
|
||||
mutex_lock(&sync_mutex);
|
||||
last_ns = atomic_load(×tamp);
|
||||
|
||||
for (unsigned i = 0; i < TEST_ROUNDS + 1; i++) {
|
||||
/* wait for periodic timer IRQ */
|
||||
mutex_lock(&sync_mutex);
|
||||
uint64_t now_ns = atomic_load(×tamp);
|
||||
diffs[i] = (int64_t)(now_ns - last_ns) - period_ns;
|
||||
last_ns = now_ns;
|
||||
}
|
||||
|
||||
timer_stop(TIM);
|
||||
|
||||
int64_t avg = 0, var = 0, min = INT64_MAX, max = INT64_MIN;
|
||||
for (unsigned i = 1; i < TEST_ROUNDS + 1; i++) {
|
||||
avg += diffs[i];
|
||||
}
|
||||
avg = (avg + TEST_ROUNDS / 2) / TEST_ROUNDS;
|
||||
for (unsigned i = 1; i < TEST_ROUNDS + 1; i++) {
|
||||
int64_t tmp = diffs[i] - avg;
|
||||
var += tmp * tmp;
|
||||
if (diffs[i] < min) {
|
||||
min = diffs[i];
|
||||
}
|
||||
if (diffs[i] > max) {
|
||||
max = diffs[i];
|
||||
}
|
||||
}
|
||||
|
||||
var = (var + TEST_ROUNDS / 2) / TEST_ROUNDS;
|
||||
|
||||
int failed = 0;
|
||||
if ((max < 500) && (min > -500) && (avg < 250) && (avg > -250)) {
|
||||
print_str("SUCCEEDED\n");
|
||||
}
|
||||
else {
|
||||
failed = 1;
|
||||
print_str("FAILED\nDiffs:\n");
|
||||
for (unsigned i = 1; i < TEST_ROUNDS + 1; i++) {
|
||||
print_s64_dec(diffs[i]);
|
||||
print_str("\n");
|
||||
}
|
||||
}
|
||||
print_str("Statistics: avg = ");
|
||||
print_s64_dec(avg);
|
||||
print_str(", min = ");
|
||||
print_s64_dec(min);
|
||||
print_str(", max = ");
|
||||
print_s64_dec(max);
|
||||
print_str(", σ² = ");
|
||||
print_s64_dec(var);
|
||||
print_str("\n");
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const int16_t speeds[] = {
|
||||
0, INT16_MAX, INT16_MIN, 1337, -1337, 42, -42, 665, -665
|
||||
};
|
||||
static const int32_t offsets[] = {
|
||||
0, -1337, +1337, INT32_MAX
|
||||
};
|
||||
int failed = 0;
|
||||
|
||||
if (IS_USED(MODULE_PERIPH_PTP_SPEED_ADJUSTMENT)) {
|
||||
for (size_t i = 0; i < ARRAY_SIZE(speeds); i++) {
|
||||
failed |= test_speed_adjustment(speeds[i]);
|
||||
}
|
||||
|
||||
/* Restore nominal clock speed */
|
||||
ptp_clock_adjust_speed(0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(offsets); i++) {
|
||||
failed |= test_clock_adjustment(offsets[i]);
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
print_str("TEST FAILED!\n");
|
||||
}
|
||||
else {
|
||||
print_str("TEST SUCCEEDED!\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
18
tests/periph_ptp_clock/tests/01-run.py
Executable file
18
tests/periph_ptp_clock/tests/01-run.py
Executable file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||
#
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
from testrunner import run
|
||||
|
||||
|
||||
def testfunc(child):
|
||||
child.expect_exact("TEST SUCCEEDED!\r\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(testfunc))
|
||||
Loading…
x
Reference in New Issue
Block a user