mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-09 21:43:40 +00:00
i2c: implement target mode for rp2040 and nrf
This commit is contained in:
@@ -23,6 +23,37 @@ var (
|
||||
errI2CSignalStopTimeout = errors.New("I2C timeout on signal stop")
|
||||
errI2CAckExpected = errors.New("I2C error: expected ACK not NACK")
|
||||
errI2CBusError = errors.New("I2C bus error")
|
||||
errI2COverflow = errors.New("I2C receive buffer overflow")
|
||||
errI2COverread = errors.New("I2C transmit buffer overflow")
|
||||
)
|
||||
|
||||
// I2CTargetEvent reflects events on the I2C bus
|
||||
type I2CTargetEvent uint8
|
||||
|
||||
const (
|
||||
// I2CReceive indicates target has received a message from the controller.
|
||||
I2CReceive I2CTargetEvent = iota
|
||||
|
||||
// I2CRequest indicates the controller is expecting a message from the target.
|
||||
I2CRequest
|
||||
|
||||
// I2CFinish indicates the controller has ended the transaction.
|
||||
//
|
||||
// I2C controllers can chain multiple receive/request messages without
|
||||
// relinquishing the bus by doing 'restarts'. I2CFinish indicates the
|
||||
// bus has been relinquished by an I2C 'stop'.
|
||||
I2CFinish
|
||||
)
|
||||
|
||||
// I2CMode determines if an I2C peripheral is in Controller or Target mode.
|
||||
type I2CMode int
|
||||
|
||||
const (
|
||||
// I2CModeController represents an I2C peripheral in controller mode.
|
||||
I2CModeController I2CMode = iota
|
||||
|
||||
// I2CModeTarget represents an I2C peripheral in target mode.
|
||||
I2CModeTarget
|
||||
)
|
||||
|
||||
// WriteRegister transmits first the register and then the data to the
|
||||
|
||||
@@ -206,6 +206,7 @@ type I2CConfig struct {
|
||||
Frequency uint32
|
||||
SCL Pin
|
||||
SDA Pin
|
||||
Mode I2CMode
|
||||
}
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
@@ -238,15 +239,21 @@ func (i2c *I2C) Configure(config I2CConfig) error {
|
||||
(nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) |
|
||||
(nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos))
|
||||
|
||||
if config.Frequency >= 400*KHz {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
|
||||
} else {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
|
||||
}
|
||||
|
||||
i2c.setPins(config.SCL, config.SDA)
|
||||
|
||||
i2c.enableAsController()
|
||||
i2c.mode = config.Mode
|
||||
|
||||
if i2c.mode == I2CModeController {
|
||||
if config.Frequency >= 400*KHz {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
|
||||
} else {
|
||||
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
|
||||
}
|
||||
|
||||
i2c.enableAsController()
|
||||
} else {
|
||||
i2c.enableAsTarget()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,19 +9,25 @@ import (
|
||||
|
||||
// I2C on the NRF528xx.
|
||||
type I2C struct {
|
||||
Bus *nrf.TWIM_Type
|
||||
Bus *nrf.TWIM_Type // Called Bus to align with Bus field in nrf51
|
||||
BusT *nrf.TWIS_Type
|
||||
mode I2CMode
|
||||
}
|
||||
|
||||
// There are 2 I2C interfaces on the NRF.
|
||||
var (
|
||||
I2C0 = &I2C{Bus: nrf.TWIM0}
|
||||
I2C1 = &I2C{Bus: nrf.TWIM1}
|
||||
I2C0 = &I2C{Bus: nrf.TWIM0, BusT: nrf.TWIS0}
|
||||
I2C1 = &I2C{Bus: nrf.TWIM1, BusT: nrf.TWIS1}
|
||||
)
|
||||
|
||||
func (i2c *I2C) enableAsController() {
|
||||
i2c.Bus.ENABLE.Set(nrf.TWIM_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
func (i2c *I2C) enableAsTarget() {
|
||||
i2c.BusT.ENABLE.Set(nrf.TWIS_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
func (i2c *I2C) disable() {
|
||||
i2c.Bus.ENABLE.Set(0)
|
||||
}
|
||||
@@ -86,6 +92,99 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Listen starts listening for I2C requests sent to specified address
|
||||
//
|
||||
// addr is the address to listen to
|
||||
func (i2c *I2C) Listen(addr uint8) error {
|
||||
i2c.BusT.ADDRESS[0].Set(uint32(addr))
|
||||
i2c.BusT.CONFIG.Set(nrf.TWIS_CONFIG_ADDRESS0_Enabled)
|
||||
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
i2c.BusT.EVENTS_ERROR.Set(0)
|
||||
i2c.BusT.EVENTS_RXSTARTED.Set(0)
|
||||
i2c.BusT.EVENTS_TXSTARTED.Set(0)
|
||||
i2c.BusT.EVENTS_WRITE.Set(0)
|
||||
i2c.BusT.EVENTS_READ.Set(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForEvent blocks the current go-routine until an I2C event is received (when in Target mode).
|
||||
//
|
||||
// The passed buffer will be populated for receive events, with the number of bytes
|
||||
// received returned in count. For other event types, buf is not modified and a count
|
||||
// of zero is returned.
|
||||
//
|
||||
// For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely.
|
||||
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
|
||||
i2c.BusT.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
|
||||
i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf)))
|
||||
|
||||
i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger)
|
||||
|
||||
i2c.Bus.TASKS_RESUME.Set(1)
|
||||
|
||||
for i2c.BusT.EVENTS_STOPPED.Get() == 0 &&
|
||||
i2c.BusT.EVENTS_READ.Get() == 0 {
|
||||
gosched()
|
||||
|
||||
if i2c.BusT.EVENTS_ERROR.Get() != 0 {
|
||||
i2c.BusT.EVENTS_ERROR.Set(0)
|
||||
return I2CReceive, 0, twisError(i2c.BusT.ERRORSRC.Get())
|
||||
}
|
||||
}
|
||||
|
||||
count = 0
|
||||
evt = I2CFinish
|
||||
err = nil
|
||||
|
||||
if i2c.BusT.EVENTS_WRITE.Get() != 0 {
|
||||
i2c.BusT.EVENTS_WRITE.Set(0)
|
||||
|
||||
// Data was sent to this target. We've waited for
|
||||
// READ or STOPPED event, so transmission should be
|
||||
// complete.
|
||||
count = int(i2c.BusT.RXD.AMOUNT.Get())
|
||||
evt = I2CReceive
|
||||
} else if i2c.BusT.EVENTS_READ.Get() != 0 {
|
||||
i2c.BusT.EVENTS_READ.Set(0)
|
||||
|
||||
// Data is requested from this target, hw will stretch
|
||||
// the controller's clock until there is a reply to
|
||||
// send
|
||||
evt = I2CRequest
|
||||
} else if i2c.BusT.EVENTS_STOPPED.Get() != 0 {
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
evt = I2CFinish
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Reply supplies the response data the controller.
|
||||
func (i2c *I2C) Reply(buf []byte) error {
|
||||
i2c.BusT.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
|
||||
i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf)))
|
||||
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
|
||||
// Trigger Tx
|
||||
i2c.BusT.TASKS_PREPARETX.Set(nrf.TWIS_TASKS_PREPARETX_TASKS_PREPARETX_Trigger)
|
||||
|
||||
// Block, waiting for Tx to complete
|
||||
for i2c.BusT.EVENTS_STOPPED.Get() == 0 {
|
||||
gosched()
|
||||
|
||||
if i2c.BusT.EVENTS_ERROR.Get() != 0 {
|
||||
return twisError(i2c.BusT.ERRORSRC.Get())
|
||||
}
|
||||
}
|
||||
|
||||
i2c.BusT.EVENTS_STOPPED.Set(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// twiCError converts an I2C controller error to Go
|
||||
func twiCError(val uint32) error {
|
||||
if val == 0 {
|
||||
@@ -100,3 +199,18 @@ func twiCError(val uint32) error {
|
||||
|
||||
return errI2CBusError
|
||||
}
|
||||
|
||||
// twisError converts an I2C target error to Go
|
||||
func twisError(val uint32) error {
|
||||
if val == 0 {
|
||||
return nil
|
||||
} else if val&nrf.TWIS_ERRORSRC_OVERFLOW_Msk == nrf.TWIS_ERRORSRC_OVERFLOW {
|
||||
return errI2COverflow
|
||||
} else if val&nrf.TWIS_ERRORSRC_DNACK_Msk == nrf.TWIS_ERRORSRC_DNACK {
|
||||
return errI2CAckExpected
|
||||
} else if val&nrf.TWIS_ERRORSRC_OVERREAD_Msk == nrf.TWIS_ERRORSRC_OVERREAD {
|
||||
return errI2COverread
|
||||
}
|
||||
|
||||
return errI2CBusError
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import "device/nrf"
|
||||
|
||||
// I2C on the NRF51 and NRF52.
|
||||
type I2C struct {
|
||||
Bus *nrf.TWI_Type
|
||||
Bus *nrf.TWI_Type
|
||||
mode I2CMode
|
||||
}
|
||||
|
||||
// There are 2 I2C interfaces on the NRF.
|
||||
@@ -19,6 +20,10 @@ func (i2c *I2C) enableAsController() {
|
||||
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
|
||||
}
|
||||
|
||||
func (i2c *I2C) enableAsTarget() {
|
||||
// Not supported on this hardware
|
||||
}
|
||||
|
||||
func (i2c *I2C) disable() {
|
||||
i2c.Bus.ENABLE.Set(0)
|
||||
}
|
||||
|
||||
@@ -20,10 +20,13 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// The I2C target implementation is based on the C implementation from
|
||||
// here: https://github.com/vmilea/pico_i2c_slave
|
||||
|
||||
// Features: Taken from datasheet.
|
||||
// Default master mode, with slave mode available (not simulataneously).
|
||||
// Default slave address of RP2040: 0x055
|
||||
// Supports 10-bit addressing in Master mode
|
||||
// Default controller mode, with target mode available (not simulataneously).
|
||||
// Default target address of RP2040: 0x055
|
||||
// Supports 10-bit addressing in controller mode
|
||||
// 16-element transmit buffer
|
||||
// 16-element receive buffer
|
||||
// Can be driven from DMA
|
||||
@@ -45,20 +48,26 @@ type I2CConfig struct {
|
||||
// SDA/SCL Serial Data and clock pins. Refer to datasheet to see
|
||||
// which pins match the desired bus.
|
||||
SDA, SCL Pin
|
||||
Mode I2CMode
|
||||
}
|
||||
|
||||
type I2C struct {
|
||||
Bus *rp.I2C0_Type
|
||||
restartOnNext bool
|
||||
mode I2CMode
|
||||
txInProgress bool
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
|
||||
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
|
||||
ErrI2CGeneric = errors.New("i2c error")
|
||||
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
|
||||
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
|
||||
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
|
||||
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
|
||||
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
|
||||
ErrI2CGeneric = errors.New("i2c error")
|
||||
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
|
||||
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
|
||||
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
|
||||
ErrI2CAlreadyListening = errors.New("i2c already listening")
|
||||
ErrI2CWrongMode = errors.New("i2c wrong mode")
|
||||
ErrI2CUnderflow = errors.New("i2c underflow")
|
||||
)
|
||||
|
||||
// Tx performs a write and then a read transfer placing the result in
|
||||
@@ -75,11 +84,26 @@ var (
|
||||
//
|
||||
// Performs only a write transfer.
|
||||
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
|
||||
if i2c.mode != I2CModeController {
|
||||
return ErrI2CWrongMode
|
||||
}
|
||||
|
||||
// timeout in microseconds.
|
||||
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
|
||||
return i2c.tx(uint8(addr), w, r, timeout)
|
||||
}
|
||||
|
||||
// Listen starts listening for I2C requests sent to specified address
|
||||
//
|
||||
// addr is the address to listen to
|
||||
func (i2c *I2C) Listen(addr uint16) error {
|
||||
if i2c.mode != I2CModeTarget {
|
||||
return ErrI2CWrongMode
|
||||
}
|
||||
|
||||
return i2c.listen(uint8(addr))
|
||||
}
|
||||
|
||||
// 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:
|
||||
//
|
||||
@@ -213,14 +237,22 @@ func (i2c *I2C) init(config I2CConfig) error {
|
||||
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
|
||||
|
||||
i2c.mode = config.Mode
|
||||
|
||||
// Configure as fast-mode with RepStart support, 7-bit addresses
|
||||
mode := uint32(rp.I2C0_IC_CON_SPEED_FAST<<rp.I2C0_IC_CON_SPEED_Pos) |
|
||||
rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
|
||||
if config.Mode == I2CModeController {
|
||||
mode |= rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE
|
||||
}
|
||||
i2c.Bus.IC_CON.Set(mode)
|
||||
|
||||
// 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)
|
||||
if config.Mode == I2CModeController {
|
||||
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)
|
||||
@@ -300,12 +332,14 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
// 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
|
||||
// controller or target 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 {
|
||||
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
|
||||
abortReason = i2c.getAbortReason()
|
||||
@@ -324,6 +358,8 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
if ticks() > deadline {
|
||||
return errI2CWriteTimeout
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
i2c.Bus.IC_CLR_STOP_DET.Get()
|
||||
}
|
||||
@@ -334,6 +370,7 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
first := rxCtr == 0
|
||||
last := rxCtr == rxlen-1
|
||||
for i2c.writeAvailable() == 0 {
|
||||
gosched()
|
||||
}
|
||||
i2c.Bus.IC_DATA_CMD.Set(
|
||||
boolToBit(first && i2c.restartOnNext)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
|
||||
@@ -349,6 +386,8 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
if ticks() > deadline {
|
||||
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
if abort {
|
||||
break
|
||||
@@ -374,6 +413,100 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// listen sets up for async handling of requests on the I2C bus.
|
||||
func (i2c *I2C) listen(addr uint8) error {
|
||||
if addr >= 0x80 || isReservedI2CAddr(addr) {
|
||||
return ErrInvalidTgtAddr
|
||||
}
|
||||
|
||||
err := i2c.disable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i2c.Bus.IC_SAR.Set(uint32(addr))
|
||||
|
||||
i2c.enable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
|
||||
rxPtr := 0
|
||||
for {
|
||||
stat := i2c.Bus.IC_RAW_INTR_STAT.Get()
|
||||
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_RX_FULL != 0 {
|
||||
b := uint8(i2c.Bus.IC_DATA_CMD.Get())
|
||||
if rxPtr < len(buf) {
|
||||
buf[rxPtr] = b
|
||||
rxPtr++
|
||||
}
|
||||
}
|
||||
|
||||
// Stop
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_STOP_DET != 0 {
|
||||
if rxPtr > 0 {
|
||||
return I2CReceive, rxPtr, nil
|
||||
}
|
||||
|
||||
i2c.Bus.IC_CLR_STOP_DET.Get() // clear
|
||||
return I2CFinish, 0, nil
|
||||
}
|
||||
|
||||
// Start or restart - ignore start, return on restart
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_START_DET != 0 {
|
||||
i2c.Bus.IC_CLR_START_DET.Get() // clear restart
|
||||
|
||||
// Restart
|
||||
if rxPtr > 0 {
|
||||
return I2CReceive, rxPtr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read request - leave flag set until we start to reply.
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ != 0 {
|
||||
return I2CRequest, 0, nil
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (i2c *I2C) Reply(buf []byte) error {
|
||||
txPtr := 0
|
||||
|
||||
stat := i2c.Bus.IC_RAW_INTR_STAT.Get()
|
||||
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 {
|
||||
return ErrI2CWrongMode
|
||||
}
|
||||
i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart
|
||||
|
||||
// Clear any dangling TX abort
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
|
||||
i2c.Bus.IC_CLR_TX_ABRT.Get()
|
||||
}
|
||||
|
||||
for txPtr < len(buf) {
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_TX_EMPTY != 0 {
|
||||
i2c.Bus.IC_DATA_CMD.Set(uint32(buf[txPtr]))
|
||||
txPtr++
|
||||
}
|
||||
|
||||
// This Tx abort is a normal case - we're sending more
|
||||
// data than controller wants to receive
|
||||
if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
|
||||
i2c.Bus.IC_CLR_TX_ABRT.Get()
|
||||
return nil
|
||||
}
|
||||
|
||||
gosched()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAvailable determines non-blocking write space available
|
||||
//
|
||||
//go:inline
|
||||
|
||||
Reference in New Issue
Block a user