PerMalmberg / Smooth

C++ framework for embedded programming on top of Espressif's ESP-IDF.
Apache License 2.0
325 stars 30 forks source link

Add support for wired ethernet #136

Closed elcojacobs closed 7 months ago

elcojacobs commented 4 years ago

First of all, your framework looks well designed so I think this could be my first of many PR's.

I added support for wired Ethernet, which required a refactor of the WiFi class too. Please let me know your thoughts on the changes.

I tested this on custom hardware with the internal MAC and a LAN8742 phy. The LAN8742 is very similar to the 8720, but not included yet in esp-idf. Will later send a PR for that to esp-idf.

Changes

Result of test app and idf.py monitor below. You can see that the ESP gets 2 ip addresses. Connections are accepted on both IPs, tested with:

echo ` date` | nc 192.168.1.115 8080 -w1
echo ` date` | nc 192.168.1.116 8080 -w1
entry 0x40080688
I (29) boot: ESP-IDF v4.2-beta1-87-gb83aa9fda 2nd stage bootloader
I (29) boot: compile time 18:26:28
I (29) boot: chip revision: 1
I (33) boot_comm: chip revision: 1, min. bootloader chip revision: 0
I (40) boot.esp32: SPI Speed      : 40MHz
I (45) boot.esp32: SPI Mode       : DIO
I (50) boot.esp32: SPI Flash Size : 4MB
I (54) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
I (63) boot: ## Label            Usage          Type ST Offset   Length
I (70) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (78) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (85) boot:  2 factory          factory app      00 00 00010000 00300000
I (93) boot:  3 app_storage      Unknown data     01 81 00310000 00084000
I (100) boot: End of partition table
I (105) boot_comm: chip revision: 1, min. application chip revision: 0
I (112) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x3b2ec (242412) map
I (213) esp_image: segment 1: paddr=0x0004b314 vaddr=0x3ffb0000 size=0x03dbc ( 15804) load
I (220) esp_image: segment 2: paddr=0x0004f0d8 vaddr=0x40080000 size=0x00404 (  1028) load
0x40080000: _WindowOverflow4 at /home/elco/repos/esp-idf/components/freertos/xtensa/xtensa_vectors.S:1730

I (221) esp_image: segment 3: paddr=0x0004f4e4 vaddr=0x40080404 size=0x00b34 (  2868) load
I (230) esp_image: segment 4: paddr=0x00050020 vaddr=0x400d0020 size=0xc4214 (803348) map
0x400d0020: _stext at ??:?

I (543) esp_image: segment 5: paddr=0x0011423c vaddr=0x40080f38 size=0x154fc ( 87292) load
I (594) boot: Loaded app from partition at offset 0x10000
I (594) boot: Disabling RNG early entropy source...
I (594) cpu_start: Pro cpu up.
I (598) cpu_start: Application information:
I (603) cpu_start: Project name:     server_socket_ethernet_test
I (609) cpu_start: App version:      9de844f-dirty
I (615) cpu_start: Compile time:     Oct 17 2020 18:38:57
I (621) cpu_start: ELF file SHA256:  d997a84045d58fce...
I (627) cpu_start: ESP-IDF:          v4.2-beta1-87-gb83aa9fda
I (633) cpu_start: Starting app cpu, entry point is 0x400818a0
0x400818a0: call_start_cpu1 at /home/elco/repos/esp-idf/components/esp32/cpu_start.c:282

I (0) cpu_start: App cpu up.
I (644) heap_init: Initializing. RAM available for dynamic allocation:
I (651) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (657) heap_init: At 3FFB9D90 len 00026270 (152 KiB): DRAM
I (663) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (669) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (676) heap_init: At 40096434 len 00009BCC (38 KiB): IRAM
I (682) cpu_start: Pro cpu start user code
I (729) spi_flash: detected chip: gd
I (730) spi_flash: flash io: dio
W (730) spi_flash: Detected size(16384k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
I (740) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (812) FSLock: Max number of open files set to: 5
I (825) system_api: Base MAC address is not set
I (825) system_api: read default base MAC address from EFUSE
I (838) App::Init: Starting Ethernet...
I (839) esp_eth.netif.glue: 24:62:ab:c3:8b:bf
I (839) esp_eth.netif.glue: ethernet attached to netif
I (853) App::Init: Starting wifi...
I (857) wifi:wifi driver task: 3ffcf9f0, prio:23, stack:3584, core=0
I (873) wifi:wifi firmware version: d3be909
I (873) wifi:wifi certification version: v7.0
I (873) wifi:config NVS flash: enabled
I (873) wifi:config nano formating: disabled
I (878) wifi:Init data frame dynamic rx buffer num: 32
I (882) wifi:Init management frame dynamic rx buffer num: 32
I (888) wifi:Init management short buffer num: 32
I (892) wifi:Init static tx buffer num: 16
I (896) wifi:Init static rx buffer size: 1600
I (900) wifi:Init static rx buffer num: 10
I (904) wifi:Init dynamic rx buffer num: 32
I (909) wifi_init: rx ba win: 16
I (911) wifi_init: tcpip mbox: 32
I (915) wifi_init: udp mbox: 6
I (919) wifi_init: tcp mbox: 6
I (923) wifi_init: tcp tx win: 5744
I (927) wifi_init: tcp rx win: 5744
I (931) wifi_init: tcp mss: 1440
I (935) wifi_init: WiFi IRAM OP enabled
I (940) wifi_init: WiFi RX IRAM OP enabled
I (1027) phy: phy_version: 4390, 6b3c1f2, Sep 10 2020, 15:09:07, 0, 0
I (1029) wifi:mode : sta (24:62:ab:c3:8b:bc)
I (1037) SocketDispatcher: Active sockets: 0
I (1637) wifi:new:<5,0>, old:<1,0>, ap:<255,255>, sta:<5,0>, prof:1
I (2173) wifi:state: init -> auth (b0)
I (2178) wifi:state: auth -> assoc (0)
I (2184) wifi:state: assoc -> run (10)
I (2211) wifi:connected with BrouwerijDeSchutter, aid = 3, channel 5, BW20, bssid = 7c:ff:4d:16:65:fa
I (2212) wifi:security: WPA2-PSK, phy: bg, rssi: -76
I (2215) wifi:pm start, type: 1

I (2352) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (3316) SocketDispatcher: Network up, sockets will be restarted.
I (3317) esp_netif_handlers: sta ip: 192.168.1.116, mask: 255.255.255.0, gw: 192.168.1.1
I (3318) SocketDispatcher: Network up, sockets will be restarted.
I (3330) Socket: [0.0.0.0, 8080, 54, 0x3ffdb9c0]: Created server socket
I (3815) esp_netif_handlers: eth ip: 192.168.1.115, mask: 255.255.255.0, gw: 192.168.1.1
I (3822) SocketDispatcher: Network up, sockets will be restarted.
I (3823) SocketDispatcher: Network up, sockets will be restarted.
I (3828) Socket: [0.0.0.0, 8080, 54, 0x3ffdb9c0]: Server stopping
E (3835) SocketDispatcher: Shutdown error: Invalid argument
I (3841) SocketDispatcher: Active sockets: 0
I (3846) SocketDispatcher: Active sockets: 0
I (3851) Socket: [0.0.0.0, 8080, 54, 0x3ffdb9c0]: Created server socket
I (13282) ServerSocket: Connection accepted
I (13284) SocketDispatcher: Active sockets: 2
I (14283) Socket: [, 0, 55, 0x3ffdd744]: Underlying socket closed (recv returned 0)
I (14283) Socket: [, 0, 55, 0x3ffdd744]: Socket stopping
I (14289) Socket: [, 0, -1, 0x3ffdd744]: Disconnected
I (14293) SocketDispatcher: Active sockets: 1
I (18954) ServerSocket: Connection accepted
I (18956) SocketDispatcher: Active sockets: 2
I (19971) Socket: [, 0, 55, 0x3ffdd744]: Underlying socket closed (recv returned 0)
I (19971) Socket: [, 0, 55, 0x3ffdd744]: Socket stopping
I (19977) Socket: [, 0, -1, 0x3ffdd744]: Disconnected
I (19980) SocketDispatcher: Active sockets: 1

Formatting For my own projects, I use clang-format. Integrates very nicely with vscode to format on save. I ran uncrustify for this PR, because my clang-format rules are different.

I use these settings and the xaver.clang-format vscode plugin: https://github.com/BrewBlox/brewblox-firmware/blob/develop/.clang-format

Have you tried/considered clang-format instead of uncrustify?

elcojacobs commented 4 years ago

Test build is failing because no stubs for esp_eth exist in mock-idf. I can add some empty functions, but not sure if that's the right approach.

The mocks here are mostly just empty stubs. On my current platform, we can run our entire app as a cross compiled executable, including network, GPIO mocking, etc. Would be nice to have this on ESP32 too.

I found this repo, but haven't tried it yet. https://github.com/mireq/esp32-simulator

PerMalmberg commented 4 years ago

Smooth can be cross compiled and network was functional when running on Linux prior to this PR so that needs to work. IO is currently just mocked.

I'll try to find some time tomorrow or at least the next few days to look at this PR.

I have considered clang format, but found uncrustify worked better, at least it did a few years ago.

elcojacobs commented 4 years ago

I added eth stubs in mock-idf.

Compile now fails on acces_point test, because it still expects to have some static functions on the Wifi class, that I have moved to the NetworkInterface class.

It does make sense to have only one Wifi instance. In theory, you can have multiple Ethernet instances on the SPI bus. But we can choose not to implement this use case.

An option is a global wifi getter and construct it on first use. It can then be removed from App members.

 Wifi& get_wifi(){
     static Wifi* wifi = new Wifi();
     return *wifi;
 }
elcojacobs commented 4 years ago

Global singleton added for Wifi, tests passing :smile:

PerMalmberg commented 4 years ago

Had a quick look, overall it looks good. I have a few things I want to think about/look closer at in an actual code-editor.

It's a bit outside the coding-part, but I feel like a small documentation section about the h/w side could be of use for the wired ethernet. Perhaps some links to resources that gets people started?

elcojacobs commented 4 years ago

I would just refer to the esp-idf docs, because the options I added to kconfig are copy-paste from their examples. The only change I made is to set the default to phy address 0. That gotcha took me a while to figure out before I got it to work. I am also using the framework functions to initialize everything, nothing custom about that. Just some C++ around the api calls.

For reference, I am using the internal MAC with a LAN8742 phy. It is cheaper and easier to get than the 8720. I let the ESP generate the 50Mhz clock for the phy on pin gpio0, and use the default pins. I use the WROOM-32D. If I remember correctly, the variants with external PSRAM don't have ethernet. Ethernet needs quite a lot of dedicated pins.

CONFIG_SMOOTH_USE_INTERNAL_ETHERNET=y
# CONFIG_SMOOTH_USE_DM9051 is not set
# CONFIG_SMOOTH_ETH_PHY_IP101 is not set
# CONFIG_SMOOTH_ETH_PHY_RTL8201 is not set
# CONFIG_SMOOTH_ETH_PHY_LAN8720 is not set
CONFIG_SMOOTH_ETH_PHY_LAN8742=y
# CONFIG_SMOOTH_ETH_PHY_DP83848 is not set
CONFIG_SMOOTH_ETH_MDC_GPIO=23
CONFIG_SMOOTH_ETH_MDIO_GPIO=18
CONFIG_SMOOTH_ETH_PHY_RST_GPIO=5
CONFIG_SMOOTH_ETH_PHY_ADDR=0

No need in duplicating the docs here: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit.html#gpio-allocation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_eth.html

I would add those links and leave it at that.

elcojacobs commented 3 years ago

Thanks for the feedback.

After playing with smooth for a bit, I actually think it might be a bit too opinionated for my goals. It uses a lot of events by default and is quite verbose, with ease of use over performance. Many of the logs generated by smooth are duplications of the logs already emitted by esp-idf.

I have been playing with asio, and I think it will be a better fit for my application. I prefer to stay a bit closer to esp-idf where possible, and use the framework provided implementations for mdns for example.

I'll still make the changes to this PR and will look at smooth for inspiration here and there, but I think it will not combine well with asio. asio is not even available when smooth is included, I am not sure why.

peterus commented 3 years ago

@elcojacobs thanks for the implementation of the ethernet part! It will help on my current project a lot! please finish your current PR if possible, if you need/want help: just give me a ping ;)

elcojacobs commented 3 years ago

I don't have time to work on this in the coming days. Feel free to pull my branch and work on it or even finish it.

PerMalmberg commented 3 years ago

After playing with smooth for a bit, I actually think it might be a bit too opinionated for my goals. It uses a lot of events by default and is quite verbose, with ease of use over performance.

That's is a correct observation; it's was one of my my goal with it.

Many of the logs generated by smooth are duplications of the logs already emitted by esp-idf.

Oh, anything in particular you have in mind here?

I have been playing with asio, and I think it will be a better fit for my application. I prefer to stay a bit closer to esp-idf where possible, and use the framework provided implementations for mdns for example.

Then you should ofc use it! The right tool for the job etc. :)

I'll still make the changes to this PR and will look at smooth for inspiration here and there, but I think it will not combine well with asio. asio is not even available when smooth is included, I am not sure why.

Apart from a possible conflict with the network layer I don't see a problem using asio together with smooth. I've not disabled asio in Smooth afaik.