samples: boards: nrf: add clock_skew
Provide a demonstration of using the timeutil skew infrastructure to measure the relative error of the two clock sources on Nordic boards. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
This commit is contained in:
parent
7b3dc4856a
commit
08f4ce4efb
8
samples/boards/nrf/clock_skew/CMakeLists.txt
Normal file
8
samples/boards/nrf/clock_skew/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(nrf_clock_skew)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
10
samples/boards/nrf/clock_skew/Kconfig
Normal file
10
samples/boards/nrf/clock_skew/Kconfig
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) 2020, Nordic Semiconductor ASA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config APP_ENABLE_HFXO
|
||||
bool "Use HFXO as HFCLK source"
|
||||
help
|
||||
Turn on the high-frequency clock, which means switching to the
|
||||
crystal as a source rather than the internal oscillator.
|
||||
|
||||
source "Kconfig.zephyr"
|
116
samples/boards/nrf/clock_skew/README.rst
Normal file
116
samples/boards/nrf/clock_skew/README.rst
Normal file
|
@ -0,0 +1,116 @@
|
|||
.. _nrf-clock-skew-sample:
|
||||
|
||||
nRF5x Clock Skew Demo
|
||||
#####################
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
This sample uses the API for correlating time sources to measure the
|
||||
skew between HFCLK (used for the CPU) and LFCLK (used for system time).
|
||||
|
||||
The ``CONFIG_APP_ENABLE_HFXO`` Kconfig option can be select to configure
|
||||
the high frequency clock to use a crystal oscillator rather than the
|
||||
default RC oscillator. (Capabilities like Bluetooth that require an
|
||||
accurate high-frequency clock generally enable this source
|
||||
automatically.) The relative error is significantly lower when HFXO is
|
||||
enabled.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
This application uses any nRF51 DK or nRF52 DK board for the demo.
|
||||
|
||||
Building, Flashing and Running
|
||||
******************************
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/boards/nrf/clock_skew
|
||||
:board: nrf52dk_nrf52840
|
||||
:goals: build flash
|
||||
:compact:
|
||||
|
||||
Running:
|
||||
|
||||
|
||||
Sample Output
|
||||
=============
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
*** Booting Zephyr OS build zephyr-v2.4.0-693-g4a3275faf567 ***
|
||||
Power-up clocks: LFCLK[ON]: Running LFXO ; HFCLK[OFF]: Running HFINT
|
||||
Start TIMER_0: 0
|
||||
Timer-running clocks: LFCLK[ON]: Running LFXO ; HFCLK[OFF]: Running HFINT
|
||||
Checking TIMER_0 at 16000000 Hz against ticks at 32768 Hz
|
||||
Timer wraps every 268 s
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:00.015667
|
||||
LF 00:00:00.404296
|
||||
Started sync: 0
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:10.001151 00:00:00.015667 00:00:09.985483
|
||||
LF 00:00:10.413818 00:00:00.404296 00:00:10.009521 00:00:00.024038
|
||||
Skew 0.997599 ; err 2401411 ppb
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:19.997456 00:00:00.015667 00:00:19.981788
|
||||
LF 00:00:20.434265 00:00:00.404296 00:00:20.029968 00:00:00.048180
|
||||
Skew 0.997595 ; err 2405464 ppb
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:29.993845 00:00:00.015667 00:00:29.978178
|
||||
LF 00:00:30.454650 00:00:00.404296 00:00:30.050354 00:00:00.072176
|
||||
Skew 0.997598 ; err 2401828 ppb
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:39.986181 00:00:00.015667 00:00:39.970514
|
||||
LF 00:00:40.475036 00:00:00.404296 00:00:40.070739 00:00:00.100225
|
||||
Skew 0.997499 ; err 2501189 ppb
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:49.981516 00:00:00.015667 00:00:49.965848
|
||||
LF 00:00:50.495422 00:00:00.404296 00:00:50.091125 00:00:00.125277
|
||||
Skew 0.997499 ; err 2501010 ppb
|
||||
|
||||
Ty Latest Base Span Err
|
||||
HF 00:00:59.976042 00:00:00.015667 00:00:59.960375
|
||||
LF 00:01:00.515808 00:00:00.404296 00:01:00.111511 00:00:00.151136
|
||||
Skew 0.997486 ; err 2514243 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 00:01:59.935661 00:00:00.015667 00:01:59.919994
|
||||
LF 00:02:00.638153 00:00:00.404296 00:02:00.233856 00:00:00.313862
|
||||
Skew 0.997390 ; err 2610445 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 00:04:59.769166 00:00:00.015667 00:04:59.753498
|
||||
LF 00:05:01.005279 00:00:00.404296 00:05:00.600982 00:00:00.847484
|
||||
Skew 0.997181 ; err 2819240 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 00:09:59.513787 00:00:00.015667 00:09:59.498119
|
||||
LF 00:10:01.617156 00:00:00.404296 00:10:01.212860 00:00:01.714741
|
||||
Skew 0.997148 ; err 2852201 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 00:30:08.384536 00:00:00.015667 00:30:08.368868
|
||||
LF 00:30:14.084594 00:00:00.404296 00:30:13.680297 00:00:05.311429
|
||||
Skew 0.997072 ; err 2928495 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 00:59:57.353602 00:00:00.015667 00:59:57.337934
|
||||
LF 01:00:07.734375 00:00:00.404296 01:00:07.330078 00:00:09.992144
|
||||
Skew 0.997230 ; err 2770006 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 02:59:33.181323 00:00:00.015667 02:59:33.165656
|
||||
LF 03:00:03.434265 00:00:00.404296 03:00:03.029968 00:00:29.864312
|
||||
Skew 0.997236 ; err 2764463 ppb
|
||||
...
|
||||
Ty Latest Base Span Err
|
||||
HF 05:59:55.031709 00:00:00.015667 05:59:55.016042
|
||||
LF 06:00:57.120941 00:00:00.404296 06:00:56.716644 00:01:01.700602
|
||||
Skew 0.997151 ; err 2849042 ppb
|
5
samples/boards/nrf/clock_skew/prj.conf
Normal file
5
samples/boards/nrf/clock_skew/prj.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
#CONFIG_CLOCK_CONTROL=y
|
||||
CONFIG_COUNTER=y
|
||||
CONFIG_COUNTER_TIMER0=y
|
||||
CONFIG_NEWLIB_LIBC=y
|
||||
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
|
7
samples/boards/nrf/clock_skew/sample.yaml
Normal file
7
samples/boards/nrf/clock_skew/sample.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
sample:
|
||||
name: Clock Skew Sample for nRF
|
||||
tests:
|
||||
sample.boards.nrf.clock_skew:
|
||||
build_only: true
|
||||
platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832 nrf51dk_nrf51422
|
||||
tags: power
|
234
samples/boards/nrf/clock_skew/src/main.c
Normal file
234
samples/boards/nrf/clock_skew/src/main.c
Normal file
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <zephyr.h>
|
||||
#include <sys/timeutil.h>
|
||||
#include <drivers/clock_control.h>
|
||||
#include <drivers/clock_control/nrf_clock_control.h>
|
||||
#include <drivers/counter.h>
|
||||
#include <nrfx_clock.h>
|
||||
|
||||
#define TIMER_NODE DT_NODELABEL(timer0)
|
||||
#define CLOCK_NODE DT_INST(0, nordic_nrf_clock)
|
||||
#define UPDATE_INTERVAL_S 10
|
||||
|
||||
static const struct device *clock0;
|
||||
static const struct device *timer0;
|
||||
static struct timeutil_sync_config sync_config;
|
||||
static uint64_t counter_ref;
|
||||
static struct timeutil_sync_state sync_state;
|
||||
static struct k_delayed_work sync_work;
|
||||
|
||||
/* Convert local time in ticks to microseconds. */
|
||||
uint64_t local_to_us(uint64_t local)
|
||||
{
|
||||
return z_tmcvt(local, sync_config.local_Hz, USEC_PER_SEC, false,
|
||||
false, false, false);
|
||||
}
|
||||
|
||||
/* Convert HFCLK reference to microseconds. */
|
||||
uint64_t ref_to_us(uint64_t ref)
|
||||
{
|
||||
return z_tmcvt(ref, sync_config.ref_Hz, USEC_PER_SEC, false,
|
||||
false, false, false);
|
||||
}
|
||||
|
||||
/* Format a microsecond timestamp to text as D d HH:MM:SS.SSSSSS. */
|
||||
static const char *us_to_text_r(uint64_t rem, char *buf, size_t len)
|
||||
{
|
||||
char *bp = buf;
|
||||
char *bpe = bp + len;
|
||||
uint32_t us;
|
||||
uint32_t s;
|
||||
uint32_t min;
|
||||
uint32_t hr;
|
||||
uint32_t d;
|
||||
|
||||
us = rem % USEC_PER_SEC;
|
||||
rem /= USEC_PER_SEC;
|
||||
s = rem % 60;
|
||||
rem /= 60;
|
||||
min = rem % 60;
|
||||
rem /= 60;
|
||||
hr = rem % 24;
|
||||
rem /= 24;
|
||||
d = rem;
|
||||
|
||||
if (d > 0) {
|
||||
bp += snprintf(bp, bpe - bp, "%u d ", d);
|
||||
}
|
||||
bp += snprintf(bp, bpe - bp, "%02u:%02u:%02u.%06u",
|
||||
hr, min, s, us);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *us_to_text(uint64_t rem)
|
||||
{
|
||||
static char ts_buf[32];
|
||||
|
||||
return us_to_text_r(rem, ts_buf, sizeof(ts_buf));
|
||||
}
|
||||
|
||||
/* Show status of various clocks */
|
||||
static void show_clocks(const char *tag)
|
||||
{
|
||||
static const char *const lfsrc_s[] = {
|
||||
#if defined(CLOCK_LFCLKSRC_SRC_LFULP)
|
||||
[NRF_CLOCK_LFCLK_LFULP] = "LFULP",
|
||||
#endif
|
||||
[NRF_CLOCK_LFCLK_RC] = "LFRC",
|
||||
[NRF_CLOCK_LFCLK_Xtal] = "LFXO",
|
||||
[NRF_CLOCK_LFCLK_Synth] = "LFSYNT",
|
||||
};
|
||||
static const char *const hfsrc_s[] = {
|
||||
[NRF_CLOCK_HFCLK_LOW_ACCURACY] = "HFINT",
|
||||
[NRF_CLOCK_HFCLK_HIGH_ACCURACY] = "HFXO",
|
||||
};
|
||||
static const char *const clkstat_s[] = {
|
||||
[CLOCK_CONTROL_STATUS_STARTING] = "STARTING",
|
||||
[CLOCK_CONTROL_STATUS_OFF] = "OFF",
|
||||
[CLOCK_CONTROL_STATUS_ON] = "ON",
|
||||
[CLOCK_CONTROL_STATUS_UNKNOWN] = "UNKNOWN",
|
||||
};
|
||||
union {
|
||||
unsigned int raw;
|
||||
nrf_clock_lfclk_t lf;
|
||||
nrf_clock_hfclk_t hf;
|
||||
} src;
|
||||
enum clock_control_status clkstat;
|
||||
bool running;
|
||||
|
||||
clkstat = clock_control_get_status(clock0, CLOCK_CONTROL_NRF_SUBSYS_LF);
|
||||
running = nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_LFCLK,
|
||||
&src.lf);
|
||||
printk("%s: LFCLK[%s]: %s %s ; ", tag, clkstat_s[clkstat],
|
||||
running ? "Running" : "Off", lfsrc_s[src.lf]);
|
||||
clkstat = clock_control_get_status(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF);
|
||||
running = nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_HFCLK,
|
||||
&src.hf);
|
||||
printk("HFCLK[%s]: %s %s\n", clkstat_s[clkstat],
|
||||
running ? "Running" : "Off", hfsrc_s[src.hf]);
|
||||
}
|
||||
|
||||
static void sync_work_handler(struct k_work *work)
|
||||
{
|
||||
uint32_t ctr;
|
||||
int rc = counter_get_value(timer0, &ctr);
|
||||
const struct timeutil_sync_instant *base = &sync_state.base;
|
||||
const struct timeutil_sync_instant *latest = &sync_state.latest;
|
||||
|
||||
if (rc == 0) {
|
||||
struct timeutil_sync_instant inst;
|
||||
uint64_t ref_span_us;
|
||||
|
||||
counter_ref += ctr - (uint32_t)counter_ref;
|
||||
inst.ref = counter_ref;
|
||||
inst.local = k_uptime_ticks();
|
||||
|
||||
rc = timeutil_sync_state_update(&sync_state, &inst);
|
||||
printf("\nTy Latest Base Span Err\n");
|
||||
printf("HF %s", us_to_text(ref_to_us(inst.ref)));
|
||||
if (rc > 0) {
|
||||
printf(" %s", us_to_text(ref_to_us(base->ref)));
|
||||
ref_span_us = ref_to_us(latest->ref - base->ref);
|
||||
printf(" %s", us_to_text(ref_span_us));
|
||||
}
|
||||
printf("\nLF %s", us_to_text(local_to_us(inst.local)));
|
||||
if (rc > 0) {
|
||||
uint64_t err_us;
|
||||
uint64_t local_span_us;
|
||||
char err_sign = ' ';
|
||||
|
||||
printf(" %s", us_to_text(local_to_us(base->local)));
|
||||
|
||||
local_span_us = local_to_us(latest->local - base->local);
|
||||
printf(" %s", us_to_text(local_span_us));
|
||||
|
||||
if (ref_span_us >= local_span_us) {
|
||||
err_us = ref_span_us - local_span_us;
|
||||
err_sign = '-';
|
||||
} else {
|
||||
err_us = local_span_us - ref_span_us;
|
||||
}
|
||||
printf(" %c%s", err_sign, us_to_text(err_us));
|
||||
}
|
||||
printf("\n");
|
||||
if (rc > 0) {
|
||||
float skew = timeutil_sync_estimate_skew(&sync_state);
|
||||
|
||||
printf("Skew %f ; err %d ppb\n", skew,
|
||||
timeutil_sync_skew_to_ppb(skew));
|
||||
} else if (rc < 0) {
|
||||
printf("Sync update error: %d\n", rc);
|
||||
}
|
||||
}
|
||||
k_delayed_work_submit(&sync_work, K_SECONDS(UPDATE_INTERVAL_S));
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
const char *clock_label = DT_LABEL(CLOCK_NODE);
|
||||
const char *timer0_label = DT_LABEL(TIMER_NODE);
|
||||
uint32_t top;
|
||||
int rc;
|
||||
|
||||
/* Grab the clock driver */
|
||||
clock0 = device_get_binding(clock_label);
|
||||
if (clock0 == NULL) {
|
||||
printk("Failed to fetch clock %s\n", clock_label);
|
||||
}
|
||||
|
||||
show_clocks("Power-up clocks");
|
||||
|
||||
if (IS_ENABLED(CONFIG_APP_ENABLE_HFXO)) {
|
||||
rc = clock_control_on(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF);
|
||||
printk("Enable HFXO got %d\n", rc);
|
||||
}
|
||||
|
||||
/* Grab the timer. */
|
||||
timer0 = device_get_binding(timer0_label);
|
||||
if (timer0 == NULL) {
|
||||
printk("Failed to fetch timer0 %s\n", timer0_label);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Apparently there's no API to configure a frequency at
|
||||
* runtime, so live with whatever we get.
|
||||
*/
|
||||
sync_config.ref_Hz = counter_get_frequency(timer0);
|
||||
if (sync_config.ref_Hz == 0) {
|
||||
printk("Timer %s has no fixed frequency\n",
|
||||
timer0_label);
|
||||
return;
|
||||
}
|
||||
|
||||
top = counter_get_top_value(timer0);
|
||||
if (top != UINT32_MAX) {
|
||||
printk("Timer %s wraps at %u (0x%08x) not at 32 bits\n",
|
||||
timer0_label, top, top);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = counter_start(timer0);
|
||||
printk("Start %s: %d\n", timer0_label, rc);
|
||||
|
||||
show_clocks("Timer-running clocks");
|
||||
|
||||
sync_config.local_Hz = CONFIG_SYS_CLOCK_TICKS_PER_SEC;
|
||||
|
||||
sync_state.cfg = &sync_config;
|
||||
|
||||
printf("Checking %s at %u Hz against ticks at %u Hz\n",
|
||||
timer0_label, sync_config.ref_Hz, sync_config.local_Hz);
|
||||
printf("Timer wraps every %u s\n",
|
||||
(uint32_t)(BIT64(32) / sync_config.ref_Hz));
|
||||
|
||||
k_delayed_work_init(&sync_work, sync_work_handler);
|
||||
rc = k_delayed_work_submit(&sync_work, K_NO_WAIT);
|
||||
|
||||
printk("Started sync: %d\n", rc);
|
||||
}
|
Loading…
Reference in a new issue