diff --git a/rp2-pio/examples/rmii/main.go b/rp2-pio/examples/rmii/main.go new file mode 100644 index 0000000..bdbd601 --- /dev/null +++ b/rp2-pio/examples/rmii/main.go @@ -0,0 +1,279 @@ +package main + +import ( + "errors" + "machine" + "strconv" + "time" + "unsafe" + + pio "github.com/tinygo-org/pio/rp2-pio" + "github.com/tinygo-org/pio/rp2-pio/piolib" +) + +// Pin configuration matching reference implementation +// Reference: https://github.com/sandeepmistry/pico-rmii-ethernet/blob/main/examples/httpd/main.c +const ( + // TX pins: GPIO 0, 1, 2 (TXD0, TXD1, TX_EN) + pinTxBase = machine.GPIO0 + + // RX pins: GPIO 3, 4, 5 (RXD0, RXD1, CRS_DV) + pinRxBase = machine.GPIO3 + pinCRSDV = machine.GPIO5 + + // MDIO pins: + pinMDC = machine.GPIO6 + pinMDIO = machine.GPIO7 + + // Reference clock: (50MHz from PHY) + pinRefClk = machine.GPIO6 +) + +// Network configuration +var ( + // MAC address (locally administered) + macAddr = [6]byte{0x02, 0x00, 0x00, 0x12, 0x34, 0x56} + + // Static IP configuration (before DHCP) + ipAddr = [4]byte{192, 168, 1, 100} + netmask = [4]byte{255, 255, 255, 0} + gateway = [4]byte{192, 168, 1, 1} +) + +func main() { + cfg := piolib.RMIIConfig{ + TxRx: piolib.RMIITxRxConfig{ + TxPin: pinTxBase, + RxPin: pinRxBase, + CRSDVPin: pinCRSDV, + RefClkPin: pinRefClk, + }, + NoZMDIO: false, + MDIO: pinMDIO, + MDC: pinMDC, + RxBufferSize: 2048, + TxBufferSize: 2048, + } + pinMDC.Configure(machine.PinConfig{Mode: machine.PinOutput}) + pinMDC.Low() + // Sleep to allow serial monitor to connect + time.Sleep(2 * time.Second) + println("=== LAN 8720 RMII ===") + device, err := NewLAN8270(pio.PIO0, cfg) + if err != nil { + panic(err) + } + // Init Loop: + for { + err = device.Init() + if err == nil { + break + } + println("init failed:", err.Error()) + println("retrying soon...") + time.Sleep(6 * time.Second) + } + status, err := device.Status() + if err != nil { + panic("status: " + err.Error()) + } + ctl, _ := device.BasicControl() + println("status", formatHex16(uint16(status)), "islinked", status.IsLinked()) + println("regctl", formatHex16(uint16(ctl)), "isenabled", ctl.IsEnabled()) + println("PHY ID1:", device.id1, "ID2:", device.id2) + +} + +// initRMII initializes the RMII interface with PIO and DMA +// Reference: netif_rmii_ethernet_low_init() from rmii_ethernet.c +func initRMII(Pio *pio.PIO) (*piolib.RMII, error) { + smTx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + smRx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + // Configure RMII + cfg := piolib.RMIIConfig{ + TxRx: piolib.RMIITxRxConfig{ + TxPin: pinTxBase, + RxPin: pinRxBase, + CRSDVPin: pinCRSDV, + RefClkPin: pinRefClk, + }, + NoZMDIO: false, + MDIO: pinMDIO, + MDC: pinMDC, + RxBufferSize: 2048, + TxBufferSize: 2048, + } + rmii, err := piolib.NewRMII(smTx, smRx, cfg) + if err != nil { + return nil, err + } + return rmii, nil +} + +const ( + regBasicControl = 0x00 + regBasicStatus = 0x01 + regPhyId1 = 0x02 + regPhyId2 = 0x03 + + regAutoNegotiationAdvertisement = 0x04 + regAutoNegotiationLinkPartnerAbility = 0x05 + regAutoNegotiationExpansion = 0x05 + regModeControlStatus = 0x11 + regSpecialModes = 0x12 + regSymbolErorCounter = 0x1a + regSpecialControlStatusIndications = 0x1b + regIRQSourceFlag = 0x1d + regIRQMask = 0x1e + regPhySpecialScontrolStatus = 0x1f +) + +type LAN8720 struct { + bus *piolib.RMII + smiaddr uint8 + id1, id2 uint16 +} + +func NewLAN8270(Pio *pio.PIO, cfg piolib.RMIIConfig) (*LAN8720, error) { + smTx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + smRx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + // Configure RMII + + rmii, err := piolib.NewRMII(smTx, smRx, cfg) + if err != nil { + return nil, err + } + return &LAN8720{bus: rmii}, nil +} + +type status uint16 +type control uint16 + +func (c *control) SetEnabled(b bool) { + *c &^= 1 << 15 + if b { + *c |= 1 << 15 + } +} +func (c control) IsEnabled() bool { + return c&(1<<15) != 0 +} + +func (s status) IsLinked() bool { + return s&(1<<2) != 0 +} + +func (lan *LAN8720) Status() (status, error) { + stat, err := lan.readReg(regBasicStatus) + return status(stat), err +} + +func (lan *LAN8720) BasicControl() (control, error) { + ct, err := lan.readReg(regBasicControl) + return control(ct), err +} + +func (lan *LAN8720) Init() error { + const maxAddr = 31 + lan.smiaddr = 255 + for addr := uint8(0); addr <= maxAddr; addr++ { + val, err := lan.bus.MDIORead(addr, regBasicStatus) + if err != nil { + continue + } + if val != 0xffff && val != 0x0000 { + lan.smiaddr = addr + break + } + time.Sleep(150 * time.Microsecond) + } + if lan.smiaddr > maxAddr { + return errors.New("no PHY found via addr scanning") + } + + ctl, err := lan.BasicControl() + if err != nil { + return errors.New("failed reading basic control: " + err.Error()) + } + ctl.SetEnabled(true) + err = lan.writeReg(regBasicControl, uint16(ctl)) + if err != nil { + return err + } + time.Sleep(50 * time.Millisecond) + ctl, err = lan.BasicControl() + + if err != nil { + return err + } else if ctl.IsEnabled() { + println("want ctl bit 16, got:", formatHex16(uint16(ctl))) + return errors.New("lan8720 reset failed") + } + lan.id1, err = lan.readReg(regPhyId1) + if err != nil { + return err + } + lan.id2, err = lan.readReg(regPhyId2) + if err != nil { + return err + } + return nil +} + +func (lan *LAN8720) readReg(reg uint8) (uint16, error) { + return lan.bus.MDIORead(lan.smiaddr, reg) +} + +func (lan *LAN8720) writeReg(reg uint8, value uint16) error { + return lan.bus.MDIOWrite(lan.smiaddr, reg, value) +} + +// Utility functions for formatting + +func formatHex16(val uint16) string { + fwd := []byte{'0', 'x'} + fwd = appendHex(fwd, byte(val>>8)) + fwd = appendHex(fwd, byte(val)) + return unsafe.String(&fwd[0], len(fwd)) +} + +func appendHexSep(dst, mac []byte, sep byte) []byte { + for i := range mac { + dst = appendHex(dst, mac[i]) + if sep != 0 && i != len(mac)-1 { + dst = append(dst, sep) + } + } + return dst +} + +func appendHex(dst []byte, b byte) []byte { + const hexChars = "0123456789abcdef" + return append(dst, hexChars[b>>4], hexChars[b&0xf]) +} + +func appendDec(dst []byte, b byte) []byte { + return strconv.AppendInt(dst, int64(b), 10) +} + +func appendDecSep(dst []byte, data []byte, sep byte) []byte { + for i := range data { + dst = appendDec(dst, data[i]) + if sep != 0 && i != len(data)-1 { + dst = append(dst, sep) + } + } + return dst +} diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go new file mode 100644 index 0000000..6301918 --- /dev/null +++ b/rp2-pio/piolib/rmii.go @@ -0,0 +1,435 @@ +//go:build rp2040 || rp2350 + +package piolib + +import ( + "errors" + "machine" + "time" + + pio "github.com/tinygo-org/pio/rp2-pio" +) + +// RMII provides a complete Reduced Media Independent Interface implementation +// with MDIO/MDC management interface for PHY register access. +// Inspired by Sandeep Mistry's implementation at https://github.com/sandeepmistry/pico-rmii-ethernet +type RMII struct { + rxtx RMIITxRx + zmdio bool + mdio machine.Pin + mdc machine.Pin + phyAddr uint8 + rxDVPin machine.Pin + rxBuffer []byte + txBuffer []byte +} + +// RMIIConfig configures the complete RMII interface including MDIO/MDC pins. +type RMIIConfig struct { + // TxRx contains the configuration for the PIO-based TX/RX interface + TxRx RMIITxRxConfig + // MDIO is the Management Data Input/Output pin for PHY register access + MDIO machine.Pin + // MDC is the Management Data Clock pin + MDC machine.Pin + // RxBufferSize is the size of the receive buffer (default 2048 if 0) + RxBufferSize int + // TxBufferSize is the size of the transmit buffer (default 2048 if 0) + TxBufferSize int + // NoZMDIO avoids using high impedance Z level for HIGH pin state on MDIO as stated by RMII specification. + NoZMDIO bool +} + +// NewRMII creates a new complete RMII interface with MDIO/MDC management. +func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { + // Create the low-level TX/RX interface + rxtx, err := NewRMIITxRx(smTx, smRx, cfg.TxRx) + if err != nil { + return nil, err + } + + // Set default buffer sizes + rxBufSize := cfg.RxBufferSize + if rxBufSize == 0 { + rxBufSize = 2048 + } + txBufSize := cfg.TxBufferSize + if txBufSize == 0 { + txBufSize = 2048 + } + + rmii := &RMII{ + rxtx: *rxtx, + mdio: cfg.MDIO, + mdc: cfg.MDC, + rxDVPin: cfg.TxRx.CRSDVPin, + rxBuffer: make([]byte, rxBufSize), + txBuffer: make([]byte, txBufSize), + zmdio: !cfg.NoZMDIO, + } + + // Configure MDIO/MDC pins + // rmii.mdCfg() + return rmii, nil +} + +// // DiscoverPHY scans MDIO addresses 0-31 to find a connected PHY. +// // Returns the PHY address or an error if no PHY is found. +// func (r *RMII) DiscoverPHY() error { +// for addr := uint8(0); addr < 32; addr++ { +// val, err := r.MDIORead(addr, 0) +// if err != nil { +// continue +// } +// if val != 0xffff && val != 0x0000 { +// r.phyAddr = addr +// return nil +// } +// } +// return errors.New("no PHY found on MDIO bus") +// } + +// InitPHY initializes the PHY with auto-negotiation settings. +// Must be called after DiscoverPHY(). +// Reference: netif_rmii_ethernet_low_init() from rmii_ethernet.c +func (r *RMII) InitPHY() error { + // Write to register 4 (Advertisement): 0x61 + // Configure advertised capabilities (10/100 Mbps support) + if err := r.MDIOWrite(r.phyAddr, 4, 0x61); err != nil { + return err + } + + // Write to register 0 (Control): 0x1000 + // Enable auto-negotiation (bit 12) + if err := r.MDIOWrite(r.phyAddr, 0, 0x1000); err != nil { + return err + } + + return nil +} + +// PHYAddr returns the discovered PHY address. Set after [RMII.DiscoverPHY] success. +func (r *RMII) PHYAddr() uint8 { + return r.phyAddr +} + +// MDIO low-level clock operations +// Reference: netif_rmii_ethernet_mdio_clock_out() and netif_rmii_ethernet_mdio_clock_in() +// from rmii_ethernet.c + +// mdioClockOut outputs a bit on MDIO while pulsing MDC clock. +func (r *RMII) mdioClockOut(bit bool) { + r.mdioSet(bit) + time.Sleep(time.Microsecond) + r.mdc.High() + time.Sleep(time.Microsecond) + r.mdc.Low() + time.Sleep(time.Microsecond) +} + +func (r *RMII) mdioSet(b bool) { + if r.zmdio { + if b { + r.mdioZHigh() + } else { + r.mdioLow() + } + } else { + r.mdio.Set(b) + } +} + +func (r *RMII) mdioZHigh() { + // RMII z pin level means high impedance, pull up resistor. + r.mdio.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) +} + +func (r *RMII) mdioLow() { + // RMII 0 pin level sets as output + r.mdio.Low() + r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) +} + +// mdioClockIn reads a bit from MDIO while pulsing MDC clock. +func (r *RMII) mdioClockIn() bool { + r.mdc.High() + time.Sleep(time.Microsecond) + bit := r.mdio.Get() + time.Sleep(time.Microsecond) + r.mdc.Low() + time.Sleep(time.Microsecond) + return bit +} + +func (r *RMII) mdCfg() { + r.mdc.Low() + r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) + r.mdc.Configure(machine.PinConfig{Mode: machine.PinOutput}) +} + +// MDIORead reads a 16-bit register from the PHY via MDIO. +// Implements IEEE 802.3 MDIO frame format for read operation. +// Reference: netif_rmii_ethernet_mdio_read() from rmii_ethernet.c +func (r *RMII) MDIORead(phyAddr uint8, regAddr uint8) (uint16, error) { + if phyAddr > 31 || regAddr > 31 { + return 0, errors.New("MDIO address out of range") + } + r.mdCfg() + + // Preamble: 32 bits of '1' + for i := 0; i < 32; i++ { + r.mdioClockOut(true) + } + + // Start of frame: 01 + r.mdioClockOut(false) + r.mdioClockOut(true) + + // Opcode: 10 (read) + r.mdioClockOut(true) + r.mdioClockOut(false) + + // PHY address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((phyAddr>>uint(i))&0x01 != 0) + } + + // Register address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((regAddr>>uint(i))&0x01 != 0) + } + + // Turnaround: switch MDIO to input, read 2 bits (should be Z0) + r.mdio.Configure(machine.PinConfig{Mode: machine.PinInput}) + r.mdioClockOut(false) // Z bit + r.mdioClockOut(false) // 0 bit + + // Read 16 data bits MSB first + var data uint16 + for i := 15; i >= 0; i-- { + data <<= 1 + data |= uint16(b2u8(r.mdioClockIn())) + } + + return data, nil +} + +// MDIOWrite writes a 16-bit value to a PHY register via MDIO. +// Implements IEEE 802.3 MDIO frame format for write operation. +// Reference: netif_rmii_ethernet_mdio_write() from rmii_ethernet.c +func (r *RMII) MDIOWrite(phyAddr uint8, regAddr uint8, value uint16) error { + if phyAddr > 31 || regAddr > 31 { + return errors.New("MDIO address out of range") + } + r.mdCfg() + + // Preamble: 32 bits of '1' + for i := 0; i < 32; i++ { + r.mdioClockOut(true) + } + + // Start of frame: 01 + r.mdioClockOut(false) + r.mdioClockOut(true) + + // Opcode: 01 (write) + r.mdioClockOut(false) + r.mdioClockOut(true) + + // PHY address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((phyAddr>>uint(i))&0x01 != 0) + } + + // Register address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((regAddr>>uint(i))&0x01 != 0) + } + + // Turnaround: 10 + r.mdioClockOut(true) + r.mdioClockOut(false) + + // Write 16 data bits MSB first + for i := 15; i >= 0; i-- { + r.mdioClockOut((value>>uint(i))&0x01 != 0) + } + + // Release MDIO bus to high impedance + r.mdio.Configure(machine.PinConfig{Mode: machine.PinInput}) + + return nil +} + +// CRC32 computes the Ethernet CRC32 for the given data. +// Uses the polynomial 0xedb88320 (reversed representation). +// Reference: netif_rmii_ethernet_crc() from rmii_ethernet.c +func (r *RMII) CRC32(data []byte) uint32 { + const polynomial = 0xedb88320 + crc := uint32(0xffffffff) + for _, b := range data { + crc ^= uint32(b) + for bit := 0; bit < 8; bit++ { + if crc&1 != 0 { + crc = (crc >> 1) ^ polynomial + } else { + crc = crc >> 1 + } + } + } + return ^crc +} + +// Pass-through methods to underlying rxtx + +// SetEnabled enables or disables both TX and RX state machines. +func (r *RMII) SetEnabled(enabled bool) { + r.rxtx.SetEnabled(enabled) +} + +func (r *RMII) EnableDMA(enabled bool) error { + err := r.rxtx.EnableRxDMA(enabled) + if err != nil { + return err + } + err = r.rxtx.EnableTxDMA(enabled) + return err +} + +// SetTimeout sets the read/write timeout for both TX and RX operations. +func (r *RMII) SetTimeout(timeout time.Duration) { + r.rxtx.SetTimeout(timeout) +} + +// RxTx returns a reference to the underlying RMIItxrx for low-level access. +func (r *RMII) RxTx() *RMIITxRx { + return &r.rxtx +} + +// Frame transmission and reception +// Reference: netif_rmii_ethernet_output() from rmii_ethernet.c + +// TxFrame transmits an Ethernet frame with preamble, SFD, data, and CRC. +// The data should be the complete Ethernet frame (destination MAC, source MAC, type, payload). +// Minimum frame size is 60 bytes (excluding preamble/SFD/CRC). +func (r *RMII) TxFrame(frame []byte) error { + if len(frame) < 60 { + return errors.New("frame too small (minimum 60 bytes)") + } + if len(frame) > 1518 { + return errors.New("frame too large (maximum 1518 bytes)") + } + + // Compute CRC32 + crc := r.CRC32(frame) + + // Encode frame: preamble + SFD + data + CRC + IPG + // Each byte is encoded as 4 nibbles with TX_EN asserted + const preambleNibbles = 31 + const sfdNibbles = 1 + dataAndCrcLen := len(frame) + 4 // frame + 4 byte CRC + const ipgNibbles = 12 * 4 // 12 bytes * 4 nibbles per byte = 48 + + totalNibbles := preambleNibbles + sfdNibbles + (dataAndCrcLen * 4) + ipgNibbles + + // Ensure we don't overflow the tx buffer + if totalNibbles > len(r.txBuffer) { + return errors.New("frame too large for TX buffer") + } + + idx := 0 + + // Preamble: 31 × 0x05 (alternating 01 pattern with TX_EN) + for i := 0; i < preambleNibbles; i++ { + r.txBuffer[idx] = 0x05 + idx++ + } + + // SFD: 1 × 0x07 (10101011 start frame delimiter) + r.txBuffer[idx] = 0x07 + idx++ + + // Encode frame data: each byte as 4 nibbles with 0x04 prefix (TX_EN bit) + for _, b := range frame { + r.txBuffer[idx] = 0x04 | ((b >> 0) & 0x03) // bits [1:0] + idx++ + r.txBuffer[idx] = 0x04 | ((b >> 2) & 0x03) // bits [3:2] + idx++ + r.txBuffer[idx] = 0x04 | ((b >> 4) & 0x03) // bits [5:4] + idx++ + r.txBuffer[idx] = 0x04 | ((b >> 6) & 0x03) // bits [7:6] + idx++ + } + + // Encode CRC: 4 bytes as nibbles + for i := 0; i < 4; i++ { + crcByte := byte(crc >> uint(i*8)) + r.txBuffer[idx] = 0x04 | ((crcByte >> 0) & 0x03) + idx++ + r.txBuffer[idx] = 0x04 | ((crcByte >> 2) & 0x03) + idx++ + r.txBuffer[idx] = 0x04 | ((crcByte >> 4) & 0x03) + idx++ + r.txBuffer[idx] = 0x04 | ((crcByte >> 6) & 0x03) + idx++ + } + + // Inter-packet gap: 12 × 0x00 (idle, TX_EN low) + for i := 0; i < ipgNibbles; i++ { + r.txBuffer[idx] = 0x00 + idx++ + } + + // Transmit via the underlying rxtx + return r.rxtx.Tx8(r.txBuffer[:idx]) +} + +// EnableRxInterrupt enables GPIO interrupt on RX_DV falling edge for frame detection. +// The callback will be invoked when a frame reception completes (RX_DV goes low). +// Reference: netif_rmii_ethernet_rx_dv_falling_callback() from rmii_ethernet.c +func (r *RMII) EnableRxInterrupt(callback func(machine.Pin)) error { + if callback == nil { + return errors.New("callback cannot be nil") + } + return r.rxDVPin.SetInterrupt(machine.PinFalling, callback) +} + +// DisableRxInterrupt disables the RX_DV falling edge interrupt. +func (r *RMII) DisableRxInterrupt() error { + return r.rxDVPin.SetInterrupt(machine.PinFalling, nil) +} + +// OnRxComplete is called when RX_DV falling edge is detected. +// This stops the RX state machine and aborts DMA, signaling frame completion. +// Users should call this from their interrupt handler. +// Reference: netif_rmii_ethernet_rx_dv_falling_callback() from rmii_ethernet.c +func (r *RMII) OnRxComplete() { + r.rxtx.smRx.SetEnabled(false) + // Note: DMA abort is internal to dmaChannel, happens automatically when disabled +} + +// StartRxDMA starts continuous DMA reception into the internal buffer. +// This should be combined with interrupt handling on RX_DV for frame detection. +// Call EnableRxInterrupt() with a callback that invokes OnRxComplete(). +func (r *RMII) StartRxDMA() error { + r.rxtx.smRx.SetEnabled(true) + return r.rxtx.Rx8(r.rxBuffer) +} + +// RxBuffer returns a reference to the internal RX buffer for direct access. +// Useful for interrupt-driven reception where you need to inspect the buffer. +func (r *RMII) RxBuffer() []byte { + return r.rxBuffer +} + +// TxBuffer returns a reference to the internal TX buffer for direct access. +func (r *RMII) TxBuffer() []byte { + return r.txBuffer +} + +func b2u8(b bool) uint8 { + if b { + return 1 + } + return 0 +} diff --git a/rp2-pio/piolib/rmiitxrx.go b/rp2-pio/piolib/rmiitxrx.go new file mode 100644 index 0000000..f8ae5ca --- /dev/null +++ b/rp2-pio/piolib/rmiitxrx.go @@ -0,0 +1,296 @@ +//go:build rp2040 || rp2350 + +package piolib + +import ( + "errors" + "machine" + "time" + "unsafe" + + pio "github.com/tinygo-org/pio/rp2-pio" +) + +// RMIITxRx is the Reduced Media Independent Interface for 100Mbps Ethernet PHY communication. +// It uses two state machines: one for TX and one for RX. +// Inspired by Sandeep Mistry's implementation at https://github.com/sandeepmistry/pico-rmii-ethernet/tree/main/src +type RMIITxRx struct { + smTx pio.StateMachine + smRx pio.StateMachine + programOffTx uint8 + programOffRx uint8 + dmaTx dmaChannel + dmaRx dmaChannel +} + +// RMIITxRxConfig configures the RMII interface pins and parameters. +type RMIITxRxConfig struct { + Baud uint32 + // TxPin is the base pin for RMII TX (TXD0, TXD1, TX_EN). + // Requires 3 consecutive pins. + TxPin machine.Pin + // RxPin is the base pin for RMII RX (RXD0, RXD1). + // Requires 2 consecutive pins. + RxPin machine.Pin + // CRSDVPin is the Carrier Sense/Data Valid pin (also called RX_DV). + CRSDVPin machine.Pin + // RefClkPin is the 50MHz reference clock input from PHY. + RefClkPin machine.Pin +} + +// NewRMIITxRx creates a new RMII interface using two state machines (TX and RX). +// The TX and RX state machines should be from the same PIO block. +func NewRMIITxRx(smTx, smRx pio.StateMachine, cfg RMIITxRxConfig) (*RMIITxRx, error) { + if smTx.PIO().BlockIndex() != smRx.PIO().BlockIndex() { + return nil, errors.New("TX and RX state machines must be from the same PIO block") + } + + // Claim state machines + smTx.TryClaim() + smRx.TryClaim() + + Pio := smTx.PIO() + var asm pio.AssemblerV0 + + // RX Program: Wait for sync pattern, then continuously read 2 bits + // .program rmii_ethernet_phy_rx_data + // wait 0 pin 2 ; Wait for CRSDV low + // wait 0 pin 0 ; Wait for RXD0 low + // wait 0 pin 1 ; Wait for RXD1 low + // wait 1 pin 2 ; Wait for CRSDV high + // wait 1 pin 0 ; Wait for RXD0 high + // wait 1 pin 1 ; Wait for RXD1 high + // .wrap_target + // in pins, 2 ; Continuously read 2 bits + // .wrap + // if program changes wrap target must be changed manually. + // Worry not, will fail to compile if wrap target is invalid. + const rxWrapTarget = 6 + rxProgram := [7]uint16{ + asm.WaitPin(false, 2).Encode(), // wait 0 pin 2 (CRSDV) + asm.WaitPin(false, 0).Encode(), // wait 0 pin 0 (RXD0) + asm.WaitPin(false, 1).Encode(), // wait 0 pin 1 (RXD1) + asm.WaitPin(true, 2).Encode(), // wait 1 pin 2 (CRSDV) + asm.WaitPin(true, 0).Encode(), // wait 1 pin 0 (RXD0) + asm.WaitPin(true, 1).Encode(), // wait 1 pin 1 (RXD1) + rxWrapTarget:// .wrap_target Once all pins have waited we receive all data in loop. + asm.In(pio.InSrcPins, 2).Encode(), // in pins, 2 + } + + // Add RX program first (longer, more likely to fail if PIO memory full) + rxOffset, err := Pio.AddProgram(rxProgram[:], -1) + if err != nil { + return nil, err + } + + // TX Program: Simple output of 3 pins (TXD0, TXD1, TX_EN) + // .program rmii_ethernet_phy_tx_data + // .wrap_target + // out pins, 3 + // .wrap + txProgram := [1]uint16{ + asm.Out(pio.OutDestPins, 3).Encode(), // out pins, 3 + } + + txOffset, err := Pio.AddProgram(txProgram[:], -1) + if err != nil { + Pio.ClearProgramSection(rxOffset, uint8(len(rxProgram))) + return nil, err + } + + // Configure RX state machine + rxcfg := pio.DefaultStateMachineConfig() + rxcfg.SetWrap(rxOffset+rxWrapTarget, rxOffset+uint8(len(rxProgram))-1) + rxcfg.SetInPins(cfg.RxPin, 2) // RXD0, RXD1 + // Shift left, autopush enabled, push threshold 32 bits + rxcfg.SetInShift(false, true, 32) + rxcfg.SetClkDivIntFrac(10, 0) + rxcfg.SetFIFOJoin(pio.FifoJoinRx) + + // Configure TX state machine + txcfg := pio.DefaultStateMachineConfig() + txcfg.SetWrap(txOffset, txOffset+uint8(len(txProgram))-1) + txcfg.SetOutPins(cfg.TxPin, 3) + // Shift right, autopull enabled, pull threshold 32 bits + txcfg.SetOutShift(true, true, 32) + // Clock divider: run at 50MHz / 4 = 12.5MHz for RMII (2 bits per clock at 50MHz = 4 clocks per 2-bit dibit) + // Reference implementation uses divider of 10 to get effective rate + txcfg.SetClkDivIntFrac(10, 0) + txcfg.SetFIFOJoin(pio.FifoJoinTx) + + // Configure pins + pinCfg := machine.PinConfig{Mode: Pio.PinMode()} + + // Configure TX pins (TXD0, TXD1, TX_EN) + for i := 0; i < 3; i++ { + pin := cfg.TxPin + machine.Pin(i) + pin.Configure(pinCfg) + } + + // Configure RX pins (RXD0, RXD1, CRSDV) + cfg.RxPin.Configure(pinCfg) + (cfg.RxPin + 1).Configure(pinCfg) + cfg.CRSDVPin.Configure(pinCfg) + + // RefClk is input from PHY, configure as input + cfg.RefClkPin.Configure(machine.PinConfig{Mode: machine.PinInput}) + + // Set TX pins as output, initially low + txPinMask := uint32(0b111 << cfg.TxPin) + smTx.SetPindirsMasked(txPinMask, txPinMask) + smTx.SetPinsMasked(0, txPinMask) + + // Set RX pins as input + rxPinMask := uint32(0b11<