Using the lwIP SNTP client with ChibiOS

A common task with embedded systems is to use the RTC to timestamp events. However, the system architect needs to find a way of synchronizing the devices RTC time with an external time source. Additionally, the designer needs to deal with the problem of drifting RTC clocks, especially for long-running devices. This article discusses an lwIP+SNTP-based approach for STM32 devices using the ChibiOS RTOS. The lwIP-specific part of this article is also applicable to other types of microcontrollers.

For high-accuracy or long-running applications, RTC clock drift also has to be taken into account. Depending on the clock source in use, the clock frequency can deviate significantly from the nominal value.

On the STM32F4 for example, you can derive the RTC clock from: The HSE/HSI main oscillator The LSI oscillator * The LSE oscillator, i.e. a 32.768 kHz external crystal.

In most applications, the RTC is used primarily to preserve a valid time even during periods where the primary power supply is unavailable. For this reason, these systems include some type of backup battery (usually a CR2032) driving the RTC. In this case you obviously can’t derive the RTC clock from HSE/HSI as these clocks aren’t active when the primary power source isn’t available.

The LSI oscillator comes for free with the device, however it has a major problem that most users aren’t aware of: It has extraorbitant tolerance values: According to the STM32F407 datasheet (Table 34: LSI oscillator characteristics), the actual crystal frequency ranges from 17 to 47 kHz with a nominal value of 32 kHz. This is equivalent to a tolerance of -53.1 / +46.8 percent points, or more than 460000 ppm. I can’t think of any real world application that can deal with this magnitude of tolerance. Although the tolerance given is over the full -40 to +105° temperature range, I can confirm that is higher than +-25% at room temperature.

The LSE oscillator is the only option left. So remember to include a 32.768 kHz crystal in your design. For this specific use I believe that crystal oscillators are not the right choice because of their relatively high power consumption.

There are some major problems left: How to initially get the current timestamp onto the device (depends on your deployment process and is usually not an issue) and how to keep the device synced with an external clock source.

The latter is especially relevant when multiple devices need to be kept in sync and/or a near-monotonic clock source is required. Even with only one percent clock drift, the RTC datetime will drift by more than three days over a single year.

For applications connected to the internet or to any other computer network, the lwIP contrib repository provides a full SNTP client. SNTP is a less accurate version of the NTP protocol which can still be used with any normal NTP server.

However, the library is lacking the most basic documentation. Therefore, I provide an example of how to use it in conjunction with the ChibiOS RTCD1 driver.

The header shown here replaces the sntp.h header that comes with the library. It includes the compile-time configuration required to work with the RTCD1 driver. For this example I chose the NTP server swisstime.ethz.ch because it has a good ping from my location. I recommend you select an appropriate server from the NTP pool.

#ifndef __SNTP_H__
#define __SNTP_H__

#ifdef __cplusplus
extern "C" {
#endif

//Custom configuration
#include <hal.h>
#include <chrtclib.h>
#define SNTP_SERVER_ADDRESS "82.197.164.46" /* swisstime.ethz.ch */
#define SNTP_UPDATE_DELAY 90000 /* SNTP update every 90 seconds */
//ChibiOS RTC drivers
#define SNTP_SET_SYSTEM_TIME(sec) rtcSetTimeUnixSec(&RTCD1, (sec))
#define SNTP_GET_SYSTEM_TIME(sec, us) \
    do{uint64_t time = rtcGetTimeUnixUsec(&RTCD1);\
       (sec) = time / 1000000;\
       (us) = time % 1000000;}while(0)

void sntp_init(void);
void sntp_stop(void);

#ifdef __cplusplus
}
#endif

#endif /* __SNTP_H__ */

/*
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 * Author: Simon Goldschmidt (lwIP raw API part)
 * Modified by Uli Koehler for ChibiOS (2014)
 */

The configuration is pretty basic and sntp.c supports some quite useful features that aren’t configure here — most notably, support for multiple NTP servers.

The second step is to configure lwIP properly. This is not a tutorial on lwIP and I assume the reader already knows how the stack works (if not, this article is probably not the best place to start). In order to avoid issues with significant deviations from ideal monotonic clocks, this sample sends SNTP requests every 90 seconds, overriding the default 60 minutes. Which one is better suited entirely depends on your application.

There are two common pitfalls that might be encountered, both of which are located in the lwipopts.h header:

Firstly, the SNTP client permanently consumes an UDP PCB. Make sure to appropriately increase the MEMP_NUM_UDP_PCB value. Needless to say, you need to enable UDP in order for SNTP to work.

Secondly, the SNTP client calls the send function repeatedly by using the lwIP sys_timeout() feature. The default options for the number of supported concurrent timeouts is quite limited and will probably not be sufficient. I recommend to increase the MEMP_NUM_SYS_TIMEOUTvalue by at least one.

Once these requirements are met, simply call

sntp_init();

after initializing the lwIP thread and event loop. If your configuration is correct, the RTC will be updated when the SNTP responses are received. DO NOT call sntp_init() repeatedly, it will call the send function using the lwIP eventloop by itself..

Note that STM32F4 devices have a millisecond-accuracy RTC. SNTP will only rarely provide an accuracy of better than 1 millisecond. Therefore, the millisecond RTC value might not be significant.

If the application crashes, it’s likely that some lwIP limits have been exceeded. In this case, refer to this previous TechOverflow articledescribing in detail how to add ARM hardware breakpointing to ChibiOS and LWIP assertions.