1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

First cut of the GoPiGo3 driver, lacking grove peripherals.

This commit is contained in:
Ulises Flynn 2017-08-24 20:11:11 -06:00
parent ca421efda6
commit 94f5ded2d6
10 changed files with 800 additions and 7 deletions

View File

@ -57,4 +57,5 @@ deps:
go.bug.st/serial.v1 \
github.com/veandco/go-sdl2/sdl \
golang.org/x/net/websocket \
golang.org/x/exp/io/spi \
golang.org/x/sys/unix

25
drivers/spi/README.MD Normal file
View File

@ -0,0 +1,25 @@
# SPI
This package provides drivers for [spi](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus) devices. It must be used along with the [raspberry pi](https://gobot.io/documentation/platforms/raspi) adaptor that supports the needed interfaces for spi devices. This uses the experimental [spi package](https://github.com/golang/exp/tree/master/io/spi) which only works on linux systems.
## Getting Started
## Installing
```
go get -d -u gobot.io/x/gobot/...
```
## Hardware Support
Gobot has a extensible system for connecting to hardware devices. The following spi devices are currently supported:
- GoPiGo3 robotics board (built for Rapsberry Pi, but in theory it could be used in another platform)
More drivers are coming soon...
## Using A Different Bus or Address
You can set a different SPI address or SPI bus than the default when initializing your SPI drivers by using optional parameters. Here is an example:
```go
blinkm := spi.NewGoPiGo3DriverDriver(e, spi.WithBus(0), spi.WithAddress(0x10), spi.With)
```

9
drivers/spi/doc.go Normal file
View File

@ -0,0 +1,9 @@
/*
Package spi provides Gobot drivers for spi devices.
Uses "golang.org/x/exp/io/spi" for spi
Installing:
go get -d -u gobot.io/x/gobot
For further information refer to spi README:
https://github.com/hybridgroup/gobot/blob/master/drivers/spi/README.md
*/
package spi // import "gobot.io/x/gobot/drivers/spi"

View File

@ -0,0 +1,410 @@
// based on https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/gopigo3.py
// you will need to run the following if using a stock raspbian image before this library will work:
// https://www.dexterindustries.com/GoPiGo/get-started-with-the-gopigo3-raspberry-pi-robot/3-program-your-raspberry-pi-robot/python-programming-language/
package spi
import (
"gobot.io/x/gobot"
"fmt"
"math"
)
// spi address for gopigo3
const goPiGo3Address = 0x08
// register addresses for gopigo3
const (
NONE byte = iota
GET_MANUFACTURER
GET_NAME
GET_HARDWARE_VERSION
GET_FIRMWARE_VERSION
GET_ID
SET_LED
GET_VOLTAGE_5V
GET_VOLTAGE_VCC
SET_SERVO
SET_MOTOR_PWM
SET_MOTOR_POSITION
SET_MOTOR_POSITION_KP
SET_MOTOR_POSITION_KD
SET_MOTOR_DPS
SET_MOTOR_LIMITS
OFFSET_MOTOR_ENCODER
GET_MOTOR_ENCODER_LEFT
GET_MOTOR_ENCODER_RIGHT
GET_MOTOR_STATUS_LEFT
GET_MOTOR_STATUS_RIGHT
SET_GROVE_TYPE
SET_GROVE_MODE
SET_GROVE_STATE
SET_GROVE_PWM_DUTY
SET_GROVE_PWM_FREQUENCY
GET_GROVE_VALUE_1
GET_GROVE_VALUE_2
GET_GROVE_STATE_1_1
GET_GROVE_STATE_1_2
GET_GROVE_STATE_2_1
GET_GROVE_STATE_2_2
GET_GROVE_VOLTAGE_1_1
GET_GROVE_VOLTAGE_1_2
GET_GROVE_VOLTAGE_2_1
GET_GROVE_VOLTAGE_2_2
GET_GROVE_ANALOG_1_1
GET_GROVE_ANALOG_1_2
GET_GROVE_ANALOG_2_1
GET_GROVE_ANALOG_2_2
START_GROVE_I2C_1
START_GROVE_I2C_2
)
const (
WHEEL_BASE_WIDTH = 117 // distance (mm) from left wheel to right wheel. This works with the initial GPG3 prototype. Will need to be adjusted.
WHEEL_DIAMETER = 66.5 // wheel diameter (mm)
WHEEL_BASE_CIRCUMFERENCE = WHEEL_BASE_WIDTH * math.Pi // circumference of the circle the wheels will trace while turning (mm)
WHEEL_CIRCUMFERENCE = WHEEL_DIAMETER * math.Pi // circumference of the wheels (mm)
MOTOR_GEAR_RATIO = 120 // motor gear ratio
ENCODER_TICKS_PER_ROTATION = 6 // encoder ticks per motor rotation (number of magnet positions)
MOTOR_TICKS_PER_DEGREE = ((MOTOR_GEAR_RATIO * ENCODER_TICKS_PER_ROTATION) / 360.0) // encoder ticks per output shaft rotation degree
GROVE_I2C_LENGTH_LIMIT = 16
MOTOR_FLOAT = -128
GROVE_INPUT_DIGITAL = 0
GROVE_OUTPUT_DIGITAL = 1
GROVE_INPUT_DIGITAL_PULLUP = 2
GROVE_INPUT_DIGITAL_PULLDOWN = 3
GROVE_INPUT_ANALOG = 4
GROVE_OUTPUT_PWM = 5
GROVE_INPUT_ANALOG_PULLUP = 6
GROVE_INPUT_ANALOG_PULLDOWN = 7
GROVE_LOW = 0
GROVE_HIGH = 1
)
// Servo contains the address for the 2 servo ports.
type Servo byte
const (
SERVO_1 = 0x01
SERVO_2 = 0x02
)
// Motor contains the address for the left and right motors.
type Motor byte
const (
MOTOR_LEFT Motor = 0x01
MOTOR_RIGHT Motor = 0x02
)
// Led contains the addresses for all leds on the board.
type Led byte
const (
LED_EYE_RIGHT Led = 0x01
LED_EYE_LEFT Led = 0x02
LED_BLINKER_LEFT Led = 0x04
LED_BLINKER_RIGHT Led = 0x08
LED_WIFI Led = 0x80
)
// Grove contains the addresses for the optional grove devices.
type Grove byte
const (
GROVE_1_1 = 0x01
GROVE_1_2 = 0x02
GROVE_2_1 = 0x04
GROVE_2_2 = 0x08
GROVE_1 = GROVE_1_1 + GROVE_1_2
GROVE_2 = GROVE_2_1 + GROVE_2_2
)
// GroveType represents the type of a grove device.
type GroveType int
const (
CUSTOM GroveType = iota
IR_DI_REMOTE
IR_EV3_REMOTE
US
I2C
)
// GroveState contains the state of a grove device.
type GroveState int
const (
VALID_DATA GroveState = iota
NOT_CONFIGURED
CONFIGURING
NO_DATA
I2C_ERROR
)
// GoPiGo3Driver is a Gobot Driver for the GoPiGo3 board.
type GoPiGo3Driver struct {
name string
connector Connector
connection Connection
Config
sCon *SpiConnection
}
// NewGoPiGo3Driver creates a new Gobot Driver for the GoPiGo3 board.
//
// Params:
// conn Connector - the Adaptor to use with this Driver
//
// Optional params:
// spi.WithBus(int): bus to use with this driver
// spi.WithAddress(int): address to use with this driver
//
func NewGoPiGo3Driver(conn Connector, options ...func(Config)) *GoPiGo3Driver {
g := &GoPiGo3Driver{
name: gobot.DefaultName("GoPiGo3"),
connector: conn,
Config: NewConfig(),
}
for _, option := range options {
option(g)
}
return g
}
func (g *GoPiGo3Driver) initialization() (err error) {
bus := g.GetBusOrDefault(g.connector.GetSpiDefaultBus())
address := g.GetAddressOrDefault(goPiGo3Address)
mode := g.connector.GetSpiDefaultMode()
maxSpeed := g.connector.GetSpiDefaultMaxSpeed()
g.connection, err = g.connector.GetSpiConnection(bus, address, mode, maxSpeed)
g.sCon = g.connection.(*SpiConnection)
return err
}
// Name returns the name of the device.
func (g *GoPiGo3Driver) Name() string { return g.name }
// SetName sets the name of the device.
func (g *GoPiGo3Driver) SetName(n string) { g.name = n }
// Connection returns the Connection of the device.
func (g *GoPiGo3Driver) Connection() gobot.Connection { return g.connector.(gobot.Connection) }
// Halt stops the driver.
func (g *GoPiGo3Driver) Halt() (err error) { return }
// Start initializes the GoPiGo3
func (g *GoPiGo3Driver) Start() (err error) {
return g.initialization()
}
// GetManufacturerName returns the manufacturer from the firmware.
func (g *GoPiGo3Driver) GetManufacturerName() (mName string, err error) {
// read 24 bytes to get manufacturer name
response, err := g.sCon.ReadBytes(goPiGo3Address, GET_MANUFACTURER, 24)
if err != nil {
return mName, err
}
if response[3] == 0xA5 {
mf := response[4:23]
return fmt.Sprintf("%s", string(mf)), nil
}
return mName, nil
}
// GetBoardName returns the board name from the firmware.
func (g *GoPiGo3Driver) GetBoardName() (bName string, err error) {
// read 24 bytes to get board name
response, err := g.sCon.ReadBytes(goPiGo3Address, GET_NAME, 24)
if err != nil {
return bName, err
}
if response[3] == 0xA5 {
mf := response[4:23]
return fmt.Sprintf("%s", string(mf)), nil
}
return bName, nil
}
// GetHardwareVersion returns the hardware version from the firmware.
func (g *GoPiGo3Driver) GetHardwareVersion() (hVer string, err error) {
response, err := g.sCon.ReadUint32(goPiGo3Address, GET_HARDWARE_VERSION)
if err != nil {
return hVer, err
}
major := response / 1000000
minor := response / 1000 % 1000
patch := response % 1000
return fmt.Sprintf("%d.%d.%d", major, minor, patch), nil
}
// GetFirmwareVersion returns the current firmware version.
func (g *GoPiGo3Driver) GetFirmwareVersion() (fVer string, err error) {
response, err := g.sCon.ReadUint32(goPiGo3Address, GET_FIRMWARE_VERSION)
if err != nil {
return fVer, err
}
major := response / 1000000
minor := response / 1000 % 1000
patch := response % 1000
return fmt.Sprintf("%d.%d.%d", major, minor, patch), nil
}
// GetSerialNumber returns the 128-bit hardware serial number of the board.
func (g *GoPiGo3Driver) GetSerialNumber() (sNum string, err error) {
// read 20 bytes to get the serial number
response, err := g.sCon.ReadBytes(goPiGo3Address, GET_ID, 20)
if err != nil {
return sNum, err
}
if response[3] == 0xA5 {
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", response[4], response[5], response[6], response[7], response[8], response[9], response[10], response[11], response[12], response[13], response[14], response[15], response[16], response[17], response[18], response[19]), nil
}
return sNum, nil
}
// Get5vVoltage returns the current voltage on the 5v line.
func (g *GoPiGo3Driver) Get5vVoltage() (voltage float32, err error) {
val, err := g.sCon.ReadUint16(goPiGo3Address, GET_VOLTAGE_5V)
return (float32(val) / 1000.0), err
}
// GetBatteryVoltage gets the battery voltage from the main battery pack (7v-12v).
func (g *GoPiGo3Driver) GetBatteryVoltage() (voltage float32, err error) {
val, err := g.sCon.ReadUint16(goPiGo3Address, GET_VOLTAGE_VCC)
return (float32(val) / 1000.0), err
}
// SetLED sets rgb values from 0 to 255.
func (g *GoPiGo3Driver) SetLED(led Led, red, green, blue uint8) error {
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
SET_LED,
byte(led),
byte(red),
byte(green),
byte(blue),
})
}
// SetServo sets a servo's position in microseconds (0-16666).
func (g *GoPiGo3Driver) SetServo(servo Servo, us uint16) error {
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
SET_SERVO,
byte(servo),
byte(us),
})
}
// SetMotorPower from -128 to 127.
func (g *GoPiGo3Driver) SetMotorPower(motor Motor, power int8) error {
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
SET_MOTOR_PWM,
byte(motor),
byte(power),
})
}
// SetMotorPosition sets the motor's position in degrees.
func (g *GoPiGo3Driver) SetMotorPosition(motor Motor, position float64) error {
positionRaw := math.Float64bits(position * MOTOR_TICKS_PER_DEGREE)
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
SET_MOTOR_POSITION,
byte(motor),
byte((positionRaw >> 24) & 0xFF),
byte((positionRaw >> 16) & 0xFF),
byte((positionRaw >> 8) & 0xFF),
byte(positionRaw & 0xFF),
})
}
// SetMotorDps sets the motor target speed in degrees per second.
func (g *GoPiGo3Driver) SetMotorDps(motor Motor, dps float64) error {
dpsUint := math.Float64bits(dps * MOTOR_TICKS_PER_DEGREE)
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
SET_MOTOR_DPS,
byte(motor),
byte((dpsUint >> 8) & 0xFF),
byte(dpsUint & 0xFF),
})
}
// SetMotorLimits sets the speed limits for a motor.
func (g *GoPiGo3Driver) SetMotorLimits(motor Motor, power int8, dps float64) error {
dpsUint := math.Float64bits(dps * MOTOR_TICKS_PER_DEGREE)
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
SET_MOTOR_LIMITS,
byte(motor),
byte(power),
byte((dpsUint >> 8) & 0xFF),
byte(dpsUint & 0xFF),
})
}
// GetMotorStatus returns the status for the given motor.
func (g *GoPiGo3Driver) GetMotorStatus(motor Motor) (flags uint8, power uint16, encoder, dps float64, err error) {
message := GET_MOTOR_STATUS_RIGHT
if motor == MOTOR_LEFT {
message = GET_MOTOR_STATUS_LEFT
}
response, err := g.sCon.ReadBytes(goPiGo3Address, message, 12)
if err != nil {
return flags, power, encoder, dps, err
}
if response[3] == 0xA5 {
flags = uint8(response[4])
power = uint16(response[5])
if power&0x80 == 0x80 {
power = power - 0x100
}
enc := uint64(response[6]<<24 | response[7]<<16 | response[8]<<8 | response[9])
if enc&0x80000000 == 0x80000000 {
encoder = float64(enc - 0x100000000)
}
d := uint64(response[10]<<8 | response[11])
if d&0x8000 == 0x8000 {
dps = float64(d - 0x10000)
}
return flags, power, encoder / MOTOR_TICKS_PER_DEGREE, dps / MOTOR_TICKS_PER_DEGREE, nil
}
return flags, power, encoder, dps, nil
}
// GetMotorEncoder reads a motor's encoder in degrees.
func (g *GoPiGo3Driver) GetMotorEncoder(motor Motor) (encoder float64, err error) {
message := GET_MOTOR_ENCODER_RIGHT
if motor == MOTOR_LEFT {
message = GET_MOTOR_ENCODER_LEFT
}
response, err := g.sCon.ReadUint32(goPiGo3Address, message)
if err != nil {
return encoder, err
}
if response&0x80000000 == 0x80000000 {
encoder = float64(encoder - 0x100000000)
}
return encoder, nil
}
// OffsetMotorEncoder offsets a motor's encoder for calibration purposes.
func (g *GoPiGo3Driver) OffsetMotorEncoder(motor Motor, offset float64) error {
offsetUint := math.Float64bits(offset * MOTOR_TICKS_PER_DEGREE)
return g.sCon.WriteBytes([]byte{
goPiGo3Address,
OFFSET_MOTOR_ENCODER,
byte(motor),
byte((offsetUint >> 24) & 0xFF),
byte((offsetUint >> 16) & 0xFF),
byte((offsetUint >> 8) & 0xFF),
byte(offsetUint & 0xFF),
})
}
//TODO: add grove functions

