Skip to content

stm32: Add support for ETH RMII peripheral (version 2) #4541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from

Conversation

dpgeorge
Copy link
Member

This is an improved version of #3808, to add full Ethernet support to the stm32 port. It is actually a complete rewrite from scratch of the Ethernet driver.

Features of this version are:

  • no use of the ST HAL, ETH driver is completely self contained
  • MPU is used to interface correctly with the ETH DMA system, but no definitions are needed in the linker script
  • improvements made to modlwip to protect against concurrency issues with the ETH driver
  • addition of Ethernet frame tracing, enabled via lan.config(trace=7)
  • enabled on NUCLEO_F767ZI, STM32F7DISC and STM32F769DISC

Tested on a NUCLEO_F767ZI and STM32F769DISC board. The following code will get it connected:

import network
lan = network.LAN()
lan.active(1)

DHCP is automatically enabled. IP can be statically assigned via lan.ifconfig(...). The API is the same as WLAN and LAN on other ports.

The following network performance was obtained with the STM32F769DISC board on a 100Mbit full-duplex LAN:

  • TCP send: 82 Mbit/sec
  • TCP recv: 89 Mbit/sec
  • UDP send: 85 Mbit/sec (no lost packets)
  • UDP recv: 30 Mbit/sec (1% packet loss)

I think the UDP receive rate can be improved by some minor improvements to extmod/modlwip.c, since the TCP receive is much higher (and close to the theoretical maximum).

@boochow
Copy link
Contributor

boochow commented Feb 23, 2019

Works fine on my NUCLEO_F767ZI. Great !!
So, is this patch going to be merged to mainline ?

@boochow
Copy link
Contributor

boochow commented Feb 23, 2019

I think the last byte of MAC address calcurated in mp_hal_get_mac() might be the same value between many STM boards, which means we might not able to connect multiple STM boards of the same netowrk IF to single LAN.
With many STM boards, the first byte of HW ID would be 0x33. I don't know why but I saw STM32 HW ID is not well randomized but highly regulated, hence the raw value of each HW ID bytes is not suitable for calculating a MAC address.
How about applying a light hash function (FNV-1 for example) to whole HW ID for calculating the MAC address?

@t35tB0t
Copy link

t35tB0t commented Feb 23, 2019

Thx for the major progress dpgeorge! Added F429 support - see below

Testing now. Pretty solid performance. Getting ~55-73Mbps HTTP/TCP data throughput on large transfers (also seeing ~80Mbps on a F767). Running simultaneous stress tests revealed holes in my exception handling with (fast) non-blocking sockets (see code example below). HTTP request responses that are ~2MB and larger are losing data. Not sure why yet. Testing with 1.792MB max for now.

FROM LINUX BOX FLOOD PING... ping -f -s 1024
51851228 packets transmitted, 51851227 received, 0% packet loss, time 31770015ms
rtt min/avg/max/mdev = 0.306/0.571/20.172/0.100 ms, pipe 2, ipg/ewma 0.612/0.633 ms

FROM OpenWRT/TPLINK STD PING...
32746 packets transmitted, 32738 packets received, 0% packet loss
round-trip min/avg/max = 0.348/0.728/39.869 ms

FROM WINDOWS PYTHON RESTTEST...
77,084 HTTP requests, 154GB total data transferred at ~390msec each 2MB transfer with...
[AMENDED]... failures. Changed to maximum transfer of 1792 KB and transfers are OK.
[AMENDED]... Writes to sockets were getting lost. HTTP request responses around
[AMENDED]... 2MB and larger result in socket errors. Large HTTP payload responses not working.
[AMENDED]... Switched to non-blocking socket and a bunch of socket.write count checks.
[AMENDED]... Now HTTP client is receiving all 1792KB of payload at up to 73 Mb/sec. 👍
[AMENDED]... With dynamic HTTP response headers and content generation, ~55 Mb/sec

NON-OPTIMIZED TEST CODE SNIPPIT USED TO ACHIEVE 73Mbps NON-BLOCKING SOCKET WRITES:

