From 658a22f48d7c6daba64db7419a8448f008ed083d Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Sun, 26 Oct 2025 17:34:41 +0000 Subject: [PATCH 01/12] add example pico_w/wifi/ntp_system_time --- README.md | 1 + pico_w/wifi/CMakeLists.txt | 1 + pico_w/wifi/ntp_system_time/CMakeLists.txt | 20 +++ pico_w/wifi/ntp_system_time/README.md | 92 +++++++++++++ pico_w/wifi/ntp_system_time/lwipopts.h | 45 +++++++ pico_w/wifi/ntp_system_time/ntp_system_time.c | 124 ++++++++++++++++++ 6 files changed, 283 insertions(+) create mode 100644 pico_w/wifi/ntp_system_time/CMakeLists.txt create mode 100644 pico_w/wifi/ntp_system_time/README.md create mode 100644 pico_w/wifi/ntp_system_time/lwipopts.h create mode 100644 pico_w/wifi/ntp_system_time/ntp_system_time.c diff --git a/README.md b/README.md index f43a7339a..db3532ace 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ App|Description [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. [picow_iperf_server](pico_w/wifi/iperf) | Runs an "iperf" server for WiFi speed testing. [picow_ntp_client](pico_w/wifi/ntp_client) | Connects to an NTP server to fetch and display the current time. +[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. [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. [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. [picow_tls_client](pico_w/wifi/tls_client) | Demonstrates how to make a HTTPS request using TLS. diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index b096d2583..d38b450be 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -14,6 +14,7 @@ else() add_subdirectory_exclude_platforms(httpd) add_subdirectory_exclude_platforms(iperf) add_subdirectory_exclude_platforms(ntp_client) + add_subdirectory_exclude_platforms(ntp_system_time) add_subdirectory_exclude_platforms(tcp_client) add_subdirectory_exclude_platforms(tcp_server) add_subdirectory_exclude_platforms(udp_beacon) diff --git a/pico_w/wifi/ntp_system_time/CMakeLists.txt b/pico_w/wifi/ntp_system_time/CMakeLists.txt new file mode 100644 index 000000000..e48815d57 --- /dev/null +++ b/pico_w/wifi/ntp_system_time/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(picow_ntp_system_time + ntp_system_time.c + ) +target_compile_definitions(picow_ntp_system_time PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + ) +target_include_directories(picow_ntp_system_time PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts +) +target_link_libraries(picow_ntp_system_time + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_sntp # LWIP sntp application + pico_aon_timer # high-level API for "always on" timer + pico_sync # thread synchronisation (mutex) + pico_stdlib + ) + +pico_add_extra_outputs(picow_ntp_system_time) \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/README.md b/pico_w/wifi/ntp_system_time/README.md new file mode 100644 index 000000000..6f45c76bc --- /dev/null +++ b/pico_w/wifi/ntp_system_time/README.md @@ -0,0 +1,92 @@ +# Overview + +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)). + +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/). + +Uses the SNTP application provided by lwIP and the Pico 'always-on timer' _(RTC on Pico/rp2040, powman timer on Pico-2/rp2350)_. + +# Running the example + +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. + +You should see something like this: + +``` +Connecting to Wi-Fi... +connect status: joining +connect status: link up +Connected +IP address 192.168.0.100 +system time not yet initialised +-> initialised system time from NTP +GMT: Sun Oct 26 10:41:07 2025 +GMT: Sun Oct 26 10:41:12 2025 +... +``` + +### To use it in your own code +Configure the lwIP callbacks and connect to the network as shown in the example. You can then initialise the background NTP synchronisation like this: + +``` +sntp_setoperatingmode(SNTP_OPMODE_POLL); +sntp_init(); +``` + +Your code can now call + +``` +void get_time_utc(struct timespec *) +``` + +whenever it wants the current UTC time. + + 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._ + + 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**. + + +# Further details + +The example uses: + +1. the [SNTP application](https://www.nongnu.org/lwip/2_0_x/group__sntp.html) provided by the lwIP network stack +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) +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 + +### lwIP SNTP + +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. + +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`. + +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). +If you reconfigure it to use _polling mode_ then your user code should periodically call `cyw43_arch_poll()`. + +### Always on timer + +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. + +On the original Pico (rp2040 device) these functions use the real time clock (RTC) and on the Pico-2 (rp2350 device) the POWMAN timer. + +For further details refer to the [SDK documentation](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer). + +### POSIX timezone + +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). + +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)**. + +The example shows a suitable definition for the Europe/London timezone: +``` +setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); +``` + +which means +``` +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. +``` + +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). + +_Note that it is entirely optional to create a local timezone: without one the Pico SDK time-conversion functions will simply use UTC._ \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/lwipopts.h b/pico_w/wifi/ntp_system_time/lwipopts.h new file mode 100644 index 000000000..5c9d7ba3c --- /dev/null +++ b/pico_w/wifi/ntp_system_time/lwipopts.h @@ -0,0 +1,45 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Extra options for the lwIP/SNTP application +// (see https://www.nongnu.org/lwip/2_1_x/group__sntp__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +// If we use SNTP we should increase the number of LWIP system timeouts by one +#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1) +#define SNTP_MAX_SERVERS LWIP_DHCP_MAX_NTP_SERVERS +#define SNTP_GET_SERVERS_FROM_DHCP LWIP_DHCP_GET_NTP_SRV +#define SNTP_SERVER_DNS 1 +#define SNTP_SERVER_ADDRESS "pool.ntp.org" +// show debug information from the lwIP/SNTP application +#define SNTP_DEBUG LWIP_DBG_ON +#define SNTP_PORT LWIP_IANA_PORT_SNTP +// verify IP addresses and port numbers of received packets +#define SNTP_CHECK_RESPONSE 2 +// compensate for packet transmission delay +#define SNTP_COMP_ROUNDTRIP 1 +#define SNTP_STARTUP_DELAY 1 +#define SNTP_STARTUP_DELAY_FUNC (LWIP_RAND() % 5000) +#define SNTP_RECV_TIMEOUT 15000 +// how often to query the NTP servers, in ms (60000 is the minimum permitted by RFC4330) +#define SNTP_UPDATE_DELAY 3600000 + +// configure SNTP to use our callback to read the system time +#define SNTP_GET_SYSTEM_TIME(sec, us) sntp_get_system_time_us(&(sec), &(us)) + +#define SNTP_RETRY_TIMEOUT SNTP_RECV_TIMEOUT +#define SNTP_RETRY_TIMEOUT_MAX (SNTP_RETRY_TIMEOUT * 10) +#define SNTP_RETRY_TIMEOUT_EXP 1 +#define SNTP_MONITOR_SERVER_REACHABILITY 1 + +// configure SNTP to use our callback to set the system time +#define SNTP_SET_SYSTEM_TIME_US(sec, us) sntp_set_system_time_us(sec, us) + +// declare our callback functions (they are defined in ntp_system_time.c) +#include "stdint.h" +void sntp_set_system_time_us(uint32_t sec, uint32_t us); +void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t *us_ptr); + +#endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c new file mode 100644 index 000000000..48a87eb4d --- /dev/null +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2025 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "lwip/apps/sntp.h" +#include "pico/util/datetime.h" +#include "pico/aon_timer.h" +#include "pico/mutex.h" + +// create a mutex to avoid reading the aon_timer at the same time as lwIP/SNTP is updating it +auto_init_mutex(aon_timer_mutex); +static bool aon_timer_is_initialised = false; + +// callback for lwIP/SNTP to set the aon_timer to UTC (see lwipopts.h) +// this is called every time the application receives a valid NTP server response +void sntp_set_system_time_us(uint32_t sec, uint32_t us) { + static struct timespec ntp_ts; + ntp_ts.tv_sec = sec; + ntp_ts.tv_nsec = us * 1000; + + if (aon_timer_is_initialised) { + // wait up to 10ms to obtain exclusive access to the aon_timer + if (mutex_enter_timeout_ms (&aon_timer_mutex, 10)) { + aon_timer_set_time(&ntp_ts); + mutex_exit(&aon_timer_mutex); // release the mutex as soon as possible + puts("-> updated system time from NTP"); + } else { + puts("-> skipped NTP system time update (aon_timer was busy)"); + } + } else { + // the aon_timer is uninitialised so we don't need exclusive access + aon_timer_is_initialised = aon_timer_start(&ntp_ts); + puts("-> initialised system time from NTP"); + } +} + +// callback for lwIP/SNTP to read system time (UTC) from the aon_timer +// when it needs to (eg) calculate the roundtrip transmission delay +void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t * us_ptr) { + static struct timespec sys_ts; + // we don't need exclusive access because we are on the background thread + aon_timer_get_time(&sys_ts); + *sec_ptr = sys_ts.tv_sec; + *us_ptr = sys_ts.tv_nsec / 1000; +} + +// function for user code to safely read the system time (UTC) asynchronously +int get_time_utc(struct timespec *ts_ptr) { + int retval = 1; + if (mutex_enter_timeout_ms(&aon_timer_mutex, 10)) { + aon_timer_get_time(ts_ptr); + mutex_exit(&aon_timer_mutex); + retval = 0; + } + return retval; +} + +int main() { + stdio_init_all(); + + // Set local timezone for London + + // BST starts at 01:00 on the last Sunday in March and ends at 02:00 on the last Sunday in October + + + // OPTIONAL: if you define a POSIX TZ here then the example will display local time instead of UTC. + // For the format see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html + setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); // <-- this is the timezone spec for Europe/London + // there is no need to call tzset() + + // Initialise the Wi-Fi chip + if (cyw43_arch_init()) { + printf("Wi-Fi init failed\n"); + return -1; + } + + // Enable wifi station mode + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("failed to connect\n"); + return 1; + } + + // display the ip address in human readable form + uint8_t *ip_address = (uint8_t*)&(netif_default->ip_addr.addr); + printf("IP address %d.%d.%d.%d\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3]); + + // initialise the lwIP/SNTP application + sntp_setoperatingmode(SNTP_OPMODE_POLL); // lwIP/SNTP also accepts SNTP_OPMODE_LISTENONLY + sntp_init(); + + + // ----- simple demonstration of how to read and display the system time ----- + // + struct timespec ts; + struct tm tm; + + while (true) { + + if(aon_timer_is_initialised) { + + // read the current time as UTC seconds and ms since the epoch + get_time_utc(&ts); + + // if you simply want to display the local time you could now do so with + // puts(ctime(&(ts.tv_sec))); + + // to unpack the hours/mins/seconds etc for the local time zone (if defined, see above) + pico_localtime_r(&(ts.tv_sec), &tm); // convert UTC linear time to broken-down local time + printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); // display as text + + } else { + puts("system time not yet initialised"); + } + + sleep_ms(5000); // do nothing for 5 seconds + } +} From 4b3e61b14b80e6e0451075ecf54b444aaa0f92de Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Sun, 26 Oct 2025 17:34:41 +0000 Subject: [PATCH 02/12] add example pico_w/wifi/ntp_system_time --- README.md | 1 + pico_w/wifi/CMakeLists.txt | 1 + pico_w/wifi/ntp_system_time/CMakeLists.txt | 20 +++ pico_w/wifi/ntp_system_time/README.md | 92 +++++++++++++ pico_w/wifi/ntp_system_time/lwipopts.h | 45 +++++++ pico_w/wifi/ntp_system_time/ntp_system_time.c | 124 ++++++++++++++++++ 6 files changed, 283 insertions(+) create mode 100644 pico_w/wifi/ntp_system_time/CMakeLists.txt create mode 100644 pico_w/wifi/ntp_system_time/README.md create mode 100644 pico_w/wifi/ntp_system_time/lwipopts.h create mode 100644 pico_w/wifi/ntp_system_time/ntp_system_time.c diff --git a/README.md b/README.md index f43a7339a..c7de58e0c 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ App|Description [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. [picow_iperf_server](pico_w/wifi/iperf) | Runs an "iperf" server for WiFi speed testing. [picow_ntp_client](pico_w/wifi/ntp_client) | Connects to an NTP server to fetch and display the current time. +[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. [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. [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. [picow_tls_client](pico_w/wifi/tls_client) | Demonstrates how to make a HTTPS request using TLS. diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index b096d2583..d38b450be 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -14,6 +14,7 @@ else() add_subdirectory_exclude_platforms(httpd) add_subdirectory_exclude_platforms(iperf) add_subdirectory_exclude_platforms(ntp_client) + add_subdirectory_exclude_platforms(ntp_system_time) add_subdirectory_exclude_platforms(tcp_client) add_subdirectory_exclude_platforms(tcp_server) add_subdirectory_exclude_platforms(udp_beacon) diff --git a/pico_w/wifi/ntp_system_time/CMakeLists.txt b/pico_w/wifi/ntp_system_time/CMakeLists.txt new file mode 100644 index 000000000..e48815d57 --- /dev/null +++ b/pico_w/wifi/ntp_system_time/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(picow_ntp_system_time + ntp_system_time.c + ) +target_compile_definitions(picow_ntp_system_time PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + ) +target_include_directories(picow_ntp_system_time PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts +) +target_link_libraries(picow_ntp_system_time + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_sntp # LWIP sntp application + pico_aon_timer # high-level API for "always on" timer + pico_sync # thread synchronisation (mutex) + pico_stdlib + ) + +pico_add_extra_outputs(picow_ntp_system_time) \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/README.md b/pico_w/wifi/ntp_system_time/README.md new file mode 100644 index 000000000..6f45c76bc --- /dev/null +++ b/pico_w/wifi/ntp_system_time/README.md @@ -0,0 +1,92 @@ +# Overview + +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)). + +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/). + +Uses the SNTP application provided by lwIP and the Pico 'always-on timer' _(RTC on Pico/rp2040, powman timer on Pico-2/rp2350)_. + +# Running the example + +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. + +You should see something like this: + +``` +Connecting to Wi-Fi... +connect status: joining +connect status: link up +Connected +IP address 192.168.0.100 +system time not yet initialised +-> initialised system time from NTP +GMT: Sun Oct 26 10:41:07 2025 +GMT: Sun Oct 26 10:41:12 2025 +... +``` + +### To use it in your own code +Configure the lwIP callbacks and connect to the network as shown in the example. You can then initialise the background NTP synchronisation like this: + +``` +sntp_setoperatingmode(SNTP_OPMODE_POLL); +sntp_init(); +``` + +Your code can now call + +``` +void get_time_utc(struct timespec *) +``` + +whenever it wants the current UTC time. + + 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._ + + 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**. + + +# Further details + +The example uses: + +1. the [SNTP application](https://www.nongnu.org/lwip/2_0_x/group__sntp.html) provided by the lwIP network stack +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) +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 + +### lwIP SNTP + +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. + +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`. + +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). +If you reconfigure it to use _polling mode_ then your user code should periodically call `cyw43_arch_poll()`. + +### Always on timer + +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. + +On the original Pico (rp2040 device) these functions use the real time clock (RTC) and on the Pico-2 (rp2350 device) the POWMAN timer. + +For further details refer to the [SDK documentation](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer). + +### POSIX timezone + +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). + +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)**. + +The example shows a suitable definition for the Europe/London timezone: +``` +setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); +``` + +which means +``` +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. +``` + +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). + +_Note that it is entirely optional to create a local timezone: without one the Pico SDK time-conversion functions will simply use UTC._ \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/lwipopts.h b/pico_w/wifi/ntp_system_time/lwipopts.h new file mode 100644 index 000000000..5c9d7ba3c --- /dev/null +++ b/pico_w/wifi/ntp_system_time/lwipopts.h @@ -0,0 +1,45 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Extra options for the lwIP/SNTP application +// (see https://www.nongnu.org/lwip/2_1_x/group__sntp__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +// If we use SNTP we should increase the number of LWIP system timeouts by one +#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1) +#define SNTP_MAX_SERVERS LWIP_DHCP_MAX_NTP_SERVERS +#define SNTP_GET_SERVERS_FROM_DHCP LWIP_DHCP_GET_NTP_SRV +#define SNTP_SERVER_DNS 1 +#define SNTP_SERVER_ADDRESS "pool.ntp.org" +// show debug information from the lwIP/SNTP application +#define SNTP_DEBUG LWIP_DBG_ON +#define SNTP_PORT LWIP_IANA_PORT_SNTP +// verify IP addresses and port numbers of received packets +#define SNTP_CHECK_RESPONSE 2 +// compensate for packet transmission delay +#define SNTP_COMP_ROUNDTRIP 1 +#define SNTP_STARTUP_DELAY 1 +#define SNTP_STARTUP_DELAY_FUNC (LWIP_RAND() % 5000) +#define SNTP_RECV_TIMEOUT 15000 +// how often to query the NTP servers, in ms (60000 is the minimum permitted by RFC4330) +#define SNTP_UPDATE_DELAY 3600000 + +// configure SNTP to use our callback to read the system time +#define SNTP_GET_SYSTEM_TIME(sec, us) sntp_get_system_time_us(&(sec), &(us)) + +#define SNTP_RETRY_TIMEOUT SNTP_RECV_TIMEOUT +#define SNTP_RETRY_TIMEOUT_MAX (SNTP_RETRY_TIMEOUT * 10) +#define SNTP_RETRY_TIMEOUT_EXP 1 +#define SNTP_MONITOR_SERVER_REACHABILITY 1 + +// configure SNTP to use our callback to set the system time +#define SNTP_SET_SYSTEM_TIME_US(sec, us) sntp_set_system_time_us(sec, us) + +// declare our callback functions (they are defined in ntp_system_time.c) +#include "stdint.h" +void sntp_set_system_time_us(uint32_t sec, uint32_t us); +void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t *us_ptr); + +#endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c new file mode 100644 index 000000000..48a87eb4d --- /dev/null +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2025 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "lwip/apps/sntp.h" +#include "pico/util/datetime.h" +#include "pico/aon_timer.h" +#include "pico/mutex.h" + +// create a mutex to avoid reading the aon_timer at the same time as lwIP/SNTP is updating it +auto_init_mutex(aon_timer_mutex); +static bool aon_timer_is_initialised = false; + +// callback for lwIP/SNTP to set the aon_timer to UTC (see lwipopts.h) +// this is called every time the application receives a valid NTP server response +void sntp_set_system_time_us(uint32_t sec, uint32_t us) { + static struct timespec ntp_ts; + ntp_ts.tv_sec = sec; + ntp_ts.tv_nsec = us * 1000; + + if (aon_timer_is_initialised) { + // wait up to 10ms to obtain exclusive access to the aon_timer + if (mutex_enter_timeout_ms (&aon_timer_mutex, 10)) { + aon_timer_set_time(&ntp_ts); + mutex_exit(&aon_timer_mutex); // release the mutex as soon as possible + puts("-> updated system time from NTP"); + } else { + puts("-> skipped NTP system time update (aon_timer was busy)"); + } + } else { + // the aon_timer is uninitialised so we don't need exclusive access + aon_timer_is_initialised = aon_timer_start(&ntp_ts); + puts("-> initialised system time from NTP"); + } +} + +// callback for lwIP/SNTP to read system time (UTC) from the aon_timer +// when it needs to (eg) calculate the roundtrip transmission delay +void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t * us_ptr) { + static struct timespec sys_ts; + // we don't need exclusive access because we are on the background thread + aon_timer_get_time(&sys_ts); + *sec_ptr = sys_ts.tv_sec; + *us_ptr = sys_ts.tv_nsec / 1000; +} + +// function for user code to safely read the system time (UTC) asynchronously +int get_time_utc(struct timespec *ts_ptr) { + int retval = 1; + if (mutex_enter_timeout_ms(&aon_timer_mutex, 10)) { + aon_timer_get_time(ts_ptr); + mutex_exit(&aon_timer_mutex); + retval = 0; + } + return retval; +} + +int main() { + stdio_init_all(); + + // Set local timezone for London + + // BST starts at 01:00 on the last Sunday in March and ends at 02:00 on the last Sunday in October + + + // OPTIONAL: if you define a POSIX TZ here then the example will display local time instead of UTC. + // For the format see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html + setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); // <-- this is the timezone spec for Europe/London + // there is no need to call tzset() + + // Initialise the Wi-Fi chip + if (cyw43_arch_init()) { + printf("Wi-Fi init failed\n"); + return -1; + } + + // Enable wifi station mode + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("failed to connect\n"); + return 1; + } + + // display the ip address in human readable form + uint8_t *ip_address = (uint8_t*)&(netif_default->ip_addr.addr); + printf("IP address %d.%d.%d.%d\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3]); + + // initialise the lwIP/SNTP application + sntp_setoperatingmode(SNTP_OPMODE_POLL); // lwIP/SNTP also accepts SNTP_OPMODE_LISTENONLY + sntp_init(); + + + // ----- simple demonstration of how to read and display the system time ----- + // + struct timespec ts; + struct tm tm; + + while (true) { + + if(aon_timer_is_initialised) { + + // read the current time as UTC seconds and ms since the epoch + get_time_utc(&ts); + + // if you simply want to display the local time you could now do so with + // puts(ctime(&(ts.tv_sec))); + + // to unpack the hours/mins/seconds etc for the local time zone (if defined, see above) + pico_localtime_r(&(ts.tv_sec), &tm); // convert UTC linear time to broken-down local time + printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); // display as text + + } else { + puts("system time not yet initialised"); + } + + sleep_ms(5000); // do nothing for 5 seconds + } +} From cea5dfbdf9df4d7d7f32605130bc2bad629719cf Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Mon, 27 Oct 2025 11:46:58 +0000 Subject: [PATCH 03/12] Minor typos --- pico_w/wifi/ntp_system_time/README.md | 8 ++++---- pico_w/wifi/ntp_system_time/ntp_system_time.c | 7 +------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pico_w/wifi/ntp_system_time/README.md b/pico_w/wifi/ntp_system_time/README.md index 6f45c76bc..d85a3b0a6 100644 --- a/pico_w/wifi/ntp_system_time/README.md +++ b/pico_w/wifi/ntp_system_time/README.md @@ -2,13 +2,13 @@ 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)). -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/). +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 [pool.ntp.org](https://www.ntppool.org/en/). Uses the SNTP application provided by lwIP and the Pico 'always-on timer' _(RTC on Pico/rp2040, powman timer on Pico-2/rp2350)_. # Running the example -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. +Provide the SSID and password of your Wi-Fi network by editing `CmakeLists.txt` or on the command line; then build and run the example as usual. You should see something like this: @@ -56,7 +56,7 @@ The example uses: ### lwIP SNTP -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. +The lwIP SNTP app provides a straightforward 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. 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`. @@ -75,7 +75,7 @@ For further details refer to the [SDK documentation](https://www.raspberrypi.com 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). -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)**. +Converting from UTC to local time often requires inconventient rules, but fortunately the Pico SDK time-conversion functions like `ctime()` and `pico_localtime_r()` can do it automatically if you define a **POSIX timezone (TZ)**. The example shows a suitable definition for the Europe/London timezone: ``` diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index 48a87eb4d..c490b13b4 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -63,12 +63,7 @@ int get_time_utc(struct timespec *ts_ptr) { int main() { stdio_init_all(); - // Set local timezone for London - - // BST starts at 01:00 on the last Sunday in March and ends at 02:00 on the last Sunday in October - - - // OPTIONAL: if you define a POSIX TZ here then the example will display local time instead of UTC. + // If you (optionally) define a POSIX TZ here then the example will display local time instead of UTC. // For the format see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); // <-- this is the timezone spec for Europe/London // there is no need to call tzset() From 815159f02346d31a9a1a5a626503ab14e97d53c0 Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Mon, 27 Oct 2025 17:41:10 +0000 Subject: [PATCH 04/12] Improve comments on UTC->local time --- README.md | 2 +- pico_w/wifi/ntp_system_time/README.md | 6 ++--- pico_w/wifi/ntp_system_time/ntp_system_time.c | 25 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c7de58e0c..5d4581b80 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ App|Description [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. [picow_iperf_server](pico_w/wifi/iperf) | Runs an "iperf" server for WiFi speed testing. [picow_ntp_client](pico_w/wifi/ntp_client) | Connects to an NTP server to fetch and display the current time. -[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. +[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. [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. [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. [picow_tls_client](pico_w/wifi/tls_client) | Demonstrates how to make a HTTPS request using TLS. diff --git a/pico_w/wifi/ntp_system_time/README.md b/pico_w/wifi/ntp_system_time/README.md index d85a3b0a6..ee4775fe7 100644 --- a/pico_w/wifi/ntp_system_time/README.md +++ b/pico_w/wifi/ntp_system_time/README.md @@ -4,11 +4,11 @@ Creates a time of day clock that periodically synchronises itself to Internet ti 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 [pool.ntp.org](https://www.ntppool.org/en/). -Uses the SNTP application provided by lwIP and the Pico 'always-on timer' _(RTC on Pico/rp2040, powman timer on Pico-2/rp2350)_. +Uses the SNTP application provided by lwIP and the Pico 'always-on timer' _(RTC on Pico/RP2040, powman timer on Pico-2/RP2350)_. # Running the example -Provide the SSID and password of your Wi-Fi network by editing `CmakeLists.txt` or on the command line; then build and run the example as usual. +Provide the SSID and password of your Wi-Fi network by editing `CMakeLists.txt` or on the command line; then build and run the example as usual. You should see something like this: @@ -67,7 +67,7 @@ If you reconfigure it to use _polling mode_ then your user code should periodica 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. -On the original Pico (rp2040 device) these functions use the real time clock (RTC) and on the Pico-2 (rp2350 device) the POWMAN timer. +On the original Pico (RP2040) these functions use the real time clock (RTC) and on the Pico-2 (RP2350) the POWMAN timer. For further details refer to the [SDK documentation](https://www.raspberrypi.com/documentation/pico-sdk/high_level.html#group_pico_aon_timer). diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index c490b13b4..e0cd3e805 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -63,11 +63,6 @@ int get_time_utc(struct timespec *ts_ptr) { int main() { stdio_init_all(); - // If you (optionally) define a POSIX TZ here then the example will display local time instead of UTC. - // For the format see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html - setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); // <-- this is the timezone spec for Europe/London - // there is no need to call tzset() - // Initialise the Wi-Fi chip if (cyw43_arch_init()) { printf("Wi-Fi init failed\n"); @@ -96,6 +91,12 @@ int main() { struct timespec ts; struct tm tm; + // OPTIONAL: set the 'TZ' env variable to the local POSIX timezone (in this case Europe/London, + // to create your own see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) + setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); + // If you set 'TZ' then functions like ctime(), localtime() and their variants will automatically + // give results converted to the local timezone instead of UTC (see below). + while (true) { if(aon_timer_is_initialised) { @@ -103,12 +104,16 @@ int main() { // read the current time as UTC seconds and ms since the epoch get_time_utc(&ts); - // if you simply want to display the local time you could now do so with - // puts(ctime(&(ts.tv_sec))); + // if you just want a string representation of the current time and you're not interested + // in the individual date/time fields, you could simply call ctime(&(ts.tv_sec)) here + // (if you have set 'TZ' then the result will be in local time, otherwise UTC) + + // unpack the individual date/time fields (if you have set 'TZ' then the values will be + // in local time, otherwise UTC) + pico_localtime_r(&(ts.tv_sec), &tm); - // to unpack the hours/mins/seconds etc for the local time zone (if defined, see above) - pico_localtime_r(&(ts.tv_sec), &tm); // convert UTC linear time to broken-down local time - printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); // display as text + // display individual date/time fields in human readable form + printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); } else { puts("system time not yet initialised"); From 6cc4cd3c3a56e8f9e2d1665e9247e0dcee3590e7 Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Mon, 27 Oct 2025 17:57:46 +0000 Subject: [PATCH 05/12] one more typo --- pico_w/wifi/ntp_system_time/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico_w/wifi/ntp_system_time/README.md b/pico_w/wifi/ntp_system_time/README.md index ee4775fe7..0626b6f98 100644 --- a/pico_w/wifi/ntp_system_time/README.md +++ b/pico_w/wifi/ntp_system_time/README.md @@ -75,7 +75,7 @@ For further details refer to the [SDK documentation](https://www.raspberrypi.com 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). -Converting from UTC to local time often requires inconventient rules, but fortunately the Pico SDK time-conversion functions like `ctime()` and `pico_localtime_r()` can do it automatically if you define a **POSIX timezone (TZ)**. +Converting from UTC to local time often requires inconvenient rules, but fortunately the Pico SDK time-conversion functions like `ctime()` and `pico_localtime_r()` can do it automatically if you define a **POSIX timezone (TZ)**. The example shows a suitable definition for the Europe/London timezone: ``` From fd98656e4ad2b2c8eedea6f6b8bbf0c36597c82d Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Mon, 27 Oct 2025 22:00:41 +0000 Subject: [PATCH 06/12] Clarify use of ctime() --- pico_w/wifi/ntp_system_time/ntp_system_time.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index e0cd3e805..a12f8ab7b 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -105,11 +105,16 @@ int main() { get_time_utc(&ts); // if you just want a string representation of the current time and you're not interested - // in the individual date/time fields, you could simply call ctime(&(ts.tv_sec)) here - // (if you have set 'TZ' then the result will be in local time, otherwise UTC) - - // unpack the individual date/time fields (if you have set 'TZ' then the values will be - // in local time, otherwise UTC) + // in the individual date/time fields, then here you can simply call: + // printf("%s", ctime(&(ts.tv_sec))); + // The string produced is the same as `asctime()` as used below. Note that if you set + // 'TZ' then the output will be in local time, otherwise UTC. + + // you can unpack the raw UTC seconds count into individual date/time fields like this. + // Again, if you set 'TZ' then the values will be in local time, otherwise UTC. Note + // that by default `pico_localtime_r()` just calls `localtime_r()` from the standard 'C' + // library, but the declaration is 'weak' so that a user can override it with their own + // implementation if desired. pico_localtime_r(&(ts.tv_sec), &tm); // display individual date/time fields in human readable form From 8af2a1f4783f9d8eaf5a7cbf4dc6443cf4d31395 Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Mon, 27 Oct 2025 22:13:20 +0000 Subject: [PATCH 07/12] document asctime() output format --- pico_w/wifi/ntp_system_time/ntp_system_time.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index a12f8ab7b..ec83cec97 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -94,21 +94,22 @@ int main() { // OPTIONAL: set the 'TZ' env variable to the local POSIX timezone (in this case Europe/London, // to create your own see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); - // If you set 'TZ' then functions like ctime(), localtime() and their variants will automatically - // give results converted to the local timezone instead of UTC (see below). + // If the environment contains a valid 'TZ' definition then functions like ctime(), localtime() + // and their variants automatically give results converted to the local timezone instead of UTC + // (see below). while (true) { if(aon_timer_is_initialised) { - // read the current time as UTC seconds and ms since the epoch + // safely read the current time as UTC seconds and ms since the epoch get_time_utc(&ts); // if you just want a string representation of the current time and you're not interested // in the individual date/time fields, then here you can simply call: // printf("%s", ctime(&(ts.tv_sec))); - // The string produced is the same as `asctime()` as used below. Note that if you set - // 'TZ' then the output will be in local time, otherwise UTC. + // The string produced is the same as `asctime()` (see below). Note that if you set 'TZ' + // then the output will be in local time, otherwise UTC. // you can unpack the raw UTC seconds count into individual date/time fields like this. // Again, if you set 'TZ' then the values will be in local time, otherwise UTC. Note @@ -119,6 +120,8 @@ int main() { // display individual date/time fields in human readable form printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); + // asctime(), ctime() and their variants produce strings of the form "Mon Oct 27 22:06:08 2025\n" + // - note the trailing '\n'. } else { puts("system time not yet initialised"); From ec5617bd8177f4ec45abc32040ae36287063f820 Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Tue, 28 Oct 2025 09:50:00 +0000 Subject: [PATCH 08/12] remove hardcoding of TZ names --- pico_w/wifi/ntp_system_time/ntp_system_time.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index ec83cec97..07fc34f33 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -94,6 +94,7 @@ int main() { // OPTIONAL: set the 'TZ' env variable to the local POSIX timezone (in this case Europe/London, // to create your own see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); + // If the environment contains a valid 'TZ' definition then functions like ctime(), localtime() // and their variants automatically give results converted to the local timezone instead of UTC // (see below). @@ -119,9 +120,10 @@ int main() { pico_localtime_r(&(ts.tv_sec), &tm); // display individual date/time fields in human readable form - printf("%s: %s", tm.tm_isdst ? "BST": "GMT", asctime(&tm)); + printf("%s: %s", tm.tm_isdst ? tzname[0]: tzname[1], asctime(&tm)); // asctime(), ctime() and their variants produce strings of the form "Mon Oct 27 22:06:08 2025\n" // - note the trailing '\n'. + // extern char *tzname[2] (from time.h) holds the names of the POSIX TZ timezones if defined } else { puts("system time not yet initialised"); From d040091338063036f87a7939fee52e6261bd03e3 Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Tue, 28 Oct 2025 10:22:35 +0000 Subject: [PATCH 09/12] print TZ name and ascii time separately for clarity --- pico_w/wifi/ntp_system_time/ntp_system_time.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index 07fc34f33..334e0d50c 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -94,7 +94,7 @@ int main() { // OPTIONAL: set the 'TZ' env variable to the local POSIX timezone (in this case Europe/London, // to create your own see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); - + // If the environment contains a valid 'TZ' definition then functions like ctime(), localtime() // and their variants automatically give results converted to the local timezone instead of UTC // (see below). @@ -119,11 +119,16 @@ int main() { // implementation if desired. pico_localtime_r(&(ts.tv_sec), &tm); - // display individual date/time fields in human readable form - printf("%s: %s", tm.tm_isdst ? tzname[0]: tzname[1], asctime(&tm)); - // asctime(), ctime() and their variants produce strings of the form "Mon Oct 27 22:06:08 2025\n" - // - note the trailing '\n'. - // extern char *tzname[2] (from time.h) holds the names of the POSIX TZ timezones if defined + // display the name of the currently active local timeszone, if defined + if (getenv("TZ")) { + printf("%s: ", tm.tm_isdst ? tzname[0]: tzname[1]); + // defines `extern char *tzname[2]` to hold the names of the POSIX timezones + } else { + printf("UTC: "); + } + + // display individual date/time fields in the form "Mon Oct 27 22:06:08 2025\n" + printf("%s", asctime(&tm)); } else { puts("system time not yet initialised"); From da300ff553cd7c51772ed4684cfc1637746bb73b Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Tue, 28 Oct 2025 13:38:01 +0000 Subject: [PATCH 10/12] Further documentation and comment tidy ups --- pico_w/wifi/ntp_system_time/README.md | 6 ++-- pico_w/wifi/ntp_system_time/lwipopts.h | 6 ++-- pico_w/wifi/ntp_system_time/ntp_system_time.c | 33 ++++++++++--------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/pico_w/wifi/ntp_system_time/README.md b/pico_w/wifi/ntp_system_time/README.md index 0626b6f98..22e952725 100644 --- a/pico_w/wifi/ntp_system_time/README.md +++ b/pico_w/wifi/ntp_system_time/README.md @@ -52,13 +52,13 @@ The example uses: 1. the [SNTP application](https://www.nongnu.org/lwip/2_0_x/group__sntp.html) provided by the lwIP network stack 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) -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 +3. an optional [POSIX timezone](https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) to convert UTC to local time ### lwIP SNTP The lwIP SNTP app provides a straightforward 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. -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`. +SNTP uses the **macros** `SNTP_GET_SYSTEM_TIME(sec, us)` and `SNTP_SET_SYSTEM_TIME_US(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`. 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). If you reconfigure it to use _polling mode_ then your user code should periodically call `cyw43_arch_poll()`. @@ -87,6 +87,6 @@ which means 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. ``` -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). +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); or you can simply choose a pre-defined one from an online resource such as [this](https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv). _Note that it is entirely optional to create a local timezone: without one the Pico SDK time-conversion functions will simply use UTC._ \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/lwipopts.h b/pico_w/wifi/ntp_system_time/lwipopts.h index 5c9d7ba3c..c61b473e2 100644 --- a/pico_w/wifi/ntp_system_time/lwipopts.h +++ b/pico_w/wifi/ntp_system_time/lwipopts.h @@ -34,12 +34,14 @@ #define SNTP_RETRY_TIMEOUT_EXP 1 #define SNTP_MONITOR_SERVER_REACHABILITY 1 -// configure SNTP to use our callback to set the system time +//* configure SNTP to use our callback functions for reading and setting the system time +#define SNTP_GET_SYSTEM_TIME(sec, us) sntp_get_system_time_us(&(sec), &(us)) #define SNTP_SET_SYSTEM_TIME_US(sec, us) sntp_set_system_time_us(sec, us) -// declare our callback functions (they are defined in ntp_system_time.c) +//* declare our callback functions (the implementations are in ntp_system_time.c) #include "stdint.h" void sntp_set_system_time_us(uint32_t sec, uint32_t us); void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t *us_ptr); + #endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index 334e0d50c..0a2439c04 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -16,8 +16,9 @@ auto_init_mutex(aon_timer_mutex); static bool aon_timer_is_initialised = false; -// callback for lwIP/SNTP to set the aon_timer to UTC (see lwipopts.h) -// this is called every time the application receives a valid NTP server response +// callback for lwIP/SNTP to set the aon_timer to UTC +// we configure SNTP to call this function when it receives a valid NTP timestamp +// (see lwipopts.h) void sntp_set_system_time_us(uint32_t sec, uint32_t us) { static struct timespec ntp_ts; ntp_ts.tv_sec = sec; @@ -40,7 +41,8 @@ void sntp_set_system_time_us(uint32_t sec, uint32_t us) { } // callback for lwIP/SNTP to read system time (UTC) from the aon_timer -// when it needs to (eg) calculate the roundtrip transmission delay +// we configure SNTP to call this function to read the current UTC system time, +// eg to calculate the roundtrip transmission delay (see lwipopts.h) void sntp_get_system_time_us(uint32_t *sec_ptr, uint32_t * us_ptr) { static struct timespec sys_ts; // we don't need exclusive access because we are on the background thread @@ -91,8 +93,9 @@ int main() { struct timespec ts; struct tm tm; - // OPTIONAL: set the 'TZ' env variable to the local POSIX timezone (in this case Europe/London, - // to create your own see https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html) + // OPTIONAL: set the 'TZ' env variable to the local POSIX timezone (in this case Europe/London) + // For the format see: https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_node/libc_431.html + // or just copy one from (eg): https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv setenv("TZ", "BST0GMT,M3.5.0/1,M10.5.0/2", 1); // If the environment contains a valid 'TZ' definition then functions like ctime(), localtime() @@ -108,15 +111,14 @@ int main() { // if you just want a string representation of the current time and you're not interested // in the individual date/time fields, then here you can simply call: - // printf("%s", ctime(&(ts.tv_sec))); - // The string produced is the same as `asctime()` (see below). Note that if you set 'TZ' - // then the output will be in local time, otherwise UTC. - - // you can unpack the raw UTC seconds count into individual date/time fields like this. - // Again, if you set 'TZ' then the values will be in local time, otherwise UTC. Note - // that by default `pico_localtime_r()` just calls `localtime_r()` from the standard 'C' - // library, but the declaration is 'weak' so that a user can override it with their own - // implementation if desired. + + // if you don't need the date/time fields, you can call `ctime()` or one of its variants + // here to convert the raw timer value into a string like "Mon Oct 27 22:06:08 2025\n". + // If you have defined a valid 'TZ' the string will be in local time, otherwise UTC. + //printf("%s", ctime(&(ts.tv_sec))); + + // you can extract the date/time fields use `localtime()` or one of its variants. If you + // have defined a valid 'TZ' then the field values will be in local time, otherwise UTC. pico_localtime_r(&(ts.tv_sec), &tm); // display the name of the currently active local timeszone, if defined @@ -127,7 +129,8 @@ int main() { printf("UTC: "); } - // display individual date/time fields in the form "Mon Oct 27 22:06:08 2025\n" + // you can use `asctime()` and its variants to convert the date/time fields into a string + // like: "Mon Oct 27 22:06:08 2025\n". If you need more flexibility consider `strftime()` printf("%s", asctime(&tm)); } else { From c4f139a7ff7fb38ff8583490b88cd7fe56f699d4 Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Tue, 28 Oct 2025 14:46:19 +0000 Subject: [PATCH 11/12] Fix typos and duplicate line in lwipopts.h --- pico_w/wifi/ntp_system_time/lwipopts.h | 4 ---- pico_w/wifi/ntp_system_time/ntp_system_time.c | 9 +++------ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/pico_w/wifi/ntp_system_time/lwipopts.h b/pico_w/wifi/ntp_system_time/lwipopts.h index c61b473e2..a1cdf11c6 100644 --- a/pico_w/wifi/ntp_system_time/lwipopts.h +++ b/pico_w/wifi/ntp_system_time/lwipopts.h @@ -25,10 +25,6 @@ #define SNTP_RECV_TIMEOUT 15000 // how often to query the NTP servers, in ms (60000 is the minimum permitted by RFC4330) #define SNTP_UPDATE_DELAY 3600000 - -// configure SNTP to use our callback to read the system time -#define SNTP_GET_SYSTEM_TIME(sec, us) sntp_get_system_time_us(&(sec), &(us)) - #define SNTP_RETRY_TIMEOUT SNTP_RECV_TIMEOUT #define SNTP_RETRY_TIMEOUT_MAX (SNTP_RETRY_TIMEOUT * 10) #define SNTP_RETRY_TIMEOUT_EXP 1 diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index 0a2439c04..b021033cf 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -109,15 +109,12 @@ int main() { // safely read the current time as UTC seconds and ms since the epoch get_time_utc(&ts); - // if you just want a string representation of the current time and you're not interested - // in the individual date/time fields, then here you can simply call: - - // if you don't need the date/time fields, you can call `ctime()` or one of its variants - // here to convert the raw timer value into a string like "Mon Oct 27 22:06:08 2025\n". + // if you don't need the date/time fields you can call `ctime()` or one of its variants + // here to convert the UTC seconds count to a string like "Mon Oct 27 22:06:08 2025\n". // If you have defined a valid 'TZ' the string will be in local time, otherwise UTC. //printf("%s", ctime(&(ts.tv_sec))); - // you can extract the date/time fields use `localtime()` or one of its variants. If you + // you can extract the date/time fields using `localtime()` or one of its variants. If you // have defined a valid 'TZ' then the field values will be in local time, otherwise UTC. pico_localtime_r(&(ts.tv_sec), &tm); From d3f5689d5a8f16a9f6cc3b9c23cb3bf4bf0aea4b Mon Sep 17 00:00:00 2001 From: Martin Crossley Date: Thu, 30 Oct 2025 16:03:46 +0000 Subject: [PATCH 12/12] explicitly include in case it isn't pulled in by lwIP setenv() requires which is included via lwIP. Make it explicit in case someone uses the timezone code in a different context. --- pico_w/wifi/ntp_system_time/ntp_system_time.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pico_w/wifi/ntp_system_time/ntp_system_time.c b/pico_w/wifi/ntp_system_time/ntp_system_time.c index b021033cf..aaddb9eeb 100644 --- a/pico_w/wifi/ntp_system_time/ntp_system_time.c +++ b/pico_w/wifi/ntp_system_time/ntp_system_time.c @@ -5,6 +5,7 @@ */ #include +#include // needed for setenv(), although also included by lwIP #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "lwip/apps/sntp.h"