If you follow my blog, you probably noticed that I use different online services to make my PCBs… it’s a way to test them in terms of quality and production time.

On the advice of my friend Mauro, I recently chose PCBWay to produce some PCBs for my future projects.

The procedure for sending a new order is very simple; first you have to enter some information about your PCB:


then, in the next page, you can configure som specific details of your printed circuit board (color, material, finishing, copper size…). It’s interesting the possibility – not often available – to choose the color of the silkscreen:


Once selected the production mode (nomal = 2-3 days or express = 24h) and the shipping method, you can add the PCB to your shopping cart and upload the Gerber files of your project. PCBWay doesn’t offer the CAM jobs for Eagle, but I verified that they accept files in the same format of Seeedstudio. If you use Eagle, you can therefore follow my tutorial to create the needed Gerbers.

Before being able to complete the payment and send the PCB to the factory, the files must pass a manual check. Yes, PCBWay manually verifies every file you upload… this process requires only some minutes and it’s definitely an added value: in one of my orders I made a small mistake and I was promptly contacted by a PCBWay engineer with an email reporting the mistake and asking me for the correct file.

When the order is in production, you can follow the progress in your personal area:


Even the website displays every step of the production process:


The quality of the PCBs I received is very good, certainly comparable to other services I used in the past. The price also is alined to the other manufacturers and PCBWay does offer an entry price of 5€ for 10 PCBs (10x10cm max).


ESP32, Wemos or not to Wemos

Wemos is a chinese manufacturer of IoT products, well known for its D1 Mini family, that includes boards based on the ESP-8266EX chip and expansion shields.

Some weeks ago, Wemos announced a board based on the new ESP32 chip, named LOLIN32. This board hosts an ESP-WROOM-32 module and can be powered by a single cell (1S) LiPo battery. The board can also recharge the battery thanks to a circuit based on the TP4054 chip.

I brought a LOLIN32 board from Wemos’s official store on Aliexpress. The board is shipped inside an anti-static bag, with a label with your name:


The bag also include some pin headers:

lolin-02 lolin-03

Curiously I noticed that other Aliexpress stores sell boards named “lolin”. I brought one of them:

lolin-04 lolin-05

Although this board has the “wemos” logo printed on it, it actually looks like a clone of the D-duino-32 project by Travis Lin: unlike the LOLIN32 indeed this board does not include the LiPo battery connector and the charging circuit while it adds a 0.96″ OLED display.

Here’s a visual comparison between the two boards:

lolin-06 lolin-07

The advantage of purchasing an original Wemos card is the availability of the electrical schematic, some tutorials and a support forum… all that is missing for the clone board (how is the OLED display connected?). My suggestion is therefore that if you want to buy a Wemos board, you should get it from the original store.

If instead you are interested in a board with a OLED display, why not buy it directly from the designer’s tindie page, to support its development?

Updated CH340G board

Some weeks ago I blogged about my project of a minimal board based on the CH340G chip.

After some tests, I slightly modified the project:

  • I added two leds that blink when data are transmitted/received
  • I added a jumper that allows to decide if you want power supply (5V) in the connector or not
  • added a 10nF capacitor to make the circuit more stable.

Eagle files in my Github repository have been updated.

Here are some photos of the new PCB (the purple color should tell you that it was manufactured by OshPark):


The new board with all the components and a comparison with the first version:




STM32 and Arduino

STM32 is a family of 32bit microcontrollers manufactured by STMicroelectronics and based on the ARM Cortex M core.

The STM32 family is divided into different lines of microcontrollers (L0-1-4, F0-1-2…) depending on their features and the use they are designed for:


These microcontroller are widely used in the industrial world… for example both Pebble watches and Fitbit bracelets are based on STM32 MCUs.

If you’re interested in quadricopters or drones, you probably have heard or used F1, F3, F4 flight control boards. The board name (“F…”) indicated indeed the STM32 microcontroller they are based on.

Thanks to the work made by the stm32duino community and to the support of ST itself, starting from the past June STM32 microcontrollers can be easily used with the Arduino IDE and it’s also possible to take advantage of most of the libraries available for Arduino.

I decided to buy a minimal development board based on the STM32F103C8T6 microcontroller (these boards are sometimes known as blue pills); let’s see how to use it with Arduino.


