W tym poście chciałbym opisać sposób konfiguracji LWIP z FreeRtos. Projekt testowałem na układzie STM32H723ZI (Nucleo).
[Źródło: https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-h753zi.html#overview]
Program:
Konfiguracja zegarów jest następująca:
Poniżej linker script dla pamięci Flash (STM32H723ZGTX_FLASH.ld).
- /*
- ******************************************************************************
- **
- ** File : LinkerScript.ld
- **
- ** Author : STM32CubeIDE
- **
- ** Abstract : Linker script for STM32H7 series
- ** 1024Kbytes FLASH and 192Kbytes RAM
- **
- ** Set heap size, stack size and stack location according
- ** to application requirements.
- **
- ** Set memory bank area and size if external memory is used.
- **
- ** Target : STMicroelectronics STM32
- **
- ** Distribution: The file is distributed as is, without any warranty
- ** of any kind.
- **
- *****************************************************************************
- ** @attention
- **
- ** Copyright (c) 2020 STMicroelectronics.
- ** All rights reserved.
- **
- ** This software component is licensed by ST under BSD 3-Clause license,
- ** the "License"; You may not use this file except in compliance with the
- ** License. You may obtain a copy of the License at:
- ** opensource.org/licenses/BSD-3-Clause
- **
- ****************************************************************************
- */
- /* Entry Point */
- ENTRY(Reset_Handler)
- /* Highest address of the user mode stack */
- _estack = 0x20020000; /* end of RAM */
- /* Generate a link error if heap and stack don't fit into RAM */
- _Min_Heap_Size = 0x2000 ; /* required amount of heap */
- _Min_Stack_Size = 0x4000 ; /* required amount of stack */
- /* Specify the memory areas */
- MEMORY
- {
- ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
- RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
- FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
- RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 320K
- RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 32K
- RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K
- }
- /* Define output sections */
- SECTIONS
- {
- /* The startup code goes first into FLASH */
- .isr_vector :
- {
- . = ALIGN(4);
- KEEP(*(.isr_vector)) /* Startup code */
- . = ALIGN(4);
- } >FLASH
- /* The program code and other data goes into FLASH */
- .text :
- {
- . = ALIGN(4);
- *(.text) /* .text sections (code) */
- *(.text*) /* .text* sections (code) */
- *(.glue_7) /* glue arm to thumb code */
- *(.glue_7t) /* glue thumb to arm code */
- *(.eh_frame)
- KEEP (*(.init))
- KEEP (*(.fini))
- . = ALIGN(4);
- _etext = .; /* define a global symbols at end of code */
- } >FLASH
- /* Constant data goes into FLASH */
- .rodata :
- {
- . = ALIGN(4);
- *(.rodata) /* .rodata sections (constants, strings, etc.) */
- *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
- . = ALIGN(4);
- } >FLASH
- .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
- .ARM : {
- __exidx_start = .;
- *(.ARM.exidx*)
- __exidx_end = .;
- } >FLASH
- .preinit_array :
- {
- PROVIDE_HIDDEN (__preinit_array_start = .);
- KEEP (*(.preinit_array*))
- PROVIDE_HIDDEN (__preinit_array_end = .);
- } >FLASH
- .init_array :
- {
- PROVIDE_HIDDEN (__init_array_start = .);
- KEEP (*(SORT(.init_array.*)))
- KEEP (*(.init_array*))
- PROVIDE_HIDDEN (__init_array_end = .);
- } >FLASH
- .fini_array :
- {
- PROVIDE_HIDDEN (__fini_array_start = .);
- KEEP (*(SORT(.fini_array.*)))
- KEEP (*(.fini_array*))
- PROVIDE_HIDDEN (__fini_array_end = .);
- } >FLASH
- /* used by the startup to initialize data */
- _sidata = LOADADDR(.data);
- /* Initialized data sections goes into RAM, load LMA copy after code */
- .data :
- {
- . = ALIGN(4);
- _sdata = .; /* create a global symbol at data start */
- *(.data) /* .data sections */
- *(.data*) /* .data* sections */
- *(.RamFunc) /* .RamFunc sections */
- *(.RamFunc*) /* .RamFunc* sections */
- . = ALIGN(4);
- _edata = .; /* define a global symbol at data end */
- } >RAM AT> FLASH
- /* Uninitialized data section */
- . = ALIGN(4);
- .bss :
- {
- /* This is used by the startup in order to initialize the .bss secion */
- _sbss = .; /* define a global symbol at bss start */
- __bss_start__ = _sbss;
- *(.bss)
- *(.bss*)
- *(COMMON)
- . = ALIGN(4);
- _ebss = .; /* define a global symbol at bss end */
- __bss_end__ = _ebss;
- } >RAM
- /* User_heap_stack section, used to check that there is enough RAM left */
- ._user_heap_stack :
- {
- . = ALIGN(8);
- PROVIDE ( end = . );
- PROVIDE ( _end = . );
- . = . + _Min_Heap_Size;
- . = . + _Min_Stack_Size;
- . = ALIGN(8);
- } >RAM
- .lwip_sec (NOLOAD) : {
- . = ABSOLUTE(0x30000000);
- *(.RxDecripSection)
- . = ABSOLUTE(0x30000060);
- *(.TxDecripSection)
- . = ABSOLUTE(0x30000200);
- *(.RxArraySection)
- } >RAM_D2 AT> ROM
- /* Remove information from the standard libraries */
- /DISCARD/ :
- {
- libc.a ( * )
- libm.a ( * )
- libgcc.a ( * )
- }
- .ARM.attributes 0 : { *(.ARM.attributes) }
- }
Jak w poprzednich przykładach dla STM32H7 należy go zmodyfikować w celu dołożenia deskryptorów ETH oraz buforów do pamięci RAM_D2. Należy zmodyfikować tylko część dla pamięci FLASH. Drugi plik dla pamięci _RAM może zostać pomięty, służy on do uruchamiania kodu z pamięci RAM.
Drugim kluczowym elementem jest konfiguracja Cortex-M7. Należy uruchomić ICache i DCache wraz z MPU (ang. Memmory Protection Unit).
Konfiguracja wygenerowana w kodzie wygląda następująco:
- void MPU_Config(void)
- {
- MPU_Region_InitTypeDef MPU_InitStruct = {0};
- /* Disables the MPU */
- HAL_MPU_Disable();
- /** Initializes and configures the Region and the memory to be protected
- */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
- MPU_InitStruct.BaseAddress = 0x30000000;
- MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
- MPU_InitStruct.SubRegionDisable = 0x0;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /** Initializes and configures the Region and the memory to be protected
- */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
- MPU_InitStruct.BaseAddress = 0x30004000;
- MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
- MPU_InitStruct.SubRegionDisable = 0x0;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /* Enables the MPU */
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
- }
Powyższa konfiguracja wydaje się działać poprawnie, natomiast dobrze by było maksymalnie ją ulepszyć w celu poprawnego działania aplikacji. Bardzo dobre przykłady można znaleźć na githubie, pod tym linkiem.
W związku z tym, że pamięć RAM_D2 jest w tym mikrokontrolerze dosyć ograniczona (32kB). Z tego powodu należy przenieść bufor np. RX do innej części pamięci. Będę tutaj postępował zgodnie z wytycznymi z powyższego linku.
Konfiguracja bloków pamięci:
Konfiguracja pamięci buforów w ETH:
Modyfikacje biblioteki LWIP:
Po wygenerowaniu projektu należy odpowiednio skonfigurować plik .FLASH:
- /* Uninitialized data section */
- . = ALIGN(4);
- .bss :
- {
- /* This is used by the startup in order to initialize the .bss secion */
- _sbss = .; /* define a global symbol at bss start */
- __bss_start__ = _sbss;
- *(.bss)
- *(.bss*)
- *(COMMON)
- . = ALIGN(32);
- *(.Rx_PoolSection)
- . = ALIGN(4);
- _ebss = .; /* define a global symbol at bss end */
- __bss_end__ = _ebss;
- } >RAM
- /* User_heap_stack section, used to check that there is enough RAM left */
- ._user_heap_stack :
- {
- . = ALIGN(8);
- PROVIDE ( end = . );
- PROVIDE ( _end = . );
- . = . + _Min_Heap_Size;
- . = . + _Min_Stack_Size;
- . = ALIGN(8);
- } >RAM
- .lwip_sec (NOLOAD) :
- {
- . = ABSOLUTE(0x30000000);
- *(.RxDecripSection)
- . = ABSOLUTE(0x30000100);
- *(.TxDecripSection)
- } >RAM_D2
Następnie przechodzimy do liku ethernetif.c. W pliku powinna znajdować się sekcja dla buforów RX oraz TX:
- #if defined ( __ICCARM__ ) /*!< IAR Compiler */
- #pragma location=0x30000000
- ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
- #pragma location=0x30000100
- ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
- #elif defined ( __CC_ARM ) /* MDK ARM Compiler */
- __attribute__((at(0x30000000))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
- __attribute__((at(0x30000100))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
- #elif defined ( __GNUC__ ) /* GNU Compiler */
- ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDecripSection"))); /* Ethernet Rx DMA Descriptors */
- ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDecripSection"))); /* Ethernet Tx DMA Descriptors */
- #endif
Natomiast należy jeszcze dopisać część dla Rx_PoolSection zdefiniowanej w pamięci RAM:
- #if defined ( __ICCARM__ ) /*!< IAR Compiler */
- #pragma location = 0x30000200
- extern u8_t memp_memory_RX_POOL_base[];
- #elif defined ( __CC_ARM ) /* MDK ARM Compiler */
- __attribute__((at(0x30000200)) extern u8_t memp_memory_RX_POOL_base[];
- #elif defined ( __GNUC__ ) /* GNU Compiler */
- __attribute__((section(".Rx_PoolSection"))) extern u8_t memp_memory_RX_POOL_base[];
- #endif
Ostatnim elementem jest zdefiniowanie makra DATA_IN_D2_RAM:
Po wygenerowaniu projektu na samym początku sprawdzam ping do urządzenia:
Pełną konfigurację wszystkich elementów systemu można podglądać w udostępnionych plikach.
Uruchomienie domyślnego wątku:
- osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 256);
- defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
W nim zostanie uruchomione LWIP (wygenerowane przez CubeMx) oraz drugi wątek obsługujący połączenie TCP:
- void StartDefaultTask(void const * argument)
- {
- /* init code for LWIP */
- MX_LWIP_Init();
- /* USER CODE BEGIN 5 */
- osThreadDef(echoTask, StartEchoTask, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
- echoTaskHandle = osThreadCreate(osThread(echoTask), NULL);
- /* Infinite loop */
- for(;;)
- {
- osDelay(1000);
- }
- /* USER CODE END 5 */
- }
Poniżej kod dla TCP Serwera z dodatkowym wysyłaniem danych po UART:
- void StartEchoTask(void const *argument)
- {
- struct netconn *conn, *newconn;
- err_t err, accept_err;
- struct netbuf *buf;
- void *data;
- u16_t len;
- LWIP_UNUSED_ARG(argument);
- conn = netconn_new(NETCONN_TCP);
- if (conn != NULL) {
- err = netconn_bind(conn, NULL, 1001);
- if (err == ERR_OK) {
- netconn_listen(conn);
- while (1) {
- accept_err = netconn_accept(conn, &newconn);
- if (accept_err == ERR_OK) {
- while (netconn_recv(newconn, &buf) == ERR_OK) {
- do {
- netbuf_data(buf, &data, &len);
- netconn_write(newconn, data, len, NETCONN_COPY);
- HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);
- HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
- }
- while (netbuf_next(buf) >= 0);
- netbuf_delete(buf);
- }
- netconn_close(newconn);
- netconn_delete(newconn);
- }
- }
- }
- else {
- netconn_delete(newconn);
- }
- }
- }
Poniżej aplikacja klienta:
- void StartTcpClientTask(void const *argument)
- {
- err_t err;
- struct netconn *conn;
- struct netbuf *buf;
- void *data;
- uint16_t len; //buffer length
- LWIP_UNUSED_ARG(argument);
- while (1)
- {
- if (gnetif.ip_addr.addr == 0 || gnetif.netmask.addr == 0 || gnetif.gw.addr == 0) {
- osDelay(1000);
- continue;
- }
- else {
- osDelay(20);
- }
- conn = netconn_new(NETCONN_TCP);
- if (conn != NULL) {
- IP4_ADDR(&server_addr, SERVER_IP_1_ADDR, SERVER_IP_2_ADDR, SERVER_IP_3_ADDR, SERVER_IP_4_ADDR);
- err = netconn_connect(conn, &server_addr, SERVER_PORT);
- if (err != ERR_OK) {
- netconn_delete(conn);
- continue;
- }
- HAL_UART_Transmit(&huart1, (uint8_t*)"zzz\r\n", 5, 1000);
- while (netconn_recv(conn, &buf) == ERR_OK) {
- do {
- netbuf_data(buf, &data, &len);
- memcpy(recData, data, len);
- netconn_write(conn, data, len, NETCONN_COPY);
- HAL_UART_Transmit(&huart1, (uint8_t*)"REC: ", 5, 1000);
- HAL_UART_Transmit(&huart1, (uint8_t*)&recData[0], len, 1000);
- HAL_UART_Transmit(&huart1, (uint8_t*)"\r\n", 2, 1000);
- HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
- if(recData[0] == 'c' && recData[1] == 'l' && recData[2] == 'o') {
- HAL_UART_Transmit(&huart1, (uint8_t*)"CLOSE", 5, 1000);
- netconn_close(conn);
- netconn_delete(conn);
- }
- } while (netbuf_next(buf) >= 0);
- netbuf_delete(buf);
- }
- netconn_close(conn);
- netconn_delete(conn);
- }
- }
- }
Uruchomienie wątku klienta następuje jak wcześniej dla serwera po uruchomieniu biblioteki LWIP:
- void StartClientTask(void)
- {
- osThreadDef(tcpClientTask, StartTcpClientTask, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
- tcpClientTaskHandle = osThreadCreate(osThread(tcpClientTask), NULL);
- }
- void StartDefaultTask(void const * argument)
- {
- /* init code for LWIP */
- MX_LWIP_Init();
- /* USER CODE BEGIN 5 */
- StartClientTask();
- /* Infinite loop */
- for(;;)
- {
- osDelay(1000);
- }
/* USER CODE END 5 */- }
Efekt działania programu jest następujący:
Projekt można pobrać pod tym linkiem.