def bigdata(socket):
    dlen = 1792*1  #Kilobytes
    bsize  = 1024  #Bytes
    buf  = "\0"*int(bsize)
    repeat = int(1024*dlen/bsize)  
   sum = 0
    fail = 0
    block = 0
    t0 = time.ticks_ms()
    for i in range(repeat):
        try:
            #time.sleep_us(80)        
            block = socket.write(buf)
        except Exception as e:
            raise Exception('Socket write error, bufidx=%d %s' % (i,e))
        while block < bsize:
            time.sleep_us(2000)
            bufi = buf[block:bsize]
            try: 
                block += socket.write(bufi)
            except Exception as e:
                fail += 1
                if fail > 16:
                    raise Exception('Socket write retries failed: ',e)
                else:
                    pass
            gc.collect()
        sum += bsize
    t1 = time.ticks_ms()
    rate = int(8*sum/(t1-t0)/1000)
    socket.write('DATA-RATE = %04d' % (rate*10))
    print("rate=%04.1f Mb/sec for dLen=%d bytes \r\n" % (rate,sum))
    gc.collect()

CODE DIFF FOR ADDING F429 SUPPORT TO #4541:

--- a/ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h
+++ b/ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h
@@ -1,6 +1,8 @@
 #define MICROPY_HW_BOARD_NAME       "NUCLEO-F429ZI"
 #define MICROPY_HW_MCU_NAME         "STM32F429"
 
+#define MICROPY_PY_LWIP             (1)
+
 #define MICROPY_HW_HAS_SWITCH       (1)
 #define MICROPY_HW_HAS_FLASH        (1)
 #define MICROPY_HW_ENABLE_RNG       (1)
@@ -81,3 +83,16 @@
 #define MICROPY_HW_USB_FS              (1)
 #define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_A9)
 #define MICROPY_HW_USB_OTG_ID_PIN      (pin_A10)
+
+
+// Ethernet via RMII
+#define MICROPY_HW_ETH_MDC          (pin_C1)
+#define MICROPY_HW_ETH_MDIO         (pin_A2)
+#define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1)
+#define MICROPY_HW_ETH_RMII_CRS_DV  (pin_A7)
+#define MICROPY_HW_ETH_RMII_RXD0    (pin_C4)
+#define MICROPY_HW_ETH_RMII_RXD1    (pin_C5)
+#define MICROPY_HW_ETH_RMII_TX_EN   (pin_G11)
+#define MICROPY_HW_ETH_RMII_TXD0    (pin_G13)
+#define MICROPY_HW_ETH_RMII_TXD1    (pin_B13)
+
diff --git a/ports/stm32/boards/NUCLEO_F429ZI/pins.csv b/ports/stm32/boards/
NUCLEO_F429ZI/pins.csv
index 8a892d3c2..c8fe3355d 100644
--- a/ports/stm32/boards/NUCLEO_F429ZI/pins.csv
+++ b/ports/stm32/boards/NUCLEO_F429ZI/pins.csv
@@ -115,3 +115,12 @@ PG2,PG2
 SW,PA0
 LED_GREEN,PG13
 LED_RED,PG14
