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:
parent
ca421efda6
commit
94f5ded2d6
1
Makefile
1
Makefile
@ -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
25
drivers/spi/README.MD
Normal 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
9
drivers/spi/doc.go
Normal 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"
|
410
drivers/spi/gopigo3_driver.go
Normal file
410
drivers/spi/gopigo3_driver.go
Normal 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
141
drivers/spi/spi.go
Normal 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
70
drivers/spi/spi_config.go
Normal 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
32
examples/raspi_gopigo3.go
Normal 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()
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
// +build example
|
||||
//
|
||||
// Do not build by default.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user