Skip to content

Commit f845b46

Browse files
aykevldeadprogram
authored andcommitted
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).
1 parent 35adbff commit f845b46

9 files changed

+87
-17
lines changed

src/machine/i2c.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ var (
3737
errI2COverflow = errors.New("I2C receive buffer overflow")
3838
errI2COverread = errors.New("I2C transmit buffer overflow")
3939
errI2CNotImplemented = errors.New("I2C operation not yet implemented")
40+
errI2CNoDevices = errors.New("i2c: bus has no devices") // simulator only
41+
errI2CMultipleDevices = errors.New("i2c: bus has address conflict") // simulator only
42+
errI2CWrongAddress = errors.New("i2c: bus has devices but none with this address") // simulator only
4043
)
4144

4245
// I2CTargetEvent reflects events on the I2C bus
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build !baremetal && (arduino || arduino_nano)
2+
3+
package machine
4+
5+
var I2C0 = &I2C{Bus: 0, PinsSDA: []Pin{PC4}, PinsSCL: []Pin{PC5}}

src/machine/machine_atsamd21_simulator.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,21 @@ var TCC2 = &timerType{
4343
{PA01, PA13, PA17}, // channel 1
4444
},
4545
}
46+
47+
var (
48+
// According to the datasheet, only some pins have I2C support. However it
49+
// looks like many boards just use any SERCOM I2C instance, even if the
50+
// datasheet says those don't support I2C. I guess they do work in practice,
51+
// then.
52+
// These are:
53+
// * PA00/PA01 for the Adafruit Circuit Playground Express (I2C1, SERCOM1).
54+
// * PB02/PB03 for the Adafruit Circuit Playground Express (I2C0, SERCOM5).
55+
// * PB08/PB09 for the Arduino Nano 33 IoT (I2C0, SERCOM4).
56+
// https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf
57+
sercomI2CM0 = &I2C{Bus: 0, PinsSDA: []Pin{PA08}, PinsSCL: []Pin{PA09}}
58+
sercomI2CM1 = &I2C{Bus: 1, PinsSDA: []Pin{PA00, PA16}, PinsSCL: []Pin{PA01, PA17}}
59+
sercomI2CM2 = &I2C{Bus: 2, PinsSDA: []Pin{PA08, PA12}, PinsSCL: []Pin{PA09, PA13}}
60+
sercomI2CM3 = &I2C{Bus: 3, PinsSDA: []Pin{PA16, PA22}, PinsSCL: []Pin{PA17, PA23}}
61+
sercomI2CM4 = &I2C{Bus: 4, PinsSDA: []Pin{PA12, PB08, PB12}, PinsSCL: []Pin{PA13, PB09, PB13}}
62+
sercomI2CM5 = &I2C{Bus: 5, PinsSDA: []Pin{PA22, PB02, PB16, PB30}, PinsSCL: []Pin{PA23, PB03, PB17, PB31}}
63+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build !baremetal && hifive1b
2+
3+
package machine
4+
5+
var I2C0 = &I2C{Bus: 0, PinsSDA: []Pin{P12}, PinsSCL: []Pin{P13}}

src/machine/machine_generic.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package machine
44

55
import (
66
"crypto/rand"
7+
"errors"
8+
"slices"
79
)
810

911
// Dummy machine package that calls out to external functions.
@@ -227,7 +229,9 @@ func adcRead(pin Pin) uint16
227229

228230
// I2C is a generic implementation of the Inter-IC communication protocol.
229231
type I2C struct {
230-
Bus uint8
232+
Bus uint8
233+
PinsSCL []Pin
234+
PinsSDA []Pin
231235
}
232236

233237
// I2CConfig is used to store config info for I2C.
@@ -239,7 +243,21 @@ type I2CConfig struct {
239243

240244
// Configure is intended to setup the I2C interface.
241245
func (i2c *I2C) Configure(config I2CConfig) error {
242-
i2cConfigure(i2c.Bus, config.SCL, config.SDA)
246+
if i2c.PinsSCL != nil {
247+
matchSCL := slices.Index(i2c.PinsSCL, config.SCL) >= 0
248+
matchSDA := slices.Index(i2c.PinsSDA, config.SDA) >= 0
249+
if !matchSCL && !matchSDA {
250+
return errors.New("i2c: SCL and SDA pins are incorrect for this I2C instance")
251+
} else if !matchSCL {
252+
return errors.New("i2c: SCL pin is incorrect for this I2C instance")
253+
} else if !matchSDA {
254+
return errors.New("i2c: SDA pin is incorrect for this I2C instance")
255+
}
256+
}
257+
if config.Frequency == 0 {
258+
config.Frequency = 100 * KHz
259+
}
260+
i2cConfigure(i2c.Bus, config.SCL, config.SDA, config.Frequency)
243261
return nil
244262
}
245263

@@ -261,19 +279,29 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
261279
rptr = &r[0]
262280
rlen = len(r)
263281
}
264-
i2cTransfer(i2c.Bus, wptr, wlen, rptr, rlen)
265-
// TODO: do something with the returned error code.
266-
return nil
282+
errCode := i2cTransfer(i2c.Bus, addr, wptr, wlen, rptr, rlen)
283+
switch errCode {
284+
case 0:
285+
return nil
286+
case 1:
287+
return errI2CNoDevices
288+
case 2:
289+
return errI2CMultipleDevices
290+
case 3:
291+
return errI2CWrongAddress
292+
default:
293+
return errI2CBusError // unknown error code
294+
}
267295
}
268296

