THE STM32 INTO THE INTERNET OF THINGS

Equip the STM32 Nucleo board with Ethernet. Along the way, you'll get plenty of practical tips on what to consider when programming a web server on a microcontroller.
ORIGINAL POST
By NA
components
Hardware Components
STM32 Nucleo F401R
X 1
details

mohr_nucleo_7.tif_fmt1.jpg

In order to connect the Nucleo board to a network, we first need a suitable interface. The WIZ5500 is a hardware Ethernet controller that supports the usual protocols such as TCP, UDP, ICMP, IPv4, ARP, IGMP, and PPPoE. Its operating voltage is 3.3 volts. The Ethernet controller operates at both 10 Mbit and 100 Mbit. As a hobbyist-friendly module, the WIZ5500 costs about 9 euros. The data transfer between the network module and the Nucleo board is done via the SPI interface. This is often used for the communication of microcontrollers with their peripherals. In principle, the data is transmitted clock-synchronously and serially. Table 2 shows how the Nucleo Board must be connected to the network module. It should be noted here that we are using the SPI2 interface of the Nucleo board. Figure 7 shows how to make the connections. To make sure we don’t forget later, we should connect the LAN directly as well. It wouldn’t be the first time that we spent hours looking for a fault—and in the end, it was only a forgotten plug connection.

Fig. 7: The Nucleo board connected to the W5500 board

 

Nucleo BoardNetwork module
5V5V
PC2MISO
PC3MOSI
PB10SCLK
PC4SCS
GNDGND
Table 2: Connections between Nucleo Board and network module.
The WIZ5500 module does not have its own Media Access Control (MAC) address. This means that the MAC must be provided externally. There are two possibilities here: For testing, just take any random sequence, because it would be quite unlikely to hit a MAC that you already have in your own network segment. The second option is to buy a chip that contains a MAC (globally unique). You read this and use it for the WIZ5500. The 24AA025E48 is for example such a chip.

For simple experiments, the first variant is absolutely fine because the MAC address only has to be unique within one network segment. The MAC is not transmitted across router boundaries. If you want to know even more about the WIZ5500 chip, take a look at the data sheet.

Library setup

The manufacturer of the WIZ5500 chip provides a special library for control. This offers us the possibility to set TCP IP parameters via simple API calls and to build a complete web server. The library can be freely downloaded from GitHub. Via the direct download link, you can download a ZIP archive that contains all the necessary files.

We unpack the archive into a directory below the src folder in our project. This is necessary because the library is available as a source and must be compiled together with the project in any case. In the unpacked files in the Ethernet subdirectory we find a file wizchip_conf.h. In this file, we have to select the chip 5500 in line 75 and save the file (Listing 2).

 

Listing 2: Select the WIZCIP 5500 in the config file of the library

#ifndef _WIZCHIP_
#define _WIZCHIP_ W5500 // W5100, W5100S, W5200, W5300, W5500
#endif

Now we have to include the library into the project. Use the right mouse button on the project in the STM32CubeIDE. Now follow the path to the configuration of the libraries Properties | C/C++ Build | Settings | Tool Settings | Include paths. Add the two paths Ethernet and Ethernet/5500 to Include paths (Fig. 8).

Fig. 8: Integrating libraries into the project

 

The library is now completely set up and included in the project. In the next section, we will show how to extend the project so that the Ethernet module can be used.

Extend project

We now need to do some preliminary work on the project to include the WIZ5500 chip on the software side as well. Before we start to extend the actual source code, we have to configure the hardware of the project. That means that we activate the second SPI interface. We already connected the hardware at the beginning of the article. Open the Pinout & Configuration overview by clicking on the Blink.ioc file. Now select the SPI2 interface in the menu item Category | Connectivity and activate it in Full Duplex Master mode. Set the pin PC_4 to GPIO output. Use this pin as the chip select (CS) line for SPI communication. Figure 9 shows which pins of the STM32 have also turned green. In this case, green means that the pins are in use for the project.

Fig. 9: Hardware configuration of the SPI2 interface

 

If you click on Save, the IDE asks if we want to have the source code for the hardware configuration generated. It is not an easy task to create this code without errors, because the microcontrollers of the STM32 family offer so many possibilities – but there are also many possibilities to do something wrong.

