niedziela, 15 września 2024

STM32H7 - STM32H753ZI - LWIP Wersja 2

 W tym poście chciałbym opisać drugi sposób przygotowania projektu STM32H7 z użyciem LWIP. 



Bufory:


Na samym początku przejdziemy przez rozmieszczenie buforów w pamięci. 

Bufory DMA RX oraz TX umieszczamy w pamięci RAMD2. Pamięć ta składa się z dwóch części SRAM1 o rozmiarze 128KB (zakres adresów to 0x30000000 do 0x3001FFFF). Druga część tej pamięci to SRAM2 także o rozmiarze 128KB (zakres adresów to 0x30020000 do 0x3003FFFF). 

Do rozmieszczenia w pamięci namy następujące bufory:
  • DMARxDscrTab - tablica odbiorcza deskryptorów DMA.
  • DMATxDscrTab - tablica nadawcza deskryptorów DMA.
  • memp_memory_RX_POOL_base - wskaźnik dla bufora pamięci wykorzystywanego przez stos LWIP do przechowywania odebranych pakietów. 
  • LWIP heap - rozmiar pamięci dla LWIP. Zarządzanie tą cześcią pamięci odbywa się przez LWIP. Pamięc jest tam alokowana dla funkcji sieciowych, buforowania danych oraz innych operacji dotyczących zarządzania komunikacją sieciową. 
Przy wykorzystywaniu 256 bajtów, mamy miejsce na szesnaście lub osiem deskryptorów. Zależy to od wielkości descryptora (16 lub 32 bajty).  STM32H7 wykorzystuje wersje 32 bajtową. 

Należy pamiętać, że z tej pamięci korzystają takie interfejsy jak QUAD-SPI, USB OTH HS czy SDMMC2. Jeśli w projekcie będą one wykorzystywane to należy zmniejszyć rozmiar buforów wykorzystywanych przez ETH i LWIP, lub część z nich przenieść do innej pamięci. W przykładzie znalezionym na stronie ST start bufforów rozpoczyna się od adresu 0x30020000, co daje wystarczający zapas na inne operacje. 

Standardowa konfiguracja jaką znalazłem, rozmieszcza bufory w następujący sposób:
  • DMARxDscrTab - 256 bajtów - adres 0x30040000,
  • DMATxDscrTab - 256 bajtów - adres 0x30040100,
  • memp_memory_RX_POOL_base -  adres 0x30040200, 
  • LWIP heap - adres 0x30020000. 
memp_memory_RX_POOL_base przy takiej adresacji zyskuje możliwość przechowywania 12 buforów 1536 + 24. Wartość 1526 jest to standardowo przyjęta wartość jaką się stosuje dla ramek ethernetowych. W tym przypadku zastosowana wartości 1536 wynika z wyrównania pamięci. Jest to wartość z zapasem, która pozwala na umieszczenie wszystkich danych wraz z nagłówkami. Dodatkowe 24 bajty będą wykorzystywane na dane dodatkowe jak struktury czy metadane. 

Ogólnie podczas doborów obszarów pamięci należy pamiętać by DMARxDscrTab było możliwie największe (minimum  to 96). Jeśli chodzi o deskryptor DMATxDscrTab to tutaj jest to głównie zależne od tego czym się aplikacja będzie zajmować i ile pakietów będzie musiała przygotowywać przed wysłaniem. W tym przypadku zalecane minimum to także 96. Natomiast w jednym przykładzie od ST skonfigurowana była wartość 32. Należy tutaj pamiętać, że gdy te wartości będą za małe mogą wystąpić problemy z poprawną komunikacją z urządzeniem.

Deskryptory przechowywane są w następujący sposób:

  1. typedef struct
  2. {
  3.   __IO uint32_t DESC0;
  4.   __IO uint32_t DESC1;
  5.   __IO uint32_t DESC2;
  6.   __IO uint32_t DESC3;
  7.   uint32_t BackupAddr0; /* used to store rx buffer 1 address */
  8.   uint32_t BackupAddr1; /* used to store rx buffer 2 address */
  9. } ETH_DMADescTypeDef;

Cały deskryptor zajmuje 24 bajty danych. Tablicę tych deskryptorów zapisujemy w pamięci w sekcji RxDecripSection oraz TxDecriptSection. Wobec tego deklaracje tych tablic (plik ethernetif.c) muszą być dostosowane do ilości miejsca jakie na nie przeznaczamy:

  1. #if defined ( __ICCARM__ ) /*!< IAR Compiler */
  2.  
  3. #pragma location=0x30040000
  4. ETH_DMADescTypeDef  DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
  5. #pragma location=0x30040100
  6. ETH_DMADescTypeDef  DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
  7.  
  8. #elif defined ( __CC_ARM )  /* MDK ARM Compiler */
  9.  
  10. __attribute__((at(0x30040000))) ETH_DMADescTypeDef  DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
  11. __attribute__((at(0x30040100))) ETH_DMADescTypeDef  DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
  12.  
  13. #elif defined ( __GNUC__ ) /* GNU Compiler */
  14.  
  15. ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDecripSection"))); /* Ethernet Rx DMA Descriptors */
  16. ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDecripSection")));   /* Ethernet Tx DMA Descriptors */
  17.  
  18. #endif