269297
//export __tinygo_i2c_configure
270-
func i2cConfigure(bus uint8, scl Pin, sda Pin)
298+
func i2cConfigure(bus uint8, scl Pin, sda Pin, frequency uint32)
271299

272300
//export __tinygo_i2c_set_baud_rate
273301
func i2cSetBaudRate(bus uint8, br uint32)
274302

275303
//export __tinygo_i2c_transfer
276-
func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int
304+
func i2cTransfer(bus uint8, addr uint16, w *byte, wlen int, r *byte, rlen int) int
277305

278306
type UART struct {
279307
Bus uint8
@@ -336,15 +364,6 @@ var (
336364
sercomUSART4 = UART{4}
337365
sercomUSART5 = UART{5}
338366

339-
sercomI2CM0 = &I2C{0}
340-
sercomI2CM1 = &I2C{1}
341-
sercomI2CM2 = &I2C{2}
342-
sercomI2CM3 = &I2C{3}
343-
sercomI2CM4 = &I2C{4}
344-
sercomI2CM5 = &I2C{5}
345-
sercomI2CM6 = &I2C{6}
346-
sercomI2CM7 = &I2C{7}
347-
348367
sercomSPIM0 = &SPI{0}
349368
sercomSPIM1 = &SPI{1}
350369
sercomSPIM2 = &SPI{2}

src/machine/machine_generic_peripherals.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@ var (
1010
UART1 = hardwareUART1
1111
SPI0 = &SPI{0}
1212
SPI1 = &SPI{1}
13-
I2C0 = &I2C{0}
1413
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build !baremetal && (microbit || pca10031 || hw_651)
2+
3+
package machine
4+
5+
var I2C0 = &I2C{Bus: 0}
6+
var I2C1 = &I2C{Bus: 1}

src/machine/machine_nrf52840_simulator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,6 @@ var PWM3 = &timerType{
5858
nil, // channel 3
5959
},
6060
}
61+
62+
var I2C0 = &I2C{Bus: 0}
63+
var I2C1 = &I2C{Bus: 1}

src/machine/machine_rp2040_simulator.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,15 @@ var PWM7 = &timerType{
9494
{GPIO15}, // channel B (1)
9595
},
9696
}
97+
98+
var I2C0 = &I2C{
99+
Bus: 0,
100+
PinsSCL: []Pin{GPIO1, GPIO5, GPIO9, GPIO13, GPIO17, GPIO21},
101+
PinsSDA: []Pin{GPIO0, GPIO4, GPIO8, GPIO12, GPIO16, GPIO20},
102+
}
103+
104+
var I2C1 = &I2C{
105+
Bus: 0,
106+
PinsSCL: []Pin{GPIO3, GPIO7, GPIO11, GPIO15, GPIO19, GPIO27},
107+
PinsSDA: []Pin{GPIO2, GPIO6, GPIO10, GPIO14, GPIO18, GPIO26},
108+
}

0 commit comments

Comments
 (0)