Autor Tema: [Aporte] LwIP, FreeRTOS CubeMX  (Leído 3409 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado tsk

  • PIC18
  • ****
  • Mensajes: 257
[Aporte] LwIP, FreeRTOS CubeMX
« en: 24 de Agosto de 2017, 04:21:17 »
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.

Código: [Seleccionar]
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.

Código: [Seleccionar]
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
Código: [Seleccionar]
perl makefsdata

cp fsdata.c Middlewares/Third_Party/LwIP/src/apps/httpd/fsdata_custom.c

Construimos el proyecto para ver que compile sin errores.

Código: [Seleccionar]
make

Código: [Seleccionar]
/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

Código: [Seleccionar]
#include "stm32f4xx_hal.h"Además añadimos el siguiente código en la tarea default

Código: [Seleccionar]
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.

Código: [Seleccionar]
$ 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

Código: [Seleccionar]
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 CGIs

Creamos una nueva carpeta llamada app para separar nuestro código del generado por CubeMX y creamos dentro los archivos cgi.h y cgi.c

Código: [Seleccionar]
mkdir app
cd app
touch cgi.c
touch cgi.h

Editamos y añadimos los archivos al proyecto

cgi.h
Código: C
  1. #ifndef __CGI_H
  2. #define __CGI_H
  3.  
  4. #include "lwip/apps/httpd.h"
  5.  
  6. /* CGI Handlers */
  7. /** Login CGI Handler **/
  8. const char * LOGIN_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[]);
  9. /** status CGI Handler **/
  10. const char * STATUS_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[]);
  11.  
  12. static const tCGI CGI_Tab[] = {
  13.         {"/api/status.cgi", STATUS_CGI_Handler},
  14.         {"/api/login.cgi",LOGIN_CGI_Handler}
  15. };
  16.  
  17. #endif

cgi.c

Código: C
  1. #include "cgi.h"
  2. #include "stm32f4xx_hal.h"
  3. /* CGI Handlers */
  4. /** Login CGI Handler **/
  5. const char * LOGIN_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[])
  6. {
  7.         HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin);
  8.         return("/index.html");
  9. }
  10. /** status CGI Handler **/
  11. const char * STATUS_CGI_Handler(int iIndex,int iNumParams,char *Param[], char *pcValue[])
  12. {
  13.         HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin);
  14. }

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.

Código: C
  1. 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:

Código: [Seleccionar]
<!--#etiqueta-->

Primero creamos el archivo status.ssi dentro de fs (el número de extensiones es limitado pero incluye XML)

Código: Text
  1. Dato 1: <!--#data1--><br>
  2. Dato 2: <!--#data2--><br>

Y recreamos nuestro fsdata_custom.c usando el makefsdata

Código: [Seleccionar]
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

Código: [Seleccionar]
touch app/ssi.h
touch app/ssi.c

ssi.h
Código: C
  1. #ifndef __SSI_H
  2. #define __SSI_H
  3.  
  4. #include "lwip/apps/httpd.h"
  5.  
  6. static const char *tags[] = {"data1","data2"};
  7. static const uint32_t ntags = 2;
  8.  
  9. /* SSI Handler function */
  10. uint16_t SSI_Handler(int32_t Idx, char *pcInsert, int32_t InsertLen);
  11.  
  12. #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
Código: C
  1. #include "ssi.h"
  2. #include <string.h>
  3.  
  4. /* SSI Handler function */
  5. uint16_t SSI_Handler(int32_t Idx, char *pcInsert, int32_t InsertLen)
  6. {
  7.         if(Idx == 0){//Etiqueta 0: data1
  8.                 sprintf(pcInsert,"Hola Mundo");
  9.                 return strlen(pcInsert);
  10.         } else if(Idx == 1){ //Etiqueta 1: data2
  11.                 sprintf(pcInsert,"%d",33);
  12.                 return strlen(pcInsert);
  13.         }
  14.  
  15.         return 0;
  16. }

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