141
drivers/spi/spi.go Normal file
View File

@ -0,0 +1,141 @@
package spi
import (
xspi "golang.org/x/exp/io/spi"
"time"
)
const (
// BusNotInitialized is the initial value for a bus
BusNotInitialized = -1
// AddressNotInitialized is the initial value for an address
AddressNotInitialized = -1
)
type SPIOperations interface {
Close() error
SetBitOrder(o xspi.Order) error
SetBitsPerWord(bits int) error
SetCSChange(leaveEnabled bool) error
SetDelay(t time.Duration) error
SetMaxSpeed(speed int) error
SetMode(mode xspi.Mode) error
Tx(w, r []byte) error
}
// SPIDevice is the interface to a specific spi bus
type SPIDevice interface {
SPIOperations
// SetAddress(int) error
}
// Connector lets Adaptors provide the interface for Drivers
// to get access to the SPI buses on platforms that support SPI.
type Connector interface {
// GetConnection returns a connection to device at the specified address
// and bus. Bus numbering starts at index 0, the range of valid buses is
// platform specific.
GetSpiConnection(busNum, address, mode int, maxSpeed int64) (device Connection, err error)
// GetDefaultBus returns the default SPI bus index
GetSpiDefaultBus() int
// GetDefaultMode returns the default SPI mode (0/1/2/3)
GetSpiDefaultMode() int
// GetSpiDefaultMaxSpeed returns the max SPI speed
GetSpiDefaultMaxSpeed() int64
}
// Connection is a connection to an SPI device with a specified address
// on a specific bus. Used as an alternative to the SPI interface.
// Implements SPIOperations to talk to the device, wrapping the
// calls in SetAddress to always target the specified device.
// Provided by an Adaptor by implementing the SPIConnector interface.
type Connection SPIOperations
type SpiConnection struct {
bus SPIDevice
mode int
maxSpeed int64
address int
}
// NewConnection creates and returns a new connection to a specific
// spi device on a bus and address
func NewConnection(bus SPIDevice, address int) (connection *SpiConnection) {
return &SpiConnection{bus: bus, address: address}
}
func (c *SpiConnection) Close() error {
return c.bus.Close()
}
func (c *SpiConnection) SetBitOrder(o xspi.Order) error {
return c.bus.SetBitOrder(o)
}
func (c *SpiConnection) SetBitsPerWord(bits int) error {
return c.bus.SetBitsPerWord(bits)
}
func (c *SpiConnection) SetCSChange(leaveEnabled bool) error {
return c.bus.SetCSChange(leaveEnabled)
}
func (c *SpiConnection) SetDelay(t time.Duration) error {
return c.bus.SetDelay(t)
}
func (c *SpiConnection) SetMaxSpeed(speed int) error {
return c.bus.SetMaxSpeed(speed)
}
func (c *SpiConnection) SetMode(mode xspi.Mode) error {
return c.bus.SetMode(mode)
}
func (c *SpiConnection) Tx(w, r []byte) error {
return c.bus.Tx(w, r)
}
func (c *SpiConnection) ReadBytes(address byte, msg byte, numBytes int) (val []byte, err error) {
w := make([]byte, numBytes)
w[0] = address
w[1] = msg
r := make([]byte, len(w))
err = c.Tx(w, r)
if err != nil {
return val, err
}
return r, nil
}
func (c *SpiConnection) ReadUint8(address, msg byte) (val uint8, err error) {
r, err := c.ReadBytes(address, msg, 8)
if err != nil {
return val, err
}
return uint8(r[4]) << 8, nil
}
func (c *SpiConnection) ReadUint16(address, msg byte) (val uint16, err error) {
r, err := c.ReadBytes(address, msg, 8)
if err != nil {
return val, err
}
return uint16(r[4])<<8 | uint16(r[5]), nil
}
func (c *SpiConnection) ReadUint32(address, msg byte) (val uint32, err error) {
r, err := c.ReadBytes(address, msg, 8)
if err != nil {
return val, err
}
return uint32(r[4])<<24 | uint32(r[5])<<16 | uint32(r[6])<<8 | uint32(r[7]), nil
}
func (c *SpiConnection) WriteBytes(w []byte) (err error) {
return c.Tx(w, nil)
}

