machine: make I2C usable in the simulator

This fixes/improves a few issues with I2C support:

  * Validate I2C pins, so only pins that are supported by the hardware
    can be used (similar to how it's done with PWM).
  * Add address to Tx API (without it, the simulator can't really
    simulate I2C).
  * Add frequency when configuring. Not currently used, but might be
    useful in the future and adding it now avoids possibly breaking
    changes.

This is a breaking change, but since the simulator doesn't support I2C
yet that seems fine to me. (It does in my local changes, but those need
to be cleaned up before I can push them).
This commit is contained in:
Ayke van Laethem
2025-07-07 13:40:44 +02:00
committed by Ron Evans
parent 35adbffa54
commit f845b469b1
9 changed files with 87 additions and 17 deletions
+3
View File
@@ -37,6 +37,9 @@ var (
errI2COverflow = errors.New("I2C receive buffer overflow")
errI2COverread = errors.New("I2C transmit buffer overflow")
errI2CNotImplemented = errors.New("I2C operation not yet implemented")
errI2CNoDevices = errors.New("i2c: bus has no devices") // simulator only
errI2CMultipleDevices = errors.New("i2c: bus has address conflict") // simulator only
errI2CWrongAddress = errors.New("i2c: bus has devices but none with this address") // simulator only
)
// I2CTargetEvent reflects events on the I2C bus
@@ -0,0 +1,5 @@
//go:build !baremetal && (arduino || arduino_nano)
package machine
var I2C0 = &I2C{Bus: 0, PinsSDA: []Pin{PC4}, PinsSCL: []Pin{PC5}}
+18
View File
@@ -43,3 +43,21 @@ var TCC2 = &timerType{
{PA01, PA13, PA17}, // channel 1
},
}
var (
// According to the datasheet, only some pins have I2C support. However it
// looks like many boards just use any SERCOM I2C instance, even if the
// datasheet says those don't support I2C. I guess they do work in practice,
// then.
// These are:
// * PA00/PA01 for the Adafruit Circuit Playground Express (I2C1, SERCOM1).
// * PB02/PB03 for the Adafruit Circuit Playground Express (I2C0, SERCOM5).
// * PB08/PB09 for the Arduino Nano 33 IoT (I2C0, SERCOM4).
// https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf
sercomI2CM0 = &I2C{Bus: 0, PinsSDA: []Pin{PA08}, PinsSCL: []Pin{PA09}}
sercomI2CM1 = &I2C{Bus: 1, PinsSDA: []Pin{PA00, PA16}, PinsSCL: []Pin{PA01, PA17}}
sercomI2CM2 = &I2C{Bus: 2, PinsSDA: []Pin{PA08, PA12}, PinsSCL: []Pin{PA09, PA13}}
sercomI2CM3 = &I2C{Bus: 3, PinsSDA: []Pin{PA16, PA22}, PinsSCL: []Pin{PA17, PA23}}
sercomI2CM4 = &I2C{Bus: 4, PinsSDA: []Pin{PA12, PB08, PB12}, PinsSCL: []Pin{PA13, PB09, PB13}}
sercomI2CM5 = &I2C{Bus: 5, PinsSDA: []Pin{PA22, PB02, PB16, PB30}, PinsSCL: []Pin{PA23, PB03, PB17, PB31}}
)
+5
View File
@@ -0,0 +1,5 @@
//go:build !baremetal && hifive1b
package machine
var I2C0 = &I2C{Bus: 0, PinsSDA: []Pin{P12}, PinsSCL: []Pin{P13}}
+35 -16
View File
@@ -4,6 +4,8 @@ package machine
import (
"crypto/rand"
"errors"
"slices"
)
// Dummy machine package that calls out to external functions.
@@ -227,7 +229,9 @@ func adcRead(pin Pin) uint16
// I2C is a generic implementation of the Inter-IC communication protocol.
type I2C struct {
Bus uint8
Bus uint8
PinsSCL []Pin
PinsSDA []Pin
}
// I2CConfig is used to store config info for I2C.
@@ -239,7 +243,21 @@ type I2CConfig struct {
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
i2cConfigure(i2c.Bus, config.SCL, config.SDA)
if i2c.PinsSCL != nil {
matchSCL := slices.Index(i2c.PinsSCL, config.SCL) >= 0
matchSDA := slices.Index(i2c.PinsSDA, config.SDA) >= 0
if !matchSCL && !matchSDA {
return errors.New("i2c: SCL and SDA pins are incorrect for this I2C instance")
} else if !matchSCL {
return errors.New("i2c: SCL pin is incorrect for this I2C instance")
} else if !matchSDA {
return errors.New("i2c: SDA pin is incorrect for this I2C instance")
}
}
if config.Frequency == 0 {
config.Frequency = 100 * KHz
}
i2cConfigure(i2c.Bus, config.SCL, config.SDA, config.Frequency)
return nil
}
@@ -261,19 +279,29 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
rptr = &r[0]
rlen = len(r)
}
i2cTransfer(i2c.Bus, wptr, wlen, rptr, rlen)
// TODO: do something with the returned error code.
return nil
errCode := i2cTransfer(i2c.Bus, addr, wptr, wlen, rptr, rlen)
switch errCode {
case 0:
return nil
case 1:
return errI2CNoDevices
case 2:
return errI2CMultipleDevices
case 3:
return errI2CWrongAddress
default:
return errI2CBusError // unknown error code
}
}
//export __tinygo_i2c_configure
func i2cConfigure(bus uint8, scl Pin, sda Pin)
func i2cConfigure(bus uint8, scl Pin, sda Pin, frequency uint32)
//export __tinygo_i2c_set_baud_rate
func i2cSetBaudRate(bus uint8, br uint32)
//export __tinygo_i2c_transfer
func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int
func i2cTransfer(bus uint8, addr uint16, w *byte, wlen int, r *byte, rlen int) int
type UART struct {
Bus uint8
@@ -336,15 +364,6 @@ var (
sercomUSART4 = UART{4}
sercomUSART5 = UART{5}
sercomI2CM0 = &I2C{0}
sercomI2CM1 = &I2C{1}
sercomI2CM2 = &I2C{2}
sercomI2CM3 = &I2C{3}
sercomI2CM4 = &I2C{4}
sercomI2CM5 = &I2C{5}
sercomI2CM6 = &I2C{6}
sercomI2CM7 = &I2C{7}
sercomSPIM0 = &SPI{0}
sercomSPIM1 = &SPI{1}
sercomSPIM2 = &SPI{2}
@@ -10,5 +10,4 @@ var (
UART1 = hardwareUART1
SPI0 = &SPI{0}
SPI1 = &SPI{1}
I2C0 = &I2C{0}
)
+6
View File
@@ -0,0 +1,6 @@
//go:build !baremetal && (microbit || pca10031 || hw_651)
package machine
var I2C0 = &I2C{Bus: 0}
var I2C1 = &I2C{Bus: 1}
@@ -58,3 +58,6 @@ var PWM3 = &timerType{
nil, // channel 3
},
}
var I2C0 = &I2C{Bus: 0}
var I2C1 = &I2C{Bus: 1}
+12
View File
@@ -94,3 +94,15 @@ var PWM7 = &timerType{
{GPIO15}, // channel B (1)
},
}
var I2C0 = &I2C{
Bus: 0,
PinsSCL: []Pin{GPIO1, GPIO5, GPIO9, GPIO13, GPIO17, GPIO21},
PinsSDA: []Pin{GPIO0, GPIO4, GPIO8, GPIO12, GPIO16, GPIO20},
}
var I2C1 = &I2C{
Bus: 0,
PinsSCL: []Pin{GPIO3, GPIO7, GPIO11, GPIO15, GPIO19, GPIO27},
PinsSDA: []Pin{GPIO2, GPIO6, GPIO10, GPIO14, GPIO18, GPIO26},
}