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:

stm32-16

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.

Bootloader

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:

stm32-17

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:

stm32-03

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

stm32-04

Now you can select the specific MCU your board uses:

stm32-05

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:

stm32-06

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

stm32-08

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:

https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json

stm32-00

Now open the Boards Manager:

stm32-01

Search STM32F1 and install the corresponding Cores package:

stm32-02

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

Drivers

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:

stm32-09

Download the following files from Clark’s repository:

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

Run the two .bat:

stm32-10

now Windows should identify the board correctly:

stm32-11

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:

stm32-12

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:

stm32-14

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

stm32-15

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:

webrelay-01

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:

netconn_listen(conn);

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

webrelay-02

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:

netconn_close(conn);
netbuf_delete(inbuf);

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:

webrelay-03

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:

webrelay-04

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);

Demo

Extend Eagle CAD tool with ULPs: Writing your first User language program

Eagle

Yahya Tawil posted a detailed how-to on writing ULP’s for Eagle CAD:

In this tutorial, you will learn how to write your first ULP in Eagle CAD to add a new capability to your CAD tool.
User Language Program (ULP) is a set of extensions for Eagle CAD users to either facilitate a routine job in an automated way or do a job that can’t be done without a ULP’s help. For example, the only way to import an image to your PCB design is by using the command import-bmp ULP. Auto-placement, exporting BOM, and renumbering parts in a schematic are all routine jobs with which ULP can help.

More details at allaboutcircuits.com.

Via the contact form.

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

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:

nvs-01

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:

nvs-02

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"

Initialization

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 = 
 esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);      
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);

Erase

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:

nvs-03

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);

Demo

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:

Panelization – using GerberPanelizer on Windows (Linux possible)

8178811494287647595

Arsenijs over at Hackaday.io writes:

This tutorial was done on Windows. Authors claim it could also be used on Linux by using Mono, but I haven’t tried and don’t understand a lot about Mono to see what could be done. I am switching to Linux nowadays, so I’d be very grateful to anybody that’d make instructions on how to launch it, however – and I’m sure other fellow Linux-wielding engineers will be grateful, too =)
This is the GitHub issue describing steps to launch it on Linux, half-successfully (thanks to @jlbrian7 for figuring this out

More details at Hackaday.io project page.

Thanks Scrubis! Via the contact form.

Adventures in robotics

On this blog I published several electronic projects and tutorials but I realized that none of those is about robotics; for this reason I’m going to start – with this post – a tutorial for dummies to teach how to build a small robot and how to program it to do different tasks, like avoid obstacles, follow a line, receive commands

robot-02

To let really everyone follow this tutorial, I chose a very cheap robotic kit (about 20 euro) from Banggood:

robot-01

The kit includes:

  • a chassis with acrylic base, DC motor, wheels and caster (the front rotating wheel)
  • an Arduino Uno (chinese clone) with its USB cable
  • a motor control board based on the L298N chip
  • a sensor shield to make it easy to connect external sensors / actuators
  • a ultrasonic distance sensor: HC-SR04
  • a small servo
  • cables, screws…

Here are some shots about the unboxing:

air-01 air-02

air-03 air-04

air-05 air-06

air-07 air-08

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:

ap-05

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 192.168.10.1/24 (/24 corresponds to 255.255.255.0 netmask) and run the DHCP server:

ESP_ERROR_CHECK(tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP));
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));
ESP_ERROR_CHECK(tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP));

SoftAP

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();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));

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

ap-01

  • 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:

ap-02

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:

ESP_ERROR_CHECK(esp_wifi_start());

Events

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

ap-03

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

case SYSTEM_EVENT_AP_START:
	printf("Access point started\n");
	break;
 
case SYSTEM_EVENT_AP_STACONNECTED:
	xEventGroupSetBits(event_group, STA_CONNECTED_BIT);
	break;
 
case SYSTEM_EVENT_AP_STADISCONNECTED:
	xEventGroupSetBits(event_group, STA_DISCONNECTED_BIT);
	break;

Authentication modes

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

ap-04

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));
ESP_ERROR_CHECK(esp_wifi_ap_get_sta_list(&amp;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],
		ip4addr_ntoa(&(station.ip)));
}

Demo

MCUXpresso IDE tutorial series

blinky-on-lpc800-dip

Erich Styger has made a series of tutorial blog posts on using the new NXP MCUXpresso IDE.

Published so far are:

  • MCUXpresso IDE: Unified Eclipse IDE for NXPs ARM Cortex-M Microcontrollers
  • MCUXpresso IDE: S-Record, Intel Hex and Binary Files
  • MCUXpresso IDE: Adding the Eclipse Marketplace Client
  • MCUXpresso IDE: Importing Kinetis Design Studio Projects
  • MCUXpresso IDE: Installing Processor Expert into Eclipse Neon
  • MCUXpresso IDE: Terminate and Disconnect a Debug Session
  • MCUXpresso IDE: Blinky the NXP LPC800-DIP Board

More details at mcuoneclipse.com.

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

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

sntp-00

esp-idf

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:

sntp_setoperatingmode(SNTP_OPMODE_POLL);

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.

sntp_opts.h

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

sntp-01

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:

sntp_init();

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;
time(&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:

sntp-02

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);
  time(&now);
  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);

sntp-03

Build your very own drink mixing robot

Yu Jiang Tham designed and built his own bartender robot named Bar Mixvah, that is available on Github:

I built a robot that mixes drinks named Bar Mixvah.  It utilizes an Arduino microcontroller switching a series of pumps via transistors on the physical layer, and the MEAN stack (MongoDB, Express.js, Angular.js, Node.js) and jQuery for the frontend and backend.  In this post, I’ll teach you how I made it.  You can follow along and build one just like it!

Check out Yu Jiang’s 3-part blog post here: Part 1, Part 2, Part 3.