In a few places, the IDE asks what it should do. In the beginning, it is important to read these questions and think about exactly what you want. After some time, you can make the IDE remember the answers. This will make work much smoother.

In Listing 3 we summarized the commands that you must include in the generated code to configure the WIZ5500. You can use the comments of the generator as a good guide. With these lines, the TCP-IP stack of the chip is activated, and it can already react to a simple ping. Please note that you are using a free IP from your local network. Of course, we must not forget to import the library for the WIZ5500. This can be done with the two import lines at the beginning of the program.

The communication between the library and our program runs via four special functions. These must be built into the source code of the main.c file. You can orientate yourself on the code generator comments. The functions control the CS signal and regulate over which SPI interface the data should run.

The last code block initializes the WIZ5500 and gives it a MAC and an IP address. When the program is loaded into the controller, it already responds to ping packets. Please test the connection first before you continue with the next steps.

 

Listing 3: Functions that must be included in the generated code

/* Private includes
----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "wizchip_conf.h"
#include "socket.h"
/* USER CODE END Includes */

/* Private user code
———————————————————*/
/* USER CODE BEGIN 0 */
void cs_sel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET);
}

void cs_desel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET);
}

uint8_t spi_rb(void) {
uint8_t rbuf;
HAL_SPI_Receive(&hspi2, &rbuf, 1, 0xFFFFFFFF);
return rbuf;
}

void spi_wb(uint8_t b) {
HAL_SPI_Transmit(&hspi2, &b, 1, 0xFFFFFFFF);
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
uint8_t bufSize[] = {2, 2, 2, 2};
reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
reg_wizchip_spi_cbfunc(spi_rb, spi_wb);
wizchip_init(bufSize, bufSize);
wiz_NetInfo netInfo = { .mac = {0x00, 0x08, 0xdc, 0xab, 0xcd, 0xef},
.ip = {192, 168, 3, 197},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 3, 1}};

wizchip_setnetinfo(&netInfo);
/* USER CODE END 2 */

Web server

Now that we have a working network interface, we can start building the web server. The program source code is only shown in excerpts. You can download the complete project at Entwickler Magazin.

In order not to make things unnecessarily complicated, our web server can switch the LED installed on the Nucleo board on and off. Figure 10 shows how the small server presents itself. When the LED image is clicked, the browser sends an Ajax request to the server. The server changes the status of the LED and sends back the corresponding SVG graphic.

Fig. 10: The application for switching the LED.

 

The libraries for building a web server are included in the project in the same way as the libraries for controlling the hardware. Copy the httpserver folder from the ZIP archive into your project and include it in the include path. Again, don’t forget to import it. As you can see in Listing 4, we import some more standard libraries that we need to be able to process strings.

The web server is relatively simple to use. It is initialized by calling the httpServer_init function. During initialization parameters like buffer and number of sockets are given. After that, the static HTML pages are registered with the function reg_httpServer_webContent. Here it is useful to outsource the HTML code to an external header file to make the code more readable. In the infinite loop, which normally contains the actual microcontroller programs, the httpServer_run function must now be called for each socket. The code snippet for this can be seen in Listing 4.

 

Listing 4: Initialize and run web server

/* Private includes
----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "wizchip_conf.h"
#include "socket.h"
#include "httpServer.h"
#include "webpages.h"
#include
#include "httpUtil.h"
#include
/* USER CODE END Includes */