70
drivers/spi/spi_config.go Normal file
View File

@ -0,0 +1,70 @@
package spi
type spiConfig struct {
bus int
address int
}
// Config is the interface which describes how a Driver can specify
// optional SPI params such as which SPI bus it wants to use.
type Config interface {
// WithBus sets which bus to use
WithBus(bus int)
// GetBusOrDefault gets which bus to use
GetBusOrDefault(def int) int
// WithAddress sets which address to use
WithAddress(address int)
// GetAddressOrDefault gets which address to use
GetAddressOrDefault(def int) int
}
// NewConfig returns a new SPI Config.
func NewConfig() Config {
return &spiConfig{bus: BusNotInitialized, address: AddressNotInitialized}
}
// WithBus sets preferred bus to use.
func (s *spiConfig) WithBus(bus int) {
s.bus = bus
}
// GetBusOrDefault returns which bus to use, either the one set using WithBus(),
// or the default value which is passed in as the one param.
func (s *spiConfig) GetBusOrDefault(d int) int {
if s.bus == BusNotInitialized {
return d
}
return s.bus
}
// WithBus sets which bus to use as a optional param.
func WithBus(bus int) func(Config) {
return func(s Config) {
s.WithBus(bus)
}
}
// WithAddress sets which address to use.
func (s *spiConfig) WithAddress(address int) {
s.address = address
}
// GetAddressOrDefault returns which address to use, either
// the one set using WithBus(), or the default value which
// is passed in as the param.
func (s *spiConfig) GetAddressOrDefault(a int) int {
if s.address == AddressNotInitialized {
return a
}
return s.address
}
// WithAddress sets which address to use as a optional param.
func WithAddress(address int) func(Config) {
return func(s Config) {
s.WithAddress(address)
}
}

