Merge pull request #1955 from cesanta/picormii

Add RMII example for RP2040
This commit is contained in:
Sergey Lyubka 2023-01-11 10:40:10 +00:00 committed by GitHub
commit 1661bd6691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 540 additions and 0 deletions

View File

@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.13)
include(pico-sdk/pico_sdk_init.cmake)
project(pico_rmii_ethernet)
# initialize the Pico SDK
pico_sdk_init()
add_compile_definitions(
MIP_DEBUG=1
MG_ENABLE_PACKED_FS=1
MG_ENABLE_MIP=1
)
add_executable(firmware
driver_rp2040_rmii.c
main.c
mongoose.c
)
# enable usb output, disable uart output
pico_enable_stdio_usb(firmware 1)
pico_enable_stdio_uart(firmware 0)
target_link_libraries(firmware hardware_pio hardware_dma pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(firmware)

View File

@ -0,0 +1,16 @@
SDK_VERSION ?= 1.4.0
SDK_REPO ?= https://github.com/raspberrypi/pico-sdk
all example:
true
build: pico-sdk
test -d build || mkdir build
cd build && cmake .. && make
pico-sdk:
git clone --depth 1 -b $(SDK_VERSION) $(SDK_REPO) $@
cd $@ && git submodule update --init
clean:
rm -rf pico-sdk build

View File

@ -0,0 +1,63 @@
# RMII with the PIO in an RP2040 for 10Mbps Ethernet
- Full-duplex 10Mbps
- RMII Tx implemented with a PIO state machine and single-buffer DMA transfers
- Once the DMA starts sending to the PIO, subsequent attempts to tx will block
- RMII Rx implemented with a PIO state machine and ping-pong buffer DMA transfers
- PIO code handles the CSR_DV signal, stops and raises an IRQ on frame ending. CRC is calculated later for minimum dead time between frames
- SMI implemented with a PIO state machine
- The RP2040 is clocked from the LAN8720 and works at 50MHz
- CRC calculation uses a fast table-based algorithm that doesn't require much storage (nibble calculation)
## Hardware connections
You can change most pins at will, bear in mind that each function requires consecutive pins, see main.c
The RETCLK (should be REFCLK...) pin can go to either GPIO20 or GPIO22
| LAN8720 | | Raspberry Pi Pico | |
| ----------- | ----------- |----------------- | -- |
| 1,2 | VCC | 3V3 | 36 |
| 3,4 | GND | GND | 38 |
| 5 | MDC | GPIO15 | 20 |
| 6 | MDIO | GPIO14 | 19 |
| 7 | CRS | GPIO8 | 11 |
| 8 | nINT / RETCLK | GPIO20 | 26 |
| 9 | RX1 | GPIO7 | 10 |
| 10 | RX0 | GPIO6 | 9 |
| 11 | TX0 | GPIO10 | 14 |
| 12 | TX-EN | GPIO12 | 16 |
| 13 |
| 14| TX1 | GPIO11 | 15 |
## Build and run
Clone Mongoose repo, go to this example, and build it:
```sh
git clone https://github.com/cesanta/mongoose
cd mongoose/examples/rp2040/pico-rmii
make build
```
This will generate a firmware file: `build/firmware.uf2`. Reboot your Pico board in bootloader mode, and copy it to the RPI disk.
Attach to the serial console, wait a couple of seconds, then plug in your Ethernet cable:
```
$ picocom /dev/ttyACM0 -i -b 115200 --imap=lfcrlf
6213 1 mongoose.c:6757:onstatechange Link up
6217 3 mongoose.c:6840:tx_dhcp_discover DHCP discover sent
621b 3 mongoose.c:6723:arp_cache_add ARP cache: added 192.168.69.2 @ 52:54:...
6222 2 mongoose.c:6749:onstatechange READY, IP: 192.168.69.243
6227 2 mongoose.c:6750:onstatechange GW: 192.168.69.1
622b 2 mongoose.c:6752:onstatechange Lease: 21600 sec
```
Note the acquired IP address printed (you can also check your DHCP server). Point your browser to that address, or run curl, you should see an "ok" message:
```
$ curl 192.168.69.243
ok
```

View File

@ -0,0 +1,366 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "pico/stdlib.h"
#include "driver_rp2040_rmii.h"
#include "mongoose.h"
#define RXSM 0
#define TXSM 1
#define SMITXSM 2
#define SMIRXSM 0 // pio1
static uint smi_wr_addr, smi_rd_addr;
static int dma_rx;
static int dma_tx;
static dma_channel_config dmacfg_rx;
static dma_channel_config dmacfg_tx;
// Max frame size with VLAN tag + CRC and excluding preamble
#define ETH_PKT_SIZE 1522
static uint8_t s_rxbuf[2][ETH_PKT_SIZE]; // ping-pong buffer
static uint8_t s_txbuf[ETH_PKT_SIZE];
static struct mip_if *s_ifp; // MIP interface
#define rmii_tx_wrap_target 0
#define rmii_tx_wrap 8
static const uint16_t rmii_tx_program_instructions[] = {
// .wrap_target
0xe000, // 0: set pins, 0 side 0
0x80a0, // 1: pull block side 0
0xe03d, // 2: set x, 29 side 0
0xf101, // 3: set pins, 1 side 1 [1]
0x1144, // 4: jmp x--, 4 side 1 [1]
0xf103, // 5: set pins, 3 side 1 [1]
0x7002, // 6: out pins, 2 side 1
0x10e6, // 7: jmp !osre, 6 side 1
// .wrap
};
static const struct pio_program rmii_tx_program = {
.instructions = rmii_tx_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config rmii_tx_program_get_default_config(uint addr) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, addr + rmii_tx_wrap_target, addr + rmii_tx_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
static inline void rmii_tx_init(PIO pio, uint sm, uint addr, uint gpio) {
pio_sm_config c = rmii_tx_program_get_default_config(addr);
pio_gpio_init(pio, gpio);
pio_gpio_init(pio, gpio + 1);
pio_gpio_init(pio, gpio + 2);
sm_config_set_out_pins(&c, gpio, 2);
sm_config_set_set_pins(&c, gpio, 2);
sm_config_set_sideset_pins(&c, gpio + 2);
pio_sm_set_consecutive_pindirs(pio, sm, gpio, 3, true);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_out_shift(&c, true, true, 8); // pull bytes, LSB first, auto
sm_config_set_clkdiv(&c,
5); // Run at 50/5 = 10MHz (2x 2-bit nibble data rate)
pio_sm_init(pio, sm, addr, &c);
pio_sm_set_enabled(pio, sm, true);
}
#define rmii_rx_wrap_target 0
#define rmii_rx_wrap 10
static const uint16_t rmii_rx_program_instructions[] = {
// .wrap_target
0x2122, // 0: wait 0 pin, 2 [1]
0x21a2, // 1: wait 1 pin, 2 [1]
0x21a0, // 2: wait 1 pin, 0 [1]
0x2121, // 3: wait 0 pin, 1 [1]
0x21a1, // 4: wait 1 pin, 1 [1]
0x4002, // 5: in pins, 2
0x00c5, // 6: jmp pin, 5
0x4002, // 7: in pins, 2
0x00c5, // 8: jmp pin, 5
0xa0c3, // 9: mov isr, null
0xc020, // 10: irq wait 0
// .wrap
};
static const struct pio_program rmii_rx_program = {
.instructions = rmii_rx_program_instructions,
.length = 11,
.origin = -1,
};
static inline pio_sm_config rmii_rx_program_get_default_config(uint addr) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, addr + rmii_rx_wrap_target, addr + rmii_rx_wrap);
return c;
}
static inline void rmii_rx_init(PIO pio, uint sm, uint addr, uint gpio) {
pio_sm_config c = rmii_rx_program_get_default_config(addr);
pio_gpio_init(pio, gpio);
pio_gpio_init(pio, gpio + 1);
pio_gpio_init(pio, gpio + 2);
sm_config_set_in_pins(&c, gpio);
pio_sm_set_consecutive_pindirs(pio, sm, gpio, 3, false);
sm_config_set_jmp_pin(&c, gpio + 2);
sm_config_set_in_shift(&c, true, true, 8); // push bytes, LSB first, auto
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c,
5); // Run at 50/5 = 10MHz (2x 2-bit nibble data rate)
pio_sm_init(pio, sm, addr, &c);
pio_sm_set_enabled(pio, sm, true);
}
#define smi_wr_wrap_target 0
#define smi_wr_wrap 4
static const uint16_t smi_wr_program_instructions[] = {
// .wrap_target
0xe03f, // 0: set x, 31 side 0
0xe101, // 1: set pins, 1 side 0 [1]
0x1141, // 2: jmp x--, 1 side 1 [1]
0x6101, // 3: out pins, 1 side 0 [1]
0x1103, // 4: jmp 3 side 1 [1]
// .wrap
};
static const struct pio_program smi_wr_program = {
.instructions = smi_wr_program_instructions,
.length = 5,
.origin = -1,
};
static inline pio_sm_config smi_wr_program_get_default_config(uint addr) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, addr + smi_wr_wrap_target, addr + smi_wr_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#define smi_rd_wrap_target 13
#define smi_rd_wrap 13
static const uint16_t smi_rd_program_instructions[] = {
0xe03f, // 0: set x, 31 side 0
0xe101, // 1: set pins, 1 side 0 [1]
0x1141, // 2: jmp x--, 1 side 1 [1]
0xf02d, // 3: set x, 13 side 1
0x6101, // 4: out pins, 1 side 0 [1]
0x1144, // 5: jmp x--, 4 side 1 [1]
0xe180, // 6: set pindirs, 0 side 0 [1]
0xf101, // 7: set pins, 1 side 1 [1]
0xa142, // 8: nop side 0 [1]
0xf12f, // 9: set x, 15 side 1 [1]
0xa042, // 10: nop side 0
0x4001, // 11: in pins, 1 side 0
0x114a, // 12: jmp x--, 10 side 1 [1]
// .wrap_target
0xf081, // 13: set pindirs, 1 side 1
// .wrap
};
static const struct pio_program smi_rd_program = {
.instructions = smi_rd_program_instructions,
.length = 14,
.origin = -1,
};
static inline pio_sm_config smi_rd_program_get_default_config(uint addr) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, addr + smi_rd_wrap_target, addr + smi_rd_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
static inline void smi_wr_init(PIO pio, uint sm, uint addr, uint gpio) {
pio_gpio_init(pio, gpio);
pio_gpio_init(pio, gpio + 1);
pio_sm_config c = smi_wr_program_get_default_config(addr);
sm_config_set_out_pins(&c, gpio, 1);
sm_config_set_set_pins(&c, gpio, 1);
sm_config_set_sideset_pins(&c, gpio + 1);
pio_sm_set_consecutive_pindirs(pio, sm, gpio, 2, true);
sm_config_set_out_shift(&c, false, true,
16); // pull half-words, MSB first, auto
sm_config_set_clkdiv(&c, 6); // Run at 50/6 = <10MHz (4x data rate)
pio_sm_init(pio, sm, addr, &c);
}
static inline void smi_rd_init(PIO pio, uint sm, uint addr, uint gpio) {
pio_gpio_init(pio, gpio);
pio_gpio_init(pio, gpio + 1);
pio_sm_config c = smi_rd_program_get_default_config(addr);
sm_config_set_in_pins(&c, gpio);
sm_config_set_out_pins(&c, gpio, 1);
sm_config_set_set_pins(&c, gpio, 1);
sm_config_set_sideset_pins(&c, gpio + 1);
pio_sm_set_consecutive_pindirs(pio, sm, gpio, 2, true);
sm_config_set_out_shift(&c, false, true,
16); // pull half-words, MSB first, auto
sm_config_set_in_shift(&c, false, true,
16); // push half-words, MSB first, auto
sm_config_set_clkdiv(&c, 6); // Run at 50/6 = <10MHz (4x data rate)
pio_sm_init(pio, sm, addr, &c);
}
static inline uint32_t crc_calc(const uint8_t *data, int length) {
static const uint32_t crclut[16] = {
// table for polynomial 0xEDB88320 (reflected)
0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4,
0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
uint32_t crc = 0xFFFFFFFF;
while (--length >= 0) {
uint8_t byte = *data++;
crc = crclut[(crc ^ byte) & 0x0F] ^ (crc >> 4);
crc = crclut[(crc ^ (byte >> 4)) & 0x0F] ^ (crc >> 4);
}
return ~crc;
}
static void eth_write_phy(int addr, int reg, int val, uint8_t gpio) {
uint16_t op = 0x5002 | (addr << 7) | (reg << 2); // b0101aaaaarrrrr10
smi_wr_init(pio0, SMITXSM, smi_wr_addr, gpio);
pio_sm_put(pio0, SMITXSM, op << 16);
pio_sm_put(pio0, SMITXSM, (uint16_t) val << 16);
pio_sm_set_enabled(pio0, SMITXSM, true);
}
static uint16_t eth_read_phy(uint addr, uint reg, uint8_t gpio) {
uint16_t op = 0x6000 | (addr << 7) | (reg << 2); // b0110aaaaarrrrr00
smi_rd_init(pio1, SMIRXSM, smi_rd_addr, gpio);
pio_sm_put(pio1, SMIRXSM, op << 16);
pio_sm_set_enabled(pio1, SMIRXSM, true);
uint16_t val = (uint16_t) pio_sm_get_blocking(pio1, SMIRXSM);
pio_sm_set_enabled(pio1, SMIRXSM, false);
return val;
}
static void rx_irq(void);
static bool mip_driver_rp2040_rmii_init(struct mip_if *ifp) {
struct mip_driver_rp2040_rmii_data *d =
(struct mip_driver_rp2040_rmii_data *) ifp->driver_data;
uint rx_sm_addr, tx_sm_addr;
s_ifp = ifp;
if (ifp->queue.len == 0) ifp->queue.len = 8192;
rx_sm_addr = pio_add_program(pio0, &rmii_rx_program);
tx_sm_addr = pio_add_program(pio0, &rmii_tx_program);
smi_wr_addr = pio_add_program(pio0, &smi_wr_program);
smi_rd_addr = pio_add_program(pio1, &smi_rd_program);
dma_tx = dma_claim_unused_channel(true);
dmacfg_tx = dma_channel_get_default_config(dma_tx);
channel_config_set_read_increment(&dmacfg_tx, true); // read from memory
channel_config_set_write_increment(&dmacfg_tx, false); // write to FIFO
channel_config_set_dreq(&dmacfg_tx, pio_get_dreq(pio0, TXSM, true));
channel_config_set_transfer_data_size(&dmacfg_tx,
DMA_SIZE_8); // transfer bytes
rmii_tx_init(pio0, TXSM, tx_sm_addr, d->tx0);
eth_write_phy(d->phy_addr, 4, 0x61,
d->mdio); // Auto Negotiation Advertisement Register: enable
// only 10BASE-T full and half duplex (two-pair), use
// IEEE 802.3 format
sleep_us(31); // give some time for PIO to work (64 bits @2.5MHz = 25.6 us)
eth_write_phy(d->phy_addr, 0, 0x1000,
d->mdio); // Basic Control Register: enable Auto-Negotiation
sleep_us(31);
dma_rx = dma_claim_unused_channel(true);
dmacfg_rx = dma_channel_get_default_config(dma_rx);
channel_config_set_read_increment(&dmacfg_rx, false); // read from FIFO
channel_config_set_write_increment(&dmacfg_rx,
true); // write to memory, increment
channel_config_set_dreq(&dmacfg_rx, pio_get_dreq(pio0, RXSM, false));
channel_config_set_transfer_data_size(&dmacfg_rx,
DMA_SIZE_8); // transfer bytes
dma_channel_configure(dma_rx, &dmacfg_rx, s_rxbuf[0],
((uint8_t *) &pio0->rxf[RXSM]) + 3, ETH_PKT_SIZE, true);
pio_set_irq0_source_enabled(pio0, pis_interrupt0,
true); // enable SM0 on PIO0_IRQ_0
irq_set_exclusive_handler(7, rx_irq); // set handler for PIO0_IRQ_0
irq_set_enabled(7, true); // enable it
rmii_rx_init(pio0, RXSM, rx_sm_addr, d->rx0);
return true;
}
static size_t mip_driver_rp2040_rmii_tx(const void *buf, size_t len,
struct mip_if *ifp) {
dma_channel_wait_for_finish_blocking(dma_tx);
memset(s_txbuf, 0, 60); // pre-pad
memcpy(s_txbuf, buf, len);
if (len < 60) len = 60; // pad
uint32_t crc = crc_calc(s_txbuf, len); // host is little-endian
memcpy(s_txbuf + len, (uint8_t *) &crc, 4);
len += 4;
sleep_us(10); // enforce IFG in case software has been lightning fast...
dma_channel_configure(dma_tx, &dmacfg_tx, ((uint8_t *) &pio0->txf[TXSM]) + 3,
s_txbuf, len, true);
return len;
}
static uint32_t s_rxno;
// The max amount of time we should keep the PIO state machine stopped is the
// IFG time (10us) and that includes irq latency
static void rx_irq(void) {
dma_channel_hw_t *hw = dma_channel_hw_addr(dma_rx);
uint32_t rxno =
(s_rxno + 1) &
1; // 2 buffers, switch to the available one as fast as possible
size_t len = ETH_PKT_SIZE - hw->transfer_count;
dma_channel_abort(dma_rx);
dma_channel_set_write_addr(dma_rx, s_rxbuf[rxno], true); // restart DMA
pio_interrupt_clear(pio0,
0); // ACK PIO IRQ so the state machine resumes receiving
// NOTE(scaprile) Here we could check addressing to avoid queuing frames not
// for us, or we can defer that for later as we do with CRC The max amount of
// time we can linger here is what it takes for the other buffer to fill
// (<8us) and that includes irq chaining
if (len >= 64 && len <= ETH_PKT_SIZE)
mip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
s_rxno = rxno;
}
static size_t mip_driver_rp2040_rmii_rx(void *buf, size_t buflen, struct mip_if *ifp) {
size_t len = mip_qread(buf, ifp);
if (len == 0) return 0;
len -= 4; // exclude CRC from frame length
uint32_t crc = crc_calc(buf, len); // calculate CRC and compare
// NOTE(scaprile) Here we could check addressing to avoid delivering frames
// not for us, though MIP already does that (now ?)
if (memcmp(&((uint8_t *) buf)[len], &crc,
sizeof(crc))) // host is little-endian
return 0;
return len;
}
static bool mip_driver_rp2040_rmii_up(struct mip_if *ifp) {
struct mip_driver_rp2040_rmii_data *d =
(struct mip_driver_rp2040_rmii_data *) ifp->driver_data;
uint32_t bsr =
eth_read_phy(d->phy_addr, 1, d->mdio); // Basic Status Register
return (bsr & (1 << 2)) ? 1 : 0; // check Link Status flag
}
struct mip_driver mip_driver_rp2040_rmii = {
mip_driver_rp2040_rmii_init,
mip_driver_rp2040_rmii_tx,
mip_driver_rp2040_rmii_rx,
mip_driver_rp2040_rmii_up,
};

View File

@ -0,0 +1,10 @@
#pragma once
struct mip_driver_rp2040_rmii_data {
uint8_t rx0; // RX0, RX1, CRS_DV; consecutive GPIO pins
uint8_t tx0; // TX0, TX1, TX-EN; consecutive GPIO pins
uint8_t mdio; // MDIO, MDC; consecutive GPIO pins
uint8_t phy_addr; // PHY address
};
extern struct mip_driver mip_driver_rp2040_rmii;

View File

@ -0,0 +1,55 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "mongoose.h"
#include "driver_rp2040_rmii.h"
#define CLKREFPIN 20 // either 20 or 22
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
mg_http_reply(c, 200, "", "ok\n");
}
}
int main(void) {
// use the RMII reference clock (50MHz) as our system clock
clock_configure_gpin(clk_sys, CLKREFPIN, 50 * MHZ, 50 * MHZ);
sleep_ms(100);
stdio_init_all();
struct mg_mgr mgr; // Initialise Mongoose event manager
mg_mgr_init(&mgr); // and attach it to the MIP interface
mg_log_set(MG_LL_DEBUG); // Set log level
MG_INFO(("Init MIP"));
// Initialise Mongoose network stack and specific driver
// Set consecutive GPIOs for RMII (tx and rx) and SMI function groups
struct mip_driver_rp2040_rmii_data driver_data = {
// see driver_rp2040_rmii.h
.rx0 = 6, // 6, 7, 8 : RX0, RX1, CRS_DV
.tx0 = 10, // 10, 11, 12 : TX0, TX1, TX-EN
.mdio = 14, // 14, 15 : MDIO, MDC
.phy_addr = 1 // check your hardware, LAN8722 is 0 or 1
};
// Specify MAC address, either set use_dhcp or enter a static config.
// For static configuration, specify IP/mask/GW in network byte order
struct mip_if mif = {
.mac = {2, 0, 1, 2, 3, 5},
.ip = 0,
.driver = &mip_driver_rp2040_rmii,
.driver_data = &driver_data,
};
mip_init(&mgr, &mif);
mg_http_listen(&mgr, "http://0.0.0.0", fn, NULL); // HTTP listener
MG_INFO(("Init done, starting main loop"));
for (;;) mg_mgr_poll(&mgr, 0); // Infinite event loop
return 0;
}

View File

@ -0,0 +1 @@
../../../mongoose.c

View File

@ -0,0 +1 @@
../../../mongoose.h