/* USER CODE BEGIN 2 */
uint8_t bufSize[] = {2, 2, 2, 2};
reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
reg_wizchip_spi_cbfunc(spi_rb, spi_wb);
wizchip_init(bufSize, bufSize);
wiz_NetInfo netInfo = { .mac = {0x00, 0x08, 0xdc, 0xab, 0xcd, 0xef},
.ip = {192, 168, 3, 197},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 3, 1}};
wizchip_setnetinfo(&netInfo);
uint8_t socknumlist[] = {2, 3, 4, 5, 6, 7};
#define DATA_BUF_SIZE 2048
#define MAX_HTTPSOCK 6
uint8_t RX_BUF[DATA_BUF_SIZE];
uint8_t TX_BUF[DATA_BUF_SIZE];
httpServer_init(TX_BUF, RX_BUF, MAX_HTTPSOCK, socknumlist);
reg_httpServer_cbfunc(NVIC_SystemReset, NULL);
reg_httpServer_webContent((uint8_t *)”index.html”, (uint8_t *)index_page);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1){
for(int i = 0; i < MAX_HTTPSOCK; i++)httpServer_run(i); // HTTP Server handler /* USER CODE END WHILE */

It gets a little trickier if you want to have dynamic code. The generation of dynamic code is controlled by the two functions predefined_get_cgi_processor and predefined_set_cgi_processor. Note that the predefined_get_cgi_processor function is for HTTP GET calls and the predefined_set_cgi_processor function is for HTTP POST. How there is a set in the name, only the gods know. Believe me, this peculiar naming has already caused some confusion. Another important point when using dynamic code is that the called web pages must necessarily end in .cgi. This takes some getting used to, especially when using Ajax. The two functions can be seen in Listing 5.

 

Listing 5: The functions for generating dynamic content

/* Private user code
---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void cs_sel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET); //CS LOW
}
void cs_desel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET); //CS HIGH
}

uint8_t spi_rb(void) {
uint8_t rbuf;
HAL_SPI_Receive(&hspi2, &rbuf, 1, 0xFFFFFFFF);
return rbuf;
}

void spi_wb(uint8_t b) {
HAL_SPI_Transmit(&hspi2, &b, 1, 0xFFFFFFFF);
}

uint8_t predefined_get_cgi_processor(uint8_t * uri_name, uint8_t * buf, uint16_t * len){
if(strcmp((const char *)uri_name, “led.cgi”) == 0){
HAL_GPIO_TogglePin (GPIOA, GPIO_PIN_5);

if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5)) {
*len = sprintf((char *)buf, led_on);
} else {
*len = sprintf((char *)buf, led_off);
}

}
return 1;
}

uint8_t predefined_set_cgi_processor(uint8_t * uri_name, uint8_t * uri, uint8_t * buf, uint16_t * len){
return 0;
}

/* USER CODE END 0 */

Even if you only want to use static HTML, you have to define at least the two functions empty, otherwise, the compiler will complain. The code in these two functions can become quite demanding. Therefore it makes sense to put them into extra files. The return code can be used to control whether the called CGI page could be generated (1) or an error occurred (0).

mohr_nucleo_7.tif_fmt1.jpg

In order to connect the Nucleo board to a network, we first need a suitable interface. The WIZ5500 is a hardware Ethernet controller that supports the usual protocols such as TCP, UDP, ICMP, IPv4, ARP, IGMP, and PPPoE. Its operating voltage is 3.3 volts. The Ethernet controller operates at both 10 Mbit and 100 Mbit. As a hobbyist-friendly module, the WIZ5500 costs about 9 euros. The data transfer between the network module and the Nucleo board is done via the SPI interface. This is often used for the communication of microcontrollers with their peripherals. In principle, the data is transmitted clock-synchronously and serially. Table 2 shows how the Nucleo Board must be connected to the network module. It should be noted here that we are using the SPI2 interface of the Nucleo board. Figure 7 shows how to make the connections. To make sure we don’t forget later, we should connect the LAN directly as well. It wouldn’t be the first time that we spent hours looking for a fault—and in the end, it was only a forgotten plug connection.

Fig. 7: The Nucleo board connected to the W5500 board

 

Nucleo BoardNetwork module
5V5V
PC2MISO
PC3MOSI
PB10SCLK
PC4SCS
GNDGND
Table 2: Connections between Nucleo Board and network module.
The WIZ5500 module does not have its own Media Access Control (MAC) address. This means that the MAC must be provided externally. There are two possibilities here: For testing, just take any random sequence, because it would be quite unlikely to hit a MAC that you already have in your own network segment. The second option is to buy a chip that contains a MAC (globally unique). You read this and use it for the WIZ5500. The 24AA025E48 is for example such a chip.

For simple experiments, the first variant is absolutely fine because the MAC address only has to be unique within one network segment. The MAC is not transmitted across router boundaries. If you want to know even more about the WIZ5500 chip, take a look at the data sheet.

Library setup

The manufacturer of the WIZ5500 chip provides a special library for control. This offers us the possibility to set TCP IP parameters via simple API calls and to build a complete web server. The library can be freely downloaded from GitHub. Via the direct download link, you can download a ZIP archive that contains all the necessary files.

We unpack the archive into a directory below the src folder in our project. This is necessary because the library is available as a source and must be compiled together with the project in any case. In the unpacked files in the Ethernet subdirectory we find a file wizchip_conf.h. In this file, we have to select the chip 5500 in line 75 and save the file (Listing 2).

 

Listing 2: Select the WIZCIP 5500 in the config file of the library

#ifndef _WIZCHIP_
#define _WIZCHIP_ W5500 // W5100, W5100S, W5200, W5300, W5500
#endif

Now we have to include the library into the project. Use the right mouse button on the project in the STM32CubeIDE. Now follow the path to the configuration of the libraries Properties | C/C++ Build | Settings | Tool Settings | Include paths. Add the two paths Ethernet and Ethernet/5500 to Include paths (Fig. 8).

Fig. 8: Integrating libraries into the project

 

The library is now completely set up and included in the project. In the next section, we will show how to extend the project so that the Ethernet module can be used.

Extend project

We now need to do some preliminary work on the project to include the WIZ5500 chip on the software side as well. Before we start to extend the actual source code, we have to configure the hardware of the project. That means that we activate the second SPI interface. We already connected the hardware at the beginning of the article. Open the Pinout & Configuration overview by clicking on the Blink.ioc file. Now select the SPI2 interface in the menu item Category | Connectivity and activate it in Full Duplex Master mode. Set the pin PC_4 to GPIO output. Use this pin as the chip select (CS) line for SPI communication. Figure 9 shows which pins of the STM32 have also turned green. In this case, green means that the pins are in use for the project.

Fig. 9: Hardware configuration of the SPI2 interface

 

If you click on Save, the IDE asks if we want to have the source code for the hardware configuration generated. It is not an easy task to create this code without errors, because the microcontrollers of the STM32 family offer so many possibilities – but there are also many possibilities to do something wrong.

In a few places, the IDE asks what it should do. In the beginning, it is important to read these questions and think about exactly what you want. After some time, you can make the IDE remember the answers. This will make work much smoother.

In Listing 3 we summarized the commands that you must include in the generated code to configure the WIZ5500. You can use the comments of the generator as a good guide. With these lines, the TCP-IP stack of the chip is activated, and it can already react to a simple ping. Please note that you are using a free IP from your local network. Of course, we must not forget to import the library for the WIZ5500. This can be done with the two import lines at the beginning of the program.

The communication between the library and our program runs via four special functions. These must be built into the source code of the main.c file. You can orientate yourself on the code generator comments. The functions control the CS signal and regulate over which SPI interface the data should run.

The last code block initializes the WIZ5500 and gives it a MAC and an IP address. When the program is loaded into the controller, it already responds to ping packets. Please test the connection first before you continue with the next steps.

 

Listing 3: Functions that must be included in the generated code

/* Private includes
----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "wizchip_conf.h"
#include "socket.h"
/* USER CODE END Includes */

/* Private user code
———————————————————*/
/* USER CODE BEGIN 0 */
void cs_sel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET);
}