+ETH_MDC,PC1
+ETH_MDIO,PA2
+ETH_RMII_REF_CLK,PA1
+ETH_RMII_CRS_DV,PA7
+ETH_RMII_RXD0,PC4
+ETH_RMII_RXD1,PC5
+ETH_RMII_TX_EN,PG11
+ETH_RMII_TXD0,PG13
+ETH_RMII_TXD1,PB13
(END)```

@dpgeorge
Copy link
Member Author

Thanks @t35tB0t for testing, and confirming that the F429 board works. I've now added a commit here to enable the ETH on that Nucleo board.

Not sure why you see errors with 2MB transfer. I'll try to reproduce it. Does the function bigdata() run on the STM or on the PC? Are you able to give precise instructions how to reproduce the 2MB transfer error?

@dpgeorge
Copy link
Member Author

So, is this patch going to be merged to mainline ?

Yes, that's what I'm planning. It's pretty much good to go in now.

I think the last byte of MAC address calcurated in mp_hal_get_mac() might be the same value between many STM boards,
...
How about applying a light hash function (FNV-1 for example) to whole HW ID for calculating the MAC address?

I did see that code you wrote in the other PR @boochow . But MAC addresses are something which really need to be assigned properly, not at random. I made the mp_hal_get_mac() function weak so it can be overridden by a board to specify a different way to get the MAC address.

But, for testing and internal use, it might be a good idea to have the function provide a random locally administered MAC address. Then it would make sense to use a hash function for the lower bits of the MAC.

@t35tB0t
Copy link

t35tB0t commented Feb 25, 2019 via email

@dpgeorge
Copy link
Member Author

But, for testing and internal use, it might be a good idea to have the function provide a random locally administered MAC address

@boochow I pushed a change here to implement this, to generate a unique local MAC address based on the MCU unique id. I just used a very simple algorithm for the "hash", based on the DFU USB algorithm used in usbd_desc.c. What do you think?

@boochow
Copy link
Contributor

boochow commented Feb 26, 2019

So, is this patch going to be merged to mainline ?

Yes, that's what I'm planning. It's pretty much good to go in now.

Great ! I'm so happy to hear this. It will open much wider micropython application world including industrial use or office use!

But, for testing and internal use, it might be a good idea to have the function provide a random locally administered MAC address

@boochow I pushed a change here to implement this, to generate a unique local MAC address based on the MCU unique id. I just used a very simple algorithm for the "hash", based on the DFU USB algorithm used in usbd_desc.c. What do you think?

First of all, I agree that the MAC address should be administered. I think your original mp_hal_get_mac() code should be used only for pyboards because it uses your vendor code 0x48 0x4a 0x30.

The new code for LAA seems good but I suppose that it is better to use id[11] somewhere and the usage of id[7] should be 'id[7] << 2' rather than 'id[7] & 0xfc'.

Some description about STM's unique device id format can be found in
https://www.st.com/content/ccc/resource/training/technical/product_training/b7/03/bd/99/54/55/4d/47/STM32L4_System_eSign.pdf/files/STM32L4_System_eSign.pdf/jcr:content/translations/en.STM32L4_System_eSign.pdf

I guess that id[0..3] shows a lot number, id[4..7] is a silicon wafer number, id[8..9] and id[10..11] are a coordinates on the wafer.
And the numbers which id[6..11] hold are in ASCII format. In short, id[6..11] equals to char[6] and each bytes is one of '0'-'9'.
The most important bytes are id[8..11] because they represents X,Y coordinates which definitely differs for each device.

id[4..7] shows a wafer number, whereas id[4] is some small value, id[5] may be a upper case alphabet, id[6] and id[7] falls between '0' and '9'. This part is the second important part but I guess that the same value might be shared between tens of devices.

id[0] is also a number ('0'-'9') and id[2] is an upper case alphabet ('A'-'Z'). I suspect that id[1] and id[3] are not used because they are always zero for all of my STM32 boards.

So, your code uses most of the bytes which have meaningful value but I think id[11] should be included. Also, It is better to avoid dropping bit 0 and 1 of id[7] because they are the most meaningful part of id[7].

@dpgeorge
Copy link
Member Author

Thanks @boochow, that's helpful. In my experience id[0] and id[2] actually contain the most information (about 7 bits each) and, yes, id[1] and id[3] are always zero. I have changed the MAC function to use as much info from the unique ID as possible.

This PR was merged in 75a3544 through 823b31e

@t35tB0t if you find some reproducible bugs/issues with the driver please open a new issue for it.

@dpgeorge dpgeorge closed this Feb 26, 2019
@dpgeorge dpgeorge deleted the stm32-eth-rmii-v2 branch February 26, 2019 12:41
@cfpena
Copy link

cfpena commented Sep 6, 2019

How can I make this works on NUCLEO_F746ZG ?

tannewt added a commit to tannewt/circuitpython that referenced this pull request Feb 22, 2023
It now handles deinit, never_reset and sharing tracking. PWM
now runs in the WAIT state as well during a time.sleep().

_reset_ok() was removed because it was called in one spot right
before deinit().

Some PWMOut were also switched to a bitmap for use instead of
reference count. That way init and deinit are idempotent.

Fixes micropython#6589. Fixes micropython#4841. Fixes micropython#4541.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants