EEVblog® Electronics Community Forum
Electronics => Microcontrollers => Topic started by: tellurium on November 10, 2025, 06:03:12 pm
-
Hi folks,
This is a fresh explainer video about STM32 micros with built-in Ethernet MAC.
It has a Mongoose promo at the end, starting at 13:04 , so you can skip it if you're not interested in Mongoose.
Let me know if you'd like to see more embedded networking explainers.
https://www.youtube.com/watch?v=6kHYvFjeuaQ (https://www.youtube.com/watch?v=6kHYvFjeuaQ)
-
@2:30
I'm not aware of any PIC32MX's that have a built in Phy.
Old PIC18F97J60 series do, very similar to the ENC28J60 but they are too old to be useful these days.
-
The STM32 HAL ethernet stack that caused a lot of trouble in its early days, meanwhile turned into something useful. I used their stack starting from their http server example and found it pretty easy to use. That was on a STM32H743, see here:
https://www.eevblog.com/forum/microcontrollers/ethernet-interface-for-mcu/msg5776329/#msg5776329 (https://www.eevblog.com/forum/microcontrollers/ethernet-interface-for-mcu/msg5776329/#msg5776329)
https://www.eevblog.com/forum/metrology/prema-bk7-derived-meter/msg5627927/#msg5627927 (https://www.eevblog.com/forum/metrology/prema-bk7-derived-meter/msg5627927/#msg5627927)
Regards, Dieter
-
@2:30
I'm not aware of any PIC32MX's that have a built in Phy.
Old PIC18F97J60 series do, very similar to the ENC28J60 but they are too old to be useful these days.
You are correct! The PIC32MX has only the MAC controller and not MAC + PHY.
-
Thanks for doing that video. It is a good description of the 32F417 project I've just finished. I have a few questions:
1) You have done your own version of MbedTLS. This is a massive piece of code, taking up some 250k of my total FLASH usage of 500k. How many years did you spend on that? I realise the crypto suite algorithms are available as open source (the MbedTLS devs say they wrote all their own) but still it is a lot of code. With TLS 1.3 one can dump a lot of it but most industrial usage is still on 1.2, and you never know what somebody is still using at the other end.
2) Your illustration of the config for the DMA and the RMII MAC config link (which IIRC is basically a 64 bit "USART") is amazingly simple and I wonder how much more code there is behind that. My project uses the ST Cube IDE (ex Cube MX, AFAIK) code which needed a huge amount of work to make it run properly. I had to dig into the RMII MAC config link to implement a MAC power-down feature and it took ages to find out the details.
3) You mention that some devs have problems with DMA not having access to some memory regions. In the 32F4 it is only the CCM RAM which DMA cannot access. Is that what you had in mind?
-
1) You have done your own version of MbedTLS. This is a massive piece of code, taking up some 250k of my total FLASH usage of 500k. How many years did you spend on that? I realise the crypto suite algorithms are available as open source, but still it is a lot of code.
2) Your illustration of the config for the DMA and the RMII MAC config link (which IIRC is basically a 64 bit "USART") is amazingly simple and I wonder how much more code there is behind that. My project uses the ST Cube IDE (ex Cube MX, AFAIK) code which needed a huge amount of work to make it run properly. I had to dig into the RMII MAC config link to implement a MAC power-down feature and it took ages to find out the details.
3) You mention that some devs have problems with DMA not having access to some memory regions. In the 32F4 it is only the CCM RAM which DMA cannot access. Is that what you had in mind?
It's not just me, we have a team of several developers working on it.
1. TLS took us quite some time. The initial effort was done in 2015-2016, at that time mbedTLS was still polarSSL, commercial and not yet bought by ARM. So there was no free, good quality TLS library out there. Well there was axTLS by Cameron Rich, but it did not support couple of things at the time, like mutial TLS, so we decided to write our own TLS stack. And we did, and it worked well with AWS IoT which was just released, it was 2016 if I am not mistaken. Our TLS was RSA only. Then ARM bought polarSSL, rebranded as mbedTLS, and made free. So there was no reason for us keeping our own stack, so we have sunset the project.
Later on we found out that a lot of our customers had issues with mbedTLS, especially on slow micros with little RAM, like F4x9. mbedTLS is very well written and capable, but it needs some expertise with tuning - and that's the issue. So we took our old project, threw away RSA and added EC (Elliptic Curve) which is more modern, smaller and faster, and made it very small and fast by default, no tuning required. It eats about 50k flash or so, and several kylobytes RAM per live connection, the precise number depends on various factors. That took quite an effort too, but it was worth it. Some customers with no hope of running TLS on their micros, managed to run it successfully with our built-in TLS.
2. There MAC -> PHY interaction in terms of code is tiny. On a software side, talking to PHY is "register" based. PHY registers are 16-bits, you either read or write to them. To read a PHY register, you write to MAC's MACMDIOAR register the PHY's address and register number you want to read, and then wait on MACMDIOAR register. While you wait, the MAC communicates with PHY over the MDIO pin. When PHY responds with the result, MAC writes it to the MACMDIODR register and signals readiness in MACMDIOAR. The writing is done similarly. The whole code for H7 is 12 lines, F4/F7 are very similar:
https://github.com/cesanta/mongoose/blob/e0740fc2ca3024eaa2f80fa73dd141c9827f86db/src/drivers/stm32h.c#L59-L73 (https://github.com/cesanta/mongoose/blob/e0740fc2ca3024eaa2f80fa73dd141c9827f86db/src/drivers/stm32h.c#L59-L73)
All PHY logic - like intialisation, getting link status is handled in https://github.com/cesanta/mongoose/blob/master/src/drivers/phy.c (https://github.com/cesanta/mongoose/blob/master/src/drivers/phy.c) which is 150 lines of code and handles all supported PHYs.
3. Yes. For example on H723, the "default" RAM region which usually starts at 0x20000000 is in D1 domain, and Ethernet controller is in D2 domain, so Ethernet won't work without linker script magic. Other controllers, like H5x3, have a simpler RAM layout and that linker script yada yada is not needed. I believe the same goes for F4xx, where the "default" RAM region IS accessible by the Ethernet DMA.
-
This is article which is basically the script for the video: https://mongoose.ws/articles/stm32-ethernet-explained . Scroll to the "Ethernet driver" section, there is a table there that shows Cube HAL driver, Threadx and Zephyr drivers. Links to their driver code are right there.
Cube driver (https://github.com/STMicroelectronics/stm32h7xx-hal-driver/blob/60a5d3cffcd3a9a29957e9e434b2aec4b4b82744/Src/stm32h7xx_hal_eth.c) is 3k+ LoC, Mongoose's driver (https://github.com/cesanta/mongoose/blob/master/src/drivers/stm32h.c) is 250 LoC which is an order of magnitude smaller.
Recently ST rewrote their Ethernet driver, but some of the issues are still lingering. Their driver works if the usage pattern is simple - but if the complexity grows, it may start to behave. For example, deadlock in the RTOS queue operation after days or weeks of operation - and when that happens, go figure and good luck with getting help.
-
Getting 250k down to 50k is pretty amazing. How much has been removed compared to MbedTLS? Also MbedTLS uses ~60k RAM in the simplest https client implementation.
I dug out the LAN8742 PHY shutdown discussion:
https://www.eevblog.com/forum/microcontrollers/how-fast-does-st-32f417-enter-standby-mode/msg4064983/#msg4064983 (https://www.eevblog.com/forum/microcontrollers/how-fast-does-st-32f417-enter-standby-mode/msg4064983/#msg4064983)
I would not change to any new ST code because there are usually bugs in there, and with ETH they can remain undiscovered for a long time, due to the complexity.
-
It's nice if somebody gets recommendations in a web forum like here, but i would never change to an otherwise unknown library based on that.
I got convinced by the ST proposed solution when i could update the LwIP library they successfully integrated into their solution without breaking things.
Regards, Dieter
-
Noticed that there are quite some copy ops between layers shown in the video. From the bits that remained unaltered in my memory, NetX and NetX Duo praised themselves as zero-copy stacks. Performance wise, is there a real advantage of these zero-copy stacks over the Mongoose stack ?
-
The "standard" STM32 ETH low level code
- uses the DMA internal to the ETH subsystem to move the packets from somewhere (maybe the PHY or maybe from some MTU-sized buffer) to RAM, where a "linked list" is maintained
- does copying between this list and the TCP/IP code (typically LWIP) in software, using memcpy; example below
/**
* @brief This function should do the actual transmission of the packet. The packet is
* contained in the pbuf that is passed to the function. This pbuf
* might be chained.
*
* @param netif the lwip network interface structure for this ethernetif
* @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
* @return ERR_OK if the packet could be sent
* an err_t value if the packet couldn't be sent
*
* @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
* strange results. You might consider waiting for space in the DMA queue
* to become available since the stack doesn't retry to send a packet
* dropped because of memory failure (except for the TCP timers).
*/
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(EthHandle.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = EthHandle.TxDesc;
bufferoffset = 0;
/* copy frame from pbufs to driver buffers */
for(q = p; q != NULL; q = q->next)
{
/* Is this buffer available? If not, goto error */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
/* Get bytes in current lwIP buffer */
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of data to copy is bigger than Tx buffer size*/
// This code never runs. See
// [url]https://www.eevblog.com/forum/microcontrollers/anyone-here-familiar-with-lwip/msg4693118/#msg4693118[/url]
while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
//osDelay(2); - was a buffer overwrite issue, not possible to reproduce later
// see mod at the end of IF_HAL_ETH_TransmitFrame() which is a better fix
// Copy data to Tx buffer - should use DMA but actually the perf diff is negligible
#ifdef SPEED_TEST
TopLED(true);
#endif
memcpy_fast( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
#ifdef SPEED_TEST
TopLED(false);
#endif
/* Point to next descriptor */
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
/* Check if the buffer is available */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy the remaining bytes */
#ifdef SPEED_TEST
TopLED(true);
#endif
memcpy_fast( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
#ifdef SPEED_TEST
TopLED(false);
#endif
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
/* Prepare transmit descriptors to give to DMA */
IF_HAL_ETH_TransmitFrame(&EthHandle, framelength);
errval = ERR_OK;
error:
/* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
if ((EthHandle.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
/* Clear TUS ETHERNET DMA flag */
EthHandle.Instance->DMASR = ETH_DMASR_TUS;
/* Resume DMA transmission*/
//__DMB();
EthHandle.Instance->DMATPDR = 0; // Any value issues a descriptor list poll demand.
}
return errval;
}
// Optimised memcpy. This is based on one in Newlib but specifically for the 32F4
// which does unaligned 32 bit transfers transparently. This avoids having to check
// buffer alignment, and 4-aligned buffers are automatically optimised internally.
//__attribute__((optimize("O2"))) // that replaces code below with a call to memcpy()!!!
void * memcpy_fast (void *__restrict dst0, const void *__restrict src0, size_t len0)
{
char *dst = dst0;
const char *src = src0;
uint32_t *aligned_dst;
const uint32_t *aligned_src;
// If the size is >=4 then do 32 bit moves, until exhausted
if ( len0 >=4 )
{
aligned_dst = (uint32_t*)dst;
aligned_src = (uint32_t*)src;
while (len0 >= 4)
{
*aligned_dst++ = *aligned_src++;
len0 -= 4;
}
dst = (char*)aligned_dst;
src = (char*)aligned_src;
}
// Finish with any single byte moves
while (len0--)
*dst++ = *src++;
return dst0;
}
I spent a lot of time on this stuff a long time ago and doing zero copy makes no difference whatsoever to performance in any realistic embedded application that has some real-world function.
A guy who used to hang out on the ST forum (very clever, despite a hobby beating up anybody with an IQ below 200) produced a zero copy driver which I would imagine worked properly, but it was only for the H7 or some such. I would not trust any code from ST to be ready for prime time.
-
It's nice if somebody gets recommendations in a web forum like here, but i would never change to an otherwise unknown library based on that.
I got convinced by the ST proposed solution when i could update the LwIP library they successfully integrated into their solution without breaking things.
Regards, Dieter
Dieter. I am not convincing you personally to use Mongoose. If you're happy with the ST / lwip solution - that's excellent.
Here's Mongoose github page: https://github.com/cesanta/mongoose (https://github.com/cesanta/mongoose) . The current star count is over 12000
Also, we are an official ST partner: https://www.st.com/content/st_com/en/partner/partner-program/partnerpage/cesanta.html (https://www.st.com/content/st_com/en/partner/partner-program/partnerpage/cesanta.html)
Mongoose is also ships as a Cube CMSIS pack.
Mongoose is integrated in thousands of projects, open source and commercial. Used by Siemens, Bosch, Google, and so on. It runs on satellites, including International Space Station.
You keep calling me a "strange person" and Mongoose "an unknown library". Please stop.
-
Noticed that there are quite some copy ops between layers shown in the video. From the bits that remained unaltered in my memory, NetX and NetX Duo praised themselves as zero-copy stacks. Performance wise, is there a real advantage of these zero-copy stacks over the Mongoose stack ?
Mongoose driver is not zero copy. The DMA buffers are static. So for every incoming frame, these are the copies that are performed:
Driver: DMA buffer -> interface queue
TCP/IP stack: interface queue -> connection's receive buffer. At this point, data is visible to the user code as c->recv.{buf,len}.
So, two copies.
I highly doubt though that Netx Duo beats Mongoose performance-wise, though I don't have numbers at hand.
What performance use case do you have in mind? Streaming lots of data from a device? Or uploading a huge file to a device? Or something else?
Performance is just one characteristic, quite important one. The others are API simplicity / usability, and support availability.
Arguably, Netx Duo is otherwise quite heavy and complex. Just take a look at their web server example in Cube.
Compare that to Mongoose's example which is just a few lines of code: https://github.com/cesanta/mongoose?tab=readme-ov-file#usage-examples
Try to extend the Netx example. Or lwip's httpd example. For instance, add real-time websocket feed to your page. Or add user authentication. Then you'll see how non-trivial that is.
-
What performance use case do you have in mind? Streaming lots of data from a device? Or uploading a huge file to a device? Or something else?
Its only my curiosity. Quite some years ago I had to write a driver for NetX Duo for a PLC protocol, so not much data, a few KB/s at most. But I had to rearrange some low level frames. My copy operations were enough to bring a Cortex M7 almost to its knees while CPU usage by NetX was barely visible.
Arguably, Netx Duo is otherwise quite heavy and complex.
If I could handle NetX Duo then presumably Mongoose should be a breeze. Great. I will try it.