mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-26 13:48:49 +08:00
add generic i2c driver
This commit is contained in:
commit
3d60ca7699
4
Makefile
4
Makefile
@ -8,9 +8,9 @@ EXAMPLES := $(shell grep -L 'joystick' $$(grep -L 'gocv' $(ALL_EXAMPLES)))
|
||||
# opencv platform currently skipped to prevent install of preconditions
|
||||
including_except := $(shell go list ./... | grep -v platforms/opencv)
|
||||
|
||||
# Run tests on nearly all directories
|
||||
# Run tests on nearly all directories without test cache
|
||||
test:
|
||||
go test -v $(including_except)
|
||||
go test -count=1 -v $(including_except)
|
||||
|
||||
# Run tests with race detection
|
||||
test_race:
|
||||
|
@ -260,6 +260,7 @@ Support for devices that use Inter-Integrated Circuit (I2C) have a shared set of
|
||||
drivers provided using the `gobot/drivers/i2c` package:
|
||||
|
||||
- [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) <=> [Drivers](https://github.com/hybridgroup/gobot/tree/master/drivers/i2c)
|
||||
- Adafruit 2x16 RGB-LCD with 5 keys
|
||||
- Adafruit Motor Hat
|
||||
- ADS1015 Analog to Digital Converter
|
||||
- ADS1115 Analog to Digital Converter
|
||||
@ -271,6 +272,7 @@ drivers provided using the `gobot/drivers/i2c` package:
|
||||
- BMP280 Barometric Pressure/Temperature/Altitude Sensor
|
||||
- BMP388 Barometric Pressure/Temperature/Altitude Sensor
|
||||
- DRV2605L Haptic Controller
|
||||
- Generic driver for read and write values to/from register address
|
||||
- Grove Digital Accelerometer
|
||||
- GrovePi Expansion Board
|
||||
- Grove RGB LCD
|
||||
@ -285,11 +287,13 @@ drivers provided using the `gobot/drivers/i2c` package:
|
||||
- MPL115A2 Barometer
|
||||
- MPU6050 Accelerometer/Gyroscope
|
||||
- PCA9685 16-channel 12-bit PWM/Servo Driver
|
||||
- PCF8591 8-bit 4xA/D & 1xD/A converter
|
||||
- SHT2x Temperature/Humidity
|
||||
- SHT3x-D Temperature/Humidity
|
||||
- SSD1306 OLED Display Controller
|
||||
- TSL2561 Digital Luminosity/Lux/Light Sensor
|
||||
- Wii Nunchuck Controller
|
||||
- YL-40 Brightness/Temperature sensor, Potentiometer, analog input, analog output Driver
|
||||
|
||||
Support for devices that use Serial Peripheral Interface (SPI) have
|
||||
a shared set of drivers provided using the `gobot/drivers/spi` package:
|
||||
|
@ -24,6 +24,7 @@ Gobot has a extensible system for connecting to hardware devices. The following
|
||||
- BMP280 Barometric Pressure/Temperature/Altitude Sensor
|
||||
- BMP388 Barometric Pressure/Temperature/Altitude Sensor
|
||||
- DRV2605L Haptic Controller
|
||||
- Generic driver for read and write values to/from register address
|
||||
- Grove Digital Accelerometer
|
||||
- GrovePi Expansion Board
|
||||
- Grove RGB LCD
|
||||
@ -44,7 +45,7 @@ Gobot has a extensible system for connecting to hardware devices. The following
|
||||
- SSD1306 OLED Display Controller
|
||||
- TSL2561 Digital Luminosity/Lux/Light Sensor
|
||||
- Wii Nunchuck Controller
|
||||
- Y-40 Brightness/Temperature sensor, Potentiometer, analog input, analog output Driver
|
||||
- YL-40 Brightness/Temperature sensor, Potentiometer, analog input, analog output Driver
|
||||
|
||||
More drivers are coming soon...
|
||||
|
||||
|
@ -22,6 +22,8 @@ var blue = castColor("blue")
|
||||
|
||||
type i2cTestAdaptor struct {
|
||||
name string
|
||||
bus int
|
||||
address int
|
||||
written []byte
|
||||
mtx sync.Mutex
|
||||
i2cConnectErr bool
|
||||
@ -153,10 +155,12 @@ func (t *i2cTestAdaptor) WriteBlockData(reg uint8, b []byte) error {
|
||||
return t.writeBytes(buf)
|
||||
}
|
||||
|
||||
func (t *i2cTestAdaptor) GetConnection( /* address */ int /* bus */, int) (connection Connection, err error) {
|
||||
func (t *i2cTestAdaptor) GetConnection(address int, bus int) (connection Connection, err error) {
|
||||
if t.i2cConnectErr {
|
||||
return nil, errors.New("Invalid i2c connection")
|
||||
}
|
||||
t.bus = bus
|
||||
t.address = address
|
||||
return t, nil
|
||||
}
|
||||
|
||||
|
81
drivers/i2c/i2c_config_test.go
Normal file
81
drivers/i2c/i2c_config_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package i2c
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
// arrange, act
|
||||
ci := NewConfig()
|
||||
// assert
|
||||
c, ok := ci.(*i2cConfig)
|
||||
if !ok {
|
||||
t.Errorf("NewConfig() should have returned a *i2cConfig")
|
||||
}
|
||||
gobottest.Assert(t, c.bus, BusNotInitialized)
|
||||
gobottest.Assert(t, c.address, AddressNotInitialized)
|
||||
}
|
||||
|
||||
func TestWithBus(t *testing.T) {
|
||||
// arrange
|
||||
c := NewConfig()
|
||||
// act
|
||||
c.WithBus(0x23)
|
||||
// assert
|
||||
gobottest.Assert(t, c.(*i2cConfig).bus, 0x23)
|
||||
}
|
||||
|
||||
func TestWithAddress(t *testing.T) {
|
||||
// arrange
|
||||
c := NewConfig()
|
||||
// act
|
||||
c.WithAddress(0x24)
|
||||
// assert
|
||||
gobottest.Assert(t, c.(*i2cConfig).address, 0x24)
|
||||
}
|
||||
|
||||
func TestGetBusOrDefaultWithBusOption(t *testing.T) {
|
||||
var tests = map[string]struct {
|
||||
init int
|
||||
bus int
|
||||
want int
|
||||
}{
|
||||
"not_initialized": {init: -1, bus: 0x25, want: 0x25},
|
||||
"initialized": {init: 0x26, bus: 0x27, want: 0x26},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
c := NewConfig()
|
||||
// act
|
||||
WithBus(tc.init)(c)
|
||||
got := c.GetBusOrDefault(tc.bus)
|
||||
// assert
|
||||
gobottest.Assert(t, got, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddressOrDefaultWithAddressOption(t *testing.T) {
|
||||
var tests = map[string]struct {
|
||||
init int
|
||||
address int
|
||||
want int
|
||||
}{
|
||||
"not_initialized": {init: -1, address: 0x28, want: 0x28},
|
||||
"initialized": {init: 0x29, address: 0x2A, want: 0x29},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
c := NewConfig()
|
||||
// act
|
||||
WithAddress(tc.init)(c)
|
||||
got := c.GetAddressOrDefault(tc.address)
|
||||
// assert
|
||||
gobottest.Assert(t, got, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
130
drivers/i2c/i2c_driver.go
Normal file
130
drivers/i2c/i2c_driver.go
Normal file
@ -0,0 +1,130 @@
|
||||
package i2c
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
)
|
||||
|
||||
// Driver implements the interface gobot.Driver.
|
||||
type Driver struct {
|
||||
name string
|
||||
defaultAddress int
|
||||
connector Connector
|
||||
connection Connection
|
||||
afterStart func() error
|
||||
beforeHalt func() error
|
||||
Config
|
||||
gobot.Commander
|
||||
mutex *sync.Mutex // mutex often needed to ensure that write-read sequences are not interrupted
|
||||
}
|
||||
|
||||
// NewDriver creates a new generic and basic i2c gobot driver.
|
||||
func NewDriver(c Connector, name string, address int, options ...func(Config)) *Driver {
|
||||
d := &Driver{
|
||||
name: gobot.DefaultName(name),
|
||||
defaultAddress: address,
|
||||
connector: c,
|
||||
afterStart: func() error { return nil },
|
||||
beforeHalt: func() error { return nil },
|
||||
Config: NewConfig(),
|
||||
Commander: gobot.NewCommander(),
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(d)
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Name returns the name of the i2c device.
|
||||
func (d *Driver) Name() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
// SetName sets the name of the i2c device.
|
||||
func (d *Driver) SetName(name string) {
|
||||
d.name = name
|
||||
}
|
||||
|
||||
// Connection returns the connection of the i2c device.
|
||||
func (d *Driver) Connection() gobot.Connection {
|
||||
return d.connector.(gobot.Connection)
|
||||
}
|
||||
|
||||
// Start initializes the i2c device.
|
||||
func (d *Driver) Start() error {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
var err error
|
||||
bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
|
||||
address := d.GetAddressOrDefault(int(d.defaultAddress))
|
||||
|
||||
if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.afterStart()
|
||||
}
|
||||
|
||||
// Halt halts the i2c device.
|
||||
func (d *Driver) Halt() error {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
if err := d.beforeHalt(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// currently there is nothing to do here for the driver
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write implements a simple write mechanism to the given register of an i2c device.
|
||||
func (d *Driver) Write(pin string, val int) error {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
register, err := driverParseRegister(pin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: create buffer from size
|
||||
// currently only one byte value is supported
|
||||
b := []byte{uint8(val)}
|
||||
return d.connection.WriteBlockData(uint8(register), b)
|
||||
}
|
||||
|
||||
// Read implements a simple read mechanism from the given register of an i2c device.
|
||||
func (d *Driver) Read(pin string) (int, error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
register, err := driverParseRegister(pin)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// TODO: create buffer from size
|
||||
// currently only one byte value is supported
|
||||
b := []byte{0}
|
||||
if err := d.connection.ReadBlockData(register, b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(b[0]), nil
|
||||
}
|
||||
|
||||
func driverParseRegister(pin string) (uint8, error) {
|
||||
register, err := strconv.ParseUint(pin, 10, 8)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Could not parse the register from given pin '%s'", pin)
|
||||
}
|
||||
return uint8(register), nil
|
||||
}
|
139
drivers/i2c/i2c_driver_test.go
Normal file
139
drivers/i2c/i2c_driver_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
package i2c
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/gobottest"
|
||||
)
|
||||
|
||||
var _ gobot.Driver = (*Driver)(nil)
|
||||
|
||||
func initDriverWithStubbedAdaptor() (*Driver, *i2cTestAdaptor) {
|
||||
a := newI2cTestAdaptor()
|
||||
d := NewDriver(a, "I2C_BASIC", 0x15)
|
||||
return d, a
|
||||
}
|
||||
|
||||
func initTestDriver() *Driver {
|
||||
d, _ := initDriverWithStubbedAdaptor()
|
||||
return d
|
||||
}
|
||||
|
||||
func TestNewDriver(t *testing.T) {
|
||||
// arrange
|
||||
a := newI2cTestAdaptor()
|
||||
// act
|
||||
var di interface{} = NewDriver(a, "I2C_BASIC", 0x15)
|
||||
// assert
|
||||
d, ok := di.(*Driver)
|
||||
if !ok {
|
||||
t.Errorf("NewDriver() should have returned a *Driver")
|
||||
}
|
||||
gobottest.Assert(t, strings.Contains(d.name, "I2C_BASIC"), true)
|
||||
gobottest.Assert(t, d.defaultAddress, 0x15)
|
||||
gobottest.Assert(t, d.connector, a)
|
||||
gobottest.Assert(t, d.connection, nil)
|
||||
gobottest.Assert(t, d.afterStart(), nil)
|
||||
gobottest.Assert(t, d.beforeHalt(), nil)
|
||||
gobottest.Refute(t, d.Config, nil)
|
||||
gobottest.Refute(t, d.Commander, nil)
|
||||
gobottest.Refute(t, d.mutex, nil)
|
||||
}
|
||||
|
||||
func TestSetName(t *testing.T) {
|
||||
// arrange
|
||||
d := initTestDriver()
|
||||
// act
|
||||
d.SetName("TESTME")
|
||||
// assert
|
||||
gobottest.Assert(t, d.Name(), "TESTME")
|
||||
}
|
||||
|
||||
func TestConnection(t *testing.T) {
|
||||
// arrange
|
||||
d := initTestDriver()
|
||||
// act, assert
|
||||
gobottest.Refute(t, d.Connection(), nil)
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
// arrange
|
||||
d, a := initDriverWithStubbedAdaptor()
|
||||
// act, assert
|
||||
gobottest.Assert(t, d.Start(), nil)
|
||||
gobottest.Assert(t, 0x15, a.address)
|
||||
}
|
||||
|
||||
func TestStartConnectError(t *testing.T) {
|
||||
// arrange
|
||||
d, a := initDriverWithStubbedAdaptor()
|
||||
a.Testi2cConnectErr(true)
|
||||
// act, assert
|
||||
gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection"))
|
||||
}
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
// arrange
|
||||
d := initTestDriver()
|
||||
// act, assert
|
||||
gobottest.Assert(t, d.Halt(), nil)
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
// arrange
|
||||
const (
|
||||
address = "82"
|
||||
wantAddress = uint8(0x52)
|
||||
value = 0x25
|
||||
)
|
||||
d, a := initDriverWithStubbedAdaptor()
|
||||
d.Start()
|
||||
// prepare all writes
|
||||
numCallsWrite := 0
|
||||
a.i2cWriteImpl = func([]byte) (int, error) {
|
||||
numCallsWrite++
|
||||
return 0, nil
|
||||
}
|
||||
// act
|
||||
err := d.Write(address, value)
|
||||
// assert
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, numCallsWrite, 1)
|
||||
gobottest.Assert(t, a.written[0], wantAddress)
|
||||
gobottest.Assert(t, a.written[1], uint8(value))
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
// arrange
|
||||
const (
|
||||
address = "83"
|
||||
wantAddress = uint8(0x53)
|
||||
want = uint8(0x44)
|
||||
)
|
||||
d, a := initDriverWithStubbedAdaptor()
|
||||
d.Start()
|
||||
// prepare all writes
|
||||
numCallsWrite := 0
|
||||
a.i2cWriteImpl = func(b []byte) (int, error) {
|
||||
numCallsWrite++
|
||||
return 0, nil
|
||||
}
|
||||
// prepare all reads
|
||||
numCallsRead := 0
|
||||
a.i2cReadImpl = func(b []byte) (int, error) {
|
||||
numCallsRead++
|
||||
b[0] = want
|
||||
return len(b), nil
|
||||
}
|
||||
// act
|
||||
val, err := d.Read(address)
|
||||
// assert
|
||||
gobottest.Assert(t, err, nil)
|
||||
gobottest.Assert(t, val, int(want))
|
||||
gobottest.Assert(t, numCallsWrite, 1)
|
||||
gobottest.Assert(t, a.written[0], wantAddress)
|
||||
gobottest.Assert(t, numCallsRead, 1)
|
||||
}
|
77
examples/digispark_driver.go
Normal file
77
examples/digispark_driver.go
Normal file
@ -0,0 +1,77 @@
|
||||
// +build example
|
||||
//
|
||||
// Do not build by default.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gobot.io/x/gobot"
|
||||
"gobot.io/x/gobot/drivers/i2c"
|
||||
"gobot.io/x/gobot/platforms/digispark"
|
||||
)
|
||||
|
||||
// This is an example for using the generic I2C driver to write and read values
|
||||
// to an i2c device. It is suitable for simple devices, e.g. EEPROM.
|
||||
// The example was tested with the EEPROM part of PCA9501.
|
||||
//
|
||||
// Procedure:
|
||||
// * write value to register (EEPROM address)
|
||||
// * read value back from register (EEPROM address) and check for differences
|
||||
func main() {
|
||||
const (
|
||||
defaultAddress = 0x7F
|
||||
myAddress = 0x44 // needs to be adjusted for your configuration
|
||||
)
|
||||
board := digispark.NewAdaptor()
|
||||
drv := i2c.NewDriver(board, "PCA9501-EEPROM", defaultAddress, i2c.WithAddress(myAddress))
|
||||
var eepromAddr uint8 = 0x00
|
||||
var register string
|
||||
var valWr uint8 = 0xFF
|
||||
var valRd int
|
||||
var err error
|
||||
|
||||
work := func() {
|
||||
gobot.Every(50*time.Millisecond, func() {
|
||||
// write a value 0-255 to EEPROM address 255-0
|
||||
eepromAddr--
|
||||
valWr++
|
||||
register = strconv.Itoa(int(eepromAddr))
|
||||
err = drv.Write(register, int(valWr))
|
||||
if err != nil {
|
||||
fmt.Println("err write:", err)
|
||||
}
|
||||
|
||||
// write process needs some time, so wait at least 5ms before read a value
|
||||
// when decreasing to much, the check below will fail
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
// read value back and check for unexpected differences
|
||||
valRd, err = drv.Read(register)
|
||||
if err != nil {
|
||||
fmt.Println("err read:", err)
|
||||
}
|
||||
if int(valWr) != valRd {
|
||||
fmt.Printf("addr: %d wr: %d differ rd: %d\n", eepromAddr, valWr, valRd)
|
||||
}
|
||||
|
||||
if eepromAddr%10 == 0 {
|
||||
fmt.Printf("addr: %d, wr: %d rd: %d\n", eepromAddr, valWr, valRd)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
robot := gobot.NewRobot("simpleDriverI2c",
|
||||
[]gobot.Connection{board},
|
||||
[]gobot.Device{drv},
|
||||
work,
|
||||
)
|
||||
|
||||
err = robot.Start()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
@ -60,17 +60,18 @@ func TestRobotWorkRegistry(t *testing.T) {
|
||||
func TestRobotAutomationFunctions(t *testing.T) {
|
||||
t.Run("Every with cancel", func(t *testing.T) {
|
||||
robot := NewRobot("testbot")
|
||||
counter := 0
|
||||
|
||||
rw := robot.Every(context.Background(), time.Millisecond*10, func() {
|
||||
_ = 1 + 1 // perform mindless computation!
|
||||
rw := robot.Every(context.Background(), time.Millisecond*100, func() {
|
||||
counter++
|
||||
})
|
||||
|
||||
time.Sleep(time.Millisecond * 25)
|
||||
time.Sleep(time.Millisecond * 225)
|
||||
rw.CallCancelFunc()
|
||||
|
||||
robot.WorkEveryWaitGroup.Wait()
|
||||
|
||||
assert.Equal(t, 2, rw.tickCount)
|
||||
assert.Equal(t, 2, counter)
|
||||
postDeleteKeys := collectStringKeysFromWorkRegistry(robot.workRegistry)
|
||||
assert.NotContains(t, postDeleteKeys, rw.id.String())
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user