Código: C
  1. http_set_ssi_handler( SSI_Handler, (char const **)tags,2);

Por lo que nos quedaría

Código: C
  1. http_set_cgi_handlers(CGI_Tab,2);
  2. http_set_ssi_handler( SSI_Handler, (char const **)tags,2);
  3. 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.
« Última modificación: 24 de Agosto de 2017, 13:16:11 por tsk »

Desconectado tsk

  • PIC18
  • ****
  • Mensajes: 257
Re:[Aporte] LwIP, FreeRTOS CubeMX - UDP Sockets: Cliente
« Respuesta #1 en: 24 de Agosto de 2017, 12:13:46 »
Sockets: Cliente UDP

¿Porqué usar Sockets? Nos proporciona una api de programación más cercana a lo que vemos en sistemas operativos Unix/Linux, por lo que podremos portar casi cualquier ejemplo a nuestro sistema embebido.

Supongamos que queremos crear una tarea periódica que envíe ciertos datos usando UDP, y para mantenerlo simple, no esperamos ningún dato de este servidor.

Lo primero que vamos a crear es nuestro servidor udp en el lado del PC.

udp_socket_server.py
Código: Python
  1. import SocketServer
  2.  
  3. class MyUDPHandler(SocketServer.BaseRequestHandler):
  4.     """
  5.    This class works similar to the TCP handler class, except that
  6.    self.request consists of a pair of data and client socket, and since
  7.    there is no connection the client address must be given explicitly
  8.    when sending data back via sendto().
  9.    """
  10.  
  11.     def handle(self):
  12.         data = self.request[0].strip()
  13.         socket = self.request[1]
  14.         print "{} wrote:".format(self.client_address[0])
  15.         print data
  16.  
  17. if __name__ == "__main__":
  18.     HOST, PORT = "0.0.0.0", 9999
  19.     server = SocketServer.UDPServer((HOST, PORT), MyUDPHandler)
  20.     server.serve_forever()

Después creamos en el directorio app dos archivos más cuyo nombre va a guardar relación con la aplicación. En este caso los voy a llamar udp_sockets.h y udp_sockets.c

upd_sockets.h
Código: C
  1. #ifndef __UDP_SOCKET_H_
  2. #define __UDP_SOCKET_H_
  3.  
  4. void udp_client_socket_init(void);
  5.  
  6. #endif

Por lo general, vamos a contar con tres partes esenciales, como se muestra en el siguiente código:

udp_socket.c
Código: C
  1. #include "lwip/sys.h"
  2. #include "lwip/sockets.h"
  3. #include "string.h"
  4. #include "udp_socket.h"
  5. #include "cmsis_os.h"
  6.  
  7. #include <stdio.h>
  8.  
  9. #define SERVER_ADDRESS "192.168.3.97"
  10. #define SERVER_PORT 9999
  11.  
  12. void udp_client_socket_req(void)
  13. {
  14.         int sock;
  15.         struct sockaddr_in local;
  16.         struct sockaddr_in to;
  17.         int tolen;
  18.         int size;
  19.         int timeout;
  20.         ip_addr_t server_address;
  21.  
  22.  
  23.         if(ipaddr_aton(SERVER_ADDRESS, &server_address))
  24.         {
  25.                 sock = socket(AF_INET, SOCK_DGRAM,0);
  26.                 if(sock >= 0)
  27.                 {
  28.                         //Local address
  29.                         memset(&local, 0, sizeof(local));
  30.                         local.sin_family = AF_INET;
  31.                         local.sin_port = htons(INADDR_ANY);
  32.                         local.sin_addr.s_addr = htonl(INADDR_ANY);
  33.  
  34.                         if(bind(sock, (struct sockaddr *)&local, sizeof(local)) == 0)
  35.                         {
  36.                                 memset(&to,0,sizeof(to));
  37.                                 to.sin_family = AF_INET;
  38.                                 to.sin_port = htons(SERVER_PORT);
  39.                                 inet_addr_from_ipaddr(&to.sin_addr, &server_address);
  40.  
  41.                                 sendto(sock,"Hola Mundo\n",11,0,(struct sockaddr *)&to, sizeof(to));
  42.                         }
  43.  
  44.                         closesocket(sock);
  45.                 }
  46.         }
  47. }
  48.  
  49. static void udp_client_socket_thread(void *args)
  50. {
  51.         while(1)
  52.         {
  53.                 udp_client_socket_req();
  54.                 osDelay(1000);
  55.         }
  56. }
  57. void udp_client_socket_init(void)
  58. {
  59.         sys_thread_new("UDP_Test", udp_client_socket_thread,NULL, DEFAULT_THREAD_STACKSIZE,osPriorityAboveNormal);
  60. }

