Este es una pequeña y muy básica muestra de como usar LwIP y FreeRTOS en STM32 usando CubeMX para generar el código. Para las pruebas usaré una tarjeta Nucle-144 (STM32F429) que tiene un PHY LAN8742A
Lo primero es inciar CubeMX y seleccionar la tarjeta Nucleo-144 con el MCU STM32F429ZI.
Habilitamos FreeRTOS, Ethernet en modo RMII y LWIP.
Para la configuración del Reloj ne HSE seleccionamos BYPASS y en SYS seleccionamos Trace Asyncronous Sw
También habilitamos USART3 ya que está conectado al ST-Link incluido en la tarjeta.
Vamos a la configuración del Reloj. Seleccionamos como fuente PLLCLK y HCLK colocamos 180 y dejamos que el programa calcule los parámetros necesarios.
Después nos vamos a la configuración de LWIP. En opciones generales sólo haremos cambios en caso de querer habilitar o deshabilitar el módulo ICMP (ping) , IGMP, DNS, UDP y TCP. Por el momento no vamos a mover nada aquí,al igual que en la pestaña Key Options.
Como queremos habilitar un servidor HTTP nos vamos a la pestaña HTTPD y lo activamos. En las opciones que se activan enseguida, habilitamos CGI y SSI, además de habilitar la opción HTTPD_USE_CUSTOM_FSDATA.
En versiones anteriores de CubeMX tenían que agregar una constante de usuario llamada LWIP_TIMEVAL_PRIVATE con un valor de 0, en la última actualización ya no es necesario, pero si a la hora de compilar les da un error de multiples definiciones de typedef struct timeval, agregan esta constante de usuario para que pueda compilar sin problemas.
Después vamos a configurar la sección de Ethernet. En la pestaña de los Parámetros, para el caso de esta Tarjeta modificamos el valor de PHY Address de 1 a 0.
Los parámetros avanzados los dejamos igual. Si tuvieramos otro PHY en el que no coincidan los registros aquí es donde podemos cambiarlos.
En este momento ya podemos generar el código para su IDE preferido.
Con el código generado entramos en la carpeta donde se generó el código y creamos una carpeta nueva llamada fs. En esta carpeta tendremos todos los archivos de nuestra web.
mkdir fs
cd fs
touch index.html
Después copiamos el makefsdata (perl script) que viene con el Middleware LwIP en Middlewares/Third_Party/LwIP/src/apps/httpd/makefsdata/makefsdata. Aunque también puede descargar una versión en C ya precompilada para su plataforma.
cp ~/STM32Cube/Repository/STM32Cube_FW_F4_V1.16.0/Middlewares/Third_Party/LwIP/src/apps/httpd/makefsdata/makefsdata .
Ejecutamos el script y copiamos el resultado en la carpeta httpd con el nombre fsdata_custom.c
perl makefsdata
cp fsdata.c Middlewares/Third_Party/LwIP/src/apps/httpd/fsdata_custom.c
Construimos el proyecto para ver que compile sin errores.
make
/usr/bin/arm-none-eabi-size build/Nucleo144_LwIP.elf
text data bss dec hex filename
40980 124 44572 85676 14eac build/Nucleo144_LwIP.elf
/usr/bin/arm-none-eabi-objcopy -O ihex build/Nucleo144_LwIP.elf build/Nucleo144_LwIP.hex
/usr/bin/arm-none-eabi-objcopy -O binary -S build/Nucleo144_LwIP.elf build/Nucleo144_LwIP.bin
Si todo ha ido bien, abrimos el archivo freertos.c y añadimos el siguiente include
#include "stm32f4xx_hal.h"
Además añadimos el siguiente código en la tarea default
HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
osDelay(100);
Compilamos y subimos a la tarjeta. En este momento veremos el LED Rojo parpadeando y podremos hacer ping a la tarjeta.
Para ver la dirección IP que le fue asignada, por el momento nos vamos la la Tabla DHCP de nuestro router.
$ ping 192.168.3.2
PING 192.168.3.2 (192.168.3.2) 56(84) bytes of data.
64 bytes from 192.168.3.2: icmp_seq=1 ttl=255 time=70.9 ms
64 bytes from 192.168.3.2: icmp_seq=2 ttl=255 time=4.52 ms
64 bytes from 192.168.3.2: icmp_seq=3 ttl=255 time=1.38 ms
64 bytes from 192.168.3.2: icmp_seq=4 ttl=255 time=1.43 ms
64 bytes from 192.168.3.2: icmp_seq=5 ttl=255 time=24.1 ms
64 bytes from 192.168.3.2: icmp_seq=6 ttl=255 time=1.26 ms
64 bytes from 192.168.3.2: icmp_seq=7 ttl=255 time=1.35 ms
64 bytes from 192.168.3.2: icmp_seq=8 ttl=255 time=1.29 ms
64 bytes from 192.168.3.2: icmp_seq=9 ttl=255 time=1.40 ms
64 bytes from 192.168.3.2: icmp_seq=10 ttl=255 time=1.38 ms
^C
--- 192.168.3.2 ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 9012ms
rtt min/avg/max/mdev = 1.265/10.905/70.900/21.101 ms
Si por azares del destino no funciona, verifiquen que en la función HAL_ETH_MspInit, el valor de la prioridad de la interrupción sea 5
HAL_NVIC_SetPriority(ETH_IRQn, 5, 0);
El siguiente paso es habilitar nuestro servidor HTTP, así que abrimos el archivo lwip.c y añadimos en la funcion MX_LWIP_Init httpd_init(); al final en la sección de código de usuario, que se encuentra después de la llamada a la función dhcp_start(&gnetif);
Compilamos y cargamos en la tarjeta, lo que nos permitirá ver nuestra página en el navegador.
Nuestros primeros CGIsCreamos una nueva carpeta llamada app para separar nuestro código del generado por CubeMX y creamos dentro los archivos cgi.h y cgi.c
mkdir app
cd app
touch cgi.c
touch cgi.h
Editamos y añadimos los archivos al proyecto
cgi.h
#ifndef __CGI_H
#define __CGI_H
#include "lwip/apps/httpd.h"
/* CGI Handlers */
/** Login CGI Handler **/
const char * LOGIN_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[]);
/** status CGI Handler **/
const char * STATUS_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[]);
static const tCGI CGI_Tab[] = {
{"/api/status.cgi", STATUS_CGI_Handler},
{"/api/login.cgi",LOGIN_CGI_Handler}
};
#endif
cgi.c
#include "cgi.h"
#include "stm32f4xx_hal.h"
/* CGI Handlers */
/** Login CGI Handler **/
const char * LOGIN_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[])
{
HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin);
return("/index.html");
}
/** status CGI Handler **/
const char * STATUS_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[])
{
HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin);
}
Por el momento lo más sencillo es tan sólo cambiar el estado de uno de los LEDs de la tarjeta. En seguida incluimos "cgi.h" en el archivo lwip.c y agregamos el siguiente código antes de iniciar le servidor http.
http_set_cgi_handlers(CGI_Tab,2);
Donde el primer parámetro es nuestra tabla de CGIs definidas en cgi.h y el segundo parámetro es la cantidad de elementos de esta tabla, que en este caso serían 2.
Compilamos y probamos. En cada ocasión que accedamos a cualquiera de las dos funciones veremos como el LED azul cambia de estado. En el caso de /api/login.cgi nos regresará a la página /index.html y /api/status.cgi no nos regresará nada.
Server Side Includes (SSI)Ahora le toca a los SSI. Esta es una de las formas en que podemos obtener datos internos del MCU y mostrarlos en la página usando etiquetas que tienen la siguiente forma:
<!--#etiqueta-->
Primero creamos el archivo status.ssi dentro de fs (el número de extensiones es limitado pero incluye XML)
Dato 1: <!--#data1--><br>
Dato 2: <!--#data2--><br>
Y recreamos nuestro fsdata_custom.c usando el makefsdata
perl makefsdata
cp fsdata.c Middlewares/Third_Party/LwIP/src/apps/httpd/fsdata_custom.c
Agregamos a nuestro proyecto los archivos ssi.h y ssi.c dentro de app
touch app/ssi.h
touch app/ssi.c
ssi.h
#ifndef __SSI_H
#define __SSI_H
#include "lwip/apps/httpd.h"
static const char *tags[] = {"data1","data2"};
static const uint32_t ntags = 2;
/* SSI Handler function */
uint16_t SSI_Handler(int32_t Idx, char *pcInsert, int32_t InsertLen);
#endif
Aquí definimos nuestras etiquetas tal, además de declarar el prototipo de la función encargada de determinar que añadir dependiendo de la etiqueta que se encuentre en el archivo respectivo.
ssi.c
#include "ssi.h"
#include <string.h>
/* SSI Handler function */
uint16_t SSI_Handler(int32_t Idx, char *pcInsert, int32_t InsertLen)
{
if(Idx == 0){//Etiqueta 0: data1
} else if(Idx == 1){ //Etiqueta 1: data2
}
return 0;
}
Idx representa el indice de la etiqueta.
Editamos el archivo lwip.c para incluir "ssi.h" y de nueva cuenta la función MX_LWIP_Init, para agregarle
http_set_ssi_handler( SSI_Handler, (char const **)tags,2);
Por lo que nos quedaría
http_set_cgi_handlers(CGI_Tab,2);
http_set_ssi_handler( SSI_Handler, (char const **)tags,2);
httpd_init();
Compilamos nuestro proyecto y lo cargamos en la tarjeta para probarlo.
Como nota final, LwIP cuenta con tres API: Raw, Netconn y Socket.
Los dos últimos son los únicos que se pueden usar de forma segura con un RTOS, el primero es cuando no vamos a usar un RTOS en nuestra aplicación.