ETH_RX_DESC_CNT oraz ETH_TX_DESC_CNT są domyślnie ustawione na 4. Co odpowiada opiasnej wcześniej minimalnej wartości 96 bajtów.  Jeśli przeznaczymy na deskryptory za mało pamięci to nie uda się skompilować projektu. 

Konfiguracja biblioteki:


Kolejnym elementem jest ustawienie wartości w bibliotece LWIP:

  • TCP_MSS - maksymalny rozmiar segmentu. Domyślnie przyjęta jest wartości 536. Należy tutaj wprowadzić wartość 1460. Wynika ona z maksymalnego rozmiaru ramki wynoszący 1500 czy 1526 gdy wliczymy całość pakietu. Wartość ta odpowiada maksymalnemu rozmiarowi danych w warstwie aplikacji. Jeśli zostanie wpisana mniejsza wartości, to będzie konieczna fragmentacja ramki. 
  • TCP_SND_BUF - maksymalny rozmiar wysyłanego bufora. W tym przypadku zalecana wartość to 5840, co odpowiada czteroktorności MSS. Pozwala to na buforowanie wystarczającej ilości danych. 
  • TCP_WND - rozmiar okna odbiorczego. Dobrze jest ustawić go na pasujący do TCP_SND_BUF. Czyli przy odbiorze możemy obsłużyć cztery segmenty danych. Zapewni to maksymlanie zrównoważone nadawanie z odbieraniem pakietów. 
  • TCP_SND_QUEUELEN - rozmiar kolejki dla wysyłanych danych. Ta wartości powinna być wielokrotnością ilości pakietów jakie są przechowywane w buforze. Tutaj zalecana wartość początkowa wynosi 16. 
Jeśli wprowadzimy takie ustawienia, bądź inne, to trzeba się upewnić, że starczy miejsca w pamięci na wszystkie wprowadzone ustawienia:

TCP_SND_BUF - 5840 (4 * 1460)
TCP_WND  - 5840 (4 * 1460)
TCP_SND_QUEUELEN - 23360 (16 * 1460)

W tym przypadku na bufory potrzeba zarezerwować 35040 bajtów danych. Nie są to oczywiście wszystkie dane potrzebne dla biblioteki LWIP. 

Za ilość pamieci jaka została zarezerwowana dla LWIP odpowiada define:

  1. #define MEM_SIZE 131048

Konfiguracja MPU w kodzie wygląda następująco:

  1. void MPU_Config(void)
  2. {
  3.   MPU_Region_InitTypeDef MPU_InitStruct = {0};
  4.  
  5.   /* Disables the MPU */
  6.   HAL_MPU_Disable();
  7.  
  8.   /** Initializes and configures the Region and the memory to be protected
  9.   */
  10.   MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  11.   MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  12.   MPU_InitStruct.BaseAddress = 0x0;
  13.   MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;
  14.   MPU_InitStruct.SubRegionDisable = 0x87;
  15.   MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  16.   MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  17.   MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
  18.   MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  19.   MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  20.   MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  21.  
  22.   HAL_MPU_ConfigRegion(&MPU_InitStruct);
  23.  
  24.   /** Initializes and configures the Region and the memory to be protected
  25.   */
  26.   MPU_InitStruct.Number = MPU_REGION_NUMBER1;
  27.   MPU_InitStruct.BaseAddress = 0x30020000;
  28.   MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
  29.   MPU_InitStruct.SubRegionDisable = 0x0;
  30.   MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  31.   MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  32.  
  33.   HAL_MPU_ConfigRegion(&MPU_InitStruct);
  34.  
  35.   /** Initializes and configures the Region and the memory to be protected
  36.   */
  37.   MPU_InitStruct.Number = MPU_REGION_NUMBER2;
  38.   MPU_InitStruct.BaseAddress = 0x30040000;
  39.   MPU_InitStruct.Size = MPU_REGION_SIZE_512B;
  40.   MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  41.   MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  42.   MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
  43.  
  44.   HAL_MPU_ConfigRegion(&MPU_InitStruct);
  45.   /* Enables the MPU */
  46.   HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  47.  
  48. }