Many boards are sold unprogrammed: the first thing to do is therefore to program a bootloader, that is a small program which will allow to upload the “real” program via USB port.

To flash the bootloader, you need an USB -> serial adapter, connected to your dev board as it follows:

  • RX -> A9
  • TX -> A10
  • VCC -> 5V
  • GND -> GND

stm32-18 stm32-19

You have also to enable the programming mode, moving the first jumper (labeled BOOT0) to position 1:


Now you need a software to flash the file with the bootloader into the chip. If you’re under Windows, you can download the Flash Loader from ST’s official website: after having registered (for free) you’ll receive the download link via email.

The bootloader is developed and maintained by Roger Clark and it’s available in his Github repository. For STMF1 boards there are several binary files, depending on the PIN the onboard led is connected to. My board uses P13, so I downloaded the file generic_boot20_pb13.bin.

Run the Demonstrator GUI program and select the serial port your adapter is connected to:


If all the connections are ok, the software should be able to detect the microcontroller:


Now you can select the specific MCU your board uses:


then select the file with the bootloader and program it. To be sure, you can ask the software to perform also a global erase of the memory:


The program will flash the chip and, when complete, will confirm the operation with a message:


If you now move back the BOOT0 jumper to the original position, you should see the led blink: this means that the bootloader is running and can’t find a program to execute… now it’s time to configure the IDE.

Arduino IDE

Open your IDE and select File – Preferences. Type the following address in the Additional Boards Manager URLs field:



Now open the Boards Manager:


Search STM32F1 and install the corresponding Cores package:


You’re almost ready to compile and run your first program…


Connect the dev board to your PC using an USB cable and verify how it is identified.

It may happen that Windows cannot correctly identify the board and displays it as Maple 003, is this case you have to install the correct drivers:


Download the following files from Clark’s repository:

  • install_STM_COM_drivers.bat
  • install_drivers.bat
  • wdi-simple.exe

Run the two .bat:


now Windows should identify the board correctly:


Sometimes Windows cannot see the additional serial port of the board. To solve the problem, you can try to program on the board a simple sketch that uses the Serial object. Open for example the blink sketch and change it as it follows:


Program the sketch choosing BluePill F103C8 as board and STM32duino bootloader as upload method.

Once the sketch is programmed and executed, Windows should detect and install the new COM port:


You’re done, now the board is fully integrated with the Arduino IDE:


ESP32 (20) – Webserver

One of the most popular projects among the ones included in my tutorial about the enc28j60 chip is fore sure WebRelay. This project allows to control an output pin of Arduino using a simple web page, designed to be accessed also using your smartphone. Today I’m going to show you how to implement a similar project with the esp32 chip; it’s also the opportunity to teach how to write a TCP server, especially a web server.

Netconn API

As you know, the esp-idf framework uses the lwip library to manage network communication. This library offers different abstraction levels: the programmer can decide to work on the raw packets or to leverage pre-build components.

To develop my TCP server, I decided to use one of those pre-build components, the Netconn API.

Using Netconn API it’s very easy to implement a server and the required steps are described below:


The netconn_new() method creates a new connection and returns a a pointer to struct netconn which represents the connection:

struct netconn *conn;
conn = netconn_new(NETCONN_TCP);

The parameter defines the connection type… the most common ones are NETCONN_TCP for a connection using the TCP protocol and NETCONN_UDP for a connection using the UDP protocol.

To use the connection in server mode, you have then to associate (bind) it to a specific port… for example a webserver normally listens on port 80 (443 if HTTPS):

netconn_bind(conn, NULL, 80);

The second parameter (NULL above) allows to bind the connection to a specific IP address and may be useful if the device has more than a network interface. If you pass NULL (or the equivalent IP_ADDR_ANY) you ask the library to bind the connection to any available interfaces.

Now you can start listening on that port with:


Working with a client connection

With the netconn_accept() method, your program can accept a new incoming connection:

struct netconn *newconn;
netconn_accept(conn, &newconn);

The method returns a pointer to a new struct netconn that represents the connection established with the client. This method is blocking: the program is stopped until a client makes a connection request.

Once the connection is established, you can use the netconn_recv() and netconn_write() methods to receive or send data to the client:

netconn_recv(struct netconn* aNetConn, struct netbuf** aNetBuf );
netconn_write(struct netconn* aNetConn, const void* aData, size_t aSize, u8_t aApiFlags);

To optimize RAM memory usage, the netconn_recv() method handles data using an internal buffer (zero-copy mode). To access the received data you therefore have to:

  • declare a new variable as a pointer to struct netbuf
  • pass the pointer address as the second parameter of the recv method
  • use the netbuf_data() method to obtain a pointer to the data within the netbuffer structure and its length


struct netbuf *inbuf;
char *buf;
u16_t buflen;
netconn_recv(conn, &inbuf);
netbuf_data(inbuf, (void**)&buf, &buflen);

Likewise, the netconn_write() method accepts, as last parameter, a flag to indicate whether or not to copy the buffer content before sending it. To save memory you can therefore, if you’re sure that the buffer won’t be modified by other theads, use NETCONN_NOCOPY as flag:

netconn_write(conn, outbuff, sizeof(outbuff), NETCONN_NOCOPY);

When the communication with the client is complete, you can close the connection and free the buffer:


HTTP server

What you learned so far can be applied to every TCP server. If you want to communicate with an Internet browser, you have to “talk” the same language, that is the HTTP protocol.

In the example program (available on Github) I implemented a very minimal version of that protocol. When you type an address in your browser (for example www.google.com), the browser connects to Google server and sends a request with the form:

GET <resource>

The request can have different fields, but the first line always contains the name of the requested resource (html page, image…). In particular if you access the homepage of the website the request will simply be GET /.

The website published by the esp32 chip to control the relay consists of only two pages:

  • off.html, displayed when the relay is off
  • on.html, displayed when the relay is on

Each page contains a label (“Relay is ON|OFF“) and an image. The image itself is a link to the other page and when you click on it, the relay status is also changed:


The program identifies the resource to be sent analyzing the first line of the request with strstr():

char *first_line = strtok(buf, "\n");
if(strstr(first_line, "GET / ")) [...]
else if(strstr(first_line, "GET /on.html ")) [...]
else if(strstr(first_line, "GET /on.png ")) [...]

An HTTP server responds to the browser by first indicating the result of the request. If ok, the return code is 200:

HTTP/1.1 200 OK

then it returns the media type of the resource and finally it sends the resource file. In this example, the possible media types are:

  • text/html for HTML pages
  • image/png for images

Static content

A webserver it normally stores the resouces for the website it publishes on an external device (memory card, hard drive…). For simple projects, you may consider to include all the content inside your program.

For example in my code HTML pages and HTTP protocol messages are defined as static arrays:

const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
const static char http_png_hdr[] = "HTTP/1.1 200 OK\nContent-type: image/png\n\n";
const static char http_off_hml[] = "";

To be able to include also the images, I used a feature of the framework (embedding binary data). You can indeed specify in the component.mk file the binary resources to be included:


In your program you can access the content of the embedded files using the following pointers:

extern const uint8_t on_png_start[] asm("_binary_on_png_start");
extern const uint8_t on_png_end[]   asm("_binary_on_png_end");
extern const uint8_t off_png_start[] asm("_binary_off_png_start");
extern const uint8_t off_png_end[]   asm("_binary_off_png_end");

The syntax is _binary_filename_start|end, replace “.” with “_” in the filename. Having pointers to both the start and the end of the resource content, it’s easy to send it with the netconn_write() method:

netconn_write(conn, on_png_start, on_png_end - on_png_start, NETCONN_NOCOPY);


CH340G board

On this site I’ve already posted about the CH340G chip, manufactured by a chinese company and often adopted as a cheap solution when a USB -> serial converter is needed.

As the chip package (SOP16) is quite easy to solder, I ordered some ICs from AliExpress and I designed a minimal demoboard, based on the reference schematics:


You can download Eagle files (both schematics and board) in my Github repository.

I used Seeedstudio’s FusionPCB service to manufacture the PCBs (if you want to learn how to use it for your PCBs you can follow my tutorial) and hare are some photos of the first prototype:



To verify if it works, you can connect the TX and RX pins with a jumper;  if you open a serial terminal, you should see each character you type:


In the end, here’s the link to the WCH official website where you can download the drivers.

AiR – Let’s build the robot

