mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
i2csoft: add support for software I2C
This commit is contained in:
@@ -205,12 +205,14 @@ endif
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/mqttsub/
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/i2csoft/adt7410/
|
||||
@md5sum ./build/test.hex
|
||||
|
||||
DRIVERS = $(wildcard */)
|
||||
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
|
||||
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
|
||||
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \
|
||||
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd
|
||||
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft
|
||||
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
|
||||
|
||||
unit-test:
|
||||
|
||||
@@ -80,6 +80,7 @@ The following 67 devices are supported.
|
||||
| [HC-SR04 Ultrasonic distance sensor](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf) | GPIO |
|
||||
| [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO/I2C |
|
||||
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
|
||||
| [software I2C driver](https://www.ti.com/lit/an/slva704/slva704.pdf) | GPIO |
|
||||
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
||||
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
|
||||
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/adt7410"
|
||||
"tinygo.org/x/drivers/i2csoft"
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c := i2csoft.New(machine.SCL_PIN, machine.SDA_PIN)
|
||||
i2c.Configure(i2csoft.I2CConfig{
|
||||
Frequency: 400e3,
|
||||
})
|
||||
|
||||
sensor := adt7410.New(i2c)
|
||||
sensor.Configure()
|
||||
|
||||
for {
|
||||
temp := sensor.ReadTempF()
|
||||
fmt.Printf("temperature: %f\r\n", temp)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package i2csoft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
)
|
||||
|
||||
// I2C is an I2C implementation by Software. Since it is implemented by
|
||||
// software, it can be used with microcontrollers that do not have I2C
|
||||
// function. This is not efficient but works around broken or missing drivers.
|
||||
type I2C struct {
|
||||
scl machine.Pin
|
||||
sda machine.Pin
|
||||
nack bool
|
||||
baudrate uint32
|
||||
}
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
type I2CConfig struct {
|
||||
Frequency uint32
|
||||
SCL machine.Pin
|
||||
SDA machine.Pin
|
||||
}
|
||||
|
||||
var (
|
||||
errSI2CAckExpected = errors.New("I2C error: expected ACK not NACK")
|
||||
)
|
||||
|
||||
// New returns the i2csoft driver. For the arguments, specify the pins to be
|
||||
// used as SCL and SDA. As I2C is implemented in software, any GPIO pin can be
|
||||
// specified.
|
||||
func New(sclPin, sdaPin machine.Pin) *I2C {
|
||||
return &I2C{
|
||||
scl: sclPin,
|
||||
sda: sdaPin,
|
||||
baudrate: 100 * 1e3,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
func (i2c *I2C) Configure(config I2CConfig) error {
|
||||
// Default I2C bus speed is 100 kHz.
|
||||
if config.Frequency != 0 {
|
||||
i2c.baudrate = config.Frequency
|
||||
}
|
||||
|
||||
// This exists for compatibility with machine.I2CConfig. SCL and SDA must
|
||||
// be set at the same time. Because Pin(0) is sometimes set, it is not
|
||||
// checked for 0.
|
||||
if config.SCL != config.SDA {
|
||||
i2c.scl = config.SCL
|
||||
i2c.sda = config.SDA
|
||||
}
|
||||
|
||||
i2c.SetBaudRate(config.Frequency)
|
||||
|
||||
// enable pins
|
||||
i2c.sda.High()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
i2c.scl.High()
|
||||
i2c.scl.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the I2C.
|
||||
func (i2c *I2C) SetBaudRate(br uint32) {
|
||||
i2c.baudrate = br
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
var err error
|
||||
i2c.nack = false
|
||||
if len(w) != 0 {
|
||||
// send start/address for write
|
||||
i2c.sendAddress(addr, true)
|
||||
|
||||
// wait until transmission complete
|
||||
|
||||
// ACK received (0: ACK, 1: NACK)
|
||||
if i2c.nack {
|
||||
i2c.signalStop()
|
||||
return errSI2CAckExpected
|
||||
}
|
||||
|
||||
// write data
|
||||
for _, b := range w {
|
||||
err = i2c.WriteByte(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = i2c.signalStop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
// send start/address for read
|
||||
i2c.sendAddress(addr, false)
|
||||
|
||||
// wait transmission complete
|
||||
|
||||
// ACK received (0: ACK, 1: NACK)
|
||||
if i2c.nack {
|
||||
i2c.signalStop()
|
||||
return errSI2CAckExpected
|
||||
}
|
||||
|
||||
// read first byte
|
||||
r[0] = i2c.readByte()
|
||||
for i := 1; i < len(r); i++ {
|
||||
// Send an ACK
|
||||
|
||||
i2c.signalRead()
|
||||
|
||||
// Read data and send the ACK
|
||||
r[i] = i2c.readByte()
|
||||
}
|
||||
|
||||
// Send NACK to end transmission
|
||||
i2c.sendNack()
|
||||
|
||||
err = i2c.signalStop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte to the I2C bus.
|
||||
func (i2c *I2C) WriteByte(data byte) error {
|
||||
// Send data byte
|
||||
i2c.scl.Low()
|
||||
i2c.sda.High()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
i2c.wait()
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
i2c.scl.Low()
|
||||
if ((data >> (7 - i)) & 1) == 1 {
|
||||
i2c.sda.High()
|
||||
} else {
|
||||
i2c.sda.Low()
|
||||
}
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
}
|
||||
|
||||
i2c.scl.Low()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
|
||||
i2c.nack = i2c.sda.Get()
|
||||
|
||||
i2c.wait()
|
||||
|
||||
// wait until transmission successful
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendAddress sends the address and start signal
|
||||
func (i2c *I2C) sendAddress(address uint16, write bool) error {
|
||||
data := (address << 1)
|
||||
if !write {
|
||||
data |= 1 // set read flag
|
||||
}
|
||||
|
||||
i2c.scl.High()
|
||||
i2c.sda.Low()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
for i := 0; i < 8; i++ {
|
||||
i2c.scl.Low()
|
||||
if ((data >> (7 - i)) & 1) == 1 {
|
||||
i2c.sda.High()
|
||||
} else {
|
||||
i2c.sda.Low()
|
||||
}
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
}
|
||||
|
||||
i2c.scl.Low()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
|
||||
i2c.nack = i2c.sda.Get()
|
||||
|
||||
i2c.wait()
|
||||
|
||||
// wait until bus ready
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c *I2C) signalStop() error {
|
||||
i2c.scl.Low()
|
||||
i2c.sda.Low()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.sda.High()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c *I2C) signalRead() error {
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.Low()
|
||||
i2c.sda.Low()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c *I2C) readByte() byte {
|
||||
var data byte
|
||||
for i := 0; i < 8; i++ {
|
||||
i2c.scl.Low()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.High()
|
||||
if i2c.sda.Get() {
|
||||
data |= 1 << (7 - i)
|
||||
}
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (i2c *I2C) sendNack() error {
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.Low()
|
||||
i2c.sda.High()
|
||||
i2c.sda.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
i2c.scl.High()
|
||||
i2c.wait()
|
||||
i2c.wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister transmits first the register and then the data to the
|
||||
// peripheral device.
|
||||
//
|
||||
// Many I2C-compatible devices are organized in terms of registers. This method
|
||||
// is a shortcut to easily write to such registers. Also, it only works for
|
||||
// devices with 7-bit addresses, which is the vast majority.
|
||||
func (i2c *I2C) WriteRegister(address uint8, register uint8, data []byte) error {
|
||||
buf := make([]uint8, len(data)+1)
|
||||
buf[0] = register
|
||||
copy(buf[1:], data)
|
||||
return i2c.Tx(uint16(address), buf, nil)
|
||||
}
|
||||
|
||||
// ReadRegister transmits the register, restarts the connection as a read
|
||||
// operation, and reads the response.
|
||||
//
|
||||
// Many I2C-compatible devices are organized in terms of registers. This method
|
||||
// is a shortcut to easily read such registers. Also, it only works for devices
|
||||
// with 7-bit addresses, which is the vast majority.
|
||||
func (i2c *I2C) ReadRegister(address uint8, register uint8, data []byte) error {
|
||||
return i2c.Tx(uint16(address), []byte{register}, data)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//go:build atsamd51 || atsame5x
|
||||
// +build atsamd51 atsame5x
|
||||
|
||||
package i2csoft
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
)
|
||||
|
||||
func (i2c *I2C) wait() {
|
||||
// atsamd51
|
||||
// 1 : about 388kHz
|
||||
// 17 : about 97kHz
|
||||
|
||||
wait := 1
|
||||
if i2c.baudrate < 400*1e3 {
|
||||
wait = 17
|
||||
}
|
||||
|
||||
for i := 0; i < wait; i++ {
|
||||
arm.Asm(`nop`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//go:build esp32
|
||||
// +build esp32
|
||||
|
||||
package i2csoft
|
||||
|
||||
import (
|
||||
"device"
|
||||
)
|
||||
|
||||
func (i2c *I2C) wait() {
|
||||
// atsamd51
|
||||
// 10 : about 387kHz
|
||||
// 56 : about 99kHz
|
||||
|
||||
wait := 10
|
||||
if i2c.baudrate < 400*1e3 {
|
||||
wait = 56
|
||||
}
|
||||
|
||||
for i := 0; i < wait; i++ {
|
||||
device.Asm(`nop`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//go:build !esp32 && !atsamd51 && !atsame5x
|
||||
// +build !esp32,!atsamd51,!atsame5x
|
||||
|
||||
package i2csoft
|
||||
|
||||
import (
|
||||
"device"
|
||||
)
|
||||
|
||||
func (i2c *I2C) wait() {
|
||||
// atsamd21 @ 48MHz
|
||||
// 1 : about 360kHz
|
||||
// 24 : about 96kHz
|
||||
wait := 1
|
||||
if i2c.baudrate < 400*1e3 {
|
||||
wait = 19
|
||||
}
|
||||
|
||||
for i := 0; i < wait; i++ {
|
||||
device.Asm(`nop`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user