udp_client_socket_init nos inicializa la tarea (udp_client_socket_thread) que va estar llamando la función que envía datos vía UDP (udp_client_socket_req) a nuestro servidor cada intervalo de tiempo definido dentro de osDelay.

Como lo hicimos anteriormente, agregamos en el archivo lwip.c el include a udp_socket.h y agregamos

Código: C
  1. udp_client_socket_init();

en seguida de httpd_init();

Para probar primero iniciamos nuestro servidor con

Código: [Seleccionar]
python udp_socket_server.py
Compilamos el proyecto y lo subimos a la tarjeta.



Para este momento nuestro sistema puede reponder ping, servir páginas web, enviar datos a un servidor por medio de UDP y lo más importante "Tener el LED Rojo de nuestra tarjeta parpadeando"

Añado vídeo

« Última modificación: 24 de Agosto de 2017, 14:02:48 por tsk »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:[Aporte] LwIP, FreeRTOS CubeMX
« Respuesta #2 en: 24 de Agosto de 2017, 15:05:46 »
Gracias por el aporte

Yo tengo unas preguntas de FreeRTOS mas que nada, si es posible ver el consumo del micro y memoria (con su Top de stack ) de cada uno de las tareas del FreeRTOS, siempre quise saber si era posible lograr eso de alguna forma "simple", tambien suele tener una especie de cuanto tiempo pasa el RTOS en cada una de las tareas.
¿Conoces si lo tiene?

El webServer lo provee ST? En un ejemplo que vi de TI ellos proveen el webserver que es quien parsea la direccion URL y variables, el encargado de manejar el lwIP es este webserver.
Tambien se maneja de la misma forma con las paginas e imagenes, lo pasan a un archivo C.
« Última modificación: 24 de Agosto de 2017, 15:16:41 por KILLERJC »

Desconectado tsk

  • PIC18
  • ****
  • Mensajes: 257
Re:[Aporte] LwIP, FreeRTOS CubeMX
« Respuesta #3 en: 24 de Agosto de 2017, 17:23:16 »
Si, de hecho puedes generar estadísticas de uso de cada tarea, por lo pronto FreeRTOS cuenta con una función llamada vTaskList, o su wrapper de cmsis os llamado osThreadList. Modifiqué el ejemplo del SSI para que en dato 2 use osThreadList.

Para emplearlo en CubeMX en la configuración de FreeRTOS (Config parameters) habilitas:
USE_TRACE_FACILITY y USE_STATS_FORMATING_FUNCTIONS

Código: [Seleccionar]
#include "ssi.h"
#include <string.h>
#include "cmsis_os.h"