Today we start building the chassis kit from Banggood that will become the robotic platform we’ll play with in the next posts…

But first, remove the protective paper from all the acrylic parts:

air-10 air-11

Let’s mount the motor on the chassis. You need the motor, two screws with their nuts, two mounting plates and the plastic wheel for the encoder. Insert the wheel on the motor shaft and place the two plates in the corresponding chassis holes:

air-12 air-13

For convenience, you can solder two wires (red and black) to the motor before mounting it on the chassis. Then secure the motor to the plates using screws and nuts. Make sure that the encoder wheel is inside:

air-14 air-15

With the help of a screwdriver, tighten the two screws so that the motor is securely fastened to the base:

air-16 air-17

Do the same for the second motor and make sure that the wheel can freely move and does not touch the chassis:

air-18 air-19

Complete the assembly inserting the two wheels in the motor shafts:

air-20 air-21

Now the caster, that is the front wheel. You can mount it on the chassis using 4 screws and nuts and the holes already present:

air-22 air-23

After having completed the assembly, you will notice that the caster is lower than the rear wheels; the robot is tilted forward. You can fix it by adding the 4 spacers between the caster and the base::

air-24 air-25

Here’s the final result:


ESP32 (19) – NVS

In today’s tutorial you’ll learn how to permanently store information, so that they are preserved even if the esp32 chip is reset or power is removed.


NVS (Non Volatile Storage) is a library included in the esp-idf framework that allows to store information (in the form of key/value) in the flash memory, the content of which is not erased when you reset the chip or remove the power.

If you remember, in a previous post I explained how the external flash memory is organized. The main purpose of that memory is for sure to store the program that will be executed by the esp32 chip. However, it’s possible to divide the flash memory in partitions: the framework offers some ready-to-use partition tables but you’re free to define custom ones.

If you use the default partition table (“Single factory app, no OTA”), you may notice that it contains a partition of type data and subtype nvs:


The default size for that partition is 24Kbyte.

Thanks to the NVS library, you can store custom data into that partition. Information is organized in key/value pairs; a label (= key) with a maximum length of 15 characters is assigned to each value:


You can store different types of data: from numeric values to text strings and byte sequences (blobbinary large object). You’ll learn that the library provides specific methods based on the type of data you want to store or retrieve.

To be able to use the library in your code, first include the following headers:

#include "esp_partition.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include "nvs.h"


The first step to use the nvs partition is to initialize the library, with the command:

esp_err_t err = nvs_flash_init();

The command returns ESP_OK if successful; on the contrary it returns one of the error codes defined in the nvs.h file (see below). In particular, if the partition was resized or changed, you may get the ESP_ERR_NVS_NO_FREE_PAGES error code. This error can be resolved “formatting” the partition.

First you have to identify the nvs partition in the flash memory:

const esp_partition_t* nvs_partition = 
if(!nvs_partition) printf("FATAL ERROR: No NVS partition found\n");

then you can format it with the esp_partition_erase_range() command:

err = (esp_partition_erase_range(nvs_partition, 0, nvs_partition->size));
if(err != ESP_OK) printf("FATAL ERROR: Unable to erase the partition\n");

The last step is to open the partition, you can do it in READONLY o READWRITE mode:

nvs_handle my_handle;
err = nvs_open("storage", NVS_READWRITE, &my_handle);
if (err != ESP_OK) printf("FATAL ERROR: Unable to open NVS\n");

Set – Get

Once the partition is open, you can store (set) new values or retrieve (get) existing ones:

There are different methods, based on the data type (i8 stands for integer with 8bits, u8 per unsigned integer 8bit…):

  • nvs_set_i8(), nvs_set_u8(), nvs_set_i16(), nvs_set_u16()…
  • nvs_set_str()
  • nvs_set_blob()

All the set methods require as parameters the partition handler, the key name and the value to be stored:

esp_err_t nvs_set_i8(nvs_handle handle, const char* key, int8_t value);

The only exception is the nvs_set_blob() method which requires as additional parameter the length of the byte array to be stored:

esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length);

After having called a set method, you have to commit the change with the method:

esp_err_t nvs_commit(nvs_handle handle);

To get data from the flash the library offers similar methods:

  • nvs_get_i8(), nvs_get_u8(), nvs_get_i16(), nvs_get_u16()…
  • nvs_get_str()
  • nvs_get_blob()

The parameters for those methods are the partition handler, the key name and a pointer to the variable that has to be updated with the value retrieved:

esp_err_t nvs_get_i8(nvs_handle handle, const char* key, int8_t* out_value);

Because of you cannot know a priori the size for string or blob values, you can do a “trick”: first call the nvs_get_string() method passing NULL as pointer to get the length of the value, then allocate a variable with the correct size and finally call again the method passing that variable to get the value:

size_t string_size;
esp_err_t err = nvs_get_str(my_handle, parameter, NULL, &string_size);
char* value = malloc(string_size);
err = nvs_get_str(my_handle, parameter, value, &string_size);


The library also include a couple of methods to erase the partition content:

esp_err_t nvs_erase_key(nvs_handle handle, const char* key);
esp_err_t nvs_erase_all(nvs_handle handle);

The first method is more selective and allows you to delete a single key while the second deletes the entire memory content.

Both the methods must be followed by the nvs_commit() command as explained before.

Error handling

All the methods in the nvs library return an error code (esp_err_t variable).

The possible errors are listed in the nvs.h file:


For example, if you’re calling a get method with a key that is not present in the flash, you’ll get the ESP_ERR_NOT_FOUND error. You should handle the different errors in your program:

esp_err_t err = nvs_get_i32(my_handle, parameter, &value);
if(err == ESP_ERR_NVS_NOT_FOUND) printf("\nKey %s not found\n", parameter);


For this tutorial I wrote a program that allows, using a simple command line, to store and retrieve information from the nvs partition. The source code is available on Github, here’s a video that shows how it works:

ESP32 (18) – Access Point

In all the examples so far, we used the esp32 chip in STAtion Mode, that is as a client which connects to an existing wifi network. In a previous article I explained in details how the different components of the esp-idf framework interact to establish the connection and how to use events to syncronize the tasks of your program.

Today you’ll learn how to configure the esp32 chip to publish its own wifi network, similar to an access point. This mode is indeed named SoftAP.

IP addressing

Every device connected to an IP network must have its unique address. You can statically assign the address or leverage a network service (DHCP, Dynamic Host Configuration Protocol) that dynamically assigns them. The main advantage in using the DHCP service is that you don’t need to know in advance – when connecting to a network – the correct settings to apply (IP address, netmask, gateway…); this is the reason why normally the device which manages the network also offers a DHCP server:


The esp-idf framework does include a DHCP server. To use it in the network you’re going to create, you have to:

  • stop the service if it’s already running – tcpip_adapter_dhcps_stop()
  • statically configure the network interface of the esp32 chip – tcpip_adapter_set_ip_info()
  • start the service – tcpip_adapter_dhcps_start()

The esp-idf framework uses two different interfaces depending if the esp32 chip is in station mode or in SoftAP mode (TCPIP_ADAPTER_IF_STA e TCPIP_ADAPTER_IF_AP). Make sure you’re configuring the correct one based on the mode you’re planning to use.

For example, to assign the address (/24 corresponds to netmask) and run the DHCP server:

tcpip_adapter_ip_info_t info;
memset(&info, 0, sizeof(info));
IP4_ADDR(&info.ip, 192, 168, 10, 1);
IP4_ADDR(&info.gw, 192, 168, 10, 1);
IP4_ADDR(&info.netmask, 255, 255, 255, 0);
ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info));


Now you can configure the SoftAP mode for the esp32 chip.

First initialize the event handler (later I’ll list the events that are available when working in this mode):

ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

Then configure the wifi stack in access point mode, with the default settings and RAM storage:

wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();

The different settings for the AP mode are available in the wifi_ap_config_t struct:


  • ssid is the name (Service Set Identifier) of the network
  • password is the password you have to specify to connect to the network (if the authentication mode requires it, see below)
  • ssid_len is the length (in characters) of the ssid string – you can set it to zero if the string is null-terminated
  • channel is the transmission channel
  • authmode is the authentication mode, see paragraph below
  • ssid_hidden allows to “hide” the network not broadcasting its SSID
  • max_connection is the maximum number of concurrent connections (max 4)
  • beacon_intereval is the transmission interval for beacon frames

