родитель
cfae2d4f9a
коммит
4f7b23c2b7
6 изменённых файлов: 522 добавлений и 9 удалений
|
@ -9,6 +9,15 @@ const (
|
||||||
xoscFreq = 12 // MHz
|
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
|
// SPI default pins
|
||||||
const (
|
const (
|
||||||
// Default Serial Clock Bus 0 for SPI communications
|
// Default Serial Clock Bus 0 for SPI communications
|
||||||
|
|
|
@ -49,8 +49,11 @@ const (
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN Pin = GPIO12
|
I2C0_SDA_PIN Pin = GPIO12
|
||||||
SCL_PIN Pin = GPIO13
|
I2C0_SCL_PIN Pin = GPIO13
|
||||||
|
|
||||||
|
I2C1_SDA_PIN Pin = GPIO18
|
||||||
|
I2C1_SCL_PIN Pin = GPIO19
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins. SPI1 not available on Nano RP2040 Connect.
|
// SPI pins. SPI1 not available on Nano RP2040 Connect.
|
||||||
|
|
|
@ -38,6 +38,15 @@ const (
|
||||||
xoscFreq = 12 // MHz
|
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
|
// SPI default pins
|
||||||
const (
|
const (
|
||||||
// Default Serial Clock Bus 0 for SPI communications
|
// Default Serial Clock Bus 0 for SPI communications
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build atmega nrf sam stm32 fe310 k210
|
// +build atmega nrf sam stm32 fe310 k210 rp2040
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
|
|
@ -47,14 +47,27 @@ type pinFunc uint8
|
||||||
// GPIO function selectors
|
// GPIO function selectors
|
||||||
const (
|
const (
|
||||||
fnJTAG pinFunc = 0
|
fnJTAG pinFunc = 0
|
||||||
fnSPI pinFunc = 1
|
fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO
|
||||||
fnUART pinFunc = 2
|
fnUART pinFunc = 2
|
||||||
fnI2C pinFunc = 3
|
fnI2C pinFunc = 3
|
||||||
fnPWM pinFunc = 4
|
// Connect a PWM slice to GPIO. There are eight PWM slices,
|
||||||
fnSIO pinFunc = 5
|
// each with two outputchannels (A/B). The B pin can also be used as an input,
|
||||||
fnPIO0 pinFunc = 6
|
// for frequency and duty cyclemeasurement
|
||||||
fnPIO1 pinFunc = 7
|
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
|
fnGPCK pinFunc = 8
|
||||||
|
// USB power control signals to/from the internal USB controller
|
||||||
fnUSB pinFunc = 9
|
fnUSB pinFunc = 9
|
||||||
fnNULL pinFunc = 0x1f
|
fnNULL pinFunc = 0x1f
|
||||||
|
|
||||||
|
@ -68,6 +81,7 @@ const (
|
||||||
PinInputPullup
|
PinInputPullup
|
||||||
PinAnalog
|
PinAnalog
|
||||||
PinUART
|
PinUART
|
||||||
|
PinI2C
|
||||||
PinSPI
|
PinSPI
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -91,7 +105,7 @@ func (p Pin) xor() {
|
||||||
|
|
||||||
// get returns the pin value
|
// get returns the pin value
|
||||||
func (p Pin) get() bool {
|
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 {
|
func (p Pin) ioCtrl() *volatile.Register32 {
|
||||||
|
@ -117,6 +131,17 @@ func (p Pin) pulloff() {
|
||||||
p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE)
|
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)<<rp.PADS_BANK0_GPIO0_SLEWFAST_Pos, rp.PADS_BANK0_GPIO0_SLEWFAST_Msk, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSchmitt enables or disables Schmitt trigger.
|
||||||
|
func (p Pin) setSchmitt(trigger bool) {
|
||||||
|
p.padCtrl().ReplaceBits(boolToBit(trigger)<<rp.PADS_BANK0_GPIO0_SCHMITT_Pos, rp.PADS_BANK0_GPIO0_SCHMITT_Msk, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// setFunc will set pin function to fn.
|
// setFunc will set pin function to fn.
|
||||||
func (p Pin) setFunc(fn pinFunc) {
|
func (p Pin) setFunc(fn pinFunc) {
|
||||||
// Set input enable, Clear output disable
|
// Set input enable, Clear output disable
|
||||||
|
@ -156,6 +181,12 @@ func (p Pin) Configure(config PinConfig) {
|
||||||
p.pulloff()
|
p.pulloff()
|
||||||
case PinUART:
|
case PinUART:
|
||||||
p.setFunc(fnUART)
|
p.setFunc(fnUART)
|
||||||
|
case PinI2C:
|
||||||
|
// IO config according to 4.3.1.3 of rp2040 datasheet.
|
||||||
|
p.setFunc(fnI2C)
|
||||||
|
p.pullup()
|
||||||
|
p.setSchmitt(true)
|
||||||
|
p.setSlew(false)
|
||||||
case PinSPI:
|
case PinSPI:
|
||||||
p.setFunc(fnSPI)
|
p.setFunc(fnSPI)
|
||||||
}
|
}
|
||||||
|
|
461
src/machine/machine_rp2040_i2c.go
Обычный файл
461
src/machine/machine_rp2040_i2c.go
Обычный файл
|
@ -0,0 +1,461 @@
|
||||||
|
// +build rp2040
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/rp"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2C on the RP2040.
|
||||||
|
var (
|
||||||
|
I2C0 = &_I2C0
|
||||||
|
_I2C0 = I2C{
|
||||||
|
Bus: rp.I2C0,
|
||||||
|
}
|
||||||
|
I2C1 = &_I2C1
|
||||||
|
_I2C1 = I2C{
|
||||||
|
Bus: rp.I2C1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 16-element transmit buffer
|
||||||
|
// 16-element receive buffer
|
||||||
|
// Can be driven from DMA
|
||||||
|
// Can generate interrupts
|
||||||
|
// Fast mode plus max transfer speed (1000kb/s)
|
||||||
|
|
||||||
|
// GPIO config
|
||||||
|
// Each controller must connect its clock SCL and data SDA to one pair of GPIOs.
|
||||||
|
// The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high.
|
||||||
|
// This applies to SCL and SDA. The GPIO pads should beconfigured for:
|
||||||
|
// Pull-up enabled
|
||||||
|
// Slew rate limited
|
||||||
|
// Schmitt trigger enabled
|
||||||
|
// Note: There should also be external pull-ups on the board as the internal pad pull-ups may not be strong enough to pull upexternal circuits.
|
||||||
|
|
||||||
|
// I2CConfig is used to store config info for I2C.
|
||||||
|
type I2CConfig struct {
|
||||||
|
Frequency uint32
|
||||||
|
// SDA/SCL Serial Data and clock pins. Refer to datasheet to see
|
||||||
|
// which pins match the desired bus.
|
||||||
|
SDA, SCL Pin
|
||||||
|
}
|
||||||
|
|
||||||
|
type I2C struct {
|
||||||
|
Bus *rp.I2C0_Type
|
||||||
|
restartOnNext 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")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tx performs a write and then a read transfer placing the result in
|
||||||
|
// in r.
|
||||||
|
//
|
||||||
|
// Passing a nil value for w or r skips the transfer corresponding to write
|
||||||
|
// or read, respectively.
|
||||||
|
//
|
||||||
|
// i2c.Tx(addr, nil, r)
|
||||||
|
// Performs only a read transfer.
|
||||||
|
//
|
||||||
|
// i2c.Tx(addr, w, nil)
|
||||||
|
// Performs only a write transfer.
|
||||||
|
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
|
||||||
|
// timeout in microseconds.
|
||||||
|
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
|
||||||
|
if len(w) > 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<<rp.I2C0_IC_CON_SPEED_Pos, rp.I2C0_IC_CON_SPEED_Msk, 0)
|
||||||
|
i2c.Bus.IC_FS_SCL_HCNT.Set(hcnt)
|
||||||
|
i2c.Bus.IC_FS_SCL_LCNT.Set(lcnt)
|
||||||
|
|
||||||
|
i2c.Bus.IC_FS_SPKLEN.Set(u32max(1, lcnt/16))
|
||||||
|
|
||||||
|
i2c.Bus.IC_SDA_HOLD.ReplaceBits(sdaTxHoldCnt<<rp.I2C0_IC_SDA_HOLD_IC_SDA_TX_HOLD_Pos, rp.I2C0_IC_SDA_HOLD_IC_SDA_TX_HOLD_Msk, 0)
|
||||||
|
i2c.enable()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (i2c *I2C) enable() {
|
||||||
|
i2c.Bus.IC_ENABLE.ReplaceBits(rp.I2C0_IC_ENABLE_ENABLE<<rp.I2C0_IC_ENABLE_ENABLE_Pos, rp.I2C0_IC_ENABLE_ENABLE_Msk, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implemented as per 4.3.10.3. Disabling DW_apb_i2c section.
|
||||||
|
//go:inline
|
||||||
|
func (i2c *I2C) disable() error {
|
||||||
|
const MAX_T_POLL_COUNT = 64 // 64 us timeout corresponds to around 1000kb/s i2c transfer rate.
|
||||||
|
deadline := ticks() + MAX_T_POLL_COUNT
|
||||||
|
i2c.Bus.IC_ENABLE.Set(0)
|
||||||
|
for i2c.Bus.IC_ENABLE_STATUS.Get()&1 != 0 {
|
||||||
|
if ticks() > 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)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
|
||||||
|
boolToBit(last && !nostop)<<rp.I2C0_IC_DATA_CMD_STOP_Pos |
|
||||||
|
rp.I2C0_IC_DATA_CMD_CMD) // -> 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
|
||||||
|
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче