├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples └── httpd │ ├── CMakeLists.txt │ └── main.c ├── pico_sdk_import.cmake └── src ├── include └── rmii_ethernet │ └── netif.h ├── lwip ├── arch │ └── cc.h ├── lwipopts.h └── sys_arch.c ├── rmii_ethernet.c ├── rmii_ethernet_phy_rx.pio └── rmii_ethernet_phy_tx.pio /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | 13 | build/ 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/lwip"] 2 | path = lib/lwip 3 | url = https://github.com/lwip-tcpip/lwip.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | # initialize pico_sdk from GIT 4 | # (note this can come from environment, CMake cache etc) 5 | # set(PICO_SDK_FETCH_FROM_GIT on) 6 | 7 | # pico_sdk_import.cmake is a single file copied from this SDK 8 | # note: this must happen before project() 9 | include(pico_sdk_import.cmake) 10 | 11 | project(pico_rmii_ethernet) 12 | 13 | # initialize the Pico SDK 14 | pico_sdk_init() 15 | 16 | set(LWIP_PATH ${CMAKE_CURRENT_LIST_DIR}/lib/lwip) 17 | 18 | add_library(pico_lwip INTERFACE) 19 | 20 | target_sources(pico_lwip INTERFACE 21 | ${LWIP_PATH}/src/core/altcp.c 22 | ${LWIP_PATH}/src/core/altcp_alloc.c 23 | ${LWIP_PATH}/src/core/altcp_tcp.c 24 | ${LWIP_PATH}/src/core/def.c 25 | ${LWIP_PATH}/src/core/dns.c 26 | ${LWIP_PATH}/src/core/inet_chksum.c 27 | ${LWIP_PATH}/src/core/init.c 28 | ${LWIP_PATH}/src/core/ip.c 29 | ${LWIP_PATH}/src/core/mem.c 30 | ${LWIP_PATH}/src/core/memp.c 31 | ${LWIP_PATH}/src/core/netif.c 32 | ${LWIP_PATH}/src/core/pbuf.c 33 | ${LWIP_PATH}/src/core/raw.c 34 | ${LWIP_PATH}/src/core/stats.c 35 | ${LWIP_PATH}/src/core/sys.c 36 | ${LWIP_PATH}/src/core/tcp.c 37 | ${LWIP_PATH}/src/core/tcp_in.c 38 | ${LWIP_PATH}/src/core/tcp_out.c 39 | ${LWIP_PATH}/src/core/timeouts.c 40 | ${LWIP_PATH}/src/core/udp.c 41 | ${LWIP_PATH}/src/core/ipv4/autoip.c 42 | ${LWIP_PATH}/src/core/ipv4/dhcp.c 43 | ${LWIP_PATH}/src/core/ipv4/etharp.c 44 | ${LWIP_PATH}/src/core/ipv4/icmp.c 45 | ${LWIP_PATH}/src/core/ipv4/igmp.c 46 | ${LWIP_PATH}/src/core/ipv4/ip4.c 47 | ${LWIP_PATH}/src/core/ipv4/ip4_addr.c 48 | ${LWIP_PATH}/src/core/ipv4/ip4_frag.c 49 | ${LWIP_PATH}/src/netif/ethernet.c 50 | 51 | ${LWIP_PATH}/src/apps/http/httpd.c 52 | ${LWIP_PATH}/src/apps/http/fs.c 53 | 54 | ${CMAKE_CURRENT_LIST_DIR}/src/lwip/sys_arch.c 55 | ) 56 | 57 | target_include_directories(pico_lwip INTERFACE 58 | ${LWIP_PATH}/src/include 59 | ${CMAKE_CURRENT_LIST_DIR}/src/lwip 60 | ) 61 | 62 | add_library(pico_rmii_ethernet INTERFACE) 63 | 64 | target_sources(pico_rmii_ethernet INTERFACE 65 | ${CMAKE_CURRENT_LIST_DIR}/src/rmii_ethernet.c 66 | ) 67 | 68 | target_include_directories(pico_rmii_ethernet INTERFACE 69 | ${CMAKE_CURRENT_LIST_DIR}/src/include 70 | ) 71 | 72 | pico_generate_pio_header(pico_rmii_ethernet ${CMAKE_CURRENT_LIST_DIR}/src/rmii_ethernet_phy_rx.pio) 73 | pico_generate_pio_header(pico_rmii_ethernet ${CMAKE_CURRENT_LIST_DIR}/src/rmii_ethernet_phy_tx.pio) 74 | 75 | target_link_libraries(pico_rmii_ethernet INTERFACE hardware_pio hardware_dma pico_stdlib pico_unique_id pico_lwip) 76 | 77 | add_subdirectory("examples/httpd") 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Sandeep Mistry 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico-rmii-ethernet 2 | 3 | Enable Ethernet connectivity on your [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) with an RMII based Ethernet PHY module. 4 | 5 | Leverages the Raspberry Pi RP2040 MCU's PIO, DMA, and dual core capabilities to create a Ethernet MAC stack in software! 6 | 7 | ## Hardware 8 | 9 | * [Raspberry Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) 10 | * Any RMII based Ethernet PHY module, such as the [Waveshare LAN8720 ETH Board](https://www.waveshare.com/lan8720-eth-board.htm) 11 | 12 | ### Wiring 13 | 14 | | RMII Module | Raspberry Pi Pico | Library Default | 15 | | ----------- | ----------------- | --------------- | 16 | | TX1 | TX0 + 1 | 11 | 17 | | TX-EN | TX0 + 2 | 12 | 18 | | TX0 | any GPIO | 10 | 19 | | RX0 | any GPIO | 6 | 20 | | RX1 | RX0 + 1 | 7 | 21 | | nINT / RETCLK | 20 or 22 | 20 | 22 | | CRS | RX0 + 2 | 8 | 23 | | MDIO | any GPIO | 14 | 24 | | MDC | MDIO + 1 | 15 | 25 | | VCC | 3V3 | | 26 | | GND | GND | | 27 | 28 | ## Examples 29 | 30 | See [examples](examples/) folder. [LWIP](https://www.nongnu.org/lwip/) is included as the TCP/IP stack. 31 | 32 | # Current Limitations 33 | 34 | * RP2040 is underclocked to 50 MHz using the RMII modules reference clock 35 | * Link speed is set to 10 Mbps (there is a issue with TX at 100 Mbps) 36 | * Built-in LWIP stack is compiled with `NO_SYS` so LWIP Netcon and Socket API's are not enabled 37 | -------------------------------------------------------------------------------- /examples/httpd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | # rest of your project 4 | add_executable(pico_rmii_ethernet_httpd 5 | main.c 6 | ) 7 | 8 | target_link_libraries(pico_rmii_ethernet_httpd pico_stdlib pico_multicore pico_rmii_ethernet) 9 | 10 | # enable usb output, disable uart output 11 | pico_enable_stdio_usb(pico_rmii_ethernet_httpd 1) 12 | pico_enable_stdio_uart(pico_rmii_ethernet_httpd 0) 13 | 14 | # create map/bin/hex/uf2 file in addition to ELF. 15 | pico_add_extra_outputs(pico_rmii_ethernet_httpd) 16 | -------------------------------------------------------------------------------- /examples/httpd/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Sandeep Mistry 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "pico/stdlib.h" 8 | #include "pico/multicore.h" 9 | 10 | #include "hardware/clocks.h" 11 | 12 | #include "lwip/dhcp.h" 13 | #include "lwip/init.h" 14 | 15 | #include "lwip/apps/httpd.h" 16 | 17 | #include "rmii_ethernet/netif.h" 18 | 19 | void netif_link_callback(struct netif *netif) 20 | { 21 | printf("netif link status changed %s\n", netif_is_link_up(netif) ? "up" : "down"); 22 | } 23 | 24 | void netif_status_callback(struct netif *netif) 25 | { 26 | printf("netif status changed %s\n", ip4addr_ntoa(netif_ip4_addr(netif))); 27 | } 28 | 29 | int main() { 30 | // LWIP network interface 31 | struct netif netif; 32 | 33 | // 34 | struct netif_rmii_ethernet_config netif_config = { 35 | pio0, // PIO: 0 36 | 0, // pio SM: 0 and 1 37 | 6, // rx pin start: 6, 7, 8 => RX0, RX1, CRS 38 | 10, // tx pin start: 10, 11, 12 => TX0, TX1, TX-EN 39 | 14, // mdio pin start: 14, 15 => ?MDIO, MDC 40 | NULL, // MAC address (optional - NULL generates one based on flash id) 41 | }; 42 | 43 | // change the system clock to use the RMII reference clock from pin 20 44 | clock_configure_gpin(clk_sys, 20, 50 * MHZ, 50 * MHZ); 45 | sleep_ms(100); 46 | 47 | // initialize stdio after the clock change 48 | stdio_init_all(); 49 | sleep_ms(5000); 50 | 51 | printf("pico rmii ethernet - httpd\n"); 52 | 53 | // initilize LWIP in NO SYS mode 54 | lwip_init(); 55 | 56 | // initialize the PIO base RMII Ethernet network interface 57 | netif_rmii_ethernet_init(&netif, &netif_config); 58 | 59 | // assign callbacks for link and status 60 | netif_set_link_callback(&netif, netif_link_callback); 61 | netif_set_status_callback(&netif, netif_status_callback); 62 | 63 | // set the default interface and bring it up 64 | netif_set_default(&netif); 65 | netif_set_up(&netif); 66 | 67 | // Start DHCP client and httpd 68 | dhcp_start(&netif); 69 | httpd_init(); 70 | 71 | // setup core 1 to monitor the RMII ethernet interface 72 | // this let's core 0 do other things :) 73 | multicore_launch_core1(netif_rmii_ethernet_loop); 74 | 75 | while (1) { 76 | tight_loop_contents(); 77 | } 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading Raspberry Pi Pico SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /src/include/rmii_ethernet/netif.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Sandeep Mistry 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #ifndef _PICO_RMII_ETHERNET_NETIF_H_ 8 | #define _PICO_RMII_ETHERNET_NETIF_H_ 9 | 10 | #include "hardware/pio.h" 11 | 12 | #include "lwip/netif.h" 13 | 14 | struct netif_rmii_ethernet_config { 15 | PIO pio; 16 | uint pio_sm_start; // uses 2 PIO sm's 17 | uint rx_pin_start; // RX0, RX1, CRS 18 | uint tx_pin_start; // TX0, TX1, TX-EN 19 | uint mdio_pin_start; // MDIO, MDC 20 | uint8_t *mac_addr; // 6 bytes 21 | }; 22 | 23 | #define NETIF_RMII_ETHERNET_DEFAULT_CONFIG() { \ 24 | .pio = pio0, \ 25 | .pio_sm_start = 0, \ 26 | .rx_pin_start = 6, \ 27 | .tx_pin_start = 10, \ 28 | .mdio_pin_start = 14, \ 29 | .mac_addr = NULL \ 30 | } 31 | 32 | err_t netif_rmii_ethernet_init(struct netif *netif, struct netif_rmii_ethernet_config *config); 33 | 34 | void netif_rmii_ethernet_poll(); 35 | 36 | void netif_rmii_ethernet_loop(); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/lwip/arch/cc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2001-2003 Swedish Institute of Computer Science. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, 6 | * are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 19 | * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 21 | * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 24 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 25 | * OF SUCH DAMAGE. 26 | * 27 | * This file is part of the lwIP TCP/IP stack. 28 | * 29 | * Author: Adam Dunkels 30 | * 31 | */ 32 | #ifndef __CC_H__ 33 | #define __CC_H__ 34 | 35 | //#include "cpu.h" 36 | 37 | typedef int sys_prot_t; 38 | 39 | 40 | 41 | /* define compiler specific symbols */ 42 | #if defined (__ICCARM__) 43 | 44 | #define PACK_STRUCT_BEGIN 45 | #define PACK_STRUCT_STRUCT 46 | #define PACK_STRUCT_END 47 | #define PACK_STRUCT_FIELD(x) x 48 | #define PACK_STRUCT_USE_INCLUDES 49 | 50 | #elif defined (__CC_ARM) 51 | 52 | #define PACK_STRUCT_BEGIN __packed 53 | #define PACK_STRUCT_STRUCT 54 | #define PACK_STRUCT_END 55 | #define PACK_STRUCT_FIELD(x) x 56 | 57 | #elif defined (__GNUC__) 58 | 59 | #define PACK_STRUCT_BEGIN 60 | #define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) 61 | #define PACK_STRUCT_END 62 | #define PACK_STRUCT_FIELD(x) x 63 | 64 | #elif defined (__TASKING__) 65 | 66 | #define PACK_STRUCT_BEGIN 67 | #define PACK_STRUCT_STRUCT 68 | #define PACK_STRUCT_END 69 | #define PACK_STRUCT_FIELD(x) x 70 | 71 | #endif 72 | 73 | #define LWIP_PLATFORM_ASSERT(x) do { if(!(x)) while(1); } while(0) 74 | 75 | #endif /* __CC_H__ */ 76 | -------------------------------------------------------------------------------- /src/lwip/lwipopts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2001-2003 Swedish Institute of Computer Science. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, 6 | * are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 19 | * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 21 | * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 24 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 25 | * OF SUCH DAMAGE. 26 | * 27 | * This file is part of the lwIP TCP/IP stack. 28 | * 29 | * Author: Simon Goldschmidt 30 | * 31 | */ 32 | #ifndef __LWIPOPTS_H__ 33 | #define __LWIPOPTS_H__ 34 | 35 | /* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */ 36 | #define NO_SYS 1 37 | #define MEM_ALIGNMENT 4 38 | #define LWIP_RAW 1 39 | #define LWIP_NETCONN 0 40 | #define LWIP_SOCKET 0 41 | #define LWIP_DHCP 1 42 | #define LWIP_ICMP 1 43 | #define LWIP_UDP 1 44 | #define LWIP_TCP 1 45 | #define ETH_PAD_SIZE 0 46 | #define LWIP_IP_ACCEPT_UDP_PORT(p) ((p) == PP_NTOHS(67)) 47 | 48 | #define LWIP_NETIF_LINK_CALLBACK 1 49 | #define LWIP_NETIF_STATUS_CALLBACK 1 50 | 51 | #define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphhr*/) 52 | #define TCP_SND_BUF (2 * TCP_MSS) 53 | 54 | #define LWIP_HTTPD_CGI 0 55 | #define LWIP_HTTPD_SSI 0 56 | #define LWIP_HTTPD_SSI_INCLUDE_TAG 0 57 | 58 | #if 0 59 | #define LWIP_DEBUG 1 60 | #define TCP_DEBUG LWIP_DBG_ON 61 | #define ETHARP_DEBUG LWIP_DBG_ON 62 | #define PBUF_DEBUG LWIP_DBG_ON 63 | #define IP_DEBUG LWIP_DBG_ON 64 | #define TCPIP_DEBUG LWIP_DBG_ON 65 | #define DHCP_DEBUG LWIP_DBG_ON 66 | #define UDP_DEBUG LWIP_DBG_ON 67 | #endif 68 | 69 | 70 | #endif /* __LWIPOPTS_H__ */ 71 | -------------------------------------------------------------------------------- /src/lwip/sys_arch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "pico/mutex.h" 8 | #include "pico/stdlib.h" 9 | 10 | #include "lwip/init.h" 11 | 12 | auto_init_mutex(lwip_mutex); 13 | 14 | /* lwip has provision for using a mutex, when applicable */ 15 | sys_prot_t sys_arch_protect(void) { 16 | mutex_enter_blocking(&lwip_mutex); 17 | 18 | return 0; 19 | } 20 | 21 | void sys_arch_unprotect(sys_prot_t pval) { 22 | (void) pval; 23 | 24 | mutex_exit(&lwip_mutex); 25 | } 26 | 27 | /* lwip needs a millisecond time source, and the TinyUSB board support code has one available */ 28 | uint32_t sys_now(void) { 29 | return to_ms_since_boot(get_absolute_time()); 30 | } 31 | -------------------------------------------------------------------------------- /src/rmii_ethernet.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Sandeep Mistry 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include 8 | 9 | #include "hardware/dma.h" 10 | 11 | #include "pico/stdlib.h" 12 | #include "pico/unique_id.h" 13 | 14 | #include "lwip/etharp.h" 15 | #include "lwip/netif.h" 16 | #include "lwip/timeouts.h" 17 | 18 | #include "rmii_ethernet_phy_rx.pio.h" 19 | #include "rmii_ethernet_phy_tx.pio.h" 20 | 21 | #include "rmii_ethernet/netif.h" 22 | 23 | #define PICO_RMII_ETHERNET_PIO (rmii_eth_netif_config.pio) 24 | #define PICO_RMII_ETHERNET_SM_RX (rmii_eth_netif_config.pio_sm_start) 25 | #define PICO_RMII_ETHERNET_SM_TX (rmii_eth_netif_config.pio_sm_start + 1) 26 | #define PICO_RMII_ETHERNET_RX_PIN (rmii_eth_netif_config.rx_pin_start) 27 | #define PICO_RMII_ETHERNET_TX_PIN (rmii_eth_netif_config.tx_pin_start) 28 | #define PICO_RMII_ETHERNET_MDIO_PIN (rmii_eth_netif_config.mdio_pin_start) 29 | #define PICO_RMII_ETHERNET_MDC_PIN (rmii_eth_netif_config.mdio_pin_start + 1) 30 | #define PICO_RMII_ETHERNET_MAC_ADDR (rmii_eth_netif_config.mac_addr) 31 | 32 | static struct netif *rmii_eth_netif; 33 | static struct netif_rmii_ethernet_config rmii_eth_netif_config = NETIF_RMII_ETHERNET_DEFAULT_CONFIG(); 34 | 35 | static uint rx_sm_offset; 36 | static uint tx_sm_offset; 37 | 38 | static int rx_dma_chan; 39 | static int tx_dma_chan; 40 | 41 | static dma_channel_config rx_dma_channel_config; 42 | static dma_channel_config tx_dma_channel_config; 43 | 44 | static int phy_address = 0; 45 | 46 | static uint8_t rx_frame[1518]; 47 | static uint8_t tx_frame[1518]; 48 | static uint8_t tx_frame_bits[1538 * 4]; 49 | 50 | static const uint32_t ethernet_polynomial_le = 0xedb88320U; 51 | 52 | static uint ethernet_frame_crc(const uint8_t *data, int length) 53 | { 54 | uint crc = 0xffffffff; /* Initial value. */ 55 | 56 | while(--length >= 0) 57 | { 58 | uint8_t current_octet = *data++; 59 | 60 | for (int bit = 8; --bit >= 0; current_octet >>= 1) { 61 | if ((crc ^ current_octet) & 1) { 62 | crc >>= 1; 63 | crc ^= ethernet_polynomial_le; 64 | } else 65 | crc >>= 1; 66 | } 67 | } 68 | 69 | return ~crc; 70 | } 71 | 72 | static uint ethernet_frame_length(const uint8_t *data, int length) { 73 | uint crc = 0xffffffff; /* Initial value. */ 74 | uint index = 0; 75 | 76 | while(--length >= 0) 77 | { 78 | uint8_t current_octet = *data++; 79 | 80 | for (int bit = 8; --bit >= 0; current_octet >>= 1) { 81 | if ((crc ^ current_octet) & 1) { 82 | crc >>= 1; 83 | crc ^= ethernet_polynomial_le; 84 | } else 85 | crc >>= 1; 86 | } 87 | 88 | index++; 89 | 90 | uint inverted_crc = ~crc; 91 | 92 | if (memcmp(data, &inverted_crc, sizeof(inverted_crc)) == 0) { 93 | return index; 94 | } 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | static void netif_rmii_ethernet_mdio_clock_out(int bit) 101 | { 102 | gpio_put(PICO_RMII_ETHERNET_MDC_PIN, 0); 103 | sleep_us(1); 104 | gpio_put(PICO_RMII_ETHERNET_MDIO_PIN, bit); 105 | gpio_put(PICO_RMII_ETHERNET_MDC_PIN, 1); 106 | sleep_us(1); 107 | } 108 | 109 | static uint netif_rmii_ethernet_mdio_clock_in() 110 | { 111 | gpio_put(PICO_RMII_ETHERNET_MDC_PIN, 0); 112 | sleep_us(1); 113 | gpio_put(PICO_RMII_ETHERNET_MDC_PIN, 1); 114 | 115 | int bit = gpio_get(PICO_RMII_ETHERNET_MDIO_PIN); 116 | sleep_us(1); 117 | 118 | return bit; 119 | } 120 | 121 | static uint16_t netif_rmii_ethernet_mdio_read(uint addr, uint reg) 122 | { 123 | gpio_init(PICO_RMII_ETHERNET_MDIO_PIN); 124 | gpio_init(PICO_RMII_ETHERNET_MDC_PIN); 125 | 126 | gpio_set_dir(PICO_RMII_ETHERNET_MDIO_PIN, GPIO_OUT); 127 | gpio_set_dir(PICO_RMII_ETHERNET_MDC_PIN, GPIO_OUT); 128 | 129 | // PRE_32 130 | for (int i = 0; i < 32; i++) { 131 | netif_rmii_ethernet_mdio_clock_out(1); 132 | } 133 | 134 | // ST 135 | netif_rmii_ethernet_mdio_clock_out(0); 136 | netif_rmii_ethernet_mdio_clock_out(1); 137 | 138 | // OP 139 | netif_rmii_ethernet_mdio_clock_out(1); 140 | netif_rmii_ethernet_mdio_clock_out(0); 141 | 142 | // PA5 143 | for (int i = 0; i < 5; i++) { 144 | uint bit = (addr >> (4 - i)) & 0x01; 145 | 146 | netif_rmii_ethernet_mdio_clock_out(bit); 147 | } 148 | 149 | // RA5 150 | for (int i = 0; i < 5; i++) { 151 | uint bit = (reg >> (4 - i)) & 0x01; 152 | 153 | netif_rmii_ethernet_mdio_clock_out(bit); 154 | } 155 | 156 | // TA 157 | gpio_set_dir(PICO_RMII_ETHERNET_MDIO_PIN, GPIO_IN); 158 | netif_rmii_ethernet_mdio_clock_out(0); 159 | netif_rmii_ethernet_mdio_clock_out(0); 160 | 161 | uint16_t data = 0; 162 | 163 | for (int i = 0; i < 16; i++) { 164 | data <<= 1; 165 | 166 | data |= netif_rmii_ethernet_mdio_clock_in(); 167 | } 168 | 169 | return data; 170 | } 171 | 172 | void netif_rmii_ethernet_mdio_write(int addr, int reg, int val) 173 | { 174 | gpio_init(PICO_RMII_ETHERNET_MDIO_PIN); 175 | gpio_init(PICO_RMII_ETHERNET_MDC_PIN); 176 | 177 | gpio_set_dir(PICO_RMII_ETHERNET_MDIO_PIN, GPIO_OUT); 178 | gpio_set_dir(PICO_RMII_ETHERNET_MDC_PIN, GPIO_OUT); 179 | 180 | // PRE_32 181 | for (int i = 0; i < 32; i++) { 182 | netif_rmii_ethernet_mdio_clock_out(1); 183 | } 184 | 185 | // ST 186 | netif_rmii_ethernet_mdio_clock_out(0); 187 | netif_rmii_ethernet_mdio_clock_out(1); 188 | 189 | // OP 190 | netif_rmii_ethernet_mdio_clock_out(0); 191 | netif_rmii_ethernet_mdio_clock_out(1); 192 | 193 | // PA5 194 | for (int i = 0; i < 5; i++) { 195 | uint bit = (addr >> (4 - i)) & 0x01; 196 | 197 | netif_rmii_ethernet_mdio_clock_out(bit); 198 | } 199 | 200 | // RA5 201 | for (int i = 0; i < 5; i++) { 202 | uint bit = (reg >> (4 - i)) & 0x01; 203 | 204 | netif_rmii_ethernet_mdio_clock_out(bit); 205 | } 206 | 207 | // TA 208 | netif_rmii_ethernet_mdio_clock_out(1); 209 | netif_rmii_ethernet_mdio_clock_out(0); 210 | 211 | for (int i = 0; i < 16; i++) { 212 | uint bit = (val >> (15 - i)) & 0x01; 213 | 214 | netif_rmii_ethernet_mdio_clock_out(bit); 215 | } 216 | 217 | gpio_set_dir(PICO_RMII_ETHERNET_MDIO_PIN, GPIO_IN); 218 | } 219 | 220 | static err_t netif_rmii_ethernet_output(struct netif *netif, struct pbuf *p) 221 | { 222 | uint tot_len = 0; 223 | 224 | memset(tx_frame, 0x00, sizeof(tx_frame)); 225 | 226 | for (struct pbuf *q = p; q != NULL; q = q->next) { 227 | memcpy(tx_frame + tot_len, q->payload, q->len); 228 | 229 | tot_len += q->len; 230 | 231 | if (q->len == q->tot_len) { 232 | break; 233 | } 234 | } 235 | 236 | if (tot_len < 60) { 237 | // pad 238 | tot_len = 60; 239 | } 240 | 241 | uint crc = ethernet_frame_crc(tx_frame, tot_len); 242 | 243 | // printf("TX: "); 244 | // for (int i = 0; i < tot_len; i++) { 245 | // printf("%02X", tx_frame[i]); 246 | // } 247 | // printf("\n"); 248 | 249 | dma_channel_wait_for_finish_blocking(tx_dma_chan); 250 | 251 | int index = 0; 252 | 253 | for (int i = 0; i < 31; i++) { 254 | tx_frame_bits[index++] = 0x05; 255 | } 256 | 257 | for (int i = 0; i < 1; i++) { 258 | tx_frame_bits[index++] = 0x07; 259 | } 260 | 261 | for (int i = 0; i < tot_len; i++) { 262 | uint8_t b = tx_frame[i]; 263 | 264 | tx_frame_bits[index++] = 0x04 | ((b >> 0) & 0x03); 265 | tx_frame_bits[index++] = 0x04 | ((b >> 2) & 0x03); 266 | tx_frame_bits[index++] = 0x04 | ((b >> 4) & 0x03); 267 | tx_frame_bits[index++] = 0x04 | ((b >> 6) & 0x03); 268 | } 269 | 270 | for (int i = 0; i < 4; i++) { 271 | uint8_t b = ((uint8_t*)&crc)[i]; 272 | 273 | tx_frame_bits[index++] = 0x04 | ((b >> 0) & 0x03); 274 | tx_frame_bits[index++] = 0x04 | ((b >> 2) & 0x03); 275 | tx_frame_bits[index++] = 0x04 | ((b >> 4) & 0x03); 276 | tx_frame_bits[index++] = 0x04 | ((b >> 6) & 0x03); 277 | } 278 | 279 | for (int i = 0; i < (12 * 4); i++) { 280 | tx_frame_bits[index++] = 0x00; 281 | } 282 | 283 | dma_channel_configure( 284 | tx_dma_chan, &tx_dma_channel_config, 285 | ((uint8_t*)&PICO_RMII_ETHERNET_PIO->txf[PICO_RMII_ETHERNET_SM_TX]) + 3, 286 | tx_frame_bits, 287 | index, 288 | false 289 | ); 290 | 291 | dma_channel_start(tx_dma_chan); 292 | 293 | return ERR_OK; 294 | } 295 | 296 | static void netif_rmii_ethernet_rx_dv_falling_callback(uint gpio, uint32_t events) { 297 | if ((PICO_RMII_ETHERNET_RX_PIN + 2) == gpio) { 298 | pio_sm_set_enabled(PICO_RMII_ETHERNET_PIO, PICO_RMII_ETHERNET_SM_RX, false); 299 | dma_channel_abort(rx_dma_chan); //dma_hw->abort = 1u << rx_dma_chan; 300 | gpio_set_irq_enabled_with_callback(PICO_RMII_ETHERNET_RX_PIN + 2, GPIO_IRQ_EDGE_FALL, false, netif_rmii_ethernet_rx_dv_falling_callback); 301 | } 302 | } 303 | 304 | static err_t netif_rmii_ethernet_low_init(struct netif *netif) { 305 | rmii_eth_netif = netif; 306 | 307 | netif->linkoutput = netif_rmii_ethernet_output; 308 | netif->output = etharp_output; 309 | netif->mtu = 1500; 310 | netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6; 311 | 312 | if (PICO_RMII_ETHERNET_MAC_ADDR != NULL) { 313 | memcpy(netif->hwaddr, PICO_RMII_ETHERNET_MAC_ADDR, 6); 314 | } else { 315 | // generate one for unique board id 316 | pico_unique_board_id_t board_id; 317 | 318 | pico_get_unique_board_id(&board_id); 319 | 320 | netif->hwaddr[0] = 0xb8; 321 | netif->hwaddr[1] = 0x27; 322 | netif->hwaddr[2] = 0xeb; 323 | memcpy(&netif->hwaddr[3], &board_id.id[5], 3); 324 | } 325 | 326 | 327 | netif->hwaddr_len = ETH_HWADDR_LEN; 328 | 329 | rx_sm_offset = pio_add_program(PICO_RMII_ETHERNET_PIO, &rmii_ethernet_phy_rx_data_program); 330 | tx_sm_offset = pio_add_program(PICO_RMII_ETHERNET_PIO, &rmii_ethernet_phy_tx_data_program); 331 | 332 | rx_dma_chan = dma_claim_unused_channel(true); 333 | tx_dma_chan = dma_claim_unused_channel(true); 334 | 335 | rx_dma_channel_config = dma_channel_get_default_config(rx_dma_chan); 336 | 337 | channel_config_set_read_increment(&rx_dma_channel_config, false); 338 | channel_config_set_write_increment(&rx_dma_channel_config, true); 339 | channel_config_set_dreq(&rx_dma_channel_config, pio_get_dreq(PICO_RMII_ETHERNET_PIO,PICO_RMII_ETHERNET_SM_RX, false)); 340 | channel_config_set_transfer_data_size(&rx_dma_channel_config, DMA_SIZE_8); 341 | 342 | tx_dma_channel_config = dma_channel_get_default_config(tx_dma_chan); 343 | 344 | channel_config_set_read_increment(&tx_dma_channel_config, true); 345 | channel_config_set_write_increment(&tx_dma_channel_config, false); 346 | channel_config_set_dreq(&tx_dma_channel_config, pio_get_dreq(PICO_RMII_ETHERNET_PIO, PICO_RMII_ETHERNET_SM_TX, true)); 347 | channel_config_set_transfer_data_size(&tx_dma_channel_config, DMA_SIZE_8); 348 | 349 | rmii_ethernet_phy_tx_init(PICO_RMII_ETHERNET_PIO, PICO_RMII_ETHERNET_SM_TX, tx_sm_offset, PICO_RMII_ETHERNET_TX_PIN); 350 | 351 | for (int i = 0; i < 32; i++) { 352 | if (netif_rmii_ethernet_mdio_read(i, 0) != 0xffff) { 353 | phy_address = i; 354 | 355 | break; 356 | } 357 | } 358 | 359 | // netif_rmii_ethernet_mdio_write(phy_address, 0, 0x2000); // 10 Mbps, auto negeotiate disabled 360 | 361 | netif_rmii_ethernet_mdio_write(phy_address, 4, 0x61); 362 | netif_rmii_ethernet_mdio_write(phy_address, 0, 0x1000); 363 | 364 | return ERR_OK; 365 | } 366 | 367 | err_t netif_rmii_ethernet_init(struct netif *netif, struct netif_rmii_ethernet_config *config) { 368 | if (config != NULL) { 369 | memcpy(&rmii_eth_netif_config, config, sizeof(rmii_eth_netif_config)); 370 | } 371 | 372 | netif_add(netif, IP4_ADDR_ANY, IP4_ADDR_ANY, IP4_ADDR_ANY, NULL, netif_rmii_ethernet_low_init, netif_input); 373 | 374 | netif->name[0] = 'e'; 375 | netif->name[1] = '0'; 376 | } 377 | 378 | void netif_rmii_ethernet_poll() { 379 | uint16_t link_status = (netif_rmii_ethernet_mdio_read(phy_address, 1) & 0x04) >> 2; 380 | 381 | if (netif_is_link_up(rmii_eth_netif) ^ link_status) { 382 | if (link_status) { 383 | // printf("netif_set_link_up\n"); 384 | netif_set_link_up(rmii_eth_netif); 385 | } else { 386 | // printf("netif_set_link_down\n"); 387 | netif_set_link_down(rmii_eth_netif); 388 | } 389 | } 390 | 391 | if (dma_channel_is_busy(rx_dma_chan)) { 392 | 393 | } else { 394 | uint rx_frame_length = ethernet_frame_length(rx_frame, sizeof(rx_frame)); 395 | 396 | if (rx_frame_length) { 397 | // printf("RX: "); 398 | // for (int i = 0; i < rx_frame_length + 4; i++) { 399 | // printf("%02X", rx_frame[i]); 400 | // } 401 | // printf("\n"); 402 | 403 | struct pbuf* p = pbuf_alloc(PBUF_RAW, rx_frame_length, PBUF_POOL); 404 | 405 | pbuf_take(p, rx_frame, rx_frame_length); 406 | 407 | if (rmii_eth_netif->input(p, rmii_eth_netif) != ERR_OK) { 408 | pbuf_free(p); 409 | } 410 | } 411 | 412 | memset(rx_frame, 0x00, sizeof(rx_frame)); 413 | 414 | dma_channel_configure( 415 | rx_dma_chan, &rx_dma_channel_config, 416 | rx_frame, 417 | ((uint8_t*)&PICO_RMII_ETHERNET_PIO->rxf[PICO_RMII_ETHERNET_SM_RX]) + 3, 418 | 1500, 419 | false 420 | ); 421 | 422 | dma_channel_start(rx_dma_chan); 423 | 424 | rmii_ethernet_phy_rx_init(PICO_RMII_ETHERNET_PIO, PICO_RMII_ETHERNET_SM_RX, rx_sm_offset, PICO_RMII_ETHERNET_RX_PIN); 425 | 426 | gpio_set_irq_enabled_with_callback(PICO_RMII_ETHERNET_RX_PIN + 2, GPIO_IRQ_EDGE_FALL, true, &netif_rmii_ethernet_rx_dv_falling_callback); 427 | } 428 | 429 | sys_check_timeouts(); 430 | } 431 | 432 | void netif_rmii_ethernet_loop() { 433 | while (1) { 434 | netif_rmii_ethernet_poll(); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /src/rmii_ethernet_phy_rx.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Sandeep Mistry 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | .program rmii_ethernet_phy_rx_data 8 | wait 0 pin 2 9 | wait 0 pin 0 10 | wait 0 pin 1 11 | wait 1 pin 2 12 | wait 1 pin 0 13 | wait 1 pin 1 14 | .wrap_target 15 | in pins, 2 16 | .wrap 17 | 18 | % c-sdk { 19 | 20 | static inline void rmii_ethernet_phy_rx_init(PIO pio, uint sm, uint offset, uint pin) { 21 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 3, false); 22 | 23 | pio_sm_config c = rmii_ethernet_phy_rx_data_program_get_default_config(offset); 24 | sm_config_set_in_pins(&c, pin); 25 | 26 | pio_gpio_init(pio, pin); 27 | pio_gpio_init(pio, pin + 1); 28 | pio_gpio_init(pio, pin + 2); 29 | 30 | sm_config_set_in_shift(&c, true, true, 8); 31 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); 32 | 33 | sm_config_set_clkdiv(&c, 10); 34 | 35 | pio_sm_init(pio, sm, offset, &c); 36 | pio_sm_set_enabled(pio, sm, true); 37 | } 38 | %} 39 | -------------------------------------------------------------------------------- /src/rmii_ethernet_phy_tx.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Sandeep Mistry 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | .program rmii_ethernet_phy_tx_data 8 | .wrap_target 9 | out pins, 3 10 | .wrap 11 | 12 | % c-sdk { 13 | 14 | static inline void rmii_ethernet_phy_tx_init(PIO pio, uint sm, uint offset, uint pin) { 15 | pio_gpio_init(pio, pin); 16 | pio_gpio_init(pio, pin + 1); 17 | pio_gpio_init(pio, pin + 2); 18 | 19 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 3, true); 20 | 21 | pio_sm_config c = rmii_ethernet_phy_tx_data_program_get_default_config(offset); 22 | sm_config_set_out_pins(&c, pin, 3); 23 | 24 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 25 | sm_config_set_out_shift(&c, true, true, 3); 26 | 27 | sm_config_set_clkdiv(&c, 10); 28 | 29 | pio_sm_init(pio, sm, offset, &c); 30 | pio_sm_set_enabled(pio, sm, true); 31 | } 32 | %} 33 | --------------------------------------------------------------------------------