Once configured, you have to apply struct using the esp_wifi_set_config() method. In the example I published on Github, all the parameters can be configured via menuconfig:


wifi_config_t ap_config = {
	.ap = {
		.ssid = CONFIG_AP_SSID,
		.password = CONFIG_AP_PASSWORD,
		.ssid_len = 0,
		.channel = CONFIG_AP_CHANNEL,
		.authmode = CONFIG_AP_AUTHMODE,
		.ssid_hidden = CONFIG_AP_SSID_HIDDEN,
		.max_connection = CONFIG_AP_MAX_CONNECTIONS,
		.beacon_interval = CONFIG_AP_BEACON_INTERVAL,			
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));

Finally start the wifi stack with:



Three are the main events available when working in access point mode:

  • SYSTEM_EVENT_AP_START, when the stack completed the start process (after esp_wifi_start)
  • SYSTEM_EVENT_AP_STACONNECTED, when a new device connects to the access point
  • SYSTEM_EVENT_AP_STADISCONNECTED, when a device disconnects


In the example, the events are notified using different event bits:

	printf("Access point started\n");
	xEventGroupSetBits(event_group, STA_CONNECTED_BIT);
	xEventGroupSetBits(event_group, STA_DISCONNECTED_BIT);

Authentication modes

In SoftAP mode, the esp32 chip supports different authentication modes:


open configures a wifi network with no authentication, while all the other modes offer some security, from the “weak” ones (WEP, now easily hackable) to the strongest ones (WPA2). Personally, I always use the WPA2_PSK mode, which offers high security and authentication, using a shared password (PSK = PreShared Key).

Some authentication modes require a minimum password length. In particular, WPA and WPA2 require a password at least 8 characters long; if you specify a password too short, the esp_wifi_set_config method will return an error.

List the connected devices

You can get the list of the devices connected to the access point with the esp_wifi_ap_get_sta_list() method, which updates a wifi_sta_list_t struct:

wifi_sta_list_t wifi_sta_list;
memset(&amp;wifi_sta_list, 0, sizeof(wifi_sta_list));

To obtain the IP settings (for example the address) of the different stations, you can then use the tcpip_adapter_get_sta_list() method:

