diff --git a/src/machine/board_feather_rp2040.go b/src/machine/board_feather_rp2040.go index 4be1b493..58816f05 100644 --- a/src/machine/board_feather_rp2040.go +++ b/src/machine/board_feather_rp2040.go @@ -9,6 +9,15 @@ const ( xoscFreq = 12 // MHz ) +// I2C Pins. +const ( + I2C0_SDA_PIN = GPIO24 + I2C0_SCL_PIN = GPIO25 + + I2C1_SDA_PIN = GPIO2 + I2C1_SCL_PIN = GPIO3 +) + // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications diff --git a/src/machine/board_nano-rp2040.go b/src/machine/board_nano-rp2040.go index 3db965a7..1b799d70 100644 --- a/src/machine/board_nano-rp2040.go +++ b/src/machine/board_nano-rp2040.go @@ -49,8 +49,11 @@ const ( // I2C pins const ( - SDA_PIN Pin = GPIO12 - SCL_PIN Pin = GPIO13 + I2C0_SDA_PIN Pin = GPIO12 + I2C0_SCL_PIN Pin = GPIO13 + + I2C1_SDA_PIN Pin = GPIO18 + I2C1_SCL_PIN Pin = GPIO19 ) // SPI pins. SPI1 not available on Nano RP2040 Connect. diff --git a/src/machine/board_pico.go b/src/machine/board_pico.go index 961c923c..2cbba390 100644 --- a/src/machine/board_pico.go +++ b/src/machine/board_pico.go @@ -38,6 +38,15 @@ const ( xoscFreq = 12 // MHz ) +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 60204f48..5e66c36e 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -// +build atmega nrf sam stm32 fe310 k210 +// +build atmega nrf sam stm32 fe310 k210 rp2040 package machine diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go index e5394685..5e3d06cc 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2040_gpio.go @@ -47,14 +47,27 @@ type pinFunc uint8 // GPIO function selectors const ( fnJTAG pinFunc = 0 - fnSPI pinFunc = 1 + fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO fnUART pinFunc = 2 fnI2C pinFunc = 3 - fnPWM pinFunc = 4 - fnSIO pinFunc = 5 - fnPIO0 pinFunc = 6 - fnPIO1 pinFunc = 7 + // Connect a PWM slice to GPIO. There are eight PWM slices, + // each with two outputchannels (A/B). The B pin can also be used as an input, + // for frequency and duty cyclemeasurement + fnPWM pinFunc = 4 + // Software control of GPIO, from the single-cycle IO (SIO) block. + // The SIO function (F5)must be selected for the processors to drive a GPIO, + // but the input is always connected,so software can check the state of GPIOs at any time. + fnSIO pinFunc = 5 + // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, + // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. + // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, + // so the PIOs canalways see the state of all pins. + fnPIO0, fnPIO1 pinFunc = 6, 7 + // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, + // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. + // e.g. Output: optional integer divide fnGPCK pinFunc = 8 + // USB power control signals to/from the internal USB controller fnUSB pinFunc = 9 fnNULL pinFunc = 0x1f @@ -68,6 +81,7 @@ const ( PinInputPullup PinAnalog PinUART + PinI2C PinSPI ) @@ -91,7 +105,7 @@ func (p Pin) xor() { // get returns the pin value func (p Pin) get() bool { - return rp.SIO.GPIO_IN.HasBits(uint32(1) << p) + return rp.SIO.GPIO_IN.HasBits(1 << p) } func (p Pin) ioCtrl() *volatile.Register32 { @@ -117,6 +131,17 @@ func (p Pin) pulloff() { p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) } +// setSlew sets pad slew rate control. +// true sets to fast. false sets to slow. +func (p Pin) setSlew(sr bool) { + p.padCtrl().ReplaceBits(boolToBit(sr)< 0 { + if err := i2c.tx(uint8(addr), w, false, timeout); nil != err { + return err + } + } + + if len(r) > 0 { + if err := i2c.rx(uint8(addr), r, false, timeout); nil != err { + return err + } + } + + return nil +} + +// Configure initializes i2c peripheral and configures I2C config's pins passed. +// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040: +// SDA: 0, 4, 8, 12, 16, 20 +// SCL: 1, 5, 9, 13, 17, 21 +// Same as above for I2C1 bus: +// SDA: 2, 6, 10, 14, 18, 26 +// SCL: 3, 7, 11, 15, 19, 27 +func (i2c *I2C) Configure(config I2CConfig) error { + if config.SCL == 0 { + // If config pins are zero valued or clock pin is invalid then we set default values. + switch i2c.Bus { + case rp.I2C0: + config.SCL = I2C0_SCL_PIN + config.SDA = I2C0_SDA_PIN + case rp.I2C1: + config.SCL = I2C1_SCL_PIN + config.SDA = I2C1_SDA_PIN + } + } + config.SDA.Configure(PinConfig{PinI2C}) + config.SCL.Configure(PinConfig{PinI2C}) + return i2c.init(config) +} + +// SetBaudrate sets the I2C frequency. It has the side effect of also +// enabling the I2C hardware if disabled beforehand. +//go:inline +func (i2c *I2C) SetBaudrate(br uint32) error { + const freqin uint32 = 125 * MHz + // Find smallest prescale value which puts o + + // TODO there are some subtleties to I2C timing which we are completely ignoring here + period := (freqin + br/2) / br + lcnt := period * 3 / 5 // oof this one hurts + hcnt := period - lcnt + // Check for out-of-range divisors: + if hcnt > rp.I2C0_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_Msk || hcnt < 8 || lcnt > rp.I2C0_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_Msk || lcnt < 8 { + return ErrInvalidI2CBaudrate + } + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + sdaTxHoldCnt := ((freqin * 3) / 10000000) + 1 + if br >= 1_000_000 { + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + sdaTxHoldCnt = ((freqin * 3) / 25000000) + 1 + } + + if sdaTxHoldCnt > lcnt-2 { + return ErrInvalidI2CBaudrate + } + err := i2c.disable() + if err != nil { + return err + } + // Always use "fast" mode (<= 400 kHz, works fine for standard mode too) + + i2c.Bus.IC_CON.ReplaceBits(rp.I2C0_IC_CON_SPEED_FAST< deadline { + return ErrRP2040I2CDisable + } + } + return nil +} + +//go:inline +func (i2c *I2C) init(config I2CConfig) error { + i2c.reset() + if err := i2c.disable(); err != nil { + return err + } + i2c.restartOnNext = false + // Configure as a fast-mode master with RepStart support, 7-bit addresses + i2c.Bus.IC_CON.Set((rp.I2C0_IC_CON_SPEED_FAST << rp.I2C0_IC_CON_SPEED_Pos) | + rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE | + rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL) // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status + + // Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0. + i2c.Bus.IC_TX_TL.Set(0) + i2c.Bus.IC_RX_TL.Set(0) + + // Always enable the DREQ signalling -- harmless if DMA isn't listening + i2c.Bus.IC_DMA_CR.Set(rp.I2C0_IC_DMA_CR_TDMAE | rp.I2C0_IC_DMA_CR_RDMAE) + return i2c.SetBaudrate(config.Frequency) +} + +// reset sets I2C register RESET bits in the reset peripheral and then clears them. +//go:inline +func (i2c *I2C) reset() { + resetVal := i2c.deinit() + rp.RESETS.RESET.ClearBits(resetVal) + // Wait until reset is done. + for !rp.RESETS.RESET_DONE.HasBits(resetVal) { + } +} + +// deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit. +//go:inline +func (i2c *I2C) deinit() (resetVal uint32) { + switch { + case i2c.Bus == rp.I2C0: + resetVal = rp.RESETS_RESET_I2C0 + case i2c.Bus == rp.I2C1: + resetVal = rp.RESETS_RESET_I2C1 + } + // Perform I2C reset. + rp.RESETS.RESET.SetBits(resetVal) + + return resetVal +} + +// tx is a primitive i2c blocking write to bus routine. timeout is time to wait +// in microseconds since calling this function for write to finish. +func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err error) { + deadline := ticks() + timeout + if addr >= 0x80 || isReservedI2CAddr(addr) { + return ErrInvalidTgtAddr + } + tlen := len(tx) + // Quick return if possible. + if tlen == 0 { + return nil + } + + err = i2c.disable() + if err != nil { + return err + } + i2c.Bus.IC_TAR.Set(uint32(addr)) + i2c.enable() + // If no timeout was passed timeoutCheck is false. + abort := false + var abortReason uint32 + byteCtr := 0 + for ; byteCtr < tlen; byteCtr++ { + first := byteCtr == 0 + last := byteCtr == tlen-1 + i2c.Bus.IC_DATA_CMD.Set( + (boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) | + (boolToBit(last && !nostop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) | + uint32(tx[byteCtr])) + + // Wait until the transmission of the address/data from the internal + // shift register has completed. For this to function correctly, the + // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag + // was set in i2c_init. + + // IC_RAW_INTR_STAT_TX_EMPTY: This bit is set to 1 when the transmit buffer is at or below + // the threshold value set in the IC_TX_TL register and the + // transmission of the address/data from the internal shift + // register for the most recently popped command is + // completed. It is automatically cleared by hardware when + // the buffer level goes above the threshold. When + // IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held + // in reset. There the TX FIFO looks like it has no data within + // it, so this bit is set to 1, provided there is activity in the + // master or slave state machines. When there is no longer + // any activity, then with ic_en=0, this bit is set to 0. + for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) { + if ticks() > deadline { + i2c.restartOnNext = nostop + println(1) + return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else. + } + } + + abortReason = i2c.getAbortReason() + if abortReason != 0 { + i2c.clearAbortReason() + abort = true + } + if abort || (last && !nostop) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occured. + + // TODO Could there be an abort while waiting for the STOP + // condition here? If so, additional code would be needed here + // to take care of the abort. + for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) { + if ticks() > deadline { + println(2) + i2c.restartOnNext = nostop + return errI2CWriteTimeout + } + } + i2c.Bus.IC_CLR_STOP_DET.Get() + } + } + + // From Pico SDK: A lot of things could have just happened due to the ingenious and + // creative design of I2C. Try to figure things out. + if abort { + switch { + case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0: + // No reported errors - seems to happen if there is nothing connected to the bus. + // Address byte not acknowledged + err = ErrI2CGeneric + case abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0: + // Address acknowledged, some data not acknowledged + fallthrough + default: + // panic("unknown i2c abortReason:" + strconv.Itoa(abortReason) + err = makeI2CBuffError(byteCtr) + } + } + + // nostop means we are now at the end of a *message* but not the end of a *transfer* + i2c.restartOnNext = nostop + return err +} + +// rx is a primitive i2c blocking read routine. timeout is time to wait +// in microseconds since calling this function for read to finish. +func (i2c *I2C) rx(addr uint8, rx []byte, nostop bool, timeout uint64) (err error) { + deadline := ticks() + timeout + if addr >= 0x80 || isReservedI2CAddr(addr) { + return ErrInvalidTgtAddr + } + rlen := len(rx) + // Quick return if possible. + if rlen == 0 { + return nil + } + err = i2c.disable() + if err != nil { + return err + } + i2c.Bus.IC_TAR.Set(uint32(addr)) + i2c.enable() + // If no timeout was passed timeoutCheck is false. + abort := false + var abortReason uint32 + byteCtr := 0 + for ; byteCtr < rlen; byteCtr++ { + first := byteCtr == 0 + last := byteCtr == rlen-1 + for i2c.writeAvailable() == 0 { + } + i2c.Bus.IC_DATA_CMD.Set( + boolToBit(first && i2c.restartOnNext)< 1 for read + + for !abort && i2c.readAvailable() == 0 { + abortReason = i2c.getAbortReason() + i2c.clearAbortReason() + if abortReason != 0 { + abort = true + } + if ticks() > deadline { + i2c.restartOnNext = nostop + return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else. + } + } + if abort { + break + } + rx[byteCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get()) + } + + if abort { + switch { + case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0: + // No reported errors - seems to happen if there is nothing connected to the bus. + // Address byte not acknowledged + err = ErrI2CGeneric + default: + // undefined abort sequence + err = makeI2CBuffError(byteCtr) + } + } + + i2c.restartOnNext = nostop + return err +} + +// writeAvailable determines non-blocking write space available +//go:inline +func (i2c *I2C) writeAvailable() uint32 { + return rp.I2C0_IC_COMP_PARAM_1_TX_BUFFER_DEPTH_Pos - i2c.Bus.IC_TXFLR.Get() +} + +// readAvailable determines number of bytes received +//go:inline +func (i2c *I2C) readAvailable() uint32 { + return i2c.Bus.IC_RXFLR.Get() +} + +// Equivalent to IC_CLR_TX_ABRT.Get() (side effect clears ABORT_REASON) +//go:inline +func (i2c *I2C) clearAbortReason() { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + i2c.Bus.IC_CLR_TX_ABRT.Get() +} + +//go:inline +func (i2c *I2C) getAbortReason() uint32 { + return i2c.Bus.IC_TX_ABRT_SOURCE.Get() +} + +// returns true if RAW_INTR_STAT bits in mask are all set. performs: +// RAW_INTR_STAT & mask == mask +//go:inline +func (i2c *I2C) interrupted(mask uint32) bool { + reg := i2c.Bus.IC_RAW_INTR_STAT.Get() + return reg&mask == mask +} + +type i2cBuffError int + +func (b i2cBuffError) Error() string { + return "i2c err after addr ack at data " + strconv.Itoa(int(b)) +} + +//go:inline +func makeI2CBuffError(idx int) error { + return i2cBuffError(idx) +} + +//go:inline +func boolToBit(a bool) uint32 { + if a { + return 1 + } + return 0 +} + +//go:inline +func u32max(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +//go:inline +func isReservedI2CAddr(addr uint8) bool { + return (addr&0x78) == 0 || (addr&0x78) == 0x78 +}