Dla pamieci flash wprowadzamy następującą konfigurację:

  1. MEMORY
  2. {
  3.   DTCMRAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
  4.   ITCMRAM    (xrw)    : ORIGIN = 0x00000000,   LENGTH = 64K
  5.   RAM_D1    (xrw)    : ORIGIN = 0x24000000,   LENGTH = 512K
  6.   RAM_D2    (xrw)    : ORIGIN = 0x30000000,   LENGTH = 288K
  7.   RAM_D3    (xrw)    : ORIGIN = 0x38000000,   LENGTH = 64K
  8.   FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 2048K
  9. }

  1.   .lwip_sec (NOLOAD) :
  2.   {
  3.     . = ABSOLUTE(0x30040000);
  4.     *(.RxDecripSection)
  5.    
  6.     . = ABSOLUTE(0x30040100);
  7.     *(.TxDecripSection)
  8.    
  9.     . = ABSOLUTE(0x30040200);
  10.     *(.Rx_PoolSection)  
  11.   } >RAM_D2

Testowałem też inne wartości dla Rx oraz Tx. Przy wykorzystywaniu prostych aplikacji TCP Echo Serwer wraz z UDP Serwer nie miało to większego znaczenia. Może przy bardziej rozbudowanych aplikacjach miało by to większe znaczenie. 

Zgodnie z tym co udało mi się znaleść w internecie najlepiej ustawić bufor RX ustawić jako:

  1. ETH_RX_BUFFER_CNT == ETH_RX_DESC_CNT

Powinno to zapewnić najlepsze wykorzystanie zasobów. Ponieważ bufor RX może być odrazu przekazywany do deskryptorów. Co oznacza brak konieczności zarządzania przyjętymi buforami, zanim trafią one do deskryptorów. Oczywiście należało by wtedy wprowadzić większą wartość RX_BUFFER_CNT niż 4 (najlepiej 12). To z kolei oznacza przeznaczenie większej ilości pamięci. Dla tego układu raczej to nie byłoby problemem. Jednak w przypadku korzystania z H723 czy H725, gdzie ilość pamięci RAMD2 jest bardziej ograniczona mogło by to już stanowić pewien problem. 

Dla takiej konfiguracji pamięć powinna wyglądać np. tak:

  1.     .lwip_sec (NOLOAD) :
  2.   {
  3.     //ETH_RX_BUFFER_CNT == ETH_RX_DESC_CNT (12) == (12)
  4.     . = ABSOLUTE(0x30040000);  
  5.     *(.RxDecripSection)
  6.    
  7.     . = ABSOLUTE(0x30040120);
  8.     *(.TxDecripSection)
  9.    
  10.     . = ABSOLUTE(0x30040200);
  11.     *(.Rx_PoolSection)  
  12.   } >RAM_D2

Przy takiej konfiguracji można zwiększyć ETH_TX_DESC_CNT na 5 ( 5*24 => 120 (DEC) => 78 (HEX)).  

Testowałem również poniższe ustawienia w pamięci z odpowiednią modyfikacją MPU. 

  1.   //----------------------------------------
  2.   .lwip_sec (NOLOAD) :
  3.   {
  4.     . = ABSOLUTE(0x30040000);
  5.     *(.RxDecripSection)
  6.    
  7.     . = ABSOLUTE(0x30040100);
  8.     *(.TxDecripSection)
  9.    
  10.     . = ABSOLUTE(0x30040200);
  11.     *(.Rx_PoolSection)  
  12.   } >RAM_D2
  13.   //----------------------------------------
  14.     .lwip_sec (NOLOAD) :
  15.   {
  16.     . = ABSOLUTE(0x30040000);
  17.     *(.RxDecripSection)
  18.    
  19.     . = ABSOLUTE(0x30040080);
  20.     *(.TxDecripSection)
  21.    
  22.     . = ABSOLUTE(0x30040100);
  23.     *(.Rx_PoolSection)  
  24.   } >RAM_D2
  25.   //----------------------------------------
  26.     .lwip_sec (NOLOAD) :
  27.   {
  28.     . = ABSOLUTE(0x30040000);
  29.     *(.RxDecripSection)
  30.    
  31.     . = ABSOLUTE(0x30040120);
  32.     *(.TxDecripSection)
  33.    
  34.     . = ABSOLUTE(0x30040200);
  35.     *(.Rx_PoolSection)  
  36.   } >RAM_D2
  37.   //----------------------------------------
  38.       .lwip_sec (NOLOAD) :
  39.   {
  40.     . = ABSOLUTE(0x30000000);
  41.     *(.RxDecripSection)
  42.    
  43.     . = ABSOLUTE(0x30000100);
  44.     *(.TxDecripSection)
  45.    
  46.     . = ABSOLUTE(0x30000200);
  47.     *(.Rx_PoolSection)  
  48.   } >RAM_D2
  49.   //----------------------------------------

Ogólnie nie ma tutaj większego znaczenia gdzie to w pamięci umieścimy. Należy pamiętać o poprawnym zarezerwowaniu miejsca, aby nie wykraczać poza buffor. Dodatkowo należy pamiętać aby bufory były dobrane odpowiednio do aplikacji. W moim przypadku wykonywałem dosyć proste testy UDP, TCP czy HTTP. Natomiast przy bardziej wymagających aplikacjach należy na te ustawienia zwrócić większą uwagę. 

Pliki projektu do pobrania z dysku Google.