Skip to content

Commit 658a22f

Browse files
author
Martin Crossley
committed
add example pico_w/wifi/ntp_system_time
1 parent aed0867 commit 658a22f

File tree

6 files changed

+283
-0
lines changed

6 files changed

+283
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ App|Description
200200
[picow_blink_fast_clock](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip) with a faster system clock to show how to reconfigure communication with the WiFi chip at build time under those circumstances.
201201
[picow_iperf_server](pico_w/wifi/iperf) | Runs an "iperf" server for WiFi speed testing.
202202
[picow_ntp_client](pico_w/wifi/ntp_client) | Connects to an NTP server to fetch and display the current time.
203+
[picow_ntp_system_time](pico_w/wifi/ntp_system_time) | Creates a background time of day clock that periodically updates itself from a pool of NTP servers and uses it to display local time.
203204
[picow_tcp_client](pico_w/wifi/tcp_client) | A simple TCP client. You can run [python_test_tcp_server.py](pico_w/wifi/python_test_tcp/python_test_tcp_server.py) for it to connect to.
204205
[picow_tcp_server](pico_w/wifi/tcp_server) | A simple TCP server. You can use [python_test_tcp_client.py](pico_w//wifi/python_test_tcp/python_test_tcp_client.py) to connect to it.
205206
[picow_tls_client](pico_w/wifi/tls_client) | Demonstrates how to make a HTTPS request using TLS.

pico_w/wifi/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ else()
1414
add_subdirectory_exclude_platforms(httpd)
1515
add_subdirectory_exclude_platforms(iperf)
1616
add_subdirectory_exclude_platforms(ntp_client)
17+
add_subdirectory_exclude_platforms(ntp_system_time)
1718
add_subdirectory_exclude_platforms(tcp_client)
1819
add_subdirectory_exclude_platforms(tcp_server)
1920
add_subdirectory_exclude_platforms(udp_beacon)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
add_executable(picow_ntp_system_time
2+
ntp_system_time.c
3+
)
4+
target_compile_definitions(picow_ntp_system_time PRIVATE
5+
WIFI_SSID=\"${WIFI_SSID}\"
6+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
7+
)
8+
target_include_directories(picow_ntp_system_time PRIVATE
9+
${CMAKE_CURRENT_LIST_DIR}
10+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
11+
)
12+
target_link_libraries(picow_ntp_system_time
13+
pico_cyw43_arch_lwip_threadsafe_background
14+
pico_lwip_sntp # LWIP sntp application
15+
pico_aon_timer # high-level API for "always on" timer
16+
pico_sync # thread synchronisation (mutex)
17+
pico_stdlib
18+
)
19+
20+
pico_add_extra_outputs(picow_ntp_system_time)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Overview
2+
3+
Creates a time of day clock that periodically synchronises itself to Internet time servers using simple NTP (see [RFC 4330](https://datatracker.ietf.org/doc/html/rfc4330)).
4+
5+
The example connects to Wi-Fi and displays the local time in the UK or another timezone that you specify, synchronised once an hour to one of the servers from [ntp.pool.org](https://www.ntppool.org/en/).
6+
7+
Uses the SNTP application provided by lwIP and the Pico 'always-on timer' _(RTC on Pico/rp2040, powman timer on Pico-2/rp2350)_.
8+
9+
# Running the example
10+
11+
First provide the SSID and password of your Wi-Fi network by editing `CmakeLists.txt` or from your environment; then build and run the example as usual.
12+
13+
You should see something like this:
14+
15+
```
16+
Connecting to Wi-Fi...
17+
connect status: joining
18+
connect status: link up
19+
Connected
20+
IP address 192.168.0.100
21+
system time not yet initialised
22+
-> initialised system time from NTP
23+
GMT: Sun Oct 26 10:41:07 2025
24+
GMT: Sun Oct 26 10:41:12 2025
25+
...
26+
```
27+
28+
### To use it in your own code
29+
Configure the lwIP callbacks and connect to the network as shown in the example. You can then initialise the background NTP synchronisation like this:
30+
31+
```
32+
sntp_setoperatingmode(SNTP_OPMODE_POLL);
33+
sntp_init();
34+
```
35+
36+
Your code can now call
37+
38+
```
39+
void get_time_utc(struct timespec *)
40+
```
41+
42+
whenever it wants the current UTC time.
43+
44+
You can also use the [pico_aon_timer API](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer) to read the time directly or to set alarms. _Note however that with a direct read it is theoretically possible (although very unlikely) to get an erroneous result if NTP was in the process of updating the timer in the background._
45+
46+
To reduce the logging level change the `SNTP_DEBUG` option in **lwipopts.h** to `LWIP_DBG_OFF` and/or remove the informational messages from `sntp_set_system_time_us()` in **ntp_system_time.c**.
47+
48+
49+
# Further details
50+
51+
The example uses:
52+
53+
1. the [SNTP application](https://www.nongnu.org/lwip/2_0_x/group__sntp.html) provided by the lwIP network stack
54+
2. the Pico SDK high level "always on timer" abstraction: [pico_aon_timer](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer)
55+
3. a [POSIX timezone](https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) to convert UTC to local time
56+
57+
### lwIP SNTP
58+
59+
The lwIP SNTP app provides a straightworward way to obtain and process timestamps from a pool of NTP servers without the complexity of a full NTP implementation. The lwIP documentation covers the [configuration options](https://www.nongnu.org/lwip/2_0_x/group__sntp.html) but the comments in the [source code](https://github.com/lwip-tcpip/lwip/blob/master/src/apps/sntp/sntp.c) are also very helpful.
60+
61+
SNTP uses the **macros** `SNTP_GET_SYSTEM_TIME(sec, us)` and `SNTP_SET_SYSTEM_TIME(sec, us)` to call user-provided functions for accessing the system clock. The example defines the macros in `lwipopts.h` and the callbacks themselves are near the top of `ntp_system_time.c`.
62+
63+
Note that the example runs lwIP/SNTP from `pico_cyw43_arch` in _threadsafe background mode_ as described in [SDK Networking](https://www.raspberrypi.com/documentation/pico-sdk/networking.html#group_pico_cyw43_arch).
64+
If you reconfigure it to use _polling mode_ then your user code should periodically call `cyw43_arch_poll()`.
65+
66+
### Always on timer
67+
68+
The SDK provides the high level [pico_aon_timer](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer) API to provide the same always-on timer functions on Pico and Pico-2 despite their hardware differences.
69+
70+
On the original Pico (rp2040 device) these functions use the real time clock (RTC) and on the Pico-2 (rp2350 device) the POWMAN timer.
71+
72+
For further details refer to the [SDK documentation](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer).
73+
74+
### POSIX timezone
75+
76+
NTP timestamps always refer to universal coordinated time (UTC) in seconds past the epoch. In contrast users and user applications often require **local time**, which varies from region to region and at different times of the year (daylight-saving time or DST).
77+
78+
Converting from UTC to local time often requires inconventient rules, but fortunately however the Pico SDK time-conversion functions like `ctime()` and `pico_localtime_r()` do it automatically if you define a **POSIX timezone (TZ)**.
79+
80+
The example shows a suitable definition for the Europe/London timezone:
81+
```
82+
setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1);
83+
```
84+
85+
which means
86+
```
87+
Normal time ("GMT") is UTC +0. Daylight-saving time ("BST") runs from 1am on the last Sunday in March to 2am on the last Sunday in October.
88+
```
89+
90+
The format to define your own POSIX timezone is pretty straightforward and can be found [here](https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html).
91+
92+
_Note that it is entirely optional to create a local timezone: without one the Pico SDK time-conversion functions will simply use UTC._
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#ifndef _LWIPOPTS_H
2+
#define _LWIPOPTS_H
3+
4+
// Extra options for the lwIP/SNTP application
5+
// (see https://www.nongnu.org/lwip/2_1_x/group__sntp__opts.html)
6+
//
7+
// This example uses a common include to avoid repetition
8+
#include "lwipopts_examples_common.h"
9+
10+
// If we use SNTP we should increase the number of LWIP system timeouts by one
11+
#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1)
12+
#define SNTP_MAX_SERVERS LWIP_DHCP_MAX_NTP_SERVERS
13+
#define SNTP_GET_SERVERS_FROM_DHCP LWIP_DHCP_GET_NTP_SRV
14+
#define SNTP_SERVER_DNS 1
15+
#define SNTP_SERVER_ADDRESS "pool.ntp.org"
16+
// show debug information from the lwIP/SNTP application
17+
#define SNTP_DEBUG LWIP_DBG_ON
18+
#define SNTP_PORT LWIP_IANA_PORT_SNTP
19+
// verify IP addresses and port numbers of received packets
20+
#define SNTP_CHECK_RESPONSE 2
21+
// compensate for packet transmission delay
22+
#define SNTP_COMP_ROUNDTRIP 1
23+
#define SNTP_STARTUP_DELAY 1
24+
#define SNTP_STARTUP_DELAY_FUNC (LWIP_RAND() % 5000)
25+
#define SNTP_RECV_TIMEOUT 15000
26+
// how often to query the NTP servers, in ms (60000 is the minimum permitted by RFC4330)
27+
#define SNTP_UPDATE_DELAY 3600000
28+
29+
// configure SNTP to use our callback to read the system time
30+
#define SNTP_GET_SYSTEM_TIME(sec, us) sntp_get_system_time_us(&(sec), &(us))
31+
32+
#define SNTP_RETRY_TIMEOUT SNTP_RECV_TIMEOUT
33+
#define SNTP_RETRY_TIMEOUT_MAX (SNTP_RETRY_TIMEOUT * 10)
34+
#define SNTP_RETRY_TIMEOUT_EXP 1
35+
#define SNTP_MONITOR_SERVER_REACHABILITY 1
36+
37+
// configure SNTP to use our callback to set the system time
38+
#define SNTP_SET_SYSTEM_TIME_US(sec, us) sntp_set_system_time_us(sec, us)
39+
40+
// declare our callback functions (they are defined in ntp_system_time.c)
41+
#include "stdint.h"
42+
void sntp_set_system_time_us(uint32_t sec, uint32_t us);
43+
void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t *us_ptr);
44+
45+
#endif /* __LWIPOPTS_H__ */
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Copyright (c) 2025 mjcross
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <stdio.h>
8+
#include "pico/stdlib.h"
9+
#include "pico/cyw43_arch.h"
10+
#include "lwip/apps/sntp.h"
11+
#include "pico/util/datetime.h"
12+
#include "pico/aon_timer.h"
13+
#include "pico/mutex.h"
14+
15+
// create a mutex to avoid reading the aon_timer at the same time as lwIP/SNTP is updating it
16+
auto_init_mutex(aon_timer_mutex);
17+
static bool aon_timer_is_initialised = false;
18+
19+
// callback for lwIP/SNTP to set the aon_timer to UTC (see lwipopts.h)
20+
// this is called every time the application receives a valid NTP server response
21+
void sntp_set_system_time_us(uint32_t sec, uint32_t us) {
22+
static struct timespec ntp_ts;
23+
ntp_ts.tv_sec = sec;
24+
ntp_ts.tv_nsec = us * 1000;
25+
26+
if (aon_timer_is_initialised) {
27+
// wait up to 10ms to obtain exclusive access to the aon_timer
28+
if (mutex_enter_timeout_ms (&aon_timer_mutex, 10)) {
29+
aon_timer_set_time(&ntp_ts);
30+
mutex_exit(&aon_timer_mutex); // release the mutex as soon as possible
31+
puts("-> updated system time from NTP");
32+
} else {
33+
puts("-> skipped NTP system time update (aon_timer was busy)");
34+
}
35+
} else {
36+
// the aon_timer is uninitialised so we don't need exclusive access
37+
aon_timer_is_initialised = aon_timer_start(&ntp_ts);
38+
puts("-> initialised system time from NTP");
39+
}
40+
}
41+
42+
// callback for lwIP/SNTP to read system time (UTC) from the aon_timer
43+
// when it needs to (eg) calculate the roundtrip transmission delay
44+
void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t * us_ptr) {
45+
static struct timespec sys_ts;
46+
// we don't need exclusive access because we are on the background thread
47+
aon_timer_get_time(&sys_ts);
48+
*sec_ptr = sys_ts.tv_sec;
49+
*us_ptr = sys_ts.tv_nsec / 1000;
50+
}
51+
52+
// function for user code to safely read the system time (UTC) asynchronously
53+
int get_time_utc(struct timespec *ts_ptr) {
54+
int retval = 1;
55+
if (mutex_enter_timeout_ms(&aon_timer_mutex, 10)) {
56+
aon_timer_get_time(ts_ptr);
57+
mutex_exit(&aon_timer_mutex);
58+
retval = 0;
59+
}
60+
return retval;
61+
}
62+
63+
int main() {
64+
stdio_init_all();
65+
66+
// Set local timezone for London
67+
68+
// BST starts at 01:00 on the last Sunday in March and ends at 02:00 on the last Sunday in October
69+
70+
71+
// OPTIONAL: if you define a POSIX TZ here then the example will display local time instead of UTC.
72+
// For the format see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html
73+
setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); // <-- this is the timezone spec for Europe/London
74+
// there is no need to call tzset()
75+
76+
// Initialise the Wi-Fi chip
77+
if (cyw43_arch_init()) {
78+
printf("Wi-Fi init failed\n");
79+
return -1;
80+
}
81+
82+
// Enable wifi station mode
83+
cyw43_arch_enable_sta_mode();
84+
printf("Connecting to Wi-Fi...\n");
85+
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
86+
printf("failed to connect\n");
87+
return 1;
88+
}
89+
90+
// display the ip address in human readable form
91+
uint8_t *ip_address = (uint8_t*)&(netif_default->ip_addr.addr);
92+
printf("IP address %d.%d.%d.%d\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
93+
94+
// initialise the lwIP/SNTP application
95+
sntp_setoperatingmode(SNTP_OPMODE_POLL); // lwIP/SNTP also accepts SNTP_OPMODE_LISTENONLY
96+
sntp_init();
97+
98+
99+
// ----- simple demonstration of how to read and display the system time -----
100+
//
101+
struct timespec ts;
102+
struct tm tm;
103+
104+
while (true) {
105+
106+
if(aon_timer_is_initialised) {
107+
108+
// read the current time as UTC seconds and ms since the epoch
109+
get_time_utc(&ts);
110+
111+
// if you simply want to display the local time you could now do so with
112+
// puts(ctime(&(ts.tv_sec)));
113+
114+
// to unpack the hours/mins/seconds etc for the local time zone (if defined, see above)
115+
pico_localtime_r(&(ts.tv_sec), &tm); // convert UTC linear time to broken-down local time
116+
printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); // display as text
117+
118+
} else {
119+
puts("system time not yet initialised");
120+
}
121+
122+
sleep_ms(5000); // do nothing for 5 seconds
123+
}
124+
}

0 commit comments

Comments
 (0)