-
Notifications
You must be signed in to change notification settings - Fork 963
add example pico_w/wifi/ntp_system_time #716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mjcross
wants to merge
13
commits into
raspberrypi:develop
Choose a base branch
from
mjcross:ntp_system_time
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
658a22f
add example pico_w/wifi/ntp_system_time
4b3e61b
add example pico_w/wifi/ntp_system_time
1cbc5e8
Merge branch 'ntp_system_time' of https://github.com/mjcross/pico-exa…
cea5dfb
Minor typos
815159f
Improve comments on UTC->local time
6cc4cd3
one more typo
fd98656
Clarify use of ctime()
8af2a1f
document asctime() output format
ec5617b
remove hardcoding of TZ names
d040091
print TZ name and ascii time separately for clarity
da300ff
Further documentation and comment tidy ups
c4f139a
Fix typos and duplicate line in lwipopts.h
d3f5689
explicitly include <stdlib.h> in case it isn't pulled in by lwIP
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 [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 | ||
|
|
||
| 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: | ||
|
|
||
| ``` | ||
| 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. 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_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()`. | ||
|
|
||
| ### 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) 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). | ||
|
|
||
| ### 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 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: | ||
| ``` | ||
| 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); 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._ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| #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 | ||
| #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 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 (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__ */ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /** | ||
| * Copyright (c) 2025 mjcross | ||
| * | ||
| * SPDX-License-Identifier: BSD-3-Clause | ||
| */ | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> // needed for setenv(), although also included by lwIP | ||
| #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 | ||
| // 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; | ||
| 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 | ||
| // 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 | ||
| 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(); | ||
|
|
||
| // 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; | ||
|
|
||
| // 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() | ||
| // and their variants automatically give results converted to the local timezone instead of UTC | ||
| // (see below). | ||
|
|
||
| while (true) { | ||
|
|
||
| if(aon_timer_is_initialised) { | ||
|
|
||
| // safely read the current time as UTC seconds and ms since the epoch | ||
| get_time_utc(&ts); | ||
|
|
||
| // 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 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); | ||
|
|
||
| // display the name of the currently active local timeszone, if defined | ||
| if (getenv("TZ")) { | ||
| printf("%s: ", tm.tm_isdst ? tzname[0]: tzname[1]); | ||
| // <time.h> defines `extern char *tzname[2]` to hold the names of the POSIX timezones | ||
| } else { | ||
| printf("UTC: "); | ||
| } | ||
|
|
||
| // 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 { | ||
| puts("system time not yet initialised"); | ||
| } | ||
|
|
||
| sleep_ms(5000); // do nothing for 5 seconds | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.