rp2040: fix spurious i2c STOP during write+read transaction

Этот коммит содержится в:
soypat 2022-03-31 21:12:25 -03:00 коммит произвёл Ron Evans
родитель e060e588ab
коммит f613cb41a3

Просмотреть файл

@ -74,19 +74,7 @@ var (
func (i2c *I2C) Tx(addr uint16, w, r []byte) error { func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
// timeout in microseconds. // timeout in microseconds.
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system. const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
if len(w) > 0 { return i2c.tx(uint8(addr), w, r, timeout)
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. // Configure initializes i2c peripheral and configures I2C config's pins passed.
@ -239,16 +227,16 @@ func (i2c *I2C) deinit() (resetVal uint32) {
return resetVal return resetVal
} }
// tx is a primitive i2c blocking write to bus routine. timeout is time to wait // tx performs blocking write followed by read to I2C bus.
// in microseconds since calling this function for write to finish. func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err error) { deadline := ticks() + timeout_us
deadline := ticks() + timeout
if addr >= 0x80 || isReservedI2CAddr(addr) { if addr >= 0x80 || isReservedI2CAddr(addr) {
return ErrInvalidTgtAddr return ErrInvalidTgtAddr
} }
tlen := len(tx) txlen := len(tx)
rxlen := len(rx)
// Quick return if possible. // Quick return if possible.
if tlen == 0 { if txlen == 0 && rxlen == 0 {
return nil return nil
} }
@ -258,17 +246,18 @@ func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err erro
} }
i2c.Bus.IC_TAR.Set(uint32(addr)) i2c.Bus.IC_TAR.Set(uint32(addr))
i2c.enable() i2c.enable()
// If no timeout was passed timeoutCheck is false.
abort := false abort := false
var abortReason uint32 var abortReason uint32
byteCtr := 0 for txCtr := 0; txCtr < txlen; txCtr++ {
for ; byteCtr < tlen; byteCtr++ { if abort {
first := byteCtr == 0 break
last := byteCtr == tlen-1 }
first := txCtr == 0
last := txCtr == txlen-1 && rxlen == 0
i2c.Bus.IC_DATA_CMD.Set( i2c.Bus.IC_DATA_CMD.Set(
(boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) | (boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
(boolToBit(last && !nostop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) | (boolToBit(last) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
uint32(tx[byteCtr])) uint32(tx[txCtr]))
// Wait until the transmission of the address/data from the internal // Wait until the transmission of the address/data from the internal
// shift register has completed. For this to function correctly, the // shift register has completed. For this to function correctly, the
@ -288,7 +277,6 @@ func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err erro
// any activity, then with ic_en=0, this bit is set to 0. // any activity, then with ic_en=0, this bit is set to 0.
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) { for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) {
if ticks() > deadline { if ticks() > deadline {
i2c.restartOnNext = nostop
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else. return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
} }
} }
@ -298,7 +286,7 @@ func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err erro
i2c.clearAbortReason() i2c.clearAbortReason()
abort = true abort = true
} }
if abort || (last && !nostop) { if abort || last {
// If the transaction was aborted or if it completed // If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occured. // successfully wait until the STOP condition has occured.
@ -307,7 +295,6 @@ func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err erro
// to take care of the abort. // to take care of the abort.
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) { for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) {
if ticks() > deadline { if ticks() > deadline {
i2c.restartOnNext = nostop
return errI2CWriteTimeout return errI2CWriteTimeout
} }
} }
@ -315,6 +302,33 @@ func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err erro
} }
} }
if rxlen > 0 && !abort {
for rxCtr := 0; rxCtr < rxlen; rxCtr++ {
first := rxCtr == 0
last := rxCtr == rxlen-1
for i2c.writeAvailable() == 0 {
}
i2c.Bus.IC_DATA_CMD.Set(
boolToBit(first && i2c.restartOnNext)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
boolToBit(last)<<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 {
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
}
}
if abort {
break
}
rx[rxCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get())
}
}
// From Pico SDK: A lot of things could have just happened due to the ingenious and // 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. // creative design of I2C. Try to figure things out.
if abort { if abort {
@ -327,78 +341,9 @@ func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err erro
// Address acknowledged, some data not acknowledged // Address acknowledged, some data not acknowledged
fallthrough fallthrough
default: default:
// panic("unknown i2c abortReason:" + strconv.Itoa(abortReason)
err = makeI2CAbortError(abortReason) err = makeI2CAbortError(abortReason)
} }
} }
// 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 = makeI2CAbortError(abortReason)
}
}
i2c.restartOnNext = nostop
return err return err
} }
@ -423,6 +368,7 @@ func (i2c *I2C) clearAbortReason() {
i2c.Bus.IC_CLR_TX_ABRT.Get() i2c.Bus.IC_CLR_TX_ABRT.Get()
} }
// getAbortReason reads IC_TX_ABRT_SOURCE register.
//go:inline //go:inline
func (i2c *I2C) getAbortReason() uint32 { func (i2c *I2C) getAbortReason() uint32 {
return i2c.Bus.IC_TX_ABRT_SOURCE.Get() return i2c.Bus.IC_TX_ABRT_SOURCE.Get()