Files
tinygo/src/machine/machine_esp32xx_i2c.go
T
Dima 180220a2e3 Esp32s3 i2c interface (#5210)
* esp32s3-i2c implement interface

* esp32s3-i2c: disable m5stamp_c3

* added simple tests without Listen

* replace smoke tests

* esp32s3-i2c: fix allocation in tx
2026-04-05 14:15:40 +02:00

337 lines
8.6 KiB
Go

//go:build (esp32c3 || esp32s3) && !m5stamp_c3
package machine
import (
"device/esp"
"runtime/volatile"
"unsafe"
)
type I2C struct {
Bus *esp.I2C_Type
funcSCL, funcSDA uint32
useExt1 bool
txCmdBuf [8]i2cCommand
}
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32 // in Hz
SCL Pin
SDA Pin
}
const (
clkXTAL = 0
clkFOSC = 1
clkXTALFrequency = uint32(40e6)
clkFOSCFrequency = uint32(17.5e6)
i2cClkSourceFrequency = clkXTALFrequency
i2cClkSource = clkXTAL
)
func (i2c *I2C) Configure(config I2CConfig) error {
if config.Frequency == 0 {
config.Frequency = 400 * KHz
}
if config.SCL == 0 {
config.SCL = SCL_PIN
}
if config.SDA == 0 {
config.SDA = SDA_PIN
}
i2c.initClock(config)
i2c.initNoiseFilter()
i2c.initPins(config)
i2c.initFrequency(config)
i2c.startMaster()
return nil
}
//go:inline
func (i2c *I2C) initClock(config I2CConfig) {
if !i2c.useExt1 {
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
} else {
initI2CExt1Clock()
}
// disable interrupts
i2c.Bus.INT_CLR.Set(0x3fff)
i2c.Bus.INT_ENA.ClearBits(0x3fff)
i2c.Bus.SetCLK_CONF_SCLK_SEL(i2cClkSource)
i2c.Bus.SetCLK_CONF_SCLK_ACTIVE(1)
i2c.Bus.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024))
i2c.Bus.SetCTR_CLK_EN(1)
}
//go:inline
func (i2c *I2C) initNoiseFilter() {
i2c.Bus.FILTER_CFG.Set(0x377)
}
//go:inline
func (i2c *I2C) initPins(config I2CConfig) {
config.SDA.configure(PinConfig{Mode: PinOutput}, i2c.funcSDA)
inFunc(i2c.funcSDA).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDA)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
config.SDA.Set(true)
config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
i2c.Bus.SetCTR_SDA_FORCE_OUT(1)
config.SCL.configure(PinConfig{Mode: PinOutput}, i2c.funcSCL)
inFunc(i2c.funcSCL).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SCL)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
config.SCL.Set(true)
// Configure the pad with the given IO mux configuration.
config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
i2c.Bus.SetCTR_SCL_FORCE_OUT(1)
}
//go:inline
func (i2c *I2C) initFrequency(config I2CConfig) {
clkmDiv := i2cClkSourceFrequency/(config.Frequency*1024) + 1
sclkFreq := i2cClkSourceFrequency / clkmDiv
halfCycle := sclkFreq / config.Frequency / 2
// SCL
sclLow := halfCycle
sclWaitHigh := uint32(0)
if config.Frequency > 50000 {
sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K
}
sclHigh := halfCycle - sclWaitHigh
// SDA
sdaHold := halfCycle / 4
sda_sample := halfCycle / 2
setup := halfCycle
hold := halfCycle
i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1)
i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh)
i2c.Bus.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25)
i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup)
i2c.Bus.SetSCL_STOP_SETUP_TIME(setup)
i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1)
i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1)
i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample)
i2c.Bus.SetSDA_HOLD_TIME(sdaHold)
}
//go:inline
func (i2c *I2C) startMaster() {
// FIFO mode for data
i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0)
// Reset TX & RX buffers
i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1)
i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0)
i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1)
i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0)
// set timeout value
i2c.Bus.TO.Set(0x10)
// enable master mode
i2c.Bus.CTR.Set(0x113)
i2c.Bus.SetCTR_CONF_UPGATE(1)
i2c.resetMaster()
}
//go:inline
func (i2c *I2C) resetMaster() {
// reset FSM
i2c.Bus.SetCTR_FSM_RST(1)
// clear the bus
i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9)
i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_EN(1)
i2c.Bus.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1)
i2c.Bus.SetCTR_CONF_UPGATE(1)
i2c.Bus.FILTER_CFG.Set(0x377)
// wait for SCL_RST_SLV_EN
for i2c.Bus.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 {
}
i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0)
}
type i2cCommandType = uint32
type i2cAck = uint32
const (
i2cCMD_RSTART i2cCommandType = 6 << 11
i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en
i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en
i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK
i2cCMD_STOP i2cCommandType = 2 << 11
i2cCMD_END i2cCommandType = 4 << 11
)
type i2cCommand struct {
cmd i2cCommandType
data []byte
head int
}
//go:linkname nanotime runtime.nanotime
func nanotime() int64
func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error {
const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk
i2c.Bus.INT_CLR.SetBits(intMask)
i2c.Bus.INT_ENA.SetBits(intMask)
i2c.Bus.SetCTR_CONF_UPGATE(1)
defer func() {
i2c.Bus.INT_CLR.SetBits(intMask)
i2c.Bus.INT_ENA.ClearBits(intMask)
}()
timeoutNS := int64(timeoutMS) * 1000000
needAddress := true
needRestart := false
readLast := false
var readTo []byte
for cmdIdx, reg := 0, &i2c.Bus.COMD0; cmdIdx < len(cmd); {
c := &cmd[cmdIdx]
switch c.cmd {
case i2cCMD_RSTART:
reg.Set(i2cCMD_RSTART)
reg = nextAddress(reg)
cmdIdx++
case i2cCMD_WRITE:
count := 32
if needAddress {
needAddress = false
i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1)
count--
i2c.Bus.SLAVE_ADDR.Set(uint32(addr))
i2c.Bus.SetCTR_CONF_UPGATE(1)
}
for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 {
i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head]))
}
reg.Set(i2cCMD_WRITE | uint32(32-count))
reg = nextAddress(reg)
if c.head < len(c.data) {
reg.Set(i2cCMD_END)
reg = nil
} else {
cmdIdx++
}
needRestart = true
case i2cCMD_READ:
if needAddress {
needAddress = false
i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1)
i2c.Bus.SLAVE_ADDR.Set(uint32(addr))
reg.Set(i2cCMD_WRITE | 1)
reg = nextAddress(reg)
}
if needRestart {
// We need to send RESTART again after i2cCMD_WRITE.
reg.Set(i2cCMD_RSTART)
reg = nextAddress(reg)
reg.Set(i2cCMD_WRITE | 1)
reg = nextAddress(reg)
i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1)
needRestart = false
}
count := 32
bytes := len(c.data) - c.head
// Only last byte in sequence must be sent with ACK set to 1 to indicate end of data.
split := bytes <= count
if split {
bytes--
}
if bytes > 32 {
bytes = 32
}
if bytes > 0 {
reg.Set(i2cCMD_READ | uint32(bytes))
reg = nextAddress(reg)
}
if split {
readLast = true
reg.Set(i2cCMD_READLAST | 1)
reg = nextAddress(reg)
readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte
cmdIdx++
} else {
reg.Set(i2cCMD_END)
readTo = c.data[c.head : c.head+bytes]
reg = nil
}
case i2cCMD_STOP:
reg.Set(i2cCMD_STOP)
reg = nil
cmdIdx++
}
if reg == nil {
// transmit now
i2c.Bus.SetCTR_CONF_UPGATE(1)
i2c.Bus.SetCTR_TRANS_START(1)
end := nanotime() + timeoutNS
var mask uint32
for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() {
if nanotime() > end {
if readTo != nil {
return errI2CReadTimeout
}
return errI2CWriteTimeout
}
}
switch {
case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast:
return errI2CAckExpected
case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0:
if readTo != nil {
return errI2CReadTimeout
}
return errI2CWriteTimeout
}
i2c.Bus.INT_CLR.SetBits(intMask)
for i := 0; i < len(readTo); i++ {
readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff)
c.head++
}
readTo = nil
reg = &i2c.Bus.COMD0
}
}
return nil
}
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
// timeout in milliseconds.
const timeout = 40 // 40ms is a reasonable time for a real-time system.
cmd := i2c.txCmdBuf[:0]
cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART})
if len(w) > 0 {
cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w})
}
if len(r) > 0 {
cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r})
}
cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP})
return i2c.transmit(addr, cmd, timeout)
}
func (i2c *I2C) SetBaudRate(br uint32) error {
return nil
}
func nextAddress(reg *volatile.Register32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4))
}