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

up2: initial work on support for UP2 board

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram 2017-12-13 09:55:16 +01:00
parent 3f8bd5afd4
commit 715ab299e8
9 changed files with 557 additions and 0 deletions

View File

@ -219,6 +219,7 @@ Gobot has a extensible system for connecting to hardware devices. The following
- [Sphero Ollie](http://www.sphero.com/ollie) <=> [Package](https://github.com/hybridgroup/gobot/tree/master/platforms/sphero/ollie)
- [Sphero SPRK+](http://www.sphero.com/sprk-plus) <=> [Package](https://github.com/hybridgroup/gobot/tree/master/platforms/sphero/sprkplus)
- [Tinker Board](https://www.asus.com/us/Single-Board-Computer/Tinker-Board/) <=> [Package](https://github.com/hybridgroup/gobot/tree/master/platforms/tinkerboard)
- [UP2](http://www.up-board.org/upsquared/) <=> [Package](https://github.com/hybridgroup/gobot/tree/master/platforms/upboard/up2)
Support for many devices that use General Purpose Input/Output (GPIO) have
a shared set of drivers provided using the `gobot/drivers/gpio` package:

View File

@ -0,0 +1,6 @@
# Upboard
This package contains the Gobot adaptor for the [UP2]() IoT platforms.
This package currently supports the following hardware:
- UP2 (Squared)

View File

@ -0,0 +1,13 @@
Copyright (c) 2014-2017 The Hybrid Group
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,41 @@
# Tinker Board
The UP2 Board is a single board SoC computer based on the Intel Apollo Lake processor. It has built-in GPIO, PWM, SPI, and I2C interfaces.
For more info about the UP2 Board, go to [http://www.up-board.org/upsquared/](http://www.up-board.org/upsquared/).
## How to Install
We recommend updating to the latest Ubuntu when using the UP2.
You would normally install Go and Gobot on your workstation. Once installed, cross compile your program on your workstation, transfer the final executable to your UP2, and run the program on the UP2 as documented here.
```
go get -d -u gobot.io/x/gobot/...
```
## How to Use
The pin numbering used by your Gobot program should match the way your board is labeled right on the board itself.
```go
r := up2.NewAdaptor()
led := gpio.NewLedDriver(r, "13")
```
## How to Connect
### Compiling
Compile your Gobot program on your workstation like this:
```bash
$ GOARCH=386 GOOS=linux go build examples/up2_blink.go
```
Once you have compiled your code, you can you can upload your program and execute it on the UP2 from your workstation using the `scp` and `ssh` commands like this:
```bash
$ scp up2_blink ubuntu@192.168.1.xxx:/home/ubuntu/
$ ssh -t ubuntu@192.168.1.xxx "./up2_blink"
```

View File

@ -0,0 +1,239 @@
package up2
import (
"errors"
"fmt"
"sync"
multierror "github.com/hashicorp/go-multierror"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/sysfs"
)
type sysfsPin struct {
pin int
pwmPin int
}
// Adaptor represents a Gobot Adaptor for the Upboard UP2
type Adaptor struct {
name string
pinmap map[string]sysfsPin
digitalPins map[int]*sysfs.DigitalPin
pwmPins map[int]*sysfs.PWMPin
i2cBuses [2]i2c.I2cDevice
mutex *sync.Mutex
}
// NewAdaptor creates a UP2 Adaptor
func NewAdaptor() *Adaptor {
c := &Adaptor{
name: gobot.DefaultName("UP2"),
mutex: &sync.Mutex{},
}
c.setPins()
return c
}
// Name returns the name of the Adaptor
func (c *Adaptor) Name() string { return c.name }
// SetName sets the name of the Adaptor
func (c *Adaptor) SetName(n string) { c.name = n }
// Connect initializes the board
func (c *Adaptor) Connect() (err error) {
return nil
}
// Finalize closes connection to board and pins
func (c *Adaptor) Finalize() (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
for _, pin := range c.digitalPins {
if pin != nil {
if e := pin.Unexport(); e != nil {
err = multierror.Append(err, e)
}
}
}
for _, pin := range c.pwmPins {
if pin != nil {
if errs := pin.Enable(false); errs != nil {
err = multierror.Append(err, errs)
}
if errs := pin.Unexport(); errs != nil {
err = multierror.Append(err, errs)
}
}
}
for _, bus := range c.i2cBuses {
if bus != nil {
if e := bus.Close(); e != nil {
err = multierror.Append(err, e)
}
}
}
return
}
// DigitalRead reads digital value from the specified pin.
func (c *Adaptor) DigitalRead(pin string) (val int, err error) {
sysfsPin, err := c.DigitalPin(pin, sysfs.IN)
if err != nil {
return
}
return sysfsPin.Read()
}
// DigitalWrite writes digital value to the specified pin.
func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
sysfsPin, err := c.DigitalPin(pin, sysfs.OUT)
if err != nil {
return err
}
return sysfsPin.Write(int(val))
}
// PwmWrite writes a PWM signal to the specified pin
func (c *Adaptor) PwmWrite(pin string, val byte) (err error) {
pwmPin, err := c.PWMPin(pin)
if err != nil {
return
}
period, err := pwmPin.Period()
if err != nil {
return err
}
duty := gobot.FromScale(float64(val), 0, 255.0)
return pwmPin.SetDutyCycle(uint32(float64(period) * duty))
}
// TODO: take into account the actual period setting, not just assume default
const pwmPeriod = 10000000
// ServoWrite writes a servo signal to the specified pin
func (c *Adaptor) ServoWrite(pin string, angle byte) (err error) {
pwmPin, err := c.PWMPin(pin)
if err != nil {
return
}
// 0.5 ms => -90
// 1.5 ms => 0
// 2.0 ms => 90
const minDuty = 100 * 0.0005 * pwmPeriod
const maxDuty = 100 * 0.0020 * pwmPeriod
duty := uint32(gobot.ToScale(gobot.FromScale(float64(angle), 0, 180), minDuty, maxDuty))
return pwmPin.SetDutyCycle(duty)
}
// DigitalPin returns matched digitalPin for specified values
func (c *Adaptor) DigitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPinner, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
i, err := c.translatePin(pin)
if err != nil {
return
}
if c.digitalPins[i] == nil {
c.digitalPins[i] = sysfs.NewDigitalPin(i)
if err = c.digitalPins[i].Export(); err != nil {
return
}
}
if err = c.digitalPins[i].Direction(dir); err != nil {
return
}
return c.digitalPins[i], nil
}
// PWMPin returns matched pwmPin for specified pin number
func (c *Adaptor) PWMPin(pin string) (sysfsPin sysfs.PWMPinner, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
i, err := c.translatePwmPin(pin)
if err != nil {
return nil, err
}
if i == -1 {
return nil, errors.New("Not a PWM pin")
}
if c.pwmPins[i] == nil {
newPin := sysfs.NewPWMPin(i)
if err = newPin.Export(); err != nil {
return
}
// Make sure pwm is disabled when setting polarity
if err = newPin.Enable(false); err != nil {
return
}
if err = newPin.InvertPolarity(false); err != nil {
return
}
if err = newPin.Enable(true); err != nil {
return
}
if err = newPin.SetPeriod(10000000); err != nil {
return
}
c.pwmPins[i] = newPin
}
sysfsPin = c.pwmPins[i]
return
}
// GetConnection returns a connection to a device on a specified bus.
// Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1.
func (c *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if (bus < 0) || (bus > 1) {
return nil, fmt.Errorf("Bus number %d out of range", bus)
}
if c.i2cBuses[bus] == nil {
c.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus))
}
return i2c.NewConnection(c.i2cBuses[bus], address), err
}
// GetDefaultBus returns the default i2c bus for this platform
func (c *Adaptor) GetDefaultBus() int {
return 0
}
func (c *Adaptor) setPins() {
c.digitalPins = make(map[int]*sysfs.DigitalPin)
c.pwmPins = make(map[int]*sysfs.PWMPin)
c.pinmap = fixedPins
}
func (c *Adaptor) translatePin(pin string) (i int, err error) {
if val, ok := c.pinmap[pin]; ok {
i = val.pin
} else {
err = errors.New("Not a valid pin")
}
return
}
func (c *Adaptor) translatePwmPin(pin string) (i int, err error) {
if val, ok := c.pinmap[pin]; ok {
i = val.pwmPin
} else {
err = errors.New("Not a valid pin")
}
return
}

View File

@ -0,0 +1,192 @@
package up2
import (
"errors"
"strings"
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/gpio"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/gobottest"
"gobot.io/x/gobot/sysfs"
)
// make sure that this Adaptor fullfills all the required interfaces
var _ gobot.Adaptor = (*Adaptor)(nil)
var _ gpio.DigitalReader = (*Adaptor)(nil)
var _ gpio.DigitalWriter = (*Adaptor)(nil)
var _ gpio.PwmWriter = (*Adaptor)(nil)
var _ gpio.ServoWriter = (*Adaptor)(nil)
var _ sysfs.DigitalPinnerProvider = (*Adaptor)(nil)
var _ sysfs.PWMPinnerProvider = (*Adaptor)(nil)
var _ i2c.Connector = (*Adaptor)(nil)
func initTestUP2Adaptor() (*Adaptor, *sysfs.MockFilesystem) {
a := NewAdaptor()
fs := sysfs.NewMockFilesystem([]string{
"/sys/class/gpio/export",
"/sys/class/gpio/unexport",
"/sys/class/gpio/gpio462/value",
"/sys/class/gpio/gpio462/direction",
"/sys/class/gpio/gpio432/value",
"/sys/class/gpio/gpio432/direction",
"/sys/class/pwm/pwmchip0/export",
"/sys/class/pwm/pwmchip0/unexport",
"/sys/class/pwm/pwmchip0/pwm0/enable",
"/sys/class/pwm/pwmchip0/pwm0/period",
"/sys/class/pwm/pwmchip0/pwm0/duty_cycle",
"/sys/class/pwm/pwmchip0/pwm0/polarity",
})
sysfs.SetFilesystem(fs)
return a, fs
}
func TestUP2AdaptorName(t *testing.T) {
a := NewAdaptor()
gobottest.Assert(t, strings.HasPrefix(a.Name(), "UP2"), true)
a.SetName("NewName")
gobottest.Assert(t, a.Name(), "NewName")
}
func TestUP2AdaptorDigitalIO(t *testing.T) {
a, fs := initTestUP2Adaptor()
a.Connect()
a.DigitalWrite("7", 1)
gobottest.Assert(t, fs.Files["/sys/class/gpio/gpio462/value"].Contents, "1")
fs.Files["/sys/class/gpio/gpio432/value"].Contents = "1"
i, _ := a.DigitalRead("13")
gobottest.Assert(t, i, 1)
gobottest.Assert(t, a.DigitalWrite("99", 1), errors.New("Not a valid pin"))
gobottest.Assert(t, a.Finalize(), nil)
}
func TestAdaptorDigitalWriteError(t *testing.T) {
a, fs := initTestUP2Adaptor()
fs.WithWriteError = true
err := a.DigitalWrite("7", 1)
gobottest.Assert(t, err, errors.New("write error"))
}
func TestAdaptorDigitalReadWriteError(t *testing.T) {
a, fs := initTestUP2Adaptor()
fs.WithWriteError = true
_, err := a.DigitalRead("7")
gobottest.Assert(t, err, errors.New("write error"))
}
func TestUP2AdaptorI2c(t *testing.T) {
a := NewAdaptor()
fs := sysfs.NewMockFilesystem([]string{
"/dev/i2c-0",
})
sysfs.SetFilesystem(fs)
sysfs.SetSyscall(&sysfs.MockSyscall{})
con, err := a.GetConnection(0xff, 0)
gobottest.Assert(t, err, nil)
con.Write([]byte{0x00, 0x01})
data := []byte{42, 42}
con.Read(data)
gobottest.Assert(t, data, []byte{0x00, 0x01})
gobottest.Assert(t, a.Finalize(), nil)
}
func TestUP2AdaptorInvalidPWMPin(t *testing.T) {
a, _ := initTestUP2Adaptor()
a.Connect()
err := a.PwmWrite("666", 42)
gobottest.Refute(t, err, nil)
err = a.ServoWrite("666", 120)
gobottest.Refute(t, err, nil)
err = a.PwmWrite("3", 42)
gobottest.Refute(t, err, nil)
err = a.ServoWrite("3", 120)
gobottest.Refute(t, err, nil)
}
func TestUP2AdaptorPWM(t *testing.T) {
a, fs := initTestUP2Adaptor()
err := a.PwmWrite("32", 100)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, fs.Files["/sys/class/pwm/pwmchip0/export"].Contents, "0")
gobottest.Assert(t, fs.Files["/sys/class/pwm/pwmchip0/pwm0/enable"].Contents, "1")
gobottest.Assert(t, fs.Files["/sys/class/pwm/pwmchip0/pwm0/duty_cycle"].Contents, "3921568")
gobottest.Assert(t, fs.Files["/sys/class/pwm/pwmchip0/pwm0/polarity"].Contents, "normal")
err = a.ServoWrite("32", 0)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, fs.Files["/sys/class/pwm/pwmchip0/pwm0/duty_cycle"].Contents, "500000")
err = a.ServoWrite("32", 180)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, fs.Files["/sys/class/pwm/pwmchip0/pwm0/duty_cycle"].Contents, "2000000")
gobottest.Assert(t, a.Finalize(), nil)
}
func TestUP2AdaptorPwmWriteError(t *testing.T) {
a, fs := initTestUP2Adaptor()
fs.WithWriteError = true
err := a.PwmWrite("32", 100)
gobottest.Assert(t, err, errors.New("write error"))
}
func TestUP2AdaptorPwmReadError(t *testing.T) {
a, fs := initTestUP2Adaptor()
fs.WithReadError = true
err := a.PwmWrite("32", 100)
gobottest.Assert(t, err, errors.New("read error"))
}
func TestUP2DefaultBus(t *testing.T) {
a, _ := initTestUP2Adaptor()
gobottest.Assert(t, a.GetDefaultBus(), 0)
}
func TestUP2GetConnectionInvalidBus(t *testing.T) {
a, _ := initTestUP2Adaptor()
_, err := a.GetConnection(0x01, 99)
gobottest.Assert(t, err, errors.New("Bus number 99 out of range"))
}
func TestUP2FinalizeErrorAfterGPIO(t *testing.T) {
a, fs := initTestUP2Adaptor()
gobottest.Assert(t, a.Connect(), nil)
gobottest.Assert(t, a.DigitalWrite("7", 1), nil)
fs.WithWriteError = true
err := a.Finalize()
gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
}
func TestUP2FinalizeErrorAfterPWM(t *testing.T) {
a, fs := initTestUP2Adaptor()
gobottest.Assert(t, a.Connect(), nil)
gobottest.Assert(t, a.PwmWrite("32", 1), nil)
fs.WithWriteError = true
err := a.Finalize()
gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
}

