Skip to content

Commit dc60797

Browse files
committed
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 <[email protected]>
1 parent bc8cbe6 commit dc60797

File tree

6 files changed

+380
-0
lines changed

6 files changed

+380
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.13.1)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(nrf_clock_skew)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})

samples/boards/nrf/clock_skew/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2020, Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config APP_ENABLE_HFXO
5+
bool "Use HFXO as HFCLK source"
6+
help
7+
Turn on the high-frequency clock, which means switching to the
8+
crystal as a source rather than the internal oscillator.
9+
10+
source "Kconfig.zephyr"
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
.. _nrf-clock-skew-sample:
2+
3+
nRF5x Clock Skew Demo
4+
#####################
5+
6+
Overview
7+
********
8+
9+
This sample uses the API for correlating time sources to measure the
10+
skew between HFCLK (used for the CPU) and LFCLK (used for system time).
11+
12+
The ``CONFIG_APP_ENABLE_HFXO`` Kconfig option can be select to configure
13+
the high frequency clock to use a crystal oscillator rather than the
14+
default RC oscillator. (Capabilities like Bluetooth that require an
15+
accurate high-frequency clock generally enable this source
16+
automatically.) The relative error is significantly lower when HFXO is
17+
enabled.
18+
19+
Requirements
20+
************
21+
22+
This application uses any nRF51 DK or nRF52 DK board for the demo.
23+
24+
Building, Flashing and Running
25+
******************************
26+
27+
.. zephyr-app-commands::
28+
:zephyr-app: samples/boards/nrf/clock_skew
29+
:board: nrf52dk_nrf52840
30+
:goals: build flash
31+
:compact:
32+
33+
Running:
34+
35+
36+
Sample Output
37+
=============
38+
39+
.. code-block:: console
40+
41+
*** Booting Zephyr OS build zephyr-v2.4.0-693-g4a3275faf567 ***
42+
Power-up clocks: LFCLK[ON]: Running LFXO ; HFCLK[OFF]: Running HFINT
43+
Start TIMER_0: 0
44+
Timer-running clocks: LFCLK[ON]: Running LFXO ; HFCLK[OFF]: Running HFINT
45+
Checking TIMER_0 at 16000000 Hz against ticks at 32768 Hz
46+
Timer wraps every 268 s
47+
48+
Ty Latest Base Span Err
49+
HF 00:00:00.015667
50+
LF 00:00:00.404296
51+
Started sync: 0
52+
53+
Ty Latest Base Span Err
54+
HF 00:00:10.001151 00:00:00.015667 00:00:09.985483
55+
LF 00:00:10.413818 00:00:00.404296 00:00:10.009521 00:00:00.024038
56+
Skew 0.997599 ; err 2401411 ppb
57+
58+
Ty Latest Base Span Err
59+
HF 00:00:19.997456 00:00:00.015667 00:00:19.981788
60+
LF 00:00:20.434265 00:00:00.404296 00:00:20.029968 00:00:00.048180
61+
Skew 0.997595 ; err 2405464 ppb
62+
63+
Ty Latest Base Span Err
64+
HF 00:00:29.993845 00:00:00.015667 00:00:29.978178
65+
LF 00:00:30.454650 00:00:00.404296 00:00:30.050354 00:00:00.072176
66+
Skew 0.997598 ; err 2401828 ppb
67+
68+
Ty Latest Base Span Err
69+
HF 00:00:39.986181 00:00:00.015667 00:00:39.970514
70+
LF 00:00:40.475036 00:00:00.404296 00:00:40.070739 00:00:00.100225
71+
Skew 0.997499 ; err 2501189 ppb
72+
73+
Ty Latest Base Span Err
74+
HF 00:00:49.981516 00:00:00.015667 00:00:49.965848
75+
LF 00:00:50.495422 00:00:00.404296 00:00:50.091125 00:00:00.125277
76+
Skew 0.997499 ; err 2501010 ppb
77+
78+
Ty Latest Base Span Err
79+
HF 00:00:59.976042 00:00:00.015667 00:00:59.960375
80+
LF 00:01:00.515808 00:00:00.404296 00:01:00.111511 00:00:00.151136
81+
Skew 0.997486 ; err 2514243 ppb
82+
...
83+
Ty Latest Base Span Err
84+
HF 00:01:59.935661 00:00:00.015667 00:01:59.919994
85+
LF 00:02:00.638153 00:00:00.404296 00:02:00.233856 00:00:00.313862
86+
Skew 0.997390 ; err 2610445 ppb
87+
...
88+
Ty Latest Base Span Err
89+
HF 00:04:59.769166 00:00:00.015667 00:04:59.753498
90+
LF 00:05:01.005279 00:00:00.404296 00:05:00.600982 00:00:00.847484
91+
Skew 0.997181 ; err 2819240 ppb
92+
...
93+
Ty Latest Base Span Err
94+
HF 00:09:59.513787 00:00:00.015667 00:09:59.498119
95+
LF 00:10:01.617156 00:00:00.404296 00:10:01.212860 00:00:01.714741
96+
Skew 0.997148 ; err 2852201 ppb
97+
...
98+
Ty Latest Base Span Err
99+
HF 00:30:08.384536 00:00:00.015667 00:30:08.368868
100+
LF 00:30:14.084594 00:00:00.404296 00:30:13.680297 00:00:05.311429
101+
Skew 0.997072 ; err 2928495 ppb
102+
...
103+
Ty Latest Base Span Err
104+
HF 00:59:57.353602 00:00:00.015667 00:59:57.337934
105+
LF 01:00:07.734375 00:00:00.404296 01:00:07.330078 00:00:09.992144
106+
Skew 0.997230 ; err 2770006 ppb
107+
...
108+
Ty Latest Base Span Err
109+
HF 02:59:33.181323 00:00:00.015667 02:59:33.165656
110+
LF 03:00:03.434265 00:00:00.404296 03:00:03.029968 00:00:29.864312
111+
Skew 0.997236 ; err 2764463 ppb
112+
...
113+
Ty Latest Base Span Err
114+
HF 05:59:55.031709 00:00:00.015667 05:59:55.016042
115+
LF 06:00:57.120941 00:00:00.404296 06:00:56.716644 00:01:01.700602
116+
Skew 0.997151 ; err 2849042 ppb
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#CONFIG_CLOCK_CONTROL=y
2+
CONFIG_COUNTER=y
3+
CONFIG_COUNTER_TIMER0=y
4+
CONFIG_NEWLIB_LIBC=y
5+
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
sample:
2+
name: Clock Skew Sample for nRF
3+
tests:
4+
sample.boards.nrf.clock_skew:
5+
build_only: true
6+
platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832 nrf51dk_nrf51422
7+
tags: power
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
* Copyright (c) 2020 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <zephyr.h>
9+
#include <sys/timeutil.h>
10+
#include <drivers/clock_control.h>
11+
#include <drivers/clock_control/nrf_clock_control.h>
12+
#include <drivers/counter.h>
13+
#include <nrfx_clock.h>
14+
15+
#define TIMER_NODE DT_NODELABEL(timer0)
16+
#define CLOCK_NODE DT_INST(0, nordic_nrf_clock)
17+
#define UPDATE_INTERVAL_S 10
18+
19+
static const struct device *clock0;
20+
static const struct device *timer0;
21+
static struct timeutil_sync_config sync_config;
22+
static uint64_t counter_ref;
23+
static struct timeutil_sync_state sync_state;
24+
static struct k_delayed_work sync_work;
25+
26+
/* Convert local time in ticks to microseconds. */
27+
uint64_t local_to_us(uint64_t local)
28+
{
29+
return z_tmcvt(local, sync_config.local_Hz, USEC_PER_SEC, false,
30+
false, false, false);
31+
}
32+
33+
/* Convert HFCLK reference to microseconds. */
34+
uint64_t ref_to_us(uint64_t ref)
35+
{
36+
return z_tmcvt(ref, sync_config.ref_Hz, USEC_PER_SEC, false,
37+
false, false, false);
38+
}
39+
40+
/* Format a microsecond timestamp to text as D d HH:MM:SS.SSSSSS. */
41+
static const char *us_to_text_r(uint64_t rem, char *buf, size_t len)
42+
{
43+
char *bp = buf;
44+
char *bpe = bp + len;
45+
uint32_t us;
46+
uint32_t s;
47+
uint32_t min;
48+
uint32_t hr;
49+
uint32_t d;
50+
51+
us = rem % USEC_PER_SEC;
52+
rem /= USEC_PER_SEC;
53+
s = rem % 60;
54+
rem /= 60;
55+
min = rem % 60;
56+
rem /= 60;
57+
hr = rem % 24;
58+
rem /= 24;
59+
d = rem;
60+
61+
if (d > 0) {
62+
bp += snprintf(bp, bpe - bp, "%u d ", d);
63+
}
64+
bp += snprintf(bp, bpe - bp, "%02u:%02u:%02u.%06u",
65+
hr, min, s, us);
66+
return buf;
67+
}
68+
69+
static const char *us_to_text(uint64_t rem)
70+
{
71+
static char ts_buf[32];
72+
73+
return us_to_text_r(rem, ts_buf, sizeof(ts_buf));
74+
}
75+
76+
/* Show status of various clocks */
77+
static void show_clocks(const char *tag)
78+
{
79+
static const char *const lfsrc_s[] = {
80+
#if defined(CLOCK_LFCLKSRC_SRC_LFULP)
81+
[NRF_CLOCK_LFCLK_LFULP] = "LFULP",
82+
#endif
83+
[NRF_CLOCK_LFCLK_RC] = "LFRC",
84+
[NRF_CLOCK_LFCLK_Xtal] = "LFXO",
85+
[NRF_CLOCK_LFCLK_Synth] = "LFSYNT",
86+
};
87+
static const char *const hfsrc_s[] = {
88+
[NRF_CLOCK_HFCLK_LOW_ACCURACY] = "HFINT",
89+
[NRF_CLOCK_HFCLK_HIGH_ACCURACY] = "HFXO",
90+
};
91+
static const char *const clkstat_s[] = {
92+
[CLOCK_CONTROL_STATUS_STARTING] = "STARTING",
93+
[CLOCK_CONTROL_STATUS_OFF] = "OFF",
94+
[CLOCK_CONTROL_STATUS_ON] = "ON",
95+
[CLOCK_CONTROL_STATUS_UNKNOWN] = "UNKNOWN",
96+
};
97+
union {
98+
unsigned int raw;
99+
nrf_clock_lfclk_t lf;
100+
nrf_clock_hfclk_t hf;
101+
} src;
102+
enum clock_control_status clkstat;
103+
bool running;
104+
105+
clkstat = clock_control_get_status(clock0, CLOCK_CONTROL_NRF_SUBSYS_LF);
106+
running = nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_LFCLK,
107+
&src.lf);
108+
printk("%s: LFCLK[%s]: %s %s ; ", tag, clkstat_s[clkstat],
109+
running ? "Running" : "Off", lfsrc_s[src.lf]);
110+
clkstat = clock_control_get_status(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF);
111+
running = nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_HFCLK,
112+
&src.hf);
113+
printk("HFCLK[%s]: %s %s\n", clkstat_s[clkstat],
114+
running ? "Running" : "Off", hfsrc_s[src.hf]);
115+
}
116+
117+
static void sync_work_handler(struct k_work *work)
118+
{
119+
uint32_t ctr;
120+
int rc = counter_get_value(timer0, &ctr);
121+
const struct timeutil_sync_instant *base = &sync_state.base;
122+
const struct timeutil_sync_instant *latest = &sync_state.latest;
123+
124+
if (rc == 0) {
125+
struct timeutil_sync_instant inst;
126+
uint64_t ref_span_us;
127+
128+
counter_ref += ctr - (uint32_t)counter_ref;
129+
inst.ref = counter_ref;
130+
inst.local = k_uptime_ticks();
131+
132+
rc = timeutil_sync_state_update(&sync_state, &inst);
133+
printf("\nTy Latest Base Span Err\n");
134+
printf("HF %s", us_to_text(ref_to_us(inst.ref)));
135+
if (rc > 0) {
136+
printf(" %s", us_to_text(ref_to_us(base->ref)));
137+
ref_span_us = ref_to_us(latest->ref - base->ref);
138+
printf(" %s", us_to_text(ref_span_us));
139+
}
140+
printf("\nLF %s", us_to_text(local_to_us(inst.local)));
141+
if (rc > 0) {
142+
uint64_t err_us;
143+
uint64_t local_span_us;
144+
char err_sign = ' ';
145+
146+
printf(" %s", us_to_text(local_to_us(base->local)));
147+
148+
local_span_us = local_to_us(latest->local - base->local);
149+
printf(" %s", us_to_text(local_span_us));
150+
151+
if (ref_span_us >= local_span_us) {
152+
err_us = ref_span_us - local_span_us;
153+
err_sign = '-';
154+
} else {
155+
err_us = local_span_us - ref_span_us;
156+
}
157+
printf(" %c%s", err_sign, us_to_text(err_us));
158+
}
159+
printf("\n");
160+
if (rc > 0) {
161+
float skew = timeutil_sync_estimate_skew(&sync_state);
162+
163+
printf("Skew %f ; err %d ppb\n", skew,
164+
timeutil_sync_skew_to_ppb(skew));
165+
} else if (rc < 0) {
166+
printf("Sync update error: %d\n", rc);
167+
}
168+
}
169+
k_delayed_work_submit(&sync_work, K_SECONDS(UPDATE_INTERVAL_S));
170+
}
171+
172+
void main(void)
173+
{
174+
const char *clock_label = DT_LABEL(CLOCK_NODE);
175+
const char *timer0_label = DT_LABEL(TIMER_NODE);
176+
uint32_t top;
177+
int rc;
178+
179+
/* Grab the clock driver */
180+
clock0 = device_get_binding(clock_label);
181+
if (clock0 == NULL) {
182+
printk("Failed to fetch clock %s\n", clock_label);
183+
}
184+
185+
show_clocks("Power-up clocks");
186+
187+
if (IS_ENABLED(CONFIG_APP_ENABLE_HFXO)) {
188+
rc = clock_control_on(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF);
189+
printk("Enable HFXO got %d\n", rc);
190+
}
191+
192+
/* Grab the timer. */
193+
timer0 = device_get_binding(timer0_label);
194+
if (timer0 == NULL) {
195+
printk("Failed to fetch timer0 %s\n", timer0_label);
196+
return;
197+
}
198+
199+
/* Apparently there's no API to configure a frequency at
200+
* runtime, so live with whatever we get.
201+
*/
202+
sync_config.ref_Hz = counter_get_frequency(timer0);
203+
if (sync_config.ref_Hz == 0) {
204+
printk("Timer %s has no fixed frequency\n",
205+
timer0_label);
206+
return;
207+
}
208+
209+
top = counter_get_top_value(timer0);
210+
if (top != UINT32_MAX) {
211+
printk("Timer %s wraps at %u (0x%08x) not at 32 bits\n",
212+
timer0_label, top, top);
213+
return;
214+
}
215+
216+
rc = counter_start(timer0);
217+
printk("Start %s: %d\n", timer0_label, rc);
218+
219+
show_clocks("Timer-running clocks");
220+
221+
sync_config.local_Hz = CONFIG_SYS_CLOCK_TICKS_PER_SEC;
222+
223+
sync_state.cfg = &sync_config;
224+
225+
printf("Checking %s at %u Hz against ticks at %u Hz\n",
226+
timer0_label, sync_config.ref_Hz, sync_config.local_Hz);
227+
printf("Timer wraps every %u s\n",
228+
(uint32_t)(BIT64(32) / sync_config.ref_Hz));
229+
230+
k_delayed_work_init(&sync_work, sync_work_handler);
231+
rc = k_delayed_work_submit(&sync_work, K_NO_WAIT);
232+
233+
printk("Started sync: %d\n", rc);
234+
}

0 commit comments

Comments
 (0)