mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-27 13:48:56 +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 \
|
go.bug.st/serial.v1 \
|
||||||
github.com/veandco/go-sdl2/sdl \
|
github.com/veandco/go-sdl2/sdl \
|
||||||
golang.org/x/net/websocket \
|
golang.org/x/net/websocket \
|
||||||
|
golang.org/x/exp/io/spi \
|
||||||
golang.org/x/sys/unix
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -12,7 +12,9 @@ import (
|
|||||||
multierror "github.com/hashicorp/go-multierror"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
"gobot.io/x/gobot"
|
"gobot.io/x/gobot"
|
||||||
"gobot.io/x/gobot/drivers/i2c"
|
"gobot.io/x/gobot/drivers/i2c"
|
||||||
|
"gobot.io/x/gobot/drivers/spi"
|
||||||
"gobot.io/x/gobot/sysfs"
|
"gobot.io/x/gobot/sysfs"
|
||||||
|
xspi "golang.org/x/exp/io/spi"
|
||||||
)
|
)
|
||||||
|
|
||||||
var readFile = func() ([]byte, error) {
|
var readFile = func() ([]byte, error) {
|
||||||
@ -21,13 +23,17 @@ var readFile = func() ([]byte, error) {
|
|||||||
|
|
||||||
// Adaptor is the Gobot Adaptor for the Raspberry Pi
|
// Adaptor is the Gobot Adaptor for the Raspberry Pi
|
||||||
type Adaptor struct {
|
type Adaptor struct {
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
name string
|
name string
|
||||||
revision string
|
revision string
|
||||||
digitalPins map[int]*sysfs.DigitalPin
|
digitalPins map[int]*sysfs.DigitalPin
|
||||||
pwmPins map[int]*PWMPin
|
pwmPins map[int]*PWMPin
|
||||||
i2cDefaultBus int
|
i2cDefaultBus int
|
||||||
i2cBuses [2]i2c.I2cDevice
|
i2cBuses [2]i2c.I2cDevice
|
||||||
|
spiDefaultBus int
|
||||||
|
spiBuses [2]spi.SPIDevice
|
||||||
|
spiDefaultMode int
|
||||||
|
spiDefaultMaxSpeed int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdaptor creates a Raspi Adaptor
|
// NewAdaptor creates a Raspi Adaptor
|
||||||
@ -44,6 +50,9 @@ func NewAdaptor() *Adaptor {
|
|||||||
s := strings.Split(string(v), " ")
|
s := strings.Split(string(v), " ")
|
||||||
version, _ := strconv.ParseInt("0x"+s[len(s)-1], 0, 64)
|
version, _ := strconv.ParseInt("0x"+s[len(s)-1], 0, 64)
|
||||||
r.i2cDefaultBus = 1
|
r.i2cDefaultBus = 1
|
||||||
|
r.spiDefaultBus = 1
|
||||||
|
r.spiDefaultMode = 0
|
||||||
|
r.spiDefaultMaxSpeed = 500000
|
||||||
if version <= 3 {
|
if version <= 3 {
|
||||||
r.revision = "1"
|
r.revision = "1"
|
||||||
r.i2cDefaultBus = 0
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +206,66 @@ func (r *Adaptor) GetDefaultBus() int {
|
|||||||
return r.i2cDefaultBus
|
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
|
// PWMPin returns a raspi.PWMPin which provides the sysfs.PWMPinner interface
|
||||||
func (r *Adaptor) PWMPin(pin string) (raspiPWMPin sysfs.PWMPinner, err error) {
|
func (r *Adaptor) PWMPin(pin string) (raspiPWMPin sysfs.PWMPinner, err error) {
|
||||||
i, err := r.translatePin(pin)
|
i, err := r.translatePin(pin)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"gobot.io/x/gobot"
|
"gobot.io/x/gobot"
|
||||||
"gobot.io/x/gobot/drivers/gpio"
|
"gobot.io/x/gobot/drivers/gpio"
|
||||||
"gobot.io/x/gobot/drivers/i2c"
|
"gobot.io/x/gobot/drivers/i2c"
|
||||||
|
"gobot.io/x/gobot/drivers/spi"
|
||||||
"gobot.io/x/gobot/gobottest"
|
"gobot.io/x/gobot/gobottest"
|
||||||
"gobot.io/x/gobot/sysfs"
|
"gobot.io/x/gobot/sysfs"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -24,6 +25,7 @@ var _ gpio.ServoWriter = (*Adaptor)(nil)
|
|||||||
var _ sysfs.DigitalPinnerProvider = (*Adaptor)(nil)
|
var _ sysfs.DigitalPinnerProvider = (*Adaptor)(nil)
|
||||||
var _ sysfs.PWMPinnerProvider = (*Adaptor)(nil)
|
var _ sysfs.PWMPinnerProvider = (*Adaptor)(nil)
|
||||||
var _ i2c.Connector = (*Adaptor)(nil)
|
var _ i2c.Connector = (*Adaptor)(nil)
|
||||||
|
var _ spi.Connector = (*Adaptor)(nil)
|
||||||
|
|
||||||
func initTestAdaptor() *Adaptor {
|
func initTestAdaptor() *Adaptor {
|
||||||
readFile = func() ([]byte, error) {
|
readFile = func() ([]byte, error) {
|
||||||
@ -91,6 +93,8 @@ func TestAdaptorFinalize(t *testing.T) {
|
|||||||
"/dev/pi-blaster",
|
"/dev/pi-blaster",
|
||||||
"/dev/i2c-1",
|
"/dev/i2c-1",
|
||||||
"/dev/i2c-0",
|
"/dev/i2c-0",
|
||||||
|
"/dev/spidev0.0",
|
||||||
|
"/dev/spidev0.1",
|
||||||
})
|
})
|
||||||
|
|
||||||
sysfs.SetFilesystem(fs)
|
sysfs.SetFilesystem(fs)
|
||||||
@ -178,6 +182,27 @@ func TestAdaptorI2c(t *testing.T) {
|
|||||||
gobottest.Assert(t, a.GetDefaultBus(), 1)
|
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) {
|
func TestAdaptorDigitalPinConcurrency(t *testing.T) {
|
||||||
|
|
||||||
oldProcs := runtime.GOMAXPROCS(0)
|
oldProcs := runtime.GOMAXPROCS(0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user