tcpip_adapter_sta_list_t adapter_sta_list;
memset(&adapter_sta_list, 0, sizeof(adapter_sta_list));
ESP_ERROR_CHECK(tcpip_adapter_get_sta_list(&wifi_sta_list, &adapter_sta_list));
for(int i = 0; i < adapter_sta_list.num; i++) {
	tcpip_adapter_sta_info_t station = adapter_sta_list.sta[i];
	printf("%d - mac: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x - IP: %s\n", i + 1,
		station.mac[0], station.mac[1], station.mac[2],
		station.mac[3], station.mac[4], station.mac[5],


ESP32 (17) – SNTP

In embedded applications it’s often necessary a clock, as accurate as possible. For example, imagine a device that has to activate an output at a given time or a logger that has to store values (temperature…) at precise intervals.

A solution widely adopted is the use of an RTC (Real Time Clock) chip. The problem becomes how you configure the RTC chip with the current time and how you keep it synchronized. Some time ago, I published a project (RTCSetup) to configure an RTC connected to an Arduino using the time of your PC; today I’ll show you how to use an Internet time service with the esp32 chip.


SNTP (Simple Network Time Protocol) is a protocol designed to synchronize the clock of devices connected to the Internet. The latest version of the protocol (SNTPv4) is defined in the RFC 4330. The word “simple” in its name is because of SNTP is a reduced and simplified version of the NTP (Network Time Protocol) protocol; protocol which, to be able to guarantee high accuracy, is quite complex.

The basic working principle of SNTP is as follows:

- the client device connects to the server using the UDP protocol on port 123

- the server answers with different fields, among which is the timestamp value, 64bit long, where:

  • the first 32 bits are the number of seconds from 01/01/1990
  • the remaining 32 bits are the fraction of seconds



In a previous post I’ve already introduced the lwip library, chosen by the esp-idf framework to implement network communcation. The library includes an app (sntp.c) which implements a SNTP client.

Let’s see how to use it in your program (the full source of the test program is available in my Github repository).

First you have to include its header file:

#include "apps/sntp/sntp.h"

Then configure the SNTP client in pool mode, to query the servers every n seconds:


Alternatively, you can configure the client in listen only mode (SNTP_OPMODE_LISTENONLY). In this configuration, the client will listen for broadcast updates, without actively query any servers.


Some configuration settings of the client, including the update delay (in milliseconds), can be changed in the sntp_opts.h file:


Now you have to tell the SNTP client which server it has to use. A common choice is to use the cluster of servers from pool.ntp.org. Alternatively, because of I live in Italy, I normally use the SNTP server offered by the Istituto Nazionale di Ricerca Meterologica (ntp1.inrim.it) that has an atomic clock as its time source.

In the example provided with this post, the server name can be configured via menuconfig:

sntp_setservername(0, CONFIG_SNTP_SERVER);

After having configured the client, you can start it with:


the client will be executed in parallel with the main program.

To get the actual time from the system clock, use the time() method which updates a time_t variable:

time_t now;

The time_t variable usually represents the time as the number of seconds from a date called Epoch (01/01/1970). To split the variable into the different time values (year, month, day…) you can use the localtime_r method which updates a tm struct:

struct tm timeinfo;
localtime_r(&now, &timeinfo);

The fields of the tm struct are defined in the time.h file:


We can verify if the first sync with the SNTP server was perfomed with success for example checking the year field:

while(timeinfo.tm_year < (2016 - 1900)) {
  printf("Time not set, waiting...\n");
  vTaskDelay(5000 / portTICK_PERIOD_MS);
  localtime_r(&now, &timeinfo);
Many date and time functions have two versions, one “normal” and one restartable (es. localtime and localtime_r). In a multithread environment like the FreeRTOS operating system is always suggested to use the functions with the _r suffix because they can be executed in parallel (reentrancy) as they don’t use common buffers.

If the struct tm is available, you can then use the strftime() method to format a string with the pattern you prefer. Many placeholders are available: they are described in the manpage of the method. Here are some examples:

char buffer[100];
// es. 08/05/2017 15:10:34
strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo);
// es. Monday, 08 May 2017 
strftime(buffer, sizeof(buffer), "%A, %d %B %Y", &timeinfo);
// es. Today is day 128 of year 2017 
strftime(buffer, sizeof(buffer), "Today is day %j %Y", &timeinfo);

Time zone

SNTP servers keep the time with the UTC (Coordinated universal time) time zone. You can change the time zone modifying the TZ environmental value with the method:

int setenv(const char *name, const char *value, int overwrite);

name is the name of the variable and value the value for it. If overwrite is > 0 the method updates the variable if already exists.

Once the value for the TZ variable is set, you can initialize the timezone conversion routine with the tzset() command.

The TZ variable can have two different kind of values, depending on whether you want to handle daylight saving time or not.

In the simplest form (no daylight saving time) the format is std offset, where std is the name for the time zone (3 or more characters long) and offset specifies the time value you must add to the local time to get a UTC value. For example Italy is in the CET (Central European Time) timezone, which is one hour ahead from UTC (therefore offset -1). The command to use in Italy is:

setenv("TZ", "CET-1", 1);

If you want to handle the daylight saving time, you can use the format std offset dst [offset],start[/time],end[/time]std and the first offset have the same meaning as above. dst and the second offset specify the name and offset for the corresponding daylight saving time zone (if the second offset is omitted, the default is 1 hour ahead of standard time). Finally, start and end are the day and time when daylight saving time starts and ends.

For Italy, in the daylight saving time we use the CEST (Central European Summer Time) time zone, active (direttiva 2008/84/CE del Parlamento Europeo) from 02:00 of the last Sunday of March to 03:00 of the last Sunday of October.

The command is therefore:

setenv("TZ", "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", 1);

M3.5.0 specify March (3), last week (5), Sunday (0).

On the Internet you can find websites that lists the TZ values for different countries.

In conclusion, if you want to convert the time to a given timezone the steps are:

  • configure the TZ  variable (setenv())
  • inizialize the timezone conversion routine (tzset())
  • update the tm struct (localtime_r())

In the example program:

// print the actual time in Italy
printf("Actual time in Italy:\n");
localtime_r(&now, &timeinfo);
strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo);
printf("%s\n", buffer);