- 
                Notifications
    You must be signed in to change notification settings 
- Fork 959
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.
          
          
      
        
          +297
        
        
          −0
        
        
          
        
      
    
  
  
     Open
                    Changes from 4 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)_. | ||
|         
                  mjcross marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| # 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. | ||
|         
                  mjcross marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| 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 | ||
|         
                  mjcross marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| ### 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`. | ||
|         
                  mjcross marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| 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 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). | ||
|  | ||
| _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,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) | ||
|         
                  mjcross marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
|  | ||
| // 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__ */ | ||
  
    
      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,119 @@ | ||
| /** | ||
| * Copyright (c) 2025 mjcross | ||
| * | ||
| * SPDX-License-Identifier: BSD-3-Clause | ||
| */ | ||
|  | ||
| #include <stdio.h> | ||
| #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(); | ||
|  | ||
| // 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 | ||
|         
                  lurch marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| // 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 | ||
|         
                  lurch marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| // 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 | ||
| } | ||
| } | ||
  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.