void cs_desel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET);
}

uint8_t spi_rb(void) {
uint8_t rbuf;
HAL_SPI_Receive(&hspi2, &rbuf, 1, 0xFFFFFFFF);
return rbuf;
}

void spi_wb(uint8_t b) {
HAL_SPI_Transmit(&hspi2, &b, 1, 0xFFFFFFFF);
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
uint8_t bufSize[] = {2, 2, 2, 2};
reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
reg_wizchip_spi_cbfunc(spi_rb, spi_wb);
wizchip_init(bufSize, bufSize);
wiz_NetInfo netInfo = { .mac = {0x00, 0x08, 0xdc, 0xab, 0xcd, 0xef},
.ip = {192, 168, 3, 197},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 3, 1}};

wizchip_setnetinfo(&netInfo);
/* USER CODE END 2 */

Web server

Now that we have a working network interface, we can start building the web server. The program source code is only shown in excerpts. You can download the complete project at Entwickler Magazin.

In order not to make things unnecessarily complicated, our web server can switch the LED installed on the Nucleo board on and off. Figure 10 shows how the small server presents itself. When the LED image is clicked, the browser sends an Ajax request to the server. The server changes the status of the LED and sends back the corresponding SVG graphic.

Fig. 10: The application for switching the LED.

 

