aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/machine
diff options
context:
space:
mode:
authorJohann Freymuth <[email protected]>2024-05-13 21:58:06 +0200
committerGitHub <[email protected]>2024-05-13 21:58:06 +0200
commit8890b57ba0f5d015560bf1c8e30d0393fc809c1f (patch)
tree8af9d55aa9d9de9c7d02611314047d59b3d45981 /src/machine
parentee3a05f1de2cfac22eff91d4bc6651ff43bbc320 (diff)
downloadtinygo-8890b57ba0f5d015560bf1c8e30d0393fc809c1f.tar.gz
tinygo-8890b57ba0f5d015560bf1c8e30d0393fc809c1f.zip
Add I2C support for esp32 (#4259)
machine/esp32: add i2c support
Diffstat (limited to 'src/machine')
-rw-r--r--src/machine/i2c.go2
-rw-r--r--src/machine/machine_esp32_i2c.go412
2 files changed, 413 insertions, 1 deletions
diff --git a/src/machine/i2c.go b/src/machine/i2c.go
index 96c192cb8..24fbf6897 100644
--- a/src/machine/i2c.go
+++ b/src/machine/i2c.go
@@ -1,4 +1,4 @@
-//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3)
+//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32
package machine
diff --git a/src/machine/machine_esp32_i2c.go b/src/machine/machine_esp32_i2c.go
new file mode 100644
index 000000000..746e722dc
--- /dev/null
+++ b/src/machine/machine_esp32_i2c.go
@@ -0,0 +1,412 @@
+//go:build esp32
+
+package machine
+
+import (
+ "device/esp"
+ "runtime/volatile"
+ "unsafe"
+)
+
+var (
+ I2C0 = &I2C{Bus: esp.I2C0, funcSCL: 29, funcSDA: 30}
+ I2C1 = &I2C{Bus: esp.I2C1, funcSCL: 95, funcSDA: 96}
+)
+
+type I2C struct {
+ Bus *esp.I2C_Type
+ funcSCL, funcSDA uint32
+ config I2CConfig
+}
+
+// I2CConfig is used to store config info for I2C.
+type I2CConfig struct {
+ Frequency uint32 // in Hz
+ SCL Pin
+ SDA Pin
+}
+
+const (
+ i2cClkSourceFrequency = uint32(80 * MHz)
+)
+
+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.config = config
+
+ i2c.initAll()
+ return nil
+}
+
+func (i2c *I2C) initAll() {
+ i2c.initClock()
+ i2c.initNoiseFilter()
+ i2c.initPins()
+ i2c.initFrequency()
+ i2c.startMaster()
+}
+
+//go:inline
+func (i2c *I2C) initClock() {
+ // reset I2C clock
+ if i2c.Bus == esp.I2C0 {
+ esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(1)
+ esp.DPORT.SetPERIP_CLK_EN_I2C0_EXT0_CLK_EN(1)
+ esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(0)
+ } else {
+ esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(1)
+ esp.DPORT.SetPERIP_CLK_EN_I2C_EXT1_CLK_EN(1)
+ esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(0)
+ }
+ // disable interrupts
+ i2c.Bus.INT_ENA.Set(0)
+ i2c.Bus.INT_CLR.Set(0x3fff)
+
+ i2c.Bus.SetCTR_CLK_EN(1)
+}
+
+//go:inline
+func (i2c *I2C) initNoiseFilter() {
+ i2c.Bus.SCL_FILTER_CFG.Set(0xF)
+ i2c.Bus.SDA_FILTER_CFG.Set(0xF)
+}
+
+//go:inline
+func (i2c *I2C) initPins() {
+ var muxConfig uint32
+ const function = 2 // function 2 is just GPIO
+
+ // SDA
+ muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos
+ // Make this pin an input pin (always).
+ muxConfig |= esp.IO_MUX_GPIO0_FUN_IE
+ // Set drive strength: 0 is lowest, 3 is highest.
+ muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos
+ i2c.config.SDA.mux().Set(muxConfig)
+ i2c.config.SDA.outFunc().Set(i2c.funcSDA)
+ inFunc(i2c.funcSDA).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SDA))
+ i2c.config.SDA.Set(true)
+ // Configure the pad with the given IO mux configuration.
+ i2c.config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
+
+ esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SDA))
+ i2c.Bus.SetCTR_SDA_FORCE_OUT(1)
+
+ // SCL
+ muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos
+ // Make this pin an input pin (always).
+ muxConfig |= esp.IO_MUX_GPIO0_FUN_IE
+ // Set drive strength: 0 is lowest, 3 is highest.
+ muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos
+ i2c.config.SCL.mux().Set(muxConfig)
+ i2c.config.SCL.outFunc().Set(i2c.funcSCL)
+ inFunc(i2c.funcSCL).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SCL))
+ i2c.config.SCL.Set(true)
+ // Configure the pad with the given IO mux configuration.
+ i2c.config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
+
+ esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SCL))
+ i2c.Bus.SetCTR_SCL_FORCE_OUT(1)
+}
+
+//go:inline
+func (i2c *I2C) initFrequency() {
+ clkmDiv := i2cClkSourceFrequency/(i2c.config.Frequency*1024) + 1
+ sclkFreq := i2cClkSourceFrequency / clkmDiv
+ halfCycle := sclkFreq / i2c.config.Frequency / 2
+ //SCL
+ sclLow := halfCycle
+ sclWaitHigh := uint32(0)
+ if i2c.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_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)
+ // set timeout value
+ i2c.Bus.SetTO_TIME_OUT(20 * halfCycle)
+}
+
+//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)
+ // enable master mode
+ i2c.Bus.SetCTR_MS_MODE(1)
+}
+
+func (i2c *I2C) resetBus() {
+ // unlike esp32c3, the esp32 i2c modules do not have a reset fsm register,
+ // so we need to:
+ // 1. disconnect the pins
+ // 2. generate a stop condition manually
+ // 3. do a full reset
+ // 4. redo all configuration
+
+ i2c.config.SDA.mux().Set(2<<esp.IO_MUX_GPIO0_MCU_SEL_Pos | esp.IO_MUX_GPIO0_FUN_IE | 1<<esp.IO_MUX_GPIO0_FUN_DRV_Pos)
+ i2c.config.SDA.outFunc().Set(0x500)
+ i2c.config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
+ i2c.config.SCL.mux().Set(2<<esp.IO_MUX_GPIO0_MCU_SEL_Pos | esp.IO_MUX_GPIO0_FUN_IE | 1<<esp.IO_MUX_GPIO0_FUN_DRV_Pos)
+ i2c.config.SCL.outFunc().Set(0x500)
+ i2c.config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
+
+ // bit-bang a read-NACK in case any device on the bus is in the middle of a write
+ i2c.config.SCL.Low()
+ i2c.config.SDA.High()
+ wait()
+ for i := 0; i < 9; i++ {
+ if i2c.config.SDA.Get() {
+ break
+ }
+ i2c.config.SCL.High()
+ wait()
+ i2c.config.SCL.Low()
+ wait()
+ }
+ i2c.config.SDA.Low()
+ i2c.config.SCL.High()
+ wait()
+ i2c.config.SDA.High()
+
+ // initAll contains initClock which contains a reset
+ i2c.initAll()
+}
+
+func wait() {
+ end := nanotime() + 5_000
+ for nanotime() < end {
+ //spin
+ }
+}
+
+type i2cCommandType = uint32
+type i2cAck = uint32
+
+const (
+ i2cCMD_RSTART i2cCommandType = 0 << 11
+ i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en
+ i2cCMD_READ i2cCommandType = 2 << 11
+ i2cCMD_READLAST i2cCommandType = 2<<11 | 1<<10 // READ + NACK
+ i2cCMD_STOP i2cCommandType = 3 << 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 {
+ if i2c.Bus.GetSR_BUS_BUSY() == 1 {
+ i2c.resetBus()
+ }
+
+ 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_ACK_ERR_INT_ST_Msk | esp.I2C_INT_STATUS_ARBITRATION_LOST_INT_ST_Msk
+ i2c.Bus.INT_CLR.Set(intMask)
+ i2c.Bus.INT_ENA.Set(intMask)
+
+ defer func() {
+ i2c.Bus.INT_CLR.Set(intMask)
+ i2c.Bus.INT_ENA.Set(0)
+ }()
+
+ 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))
+ }
+ 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_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 {
+ // timeout leaves the bus in an undefined state, reset
+ i2c.resetBus()
+ if readTo != nil {
+ return errI2CReadTimeout
+ }
+ return errI2CWriteTimeout
+ }
+ }
+ switch {
+ case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast:
+ return errI2CAckExpected
+ case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0:
+ // timeout leaves the bus in an undefined state, reset
+ i2c.resetBus()
+ 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 microseconds.
+ const timeout = 40 // 40ms is a reasonable time for a real-time system.
+
+ cmd := make([]i2cCommand, 0, 8)
+ 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 errI2CNotImplemented
+}
+
+func (p Pin) pinReg() *volatile.Register32 {
+ return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4)))
+}
+
+func nextAddress(reg *volatile.Register32) *volatile.Register32 {
+ return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4))
+}
+
+// CheckDevice does an empty I2C transaction at the specified address.
+// This can be used to find out if any device with that address is
+// connected, e.g. for enumerating all devices on the bus.
+func (i2c *I2C) CheckDevice(addr uint16) bool {
+ // timeout in microseconds.
+ const timeout = 40 // 40ms is a reasonable time for a real-time system.
+
+ cmd := []i2cCommand{
+ {cmd: i2cCMD_RSTART},
+ {cmd: i2cCMD_WRITE},
+ {cmd: i2cCMD_STOP},
+ }
+ return i2c.transmit(addr, cmd, timeout) == nil
+}