View File

@ -0,0 +1,7 @@
/*
Package up2 contains the Gobot adaptor for the Upboard UP2.
For further information refer to the UP2 README:
https://github.com/hybridgroup/gobot/blob/master/platforms/upboard/up2/README.md
*/
package up2 // import "gobot.io/x/gobot/platforms/upboard/up2"

View File

@ -0,0 +1,48 @@
package up2
var fixedPins = map[string]sysfsPin{
"7": {
pin: 462, // GPIO4
pwmPin: -1,
},
"13": {
pin: 432, // GPIO27
pwmPin: -1,
},
"15": {
pin: 431, // GPIO22
pwmPin: -1,
},
"16": {
pin: 471, // PWM3
pwmPin: 3,
},
"18": {
pin: 405, // GPIO24
pwmPin: -1,
},
"22": {
pin: 402, // GPIO25
pwmPin: -1,
},
"29": {
pin: 430, // GPIO5
pwmPin: -1,
},
"31": {
pin: 404, // GPIO6
pwmPin: -1,
},
"32": {
pin: 468, // PWM0
pwmPin: 0,
},
"33": {
pin: 469, // PWM1
pwmPin: 1,
},
"37": {
pin: 403, // GPIO26
pwmPin: -1,
},
}

View File

@ -0,0 +1,10 @@
/*
Package upboard contains Gobot adaptors for the Upboard SoC boards.
This package currently supports the following hardware:
- UP2 (Squared)
For further information refer to the Upboard README:
https://gobot.io/x/gobot/blob/master/platforms/upboard/README.md
*/
package upboard