The libraries for building a web server are included in the project in the same way as the libraries for controlling the hardware. Copy the httpserver folder from the ZIP archive into your project and include it in the include path. Again, don’t forget to import it. As you can see in Listing 4, we import some more standard libraries that we need to be able to process strings.

The web server is relatively simple to use. It is initialized by calling the httpServer_init function. During initialization parameters like buffer and number of sockets are given. After that, the static HTML pages are registered with the function reg_httpServer_webContent. Here it is useful to outsource the HTML code to an external header file to make the code more readable. In the infinite loop, which normally contains the actual microcontroller programs, the httpServer_run function must now be called for each socket. The code snippet for this can be seen in Listing 4.

 

Listing 4: Initialize and run web server

/* Private includes
----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "wizchip_conf.h"
#include "socket.h"
#include "httpServer.h"
#include "webpages.h"
#include
#include "httpUtil.h"
#include
/* USER CODE END Includes */

/* USER CODE BEGIN 2 */
uint8_t bufSize[] = {2, 2, 2, 2};
reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
reg_wizchip_spi_cbfunc(spi_rb, spi_wb);
wizchip_init(bufSize, bufSize);
wiz_NetInfo netInfo = { .mac = {0x00, 0x08, 0xdc, 0xab, 0xcd, 0xef},
.ip = {192, 168, 3, 197},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 3, 1}};
wizchip_setnetinfo(&netInfo);
uint8_t socknumlist[] = {2, 3, 4, 5, 6, 7};
#define DATA_BUF_SIZE 2048
#define MAX_HTTPSOCK 6
uint8_t RX_BUF[DATA_BUF_SIZE];
uint8_t TX_BUF[DATA_BUF_SIZE];
httpServer_init(TX_BUF, RX_BUF, MAX_HTTPSOCK, socknumlist);
reg_httpServer_cbfunc(NVIC_SystemReset, NULL);
reg_httpServer_webContent((uint8_t *)”index.html”, (uint8_t *)index_page);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1){
for(int i = 0; i < MAX_HTTPSOCK; i++)httpServer_run(i); // HTTP Server handler /* USER CODE END WHILE */

It gets a little trickier if you want to have dynamic code. The generation of dynamic code is controlled by the two functions predefined_get_cgi_processor and predefined_set_cgi_processor. Note that the predefined_get_cgi_processor function is for HTTP GET calls and the predefined_set_cgi_processor function is for HTTP POST. How there is a set in the name, only the gods know. Believe me, this peculiar naming has already caused some confusion. Another important point when using dynamic code is that the called web pages must necessarily end in .cgi. This takes some getting used to, especially when using Ajax. The two functions can be seen in Listing 5.

 

Listing 5: The functions for generating dynamic content

/* Private user code
---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void cs_sel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET); //CS LOW
}
void cs_desel() {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET); //CS HIGH
}

uint8_t spi_rb(void) {
uint8_t rbuf;
HAL_SPI_Receive(&hspi2, &rbuf, 1, 0xFFFFFFFF);
return rbuf;
}

void spi_wb(uint8_t b) {
HAL_SPI_Transmit(&hspi2, &b, 1, 0xFFFFFFFF);
}

uint8_t predefined_get_cgi_processor(uint8_t * uri_name, uint8_t * buf, uint16_t * len){
if(strcmp((const char *)uri_name, “led.cgi”) == 0){
HAL_GPIO_TogglePin (GPIOA, GPIO_PIN_5);

if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5)) {
*len = sprintf((char *)buf, led_on);
} else {
*len = sprintf((char *)buf, led_off);
}

}
return 1;
}

uint8_t predefined_set_cgi_processor(uint8_t * uri_name, uint8_t * uri, uint8_t * buf, uint16_t * len){
return 0;
}

/* USER CODE END 0 */

Even if you only want to use static HTML, you have to define at least the two functions empty, otherwise, the compiler will complain. The code in these two functions can become quite demanding. Therefore it makes sense to put them into extra files. The return code can be used to control whether the called CGI page could be generated (1) or an error occurred (0).

COMMENTS

Please Login to comment
  Subscribe  
Notify of