Last post I promised to bring up the Ethernet connection on the board. Bringing up embedded ethernet connections is a bit like home improvement on an older home. At the start it seems like smooth sailing, then you hit a small snag, open a tiny hole in a wall, peek inside and… BAM. Hours later you are wishing you had just tossed a throw rug and some spackle on the original problem and prayed no one would notice. This was no exception, though I’ve been through the process enough times that it really only ended up being a few hours of work.

It all starts with the low level network driver. Low level Ethernet peripherals are almost always spectacularly complex and inevitably come with thin, crappy, documentation. If the chip vendor has done a sample driver, trust me, that’s really where you want to start.

I built and loaded one of the ST provided networking samples with IAR. Once it passed some basic ethernet stress tests, I was ready to port that driver over to the free Eclipse/GCC compiler combo we are using.

But first I had to make a decision. The driver, which is in a file called “ethernetif.c”, is structured to rely on some POSIX style operating system services. Honestly, I was really hoping to avoid any type of OS or RTOS. We already have a simple scheduler and I have good stand-ins for critical sections and semaphores as well. But I took a quick look at the USB stack from ST we will also eventually want to use and it’s a similar situation, so I decided to just go with the flow.

So, before porting the network stack, I first ported over FreeRTOS. It’s not terrible and the port was quick and painless. Still, I was pretty quickly reminded why I like to keep firmware lean and mean whenever possible.

Next step I moved the driver over and just tried stress testing it at a low level with Ethernet Frames. Since I had already done this once, I was just going through the motions not really expecting a problem but, BAM. The ported driver worked in simple cases, but messed up in stress testing.

To cut a long story short, it all boils down to the GCC compiler not really being well supported by ST, even though they offer their own version of Eclipse/GCC called STM32CubeIDE:

GCC support is clearly a lot weaker than for the commercial Keil and IAR tools. For example… The Ethernet peripheral built into our MCU can be pointed to different SRAM locations for data operations. But this more advanced STM32F7xx Cortex M7 core also has data and instruction caches for a performance boost. The problem is we don’t want memory we have assigned to the Ethernet peripheral to use caching. Sort of like declaring a variable “volatile” in C/C++, we want to make sure we examine the real memory space, not a cached copy, every time.

No problem, the chip has a MPU or “memory protection unit” built in as well. We can use it to make part of the memory interact with the caches as we wish, and ST did just this in their sample:

static void MPU_Config(void)
{
  MPU_Region_InitTypeDef MPU_InitStruct;
  
  /* Disable the MPU */
  HAL_MPU_Disable();
  
  /* Configure the MPU as Normal Non Cacheable for Ethernet Buffers in the SRAM2 */
  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.BaseAddress = 0x2007C000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  MPU_InitStruct.SubRegionDisable = 0x00;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);
  
  /* Configure the MPU as Device for Ethernet Descriptors in the SRAM2 */
  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.BaseAddress = 0x2007C000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER1;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  MPU_InitStruct.SubRegionDisable = 0x00;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);
  
  /* Enable the MPU */
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

At first glance it’s a little weird that in this code one memory region completely overlaps the other, but it makes sense if you read through the MCU documentation which is, of course, hard to find. Normally the go-to place for info on STM32Fxxx chip peripherals is something called the “Reference Manual”, but the version for our chip has no mention of an MPU anywhere in the text. The datasheet for our chip only states that the MPU exists, but gives no details.

The two places that explain it are an app note, and the Programmer’s Reference for the core. If you wade through that info, the two blocks above make sense. But even without digging into all the details, we can see that we want the memory we assign to the Ethernet peripheral to be in the 16K around 0x2007C000. If we look at their driver code, this appears to be the case:

#if defined ( __CC_ARM   )
ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] __attribute__((at(0x2007C000)));/* Ethernet Rx DMA Descriptors */

ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] __attribute__((at(0x2007C080)));/* Ethernet Tx DMA Descriptors */

uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__((at(0x2007C100))); /* Ethernet Receive Buffers */

uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __attribute__((at(0x2007D8D0))); /* Ethernet Transmit Buffers */

#elif defined ( __ICCARM__ ) /*!< IAR Compiler */
  #pragma data_alignment=4 

#pragma location=0x2007C000
__no_init ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx DMA Descriptors */

#pragma location=0x2007C080
__no_init ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptors */

#pragma location=0x2007C100
__no_init uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffers */

#pragma location=0x2007D8D0
__no_init uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffers */

#elif defined ( __GNUC__ ) /*!< GNU Compiler */

ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] __attribute__((section(".RxDecripSection")));/* Ethernet Rx DMA Descriptors */

ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] __attribute__((section(".TxDescripSection")));/* Ethernet Tx DMA Descriptors */

uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__((section(".RxarraySection"))); /* Ethernet Receive Buffers */

uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __attribute__((section(".TxarraySection"))); /* Ethernet Transmit Buffers */

#endif

Four memory buffers are allocated and, depending on which compiler is used, are forced to 0x2007C000 – 0x2007D8D0 different, compiler specific ways. For Keil and IAR, the addresses are called out as absolutes. For GCC, the GNU compiler we are using, placing something at a specific memory location is done indirectly. Basically allocations can be expressly put in a named “section” and then a loader file instructs the GCC linker to put sections in defined memory spaces. Enter the ST provided sample loader file:

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 2048K
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 512K
Memory_B1(xrw)   : ORIGIN = 0x2007C000, LENGTH = 0x80
Memory_B2(xrw)   : ORIGIN = 0x2007C080, LENGTH = 0x80
Memory_B3(xrw)   : ORIGIN = 0x2007C100, LENGTH = 0x17d0 
Memory_B4(xrw)   : ORIGIN = 0x2007D8D0, LENGTH = 0x17d0
ITCMRAM (xrw)      : ORIGIN = 0x00000000, LENGTH = 16K
}

The memory spaces above are correctly defined. But later in the file where the sections are defined, we’ve got trouble:

.RxDescripSection (NOLOAD) : { *(.RxDescripSection) } >Memory_B1
.TxDescripSection (NOLOAD) : { *(.TxDescripSection) } >Memory_B2
.RxBUF (NOLOAD) : { *(.RxBUF) } >Memory_B3
.TxBUF (NOLOAD) : { *(.TxBUF) } >Memory_B4

If you look back at the GCC specific assignments in the driver code above you will see that three of the four sections used in the driver source code do not exist in the matching loader file. “.RxDecripSection” has a typo (should be .RxDescripSection, with an added s), and .RxarraySection and .TxarraySection are nowhere to be found. They should be .RxBUF and .TxBUF. Only our TX Descriptors were in memory at the correct location with the desired attributes, hence our results. It worked well enough to pass a few seconds of testing at ST, but died under stress testing here.

With that changing-the-vanity-light-lead-to-resealing-the-pan-in-the-shower adventure out of the way, the rest of basic network support went quickly. The basic stack is LwIP (Light Weight IP). A stack originally developed by Adam Dunkels. Back in the dark ages I contributed a few enhancements and fixes, so I have no reservations using it here.

I brought the stack up in two stages. First, I just initialized TCP and bound the network driver to the stack. If there is no network connection detected, the display now tells you so:

If a connection is found, you can see the card initially having no IP address, and then getting one from a DHCP server:

Once the card has an address you can ‘ping’ it from a computer on the network:

For the second test I threw in a simple HTTP server sample. In addition to the dirt simple fixed page you can see at the top of this post, I also threw in a dynamic web page from the ST.com sample:

This page auto-refreshes and shows the current FreeRTOS tasks and their status. I cranked the refresh rate up and used it to confirm there is no impact on our interrupt driven scanning.

For reasons I will explain in another post, we won’t be using the HTTP protocol to control our projector. I also have a tick list for networking I will still have to do at some point (ex. read network settings from the SD card, handle all the hot-patching cases, etc.), but what you see here is pushed.

Our assembled PCBs are already on their way back to me so, next stop, hardware wakeup!