uint16_t SSI_Handler(int32_t Idx, char *pcInsert, int32_t InsertLen)
{
if(Idx == 0){
sprintf(pcInsert,"Hola Mundo");
return strlen(pcInsert);
} else if(Idx == 1){
osThreadList(pcInsert);
return strlen(pcInsert);
}

return 0;
Código: [Seleccionar]
Dato 1: <!--#data1-->Hola Mundo<br>
Dato 2: <!--#data2-->tcpip_thread    R 6 808 3
IDLE            R 0 115 2
UDP_Test        B 4 868 5
defaultTask    B 3 38 1
EthIf          S 6 299 4
<br>

Para el otro se tiene que habilitar GENERATE_RUN_TIME_STATS y proveer otras dos funciones, siendo una de ellas una función que le regrese el tiempo(aunque en este momento no lo recuerdo de forma explícita http://www.freertos.org/rtos-run-time-stats.html)

Cada empresa hace sus adaptaciones del web server que provee LwIP, pero provienen del mismo lugar. En el caso de TI, creo que tienen dos ejemplos, uno usando LwIP y otro uIP pero todo lo demás se maneja de la misma forma con relación a las páginas e imágenes, donde makefsdata te genera el archivo en C.

Añado otra modificación

Cuando habilitas GENERATE_RUN_TIME_STATS

En freertos.c te agrega las funciones que requieres.

Código: C
  1. __weak void configureTimerForRunTimeStats(void)
  2. {
  3.  
  4. }
  5.  
  6. __weak unsigned long getRunTimeCounterValue(void)
  7. {
  8.    return 0;
  9. }

Por lo tanto si se requiere configurar un Timer en configureTimerForRunTimeStats, y se necesita retornar el valor del contador del timer en getRunTimeCounterValue.

Por lo pronto, para no configurar otro timer, y como prueba, puedes regresar el valor de HAL_Tick();

Código: C
  1. __weak void configureTimerForRunTimeStats(void)
  2. {
  3.  
  4. }
  5.  
  6. __weak unsigned long getRunTimeCounterValue(void)
  7. {
  8. return HAL_GetTick();
  9. }

Para visualizar el resultado, se puede modificar de nueva cuenta ssi.c y dejarlo de la siguiente forma, agregando FreeRTOS.h y haciendo uso de la función vTaskGetRunTimeStats

Código: C
  1. #include "ssi.h"
  2. #include <string.h>
  3. #include "FreeRTOS.h"
  4. #include "cmsis_os.h"
  5.  
  6. uint16_t SSI_Handler(int32_t Idx, char *pcInsert, int32_t InsertLen)
  7. {
  8.         if(Idx == 0){
  9.                 //sprintf(pcInsert,"Hola Mundo");
  10.                 vTaskGetRunTimeStats(pcInsert);
  11.                 return strlen(pcInsert);
  12.         } else if(Idx == 1){
  13.                 osThreadList(pcInsert);
  14.                 //sprintf(pcInsert,"%d",strlen(buff));
  15.                 return strlen(pcInsert);
  16.         }
  17.  
  18.         return 0;
  19. }

Dando como resultado

Código: [Seleccionar]
Dato 1: <!--#data1-->tcpip_thread    93 <1%
IDLE            19715 86%
UDP_Test        0 <1%
defaultTask    3004 13%
EthIf          27 <1%
<br>
Dato 2: <!--#data2-->tcpip_thread    R 6 858 3
IDLE            R 0 115 2
defaultTask    B 3 29 1
UDP_Test        B 4 868 5
EthIf          S 6 302 4
<br>

Código: [Seleccionar]
Dato 1: <!--#data1-->tcpip_thread    100 <1%
IDLE            111998 97%
UDP_Test        0 <1%
defaultTask    3004 2%
EthIf          28 <1%
<br>
Dato 2: <!--#data2-->tcpip_thread    R 6 858 3
IDLE            R 0 115 2
defaultTask    B 3 29 1
UDP_Test        B 4 868 5
EthIf          S 6 302 4
<br>

Probablemente no sea tan preciso, y se pueda obtener mejores resultados con un Timer que corra a mayor frecuencia.
« Última modificación: 24 de Agosto de 2017, 22:46:04 por tsk »


 

anything