32
examples/raspi_gopigo3.go Normal file
View File

@ -0,0 +1,32 @@
// +build example
//
// Do not build by default.
package main
import (
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/spi"
"gobot.io/x/gobot/platforms/raspi"
)
func main() {
r := raspi.NewAdaptor()
g := spi.NewGoPiGo3Driver(r)
g.Start()
work := func() {
g.SetLED(spi.LED_EYE_LEFT, 255, 0, 0)
g.SetLED(spi.LED_EYE_RIGHT, 0, 255, 0)
time.Sleep(1 * time.Second)
g.SetLED(spi.LED_EYE_LEFT, 0, 255, 0)
g.SetLED(spi.LED_EYE_RIGHT, 255, 0, 0)
}
robot := gobot.NewRobot("gopigo3-bot",
[]gobot.Connection{r},
[]gobot.Device{g},
work,
)
robot.Start()
}

View File

@ -1,3 +1,7 @@
// +build example
//
// Do not build by default.
package main
import (

View File

@ -12,7 +12,9 @@ import (
multierror "github.com/hashicorp/go-multierror"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/drivers/spi"
"gobot.io/x/gobot/sysfs"
xspi "golang.org/x/exp/io/spi"
)
var readFile = func() ([]byte, error) {
@ -21,13 +23,17 @@ var readFile = func() ([]byte, error) {
// Adaptor is the Gobot Adaptor for the Raspberry Pi
type Adaptor struct {
mutex *sync.Mutex
name string
revision string
digitalPins map[int]*sysfs.DigitalPin
pwmPins map[int]*PWMPin
i2cDefaultBus int
i2cBuses [2]i2c.I2cDevice
mutex *sync.Mutex
name string
revision string
digitalPins map[int]*sysfs.DigitalPin
pwmPins map[int]*PWMPin
i2cDefaultBus int
i2cBuses [2]i2c.I2cDevice
spiDefaultBus int
spiBuses [2]spi.SPIDevice
spiDefaultMode int
spiDefaultMaxSpeed int64
}
// NewAdaptor creates a Raspi Adaptor
@ -44,6 +50,9 @@ func NewAdaptor() *Adaptor {
s := strings.Split(string(v), " ")
version, _ := strconv.ParseInt("0x"+s[len(s)-1], 0, 64)
r.i2cDefaultBus = 1
r.spiDefaultBus = 1
r.spiDefaultMode = 0
r.spiDefaultMaxSpeed = 500000
if version <= 3 {
r.revision = "1"
r.i2cDefaultBus = 0
@ -106,6 +115,13 @@ func (r *Adaptor) Finalize() (err error) {
}
}
}
for _, bus := range r.spiBuses {
if bus != nil {
if e := bus.Close(); e != nil {
err = multierror.Append(err, e)
}
}
}
return
}
@ -190,6 +206,66 @@ func (r *Adaptor) GetDefaultBus() int {
return r.i2cDefaultBus
}
// GetSpiConnection returns an spi connection to a device on a specified bus.
// Valid bus number is [0..1] which corresponds to /dev/spidev0.0 through /dev/spidev0.1.
func (r *Adaptor) GetSpiConnection(busNum, address, mode int, maxSpeed int64) (connection spi.Connection, err error) {
if (busNum < 0) || (busNum > 1) {
return nil, fmt.Errorf("Bus number %d out of range", busNum)
}
device, err := r.getSpiBus(busNum, address, mode, maxSpeed)
return spi.NewConnection(device, address), err
}
func (r *Adaptor) getSpiBus(busNum, address, mode int, maxSpeed int64) (_ spi.SPIDevice, err error) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.spiBuses[busNum] == nil {
var spiMode xspi.Mode
switch mode {
case 0:
spiMode = xspi.Mode0
case 1:
spiMode = xspi.Mode1
case 2:
spiMode = xspi.Mode2
case 3:
spiMode = xspi.Mode3
default:
spiMode = xspi.Mode0
}
dev := fmt.Sprintf("/dev/spidev0.%d", busNum)
devfs := &xspi.Devfs{
Dev: dev,
Mode: spiMode,
MaxSpeed: maxSpeed,
}
if r.spiBuses[busNum] == nil {
bus, err := xspi.Open(devfs)
if err != nil {
return nil, err
}
r.spiBuses[busNum] = spi.NewConnection(bus, address)
}
}
return r.spiBuses[busNum], err
}
// GetSpiDefaultBus returns the default spi bus for this platform.
func (r *Adaptor) GetSpiDefaultBus() int {
return r.spiDefaultBus
}
// GetSpiDefaultMode returns the default spi mode for this platform.
func (r *Adaptor) GetSpiDefaultMode() int {
return r.spiDefaultMode
}
// GetDefaultMaxSpeed returns the default spi bus for this platform.
func (r *Adaptor) GetSpiDefaultMaxSpeed() int64 {
return r.spiDefaultMaxSpeed
}
// PWMPin returns a raspi.PWMPin which provides the sysfs.PWMPinner interface
func (r *Adaptor) PWMPin(pin string) (raspiPWMPin sysfs.PWMPinner, err error) {
i, err := r.translatePin(pin)

View File

@ -8,6 +8,7 @@ import (
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/gpio"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/drivers/spi"
"gobot.io/x/gobot/gobottest"
"gobot.io/x/gobot/sysfs"
"runtime"
@ -24,6 +25,7 @@ var _ gpio.ServoWriter = (*Adaptor)(nil)
var _ sysfs.DigitalPinnerProvider = (*Adaptor)(nil)
var _ sysfs.PWMPinnerProvider = (*Adaptor)(nil)
var _ i2c.Connector = (*Adaptor)(nil)
var _ spi.Connector = (*Adaptor)(nil)
func initTestAdaptor() *Adaptor {
readFile = func() ([]byte, error) {
@ -91,6 +93,8 @@ func TestAdaptorFinalize(t *testing.T) {
"/dev/pi-blaster",
"/dev/i2c-1",
"/dev/i2c-0",
"/dev/spidev0.0",
"/dev/spidev0.1",
})
sysfs.SetFilesystem(fs)
@ -178,6 +182,27 @@ func TestAdaptorI2c(t *testing.T) {
gobottest.Assert(t, a.GetDefaultBus(), 1)
}
func TestAdaptorSPI(t *testing.T) {
a := initTestAdaptor()
fs := sysfs.NewMockFilesystem([]string{
"/dev/spidev0.1",
})
sysfs.SetFilesystem(fs)
sysfs.SetSyscall(&sysfs.MockSyscall{})
// TODO: find a better way to test this
_, err := a.GetSpiConnection(1, 0xC0, 0, 500000)
gobottest.Assert(t, err, err)
gobottest.Assert(t, a.GetSpiDefaultBus(), 1)
gobottest.Assert(t, a.GetSpiDefaultMode(), 0)
gobottest.Assert(t, a.GetSpiDefaultMaxSpeed(), int64(500000))
_, err = a.GetSpiConnection(1, 0xC0, 1, 500000)
_, err = a.GetSpiConnection(1, 0xC0, 2, 500000)
_, err = a.GetSpiConnection(1, 0xC0, 3, 500000)
_, err = a.GetSpiConnection(1, 0xC0, 5, 500000)
_, err = a.GetSpiConnection(4, 0xC0, 0, 500000)
}
func TestAdaptorDigitalPinConcurrency(t *testing.T) {
oldProcs := runtime.GOMAXPROCS(0)