Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Ryan Cox 2015-08-24 22:01:00 -07:00
commit 9166155d1d
18 changed files with 1503 additions and 53 deletions

View File

@ -5,8 +5,9 @@ branches:
- go-rpi
go:
- 1.2
- 1.2.1
- 1.2.2
- 1.3.3
- 1.4
- tip
script:

View File

@ -5,3 +5,4 @@ Marco P. Monteiro <marco_monteiro@sv.comcast.com>
Nikesh Vora <nikesh.voratp@gmail.com>
Steve Beaulac <steve@beaulac.me>
Al S-M <asm@rndm.io>
Ben Delarre <ben@delarre.net>

View File

@ -4,7 +4,7 @@
It allows you to start your hardware hack on easily available hobby boards (like the Raspberry Pi, BeagleBone Black, etc.) by giving you staight forward access to the board's capabilities as well as a plethora of **sensors** (like accelerometers, gyroscopes, thermometers, etc.) and **controllers** (PWM generators, digital-to-analog convertors) for which we have written drivers. And when things get serious, you dont have to throw away the code. You carry forward the effort onto more custom designed boards where the HAL abstraction of EMBD will save you precious time.
Development supported and sponsored by [**ThoughtWorks**](http://www.thoughtworks.com/)
Development supported and sponsored by [**SoStronk**](https://www.sostronk.com) and [**ThoughtWorks**](http://www.thoughtworks.com/)
Also, you might be interested in: [Why Golang?](https://github.com/kidoman/embd/wiki/Why-Go)
@ -66,9 +66,10 @@ Join the [mailing list](https://groups.google.com/forum/#!forum/go-embd)
## Platforms Supported
* [RaspberryPi](http://www.raspberrypi.org/)
* [RaspberryPi](http://www.raspberrypi.org/) (including [A+](http://www.raspberrypi.org/products/model-a-plus/) and [B+](http://www.raspberrypi.org/products/model-b-plus/))
* [RaspberryPi 2](http://www.raspberrypi.org/)
* [BeagleBone Black](http://beagleboard.org/Products/BeagleBone%20Black)
* [Intel Galileo](http://www.intel.com/content/www/us/en/do-it-yourself/galileo-maker-quark-board.html) **coming soon**
* [Intel Edison](http://www.intel.com/content/www/us/en/do-it-yourself/galileo-maker-quark-board.html) **coming soon**
* [Radxa](http://radxa.com/) **coming soon**
* [Cubietruck](http://www.cubietruck.com/) **coming soon**
* Bring Your Own **coming soon**
@ -224,7 +225,7 @@ platforms.
* **US020** Ultrasonic proximity sensor [Documentation](http://godoc.org/github.com/kidoman/embd/sensor/us020), [Product Page](http://www.digibay.in/sensor/object-detection-and-proximity?product_id=239)
* **BH1750FVI** Luminosity sensor [Documentation](http://godoc.org/github.com/kidoman/embd/sensor/us020), [Datasheet](http://www.elechouse.com/elechouse/images/product/Digital%20light%20Sensor/bh1750fvi-e.pdf)
* **BH1750FVI** Luminosity sensor [Documentation](http://godoc.org/github.com/kidoman/embd/sensor/bh1750fvi), [Datasheet](http://www.elechouse.com/elechouse/images/product/Digital%20light%20Sensor/bh1750fvi-e.pdf)
## Interfaces

View File

@ -0,0 +1,648 @@
/*
Package hd44780 allows controlling an HD44780-compatible character LCD
controller. Currently the library is write-only and does not support
reading from the display controller.
Resources
This library is based three other HD44780 libraries:
Adafruit https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_CharLCD/Adafruit_CharLCD.py
hwio https://github.com/mrmorphic/hwio/blob/master/devices/hd44780/hd44780_i2c.go
LiquidCrystal https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp
*/
package hd44780
import (
"time"
"github.com/golang/glog"
"github.com/kidoman/embd"
)
type entryMode byte
type displayMode byte
type functionMode byte
// RowAddress defines the cursor (DDRAM) address of the first column of each row, up to 4 rows.
// You must use the RowAddress value that matches the number of columns on your character display
// for the SetCursor function to work correctly.
type RowAddress [4]byte
var (
// RowAddress16Col are row addresses for a 16-column display
RowAddress16Col RowAddress = [4]byte{0x00, 0x40, 0x10, 0x50}
// RowAddress20Col are row addresses for a 20-column display
RowAddress20Col RowAddress = [4]byte{0x00, 0x40, 0x14, 0x54}
)
// BacklightPolarity is used to set the polarity of the backlight switch, either positive or negative.
type BacklightPolarity bool
const (
// Negative indicates that the backlight is active-low and must have a logical low value to enable.
Negative BacklightPolarity = false
// Positive indicates that the backlight is active-high and must have a logical high value to enable.
Positive BacklightPolarity = true
writeDelay = 37 * time.Microsecond
pulseDelay = 1 * time.Microsecond
clearDelay = 1520 * time.Microsecond
// Initialize display
lcdInit byte = 0x33 // 00110011
lcdInit4bit byte = 0x32 // 00110010
// Commands
lcdClearDisplay byte = 0x01 // 00000001
lcdReturnHome byte = 0x02 // 00000010
lcdCursorShift byte = 0x10 // 00010000
lcdSetCGRamAddr byte = 0x40 // 01000000
lcdSetDDRamAddr byte = 0x80 // 10000000
// Cursor and display move flags
lcdCursorMove byte = 0x00 // 00000000
lcdDisplayMove byte = 0x08 // 00001000
lcdMoveLeft byte = 0x00 // 00000000
lcdMoveRight byte = 0x04 // 00000100
// Entry mode flags
lcdSetEntryMode entryMode = 0x04 // 00000100
lcdEntryDecrement entryMode = 0x00 // 00000000
lcdEntryIncrement entryMode = 0x02 // 00000010
lcdEntryShiftOff entryMode = 0x00 // 00000000
lcdEntryShiftOn entryMode = 0x01 // 00000001
// Display mode flags
lcdSetDisplayMode displayMode = 0x08 // 00001000
lcdDisplayOff displayMode = 0x00 // 00000000
lcdDisplayOn displayMode = 0x04 // 00000100
lcdCursorOff displayMode = 0x00 // 00000000
lcdCursorOn displayMode = 0x02 // 00000010
lcdBlinkOff displayMode = 0x00 // 00000000
lcdBlinkOn displayMode = 0x01 // 00000001
// Function mode flags
lcdSetFunctionMode functionMode = 0x20 // 00100000
lcd4BitMode functionMode = 0x00 // 00000000
lcd8BitMode functionMode = 0x10 // 00010000
lcd1Line functionMode = 0x00 // 00000000
lcd2Line functionMode = 0x08 // 00001000
lcd5x8Dots functionMode = 0x00 // 00000000
lcd5x10Dots functionMode = 0x04 // 00000100
)
// HD44780 represents an HD44780-compatible character LCD controller.
type HD44780 struct {
Connection
eMode entryMode
dMode displayMode
fMode functionMode
rowAddr RowAddress
}
// NewGPIO creates a new HD44780 connected by a 4-bit GPIO bus.
func NewGPIO(
rs, en, d4, d5, d6, d7, backlight interface{},
blPolarity BacklightPolarity,
rowAddr RowAddress,
modes ...ModeSetter,
) (*HD44780, error) {
pinKeys := []interface{}{rs, en, d4, d5, d6, d7, backlight}
pins := [7]embd.DigitalPin{}
for idx, key := range pinKeys {
if key == nil {
continue
}
var digitalPin embd.DigitalPin
if pin, ok := key.(embd.DigitalPin); ok {
digitalPin = pin
} else {
var err error
digitalPin, err = embd.NewDigitalPin(key)
if err != nil {
glog.V(1).Infof("hd44780: error creating digital pin %+v: %s", key, err)
return nil, err
}
}
pins[idx] = digitalPin
}
for _, pin := range pins {
if pin == nil {
continue
}
err := pin.SetDirection(embd.Out)
if err != nil {
glog.Errorf("hd44780: error setting pin %+v to out direction: %s", pin, err)
return nil, err
}
}
return New(
NewGPIOConnection(
pins[0],
pins[1],
pins[2],
pins[3],
pins[4],
pins[5],
pins[6],
blPolarity),
rowAddr,
modes...,
)
}
// NewI2C creates a new HD44780 connected by an I²C bus.
func NewI2C(
i2c embd.I2CBus,
addr byte,
pinMap I2CPinMap,
rowAddr RowAddress,
modes ...ModeSetter,
) (*HD44780, error) {
return New(NewI2CConnection(i2c, addr, pinMap), rowAddr, modes...)
}
// New creates a new HD44780 connected by a Connection bus.
func New(bus Connection, rowAddr RowAddress, modes ...ModeSetter) (*HD44780, error) {
controller := &HD44780{
Connection: bus,
eMode: 0x00,
dMode: 0x00,
fMode: 0x00,
rowAddr: rowAddr,
}
err := controller.lcdInit()
if err != nil {
return nil, err
}
err = controller.SetMode(append(DefaultModes, modes...)...)
if err != nil {
return nil, err
}
return controller, nil
}
func (controller *HD44780) lcdInit() error {
glog.V(2).Info("hd44780: initializing display")
err := controller.WriteInstruction(lcdInit)
if err != nil {
return err
}
glog.V(2).Info("hd44780: initializing display in 4-bit mode")
return controller.WriteInstruction(lcdInit4bit)
}
// DefaultModes are the default initialization modes for an HD44780.
// ModeSetters passed in to a constructor will override these default values.
var DefaultModes []ModeSetter = []ModeSetter{
FourBitMode,
OneLine,
Dots5x8,
EntryIncrement,
EntryShiftOff,
DisplayOn,
CursorOff,
BlinkOff,
}
// ModeSetter defines a function used for setting modes on an HD44780.
// ModeSetters must be used with the SetMode function or in a constructor.
type ModeSetter func(*HD44780)
// EntryDecrement is a ModeSetter that sets the HD44780 to entry decrement mode.
func EntryDecrement(hd *HD44780) { hd.eMode &= ^lcdEntryIncrement }
// EntryIncrement is a ModeSetter that sets the HD44780 to entry increment mode.
func EntryIncrement(hd *HD44780) { hd.eMode |= lcdEntryIncrement }
// EntryShiftOff is a ModeSetter that sets the HD44780 to entry shift off mode.
func EntryShiftOff(hd *HD44780) { hd.eMode &= ^lcdEntryShiftOn }
// EntryShiftOn is a ModeSetter that sets the HD44780 to entry shift on mode.
func EntryShiftOn(hd *HD44780) { hd.eMode |= lcdEntryShiftOn }
// DisplayOff is a ModeSetter that sets the HD44780 to display off mode.
func DisplayOff(hd *HD44780) { hd.dMode &= ^lcdDisplayOn }
// DisplayOn is a ModeSetter that sets the HD44780 to display on mode.
func DisplayOn(hd *HD44780) { hd.dMode |= lcdDisplayOn }
// CursorOff is a ModeSetter that sets the HD44780 to cursor off mode.
func CursorOff(hd *HD44780) { hd.dMode &= ^lcdCursorOn }
// CursorOn is a ModeSetter that sets the HD44780 to cursor on mode.
func CursorOn(hd *HD44780) { hd.dMode |= lcdCursorOn }
// BlinkOff is a ModeSetter that sets the HD44780 to cursor blink off mode.
func BlinkOff(hd *HD44780) { hd.dMode &= ^lcdBlinkOn }
// BlinkOn is a ModeSetter that sets the HD44780 to cursor blink on mode.
func BlinkOn(hd *HD44780) { hd.dMode |= lcdBlinkOn }
// FourBitMode is a ModeSetter that sets the HD44780 to 4-bit bus mode.
func FourBitMode(hd *HD44780) { hd.fMode &= ^lcd8BitMode }
// EightBitMode is a ModeSetter that sets the HD44780 to 8-bit bus mode.
func EightBitMode(hd *HD44780) { hd.fMode |= lcd8BitMode }
// OneLine is a ModeSetter that sets the HD44780 to 1-line display mode.
func OneLine(hd *HD44780) { hd.fMode &= ^lcd2Line }
// TwoLine is a ModeSetter that sets the HD44780 to 2-line display mode.
func TwoLine(hd *HD44780) { hd.fMode |= lcd2Line }
// Dots5x8 is a ModeSetter that sets the HD44780 to 5x8-pixel character mode.
func Dots5x8(hd *HD44780) { hd.fMode &= ^lcd5x10Dots }
// Dots5x10 is a ModeSetter that sets the HD44780 to 5x10-pixel character mode.
func Dots5x10(hd *HD44780) { hd.fMode |= lcd5x10Dots }
// EntryIncrementEnabled returns true if entry increment mode is enabled.
func (hd *HD44780) EntryIncrementEnabled() bool { return hd.eMode&lcdEntryIncrement > 0 }
// EntryShiftEnabled returns true if entry shift mode is enabled.
func (hd *HD44780) EntryShiftEnabled() bool { return hd.eMode&lcdEntryShiftOn > 0 }
// DisplayEnabled returns true if the display is on.
func (hd *HD44780) DisplayEnabled() bool { return hd.dMode&lcdDisplayOn > 0 }
// CursorEnabled returns true if the cursor is on.
func (hd *HD44780) CursorEnabled() bool { return hd.dMode&lcdCursorOn > 0 }
// BlinkEnabled returns true if the cursor blink mode is enabled.
func (hd *HD44780) BlinkEnabled() bool { return hd.dMode&lcdBlinkOn > 0 }
// EightBitModeEnabled returns true if 8-bit bus mode is enabled and false if 4-bit
// bus mode is enabled.
func (hd *HD44780) EightBitModeEnabled() bool { return hd.fMode&lcd8BitMode > 0 }
// TwoLineEnabled returns true if 2-line display mode is enabled and false if 1-line
// display mode is enabled.
func (hd *HD44780) TwoLineEnabled() bool { return hd.fMode&lcd2Line > 0 }
// Dots5x10Enabled returns true if 5x10-pixel characters are enabled.
func (hd *HD44780) Dots5x10Enabled() bool { return hd.fMode&lcd5x8Dots > 0 }
// SetModes modifies the entry mode, display mode, and function mode with the
// given mode setter functions.
func (hd *HD44780) SetMode(modes ...ModeSetter) error {
for _, m := range modes {
m(hd)
}
functions := []func() error{
func() error { return hd.setEntryMode() },
func() error { return hd.setDisplayMode() },
func() error { return hd.setFunctionMode() },
}
for _, f := range functions {
err := f()
if err != nil {
return err
}
}
return nil
}
func (hd *HD44780) setEntryMode() error {
return hd.WriteInstruction(byte(lcdSetEntryMode | hd.eMode))
}
func (hd *HD44780) setDisplayMode() error {
return hd.WriteInstruction(byte(lcdSetDisplayMode | hd.dMode))
}
func (hd *HD44780) setFunctionMode() error {
return hd.WriteInstruction(byte(lcdSetFunctionMode | hd.fMode))
}
// DisplayOff sets the display mode to off.
func (hd *HD44780) DisplayOff() error {
DisplayOff(hd)
return hd.setDisplayMode()
}
// DisplayOn sets the display mode to on.
func (hd *HD44780) DisplayOn() error {
DisplayOn(hd)
return hd.setDisplayMode()
}
// CursorOff turns the cursor off.
func (hd *HD44780) CursorOff() error {
CursorOff(hd)
return hd.setDisplayMode()
}
// CursorOn turns the cursor on.
func (hd *HD44780) CursorOn() error {
CursorOn(hd)
return hd.setDisplayMode()
}
// BlinkOff sets cursor blink mode off.
func (hd *HD44780) BlinkOff() error {
BlinkOff(hd)
return hd.setDisplayMode()
}
// BlinkOn sets cursor blink mode on.
func (hd *HD44780) BlinkOn() error {
BlinkOn(hd)
return hd.setDisplayMode()
}
// ShiftLeft shifts the cursor and all characters to the left.
func (hd *HD44780) ShiftLeft() error {
return hd.WriteInstruction(lcdCursorShift | lcdDisplayMove | lcdMoveLeft)
}
// ShiftRight shifts the cursor and all characters to the right.
func (hd *HD44780) ShiftRight() error {
return hd.WriteInstruction(lcdCursorShift | lcdDisplayMove | lcdMoveRight)
}
// Home moves the cursor and all characters to the home position.
func (hd *HD44780) Home() error {
err := hd.WriteInstruction(lcdReturnHome)
time.Sleep(clearDelay)
return err
}
// Clear clears the display and mode settings sets the cursor to the home position.
func (hd *HD44780) Clear() error {
err := hd.WriteInstruction(lcdClearDisplay)
if err != nil {
return err
}
time.Sleep(clearDelay)
// have to set mode here because clear also clears some mode settings
return hd.SetMode()
}
// SetCursor sets the input cursor to the given position.
func (hd *HD44780) SetCursor(col, row int) error {
return hd.SetDDRamAddr(byte(col) + hd.lcdRowOffset(row))
}
func (hd *HD44780) lcdRowOffset(row int) byte {
// Offset for up to 4 rows
if row > 3 {
row = 3
}
return hd.rowAddr[row]
}
// SetDDRamAddr sets the input cursor to the given address.
func (hd *HD44780) SetDDRamAddr(value byte) error {
return hd.WriteInstruction(lcdSetDDRamAddr | value)
}
// WriteInstruction writes a byte to the bus with register select in data mode.
func (hd *HD44780) WriteChar(value byte) error {
return hd.Write(true, value)
}
// WriteInstruction writes a byte to the bus with register select in command mode.
func (hd *HD44780) WriteInstruction(value byte) error {
return hd.Write(false, value)
}
// Close closes the underlying Connection.
func (hd *HD44780) Close() error {
return hd.Connection.Close()
}
// Connection abstracts the different methods of communicating with an HD44780.
type Connection interface {
// Write writes a byte to the HD44780 controller with the register select
// flag either on or off.
Write(rs bool, data byte) error
// BacklightOff turns the optional backlight off.
BacklightOff() error
// BacklightOn turns the optional backlight on.
BacklightOn() error
// Close closes all open resources.
Close() error
}
// GPIOConnection implements Connection using a 4-bit GPIO bus.
type GPIOConnection struct {
RS, EN embd.DigitalPin
D4, D5, D6, D7 embd.DigitalPin
Backlight embd.DigitalPin
BLPolarity BacklightPolarity
}
// NewGPIOConnection returns a new Connection based on a 4-bit GPIO bus.
func NewGPIOConnection(
rs, en, d4, d5, d6, d7, backlight embd.DigitalPin,
blPolarity BacklightPolarity,
) *GPIOConnection {
return &GPIOConnection{
RS: rs,
EN: en,
D4: d4,
D5: d5,
D6: d6,
D7: d7,
Backlight: backlight,
BLPolarity: blPolarity,
}
}
// BacklightOff turns the optional backlight off.
func (conn *GPIOConnection) BacklightOff() error {
if conn.Backlight != nil {
return conn.Backlight.Write(conn.backlightSignal(false))
}
return nil
}
// BacklightOn turns the optional backlight on.
func (conn *GPIOConnection) BacklightOn() error {
if conn.Backlight != nil {
return conn.Backlight.Write(conn.backlightSignal(true))
}
return nil
}
func (conn *GPIOConnection) backlightSignal(state bool) int {
if state == bool(conn.BLPolarity) {
return embd.High
} else {
return embd.Low
}
}
// Write writes a register select flag and byte to the 4-bit GPIO connection.
func (conn *GPIOConnection) Write(rs bool, data byte) error {
glog.V(3).Infof("hd44780: writing to GPIO RS: %t, data: %#x", rs, data)
rsInt := embd.Low
if rs {
rsInt = embd.High
}
functions := []func() error{
func() error { return conn.RS.Write(rsInt) },
func() error { return conn.D4.Write(int((data >> 4) & 0x01)) },
func() error { return conn.D5.Write(int((data >> 5) & 0x01)) },
func() error { return conn.D6.Write(int((data >> 6) & 0x01)) },
func() error { return conn.D7.Write(int((data >> 7) & 0x01)) },
func() error { return conn.pulseEnable() },
func() error { return conn.D4.Write(int(data & 0x01)) },
func() error { return conn.D5.Write(int((data >> 1) & 0x01)) },
func() error { return conn.D6.Write(int((data >> 2) & 0x01)) },
func() error { return conn.D7.Write(int((data >> 3) & 0x01)) },
func() error { return conn.pulseEnable() },
}
for _, f := range functions {
err := f()
if err != nil {
return err
}
}
time.Sleep(writeDelay)
return nil
}
func (conn *GPIOConnection) pulseEnable() error {
values := []int{embd.Low, embd.High, embd.Low}
for _, v := range values {
time.Sleep(pulseDelay)
err := conn.EN.Write(v)
if err != nil {
return err
}
}
return nil
}
// Close closes all open DigitalPins.
func (conn *GPIOConnection) Close() error {
glog.V(2).Info("hd44780: closing all GPIO pins")
pins := []embd.DigitalPin{
conn.RS,
conn.EN,
conn.D4,
conn.D5,
conn.D6,
conn.D7,
conn.Backlight,
}
for _, pin := range pins {
err := pin.Close()
if err != nil {
glog.Errorf("hd44780: error closing pin %+v: %s", pin, err)
return err
}
}
return nil
}
// I2CConnection implements Connection using an I²C bus.
type I2CConnection struct {
I2C embd.I2CBus
Addr byte
PinMap I2CPinMap
Backlight bool
}
// I2CPinMap represents a mapping between the pins on an I²C port expander and
// the pins on the HD44780 controller.
type I2CPinMap struct {
RS, RW, EN byte
D4, D5, D6, D7 byte
Backlight byte
BLPolarity BacklightPolarity
}
var (
// MJKDZPinMap is the standard pin mapping for an MJKDZ-based I²C backpack.
MJKDZPinMap I2CPinMap = I2CPinMap{
RS: 6, RW: 5, EN: 4,
D4: 0, D5: 1, D6: 2, D7: 3,
Backlight: 7,
BLPolarity: Negative,
}
// PCF8574PinMap is the standard pin mapping for a PCF8574-based I²C backpack.
PCF8574PinMap I2CPinMap = I2CPinMap{
RS: 0, RW: 1, EN: 2,
D4: 4, D5: 5, D6: 6, D7: 7,
Backlight: 3,
BLPolarity: Positive,
}
)
// NewI2CConnection returns a new Connection based on an I²C bus.
func NewI2CConnection(i2c embd.I2CBus, addr byte, pinMap I2CPinMap) *I2CConnection {
return &I2CConnection{
I2C: i2c,
Addr: addr,
PinMap: pinMap,
}
}
// BacklightOff turns the optional backlight off.
func (conn *I2CConnection) BacklightOff() error {
conn.Backlight = false
return conn.Write(false, 0x00)
}
// BacklightOn turns the optional backlight on.
func (conn *I2CConnection) BacklightOn() error {
conn.Backlight = true
return conn.Write(false, 0x00)
}
// Write writes a register select flag and byte to the I²C connection.
func (conn *I2CConnection) Write(rs bool, data byte) error {
var instructionHigh byte = 0x00
instructionHigh |= ((data >> 4) & 0x01) << conn.PinMap.D4
instructionHigh |= ((data >> 5) & 0x01) << conn.PinMap.D5
instructionHigh |= ((data >> 6) & 0x01) << conn.PinMap.D6
instructionHigh |= ((data >> 7) & 0x01) << conn.PinMap.D7
var instructionLow byte = 0x00
instructionLow |= (data & 0x01) << conn.PinMap.D4
instructionLow |= ((data >> 1) & 0x01) << conn.PinMap.D5
instructionLow |= ((data >> 2) & 0x01) << conn.PinMap.D6
instructionLow |= ((data >> 3) & 0x01) << conn.PinMap.D7
instructions := []byte{instructionHigh, instructionLow}
for _, ins := range instructions {
if rs {
ins |= 0x01 << conn.PinMap.RS
}
if conn.Backlight == bool(conn.PinMap.BLPolarity) {
ins |= 0x01 << conn.PinMap.Backlight
}
glog.V(3).Infof("hd44780: writing to I2C: %#x", ins)
err := conn.pulseEnable(ins)
if err != nil {
return err
}
}
time.Sleep(writeDelay)
return nil
}
func (conn *I2CConnection) pulseEnable(data byte) error {
bytes := []byte{data, data | (0x01 << conn.PinMap.EN), data}
for _, b := range bytes {
time.Sleep(pulseDelay)
err := conn.I2C.WriteByte(conn.Addr, b)
if err != nil {
return err
}
}
return nil
}
// Close closes the I²C connection.
func (conn *I2CConnection) Close() error {
glog.V(2).Info("hd44780: closing I2C bus")
return conn.I2C.Close()
}

View File

@ -0,0 +1,318 @@
package hd44780
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/kidoman/embd"
)
const (
testAddr byte = 0x20
cols = 20
rows = 4
)
var testRowAddr RowAddress = RowAddress20Col
type mockDigitalPin struct {
direction embd.Direction
values chan int
closed bool
}
func newMockDigitalPin() *mockDigitalPin {
return &mockDigitalPin{
values: make(chan int, 256),
closed: false,
}
}
func (pin *mockDigitalPin) Watch(edge embd.Edge, handler func(embd.DigitalPin)) error { return nil }
func (pin *mockDigitalPin) StopWatching() error { return nil }
func (pin *mockDigitalPin) N() int { return 0 }
func (pin *mockDigitalPin) Read() (int, error) { return 0, nil }
func (pin *mockDigitalPin) TimePulse(state int) (time.Duration, error) { return time.Duration(0), nil }
func (pin *mockDigitalPin) ActiveLow(b bool) error { return nil }
func (pin *mockDigitalPin) PullUp() error { return nil }
func (pin *mockDigitalPin) PullDown() error { return nil }
func (pin *mockDigitalPin) Write(val int) error {
pin.values <- val
return nil
}
func (pin *mockDigitalPin) SetDirection(dir embd.Direction) error {
pin.direction = dir
return nil
}
func (pin *mockDigitalPin) Close() error {
pin.closed = true
return nil
}
type mockGPIOConnection struct {
rs, en *mockDigitalPin
d4, d5, d6, d7 *mockDigitalPin
backlight *mockDigitalPin
writes []instruction
}
type instruction struct {
rs int
data byte
}
func (conn *mockGPIOConnection) Write(rs bool, data byte) error { return nil }
func (conn *mockGPIOConnection) BacklightOff() error { return nil }
func (conn *mockGPIOConnection) BacklightOn() error { return nil }
func (conn *mockGPIOConnection) Close() error { return nil }
func (ins *instruction) printAsBinary() string {
return fmt.Sprintf("RS:%d|Byte:%s", ins.rs, printByteAsBinary(ins.data))
}
func printInstructionsAsBinary(ins []instruction) string {
var binary []string
for _, i := range ins {
binary = append(binary, i.printAsBinary())
}
return fmt.Sprintf("%+v", binary)
}
func newMockGPIOConnection() *mockGPIOConnection {
conn := &mockGPIOConnection{
rs: newMockDigitalPin(),
en: newMockDigitalPin(),
d4: newMockDigitalPin(),
d5: newMockDigitalPin(),
d6: newMockDigitalPin(),
d7: newMockDigitalPin(),
backlight: newMockDigitalPin(),
}
go func() {
for {
var b byte = 0x00
var rs int = 0
// wait for EN low,high,low then read high nibble
if <-conn.en.values == embd.Low &&
<-conn.en.values == embd.High &&
<-conn.en.values == embd.Low {
rs = <-conn.rs.values
b |= byte(<-conn.d4.values) << 4
b |= byte(<-conn.d5.values) << 5
b |= byte(<-conn.d6.values) << 6
b |= byte(<-conn.d7.values) << 7
}
// wait for EN low,high,low then read low nibble
if <-conn.en.values == embd.Low &&
<-conn.en.values == embd.High &&
<-conn.en.values == embd.Low {
b |= byte(<-conn.d4.values)
b |= byte(<-conn.d5.values) << 1
b |= byte(<-conn.d6.values) << 2
b |= byte(<-conn.d7.values) << 3
conn.writes = append(conn.writes, instruction{rs, b})
}
}
}()
return conn
}
func (conn *mockGPIOConnection) pins() []*mockDigitalPin {
return []*mockDigitalPin{conn.rs, conn.en, conn.d4, conn.d5, conn.d6, conn.d7, conn.backlight}
}
type mockI2CBus struct {
writes []byte
closed bool
}
func (bus *mockI2CBus) ReadByte(addr byte) (byte, error) { return 0x00, nil }
func (bus *mockI2CBus) WriteBytes(addr byte, value []byte) error { return nil }
func (bus *mockI2CBus) ReadFromReg(addr, reg byte, value []byte) error { return nil }
func (bus *mockI2CBus) ReadByteFromReg(addr, reg byte) (byte, error) { return 0x00, nil }
func (bus *mockI2CBus) ReadWordFromReg(addr, reg byte) (uint16, error) { return 0, nil }
func (bus *mockI2CBus) WriteToReg(addr, reg byte, value []byte) error { return nil }
func (bus *mockI2CBus) WriteByteToReg(addr, reg, value byte) error { return nil }
func (bus *mockI2CBus) WriteWordToReg(addr, reg byte, value uint16) error { return nil }
func (bus *mockI2CBus) WriteByte(addr, value byte) error {
bus.writes = append(bus.writes, value)
return nil
}
func (bus *mockI2CBus) Close() error {
bus.closed = true
return nil
}
func newMockI2CBus() *mockI2CBus {
return &mockI2CBus{closed: false}
}
func printByteAsBinary(b byte) string {
return fmt.Sprintf("%08b(%#x)", b, b)
}
func printBytesAsBinary(bytes []byte) string {
var binary []string
for _, w := range bytes {
binary = append(binary, printByteAsBinary(w))
}
return fmt.Sprintf("%+v", binary)
}
func TestInitialize4Bit_directionOut(t *testing.T) {
mock := newMockGPIOConnection()
NewGPIO(mock.rs, mock.en, mock.d4, mock.d5, mock.d6, mock.d7, mock.backlight, Negative, testRowAddr)
for idx, pin := range mock.pins() {
if pin.direction != embd.Out {
t.Errorf("Pin %d not set to direction Out", idx)
}
}
}
func TestInitialize4Bit_lcdInit(t *testing.T) {
mock := newMockGPIOConnection()
gpio, _ := NewGPIO(mock.rs, mock.en, mock.d4, mock.d5, mock.d6, mock.d7, mock.backlight, Negative, testRowAddr)
instructions := []instruction{
instruction{embd.Low, lcdInit},
instruction{embd.Low, lcdInit4bit},
instruction{embd.Low, byte(gpio.eMode | lcdSetEntryMode)},
instruction{embd.Low, byte(gpio.dMode | lcdSetDisplayMode)},
instruction{embd.Low, byte(gpio.fMode | lcdSetFunctionMode)},
}
if !reflect.DeepEqual(instructions, mock.writes) {
t.Errorf(
"\nExpected\t%s\nActual\t\t%+v",
printInstructionsAsBinary(instructions),
printInstructionsAsBinary(mock.writes))
}
}
func TestGPIOConnectionClose(t *testing.T) {
mock := newMockGPIOConnection()
bus, _ := NewGPIO(mock.rs, mock.en, mock.d4, mock.d5, mock.d6, mock.d7, mock.backlight, Negative, testRowAddr)
bus.Close()
for idx, pin := range mock.pins() {
if !pin.closed {
t.Errorf("Pin %d was not closed", idx)
}
}
}
func TestI2CConnectionPinMap(t *testing.T) {
cases := []map[string]interface{}{
map[string]interface{}{
"instruction": lcdDisplayMove | lcdMoveRight,
"pinMap": MJKDZPinMap,
"expected": []byte{
0x0, // 00000000 high nibble
0x10, // 00010000
0x0, // 00000000
0xc, // 00001100 low nibble
0x1c, // 00011100
0xc, // 00001100
},
},
map[string]interface{}{
"instruction": lcdDisplayMove | lcdMoveRight,
"pinMap": PCF8574PinMap,
"expected": []byte{
0x8, // 00001000 high nibble
0xc, // 00001100
0x8, // 00001000
0xc8, // 11001000 low nibble
0xcc, // 11001100
0xc8, // 11001000
},
},
}
for idx, c := range cases {
instruction := c["instruction"].(byte)
pinMap := c["pinMap"].(I2CPinMap)
expected := c["expected"].([]byte)
i2c := newMockI2CBus()
conn := NewI2CConnection(i2c, testAddr, pinMap)
rawInstruction := instruction
// instructions (RS = false) with backlight on
conn.Backlight = true
conn.Write(false, rawInstruction)
if !reflect.DeepEqual(expected, i2c.writes) {
t.Errorf(
"Case %d:\nExpected\t%s\nActual\t\t%s",
idx+1,
printBytesAsBinary(expected),
printBytesAsBinary(i2c.writes))
}
}
}
func TestI2CConnectionClose(t *testing.T) {
i2c := newMockI2CBus()
conn := NewI2CConnection(i2c, testAddr, MJKDZPinMap)
conn.Close()
if !i2c.closed {
t.Error("I2C bus was not closed")
}
}
func TestNewGPIO_initPins(t *testing.T) {
var pins []*mockDigitalPin
for i := 0; i < 7; i++ {
pins = append(pins, newMockDigitalPin())
}
NewGPIO(
pins[0],
pins[1],
pins[2],
pins[3],
pins[4],
pins[5],
pins[6],
Negative,
testRowAddr,
)
for idx, pin := range pins {
if pin.direction != embd.Out {
t.Errorf("Pin %d not set to direction Out(%d), set to %d", idx, embd.Out, pin.direction)
}
}
}
func TestDefaultModes(t *testing.T) {
display, _ := New(newMockGPIOConnection(), testRowAddr)
if display.EightBitModeEnabled() {
t.Error("Expected display to be initialized in 4-bit mode")
}
if display.TwoLineEnabled() {
t.Error("Expected display to be initialized in one-line mode")
}
if display.Dots5x10Enabled() {
t.Error("Expected display to be initialized in 5x8-dots mode")
}
if !display.EntryIncrementEnabled() {
t.Error("Expected display to be initialized in entry increment mode")
}
if display.EntryShiftEnabled() {
t.Error("Expected display to be initialized in entry shift off mode")
}
if !display.DisplayEnabled() {
t.Error("Expected display to be initialized in display on mode")
}
if display.CursorEnabled() {
t.Error("Expected display to be initialized in cursor off mode")
}
if display.BlinkEnabled() {
t.Error("Expected display to be initialized in blink off mode")
}
}

View File

@ -42,10 +42,6 @@ func execOutput(name string, arg ...string) (output string, err error) {
return
}
func nodeName() (string, error) {
return execOutput("uname", "-n")
}
func parseVersion(str string) (major, minor, patch int, err error) {
versionNumber := strings.Split(str, "-")
parts := strings.Split(versionNumber[0], ".")
@ -77,24 +73,35 @@ func kernelVersion() (major, minor, patch int, err error) {
return parseVersion(output)
}
func getPiRevision() (int, error) {
//default return code of a rev2 board
cpuinfo, err := ioutil.ReadFile("/proc/cpuinfo")
func cpuInfo() (model, hardware string, revision int, err error) {
output, err := ioutil.ReadFile("/proc/cpuinfo")
if err != nil {
return 4, err
return "", "", 0, err
}
for _, line := range strings.Split(string(cpuinfo), "\n") {
fields := strings.Fields(line)
if len(fields) > 0 && fields[0] == "Revision" {
rev, err := strconv.ParseInt(fields[2], 16, 8)
return int(rev), err
for _, line := range strings.Split(string(output), "\n") {
fields := strings.Split(line, ":")
if len(fields) < 1 {
continue
}
switch {
case strings.HasPrefix(fields[0], "Revision"):
revs := strings.TrimSpace(fields[1])
rev, err := strconv.ParseInt(revs, 16, 32)
if err != nil {
continue
}
revision = int(rev)
case strings.HasPrefix(fields[0], "Hardware"):
hardware = fields[1]
case strings.HasPrefix(fields[0], "model name"):
model = fields[1]
}
}
return 4, nil
return model, hardware, revision, nil
}
// DetectHost returns the detected host and its revision number.
func DetectHost() (Host, int, error) {
func DetectHost() (host Host, rev int, err error) {
major, minor, patch, err := kernelVersion()
if err != nil {
return HostNull, 0, err
@ -104,23 +111,17 @@ func DetectHost() (Host, int, error) {
return HostNull, 0, fmt.Errorf("embd: linux kernel versions lower than 3.8 are not supported. you have %v.%v.%v", major, minor, patch)
}
node, err := nodeName()
model, hardware, rev, err := cpuInfo()
if err != nil {
return HostNull, 0, err
}
var host Host
var rev int
switch node {
case "raspberrypi":
host = HostRPi
rev, _ = getPiRevision()
case "beaglebone":
host = HostBBB
switch {
case strings.Contains(model, "ARMv7") && (strings.Contains(hardware, "AM33XX") || strings.Contains(hardware, "AM335X")):
return HostBBB, rev, nil
case strings.Contains(hardware, "BCM2708") || strings.Contains(hardware, "BCM2709"):
return HostRPi, rev, nil
default:
return HostNull, 0, fmt.Errorf("embd: your host %q is not supported at this moment. please request support at https://github.com/kidoman/embd/issues", node)
return HostNull, 0, fmt.Errorf(`embd: your host "%v:%v" is not supported at this moment. request support at https://github.com/kidoman/embd/issues`, host, model)
}
return host, rev, nil
}

View File

@ -14,7 +14,7 @@ func detect(c *cli.Context) {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("detected host %v (rev %v)\n", host, rev)
fmt.Printf("detected host %v (rev %#x)\n", host, rev)
}
var detectCmd = cli.Command{

View File

@ -125,6 +125,9 @@ type PWMPin interface {
// GPIODriver implements a generic GPIO driver.
type GPIODriver interface {
// PinMap returns the pinmap for this driver.
PinMap() PinMap
// Unregister unregisters the pin from the driver. Should be called when the pin is closed.
Unregister(string) error

View File

@ -107,6 +107,10 @@ func (io *gpioDriver) PWMPin(key interface{}) (PWMPin, error) {
return p, nil
}
func (io *gpioDriver) PinMap() PinMap {
return io.pinMap
}
func (io *gpioDriver) Close() error {
for _, p := range io.initializedPins {
if err := p.Close(); err != nil {

View File

@ -31,33 +31,33 @@ func (i *interrupt) Signal() {
i.handler(i.pin)
}
type ePollListener struct {
epollFd int
type epollListener struct {
mu sync.Mutex // Guards the following.
fd int
interruptablePins map[int]*interrupt
mu sync.Mutex
}
var ePollListenerInstance *ePollListener
var epollListenerInstance *epollListener
func getEPollListenerInstance() *ePollListener {
if ePollListenerInstance == nil {
ePollListenerInstance = initEPollListener()
func getEpollListenerInstance() *epollListener {
if epollListenerInstance == nil {
epollListenerInstance = initEpollListener()
}
return ePollListenerInstance
return epollListenerInstance
}
func initEPollListener() *ePollListener {
epollFd, err := syscall.EpollCreate1(0)
func initEpollListener() *epollListener {
fd, err := syscall.EpollCreate1(0)
if err != nil {
panic(fmt.Sprintf("Unable to create epoll: %v", err))
}
listener := &ePollListener{epollFd: epollFd, interruptablePins: make(map[int]*interrupt)}
listener := &epollListener{fd: fd, interruptablePins: make(map[int]*interrupt)}
go func() {
var epollEvents [MaxGPIOInterrupt]syscall.EpollEvent
for {
n, err := syscall.EpollWait(listener.epollFd, epollEvents[:], -1)
n, err := syscall.EpollWait(listener.fd, epollEvents[:], -1)
if err != nil {
panic(fmt.Sprintf("EpollWait error: %v", err))
}
@ -72,7 +72,7 @@ func initEPollListener() *ePollListener {
}
func registerInterrupt(pin *digitalPin, handler func(embd.DigitalPin)) error {
l := getEPollListenerInstance()
l := getEpollListenerInstance()
pinFd := int(pin.val.Fd())
@ -92,7 +92,7 @@ func registerInterrupt(pin *digitalPin, handler func(embd.DigitalPin)) error {
event.Fd = int32(pinFd)
if err := syscall.EpollCtl(l.epollFd, syscall.EPOLL_CTL_ADD, pinFd, &event); err != nil {
if err := syscall.EpollCtl(l.fd, syscall.EPOLL_CTL_ADD, pinFd, &event); err != nil {
return err
}
@ -102,7 +102,7 @@ func registerInterrupt(pin *digitalPin, handler func(embd.DigitalPin)) error {
}
func unregisterInterrupt(pin *digitalPin) error {
l := getEPollListenerInstance()
l := getEpollListenerInstance()
pinFd := int(pin.val.Fd())
@ -113,7 +113,7 @@ func unregisterInterrupt(pin *digitalPin) error {
return nil
}
if err := syscall.EpollCtl(l.epollFd, syscall.EPOLL_CTL_DEL, pinFd, nil); err != nil {
if err := syscall.EpollCtl(l.fd, syscall.EPOLL_CTL_DEL, pinFd, nil); err != nil {
return err
}

View File

@ -233,6 +233,13 @@ func (b *spiBus) ReceiveByte() (byte, error) {
return byte(d[0]), nil
}
func (b *spiBus) Write(data []byte) (n int, err error) {
if err := b.init(); err != nil {
return 0, err
}
return b.file.Write(data)
}
func (b *spiBus) Close() error {
b.mu.Lock()
defer b.mu.Unlock()

View File

@ -1,5 +1,5 @@
/*
Package rpi provides Raspberry Pi support.
Package rpi provides Raspberry Pi (including A+/B+) support.
The following features are supported on Linux kernel 3.8+
GPIO (digital (rw))
@ -55,13 +55,31 @@ var rev2Pins = embd.PinMap{
&embd.PinDesc{ID: "P1_26", Aliases: []string{"7", "GPIO_7", "CE1", "SPI0_CE1_N"}, Caps: embd.CapDigital | embd.CapSPI, DigitalLogical: 7},
}
// This is the same as the Rev 2 for the first 26 pins.
var rev3Pins = append(append(embd.PinMap(nil), rev2Pins...), embd.PinMap{
&embd.PinDesc{ID: "P1_29", Aliases: []string{"5", "GPIO_5"}, Caps: embd.CapDigital, DigitalLogical: 5},
&embd.PinDesc{ID: "P1_31", Aliases: []string{"6", "GPIO_6"}, Caps: embd.CapDigital, DigitalLogical: 6},
&embd.PinDesc{ID: "P1_32", Aliases: []string{"12", "GPIO_12"}, Caps: embd.CapDigital, DigitalLogical: 12},
&embd.PinDesc{ID: "P1_33", Aliases: []string{"13", "GPIO_13"}, Caps: embd.CapDigital, DigitalLogical: 13},
&embd.PinDesc{ID: "P1_35", Aliases: []string{"19", "GPIO_19"}, Caps: embd.CapDigital, DigitalLogical: 19},
&embd.PinDesc{ID: "P1_36", Aliases: []string{"16", "GPIO_16"}, Caps: embd.CapDigital, DigitalLogical: 16},
&embd.PinDesc{ID: "P1_37", Aliases: []string{"26", "GPIO_26"}, Caps: embd.CapDigital, DigitalLogical: 26},
&embd.PinDesc{ID: "P1_38", Aliases: []string{"20", "GPIO_20"}, Caps: embd.CapDigital, DigitalLogical: 20},
&embd.PinDesc{ID: "P1_40", Aliases: []string{"21", "GPIO_21"}, Caps: embd.CapDigital, DigitalLogical: 21},
}...)
var ledMap = embd.LEDMap{
"led0": []string{"0", "led0", "LED0"},
}
func init() {
embd.Register(embd.HostRPi, func(rev int) *embd.Descriptor {
var pins = rev2Pins
// Refer to http://elinux.org/RPi_HardwareHistory#Board_Revision_History
// for details.
pins := rev3Pins
if rev < 16 {
pins = rev2Pins
}
if rev < 4 {
pins = rev1Pins
}

View File

@ -0,0 +1,106 @@
/*
Package characterdisplay provides an ease-of-use layer on top of a character
display controller.
*/
package characterdisplay
// Controller is an interface that describes the basic functionality of a character
// display controller.
type Controller interface {
DisplayOff() error // turns the display off
DisplayOn() error // turns the display on
CursorOff() error // sets the cursor visibility to off
CursorOn() error // sets the cursor visibility to on
BlinkOff() error // sets the cursor blink off
BlinkOn() error // sets the cursor blink on
ShiftLeft() error // moves the cursor and text one column to the left
ShiftRight() error // moves the cursor and text one column to the right
BacklightOff() error // turns the display backlight off
BacklightOn() error // turns the display backlight on
Home() error // moves the cursor to the home position
Clear() error // clears the display and moves the cursor to the home position
WriteChar(byte) error // writes a character to the display
SetCursor(col, row int) error // sets the cursor position
Close() error // closes the controller resources
}
// Display represents an abstract character display and provides a
// ease-of-use layer on top of a character display controller.
type Display struct {
Controller
cols, rows int
p *position
}
type position struct {
col int
row int
}
// New creates a new Display
func New(controller Controller, cols, rows int) *Display {
return &Display{
Controller: controller,
cols: cols,
rows: rows,
p: &position{0, 0},
}
}
// Home moves the cursor and all characters to the home position.
func (disp *Display) Home() error {
disp.setCurrentPosition(0, 0)
return disp.Controller.Home()
}
// Clear clears the display, preserving the mode settings and setting the correct home.
func (disp *Display) Clear() error {
disp.setCurrentPosition(0, 0)
return disp.Controller.Clear()
}
// Message prints the given string on the display, including interpreting newline
// characters and wrapping at the end of lines.
func (disp *Display) Message(message string) error {
bytes := []byte(message)
for _, b := range bytes {
if b == byte('\n') {
err := disp.Newline()
if err != nil {
return err
}
continue
}
err := disp.WriteChar(b)
if err != nil {
return err
}
disp.p.col++
if disp.p.col >= disp.cols || disp.p.col < 0 {
err := disp.Newline()
if err != nil {
return err
}
}
}
return nil
}
// Newline moves the input cursor to the beginning of the next line.
func (disp *Display) Newline() error {
return disp.SetCursor(0, disp.p.row+1)
}
// SetCursor sets the input cursor to the given position.
func (disp *Display) SetCursor(col, row int) error {
if row >= disp.rows {
row = disp.rows - 1
}
disp.setCurrentPosition(col, row)
return disp.Controller.SetCursor(col, row)
}
func (disp *Display) setCurrentPosition(col, row int) {
disp.p.col = col
disp.p.row = row
}

View File

@ -0,0 +1,129 @@
package characterdisplay
import (
"reflect"
"testing"
"time"
)
const (
rows = 4
cols = 20
)
type mockController struct {
calls chan call
}
type call struct {
name string
arguments []interface{}
}
func noArgCall(name string) call {
return call{name, []interface{}{}}
}
func (mock *mockController) DisplayOff() error { mock.calls <- noArgCall("DisplayOff"); return nil }
func (mock *mockController) DisplayOn() error { mock.calls <- noArgCall("DisplayOn"); return nil }
func (mock *mockController) CursorOff() error { mock.calls <- noArgCall("CursorOff"); return nil }
func (mock *mockController) CursorOn() error { mock.calls <- noArgCall("CursorOn"); return nil }
func (mock *mockController) BlinkOff() error { mock.calls <- noArgCall("BlinkOff"); return nil }
func (mock *mockController) BlinkOn() error { mock.calls <- noArgCall("BlinkOn"); return nil }
func (mock *mockController) ShiftLeft() error { mock.calls <- noArgCall("ShiftLeft"); return nil }
func (mock *mockController) ShiftRight() error { mock.calls <- noArgCall("ShiftRight"); return nil }
func (mock *mockController) BacklightOff() error { mock.calls <- noArgCall("BacklightOff"); return nil }
func (mock *mockController) BacklightOn() error { mock.calls <- noArgCall("BacklightOn"); return nil }
func (mock *mockController) Home() error { mock.calls <- noArgCall("Home"); return nil }
func (mock *mockController) Clear() error { mock.calls <- noArgCall("Clear"); return nil }
func (mock *mockController) Close() error { mock.calls <- noArgCall("Close"); return nil }
func (mock *mockController) WriteChar(b byte) error {
mock.calls <- call{"WriteChar", []interface{}{b}}
return nil
}
func (mock *mockController) SetCursor(col, row int) error {
mock.calls <- call{"SetCursor", []interface{}{col, row}}
return nil
}
func (mock *mockController) testExpectedCalls(expectedCalls []call, t *testing.T) {
for _, expectedCall := range expectedCalls {
select {
case actualCall := <-mock.calls:
if !reflect.DeepEqual(expectedCall, actualCall) {
t.Errorf("Expected call %+v, actual call %+v", expectedCall, actualCall)
}
case <-time.After(time.Millisecond * 1):
t.Errorf("Timeout reading next call. Expected call %+v", expectedCall)
}
}
ExtraCallsCheck:
for {
select {
case extraCall := <-mock.calls:
t.Errorf("Unexpected call %+v", extraCall)
case <-time.After(time.Millisecond * 1):
break ExtraCallsCheck
}
}
}
func newMockController() *mockController {
return &mockController{make(chan call, 256)}
}
func TestNewline(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.Newline()
expectedCalls := []call{
call{"SetCursor", []interface{}{0, 1}},
}
mock.testExpectedCalls(expectedCalls, t)
}
func TestMessage(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.Message("ab")
expectedCalls := []call{
call{"WriteChar", []interface{}{byte('a')}},
call{"WriteChar", []interface{}{byte('b')}},
}
mock.testExpectedCalls(expectedCalls, t)
}
func TestMessage_newLine(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.Message("a\nb")
expectedCalls := []call{
call{"WriteChar", []interface{}{byte('a')}},
call{"SetCursor", []interface{}{0, 1}},
call{"WriteChar", []interface{}{byte('b')}},
}
mock.testExpectedCalls(expectedCalls, t)
}
func TestMessage_wrap(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.SetCursor(cols-1, 0)
disp.Message("ab")
expectedCalls := []call{
call{"SetCursor", []interface{}{cols - 1, 0}},
call{"WriteChar", []interface{}{byte('a')}},
call{"SetCursor", []interface{}{0, 1}},
call{"WriteChar", []interface{}{byte('b')}},
}
mock.testExpectedCalls(expectedCalls, t)
}

109
samples/28bjy-48.go Normal file
View File

@ -0,0 +1,109 @@
// +build ignore
package main
// Control a stepper motor (28BJY-48)
//
// Datasheet:
// http://www.raspberrypi-spy.co.uk/wp-content/uploads/2012/07/Stepper-Motor-28BJY-48-Datasheet.pdf
//
// this is a port of Matt Hawkins' example impl from
// http://www.raspberrypi-spy.co.uk/2012/07/stepper-motor-control-in-python/
// (link privides additional instructions for wiring your pi)
import (
"flag"
"fmt"
"os"
"os/signal"
"time"
"github.com/kidoman/embd"
_ "github.com/kidoman/embd/host/rpi"
)
func main() {
stepDelay := flag.Int("step-delay", 10, "milliseconds between steps")
flag.Parse()
if err := embd.InitGPIO(); err != nil {
panic(err)
}
defer embd.CloseGPIO()
// Physical pins 11,15,16,18 on rasp pi
// GPIO17,GPIO22,GPIO23,GPIO24
stepPinNums := []int{17, 22, 23, 24}
stepPins := make([]embd.DigitalPin, 4)
for i, pinNum := range stepPinNums {
pin, err := embd.NewDigitalPin(pinNum)
if err != nil {
panic(err)
}
defer pin.Close()
if err := pin.SetDirection(embd.Out); err != nil {
panic(err)
}
if err := pin.Write(embd.Low); err != nil {
panic(err)
}
defer pin.SetDirection(embd.In)
stepPins[i] = pin
}
// Define sequence described in manufacturer's datasheet
seq := [][]int{
[]int{1, 0, 0, 0},
[]int{1, 1, 0, 0},
[]int{0, 1, 0, 0},
[]int{0, 1, 1, 0},
[]int{0, 0, 1, 0},
[]int{0, 0, 1, 1},
[]int{0, 0, 0, 1},
[]int{1, 0, 0, 1},
}
stepCount := len(seq) - 1
stepDir := 2 // Set to 1 or 2 for clockwise, -1 or -2 for counter-clockwise
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, os.Kill)
defer signal.Stop(quit)
// Start main loop
ticker := time.NewTicker(time.Duration(*stepDelay) * time.Millisecond)
defer ticker.Stop()
var stepCounter int
for {
select {
case <-ticker.C:
// set pins to appropriate values for given position in the sequence
for i, pin := range stepPins {
if seq[stepCounter][i] != 0 {
fmt.Printf("Enable pin %d, step %d\n", i, stepCounter)
if err := pin.Write(embd.High); err != nil {
panic(err)
}
} else {
if err := pin.Write(embd.Low); err != nil {
panic(err)
}
}
}
stepCounter += stepDir
// If we reach the end of the sequence start again
if stepCounter >= stepCount {
stepCounter = 0
} else if stepCounter < 0 {
stepCounter = stepCount
}
case <-quit:
return
}
}
}

View File

@ -0,0 +1,45 @@
// +build ignore
package main
import (
"flag"
"time"
"github.com/kidoman/embd"
"github.com/kidoman/embd/controller/hd44780"
"github.com/kidoman/embd/interface/display/characterdisplay"
_ "github.com/kidoman/embd/host/all"
)
func main() {
flag.Parse()
if err := embd.InitI2C(); err != nil {
panic(err)
}
defer embd.CloseI2C()
bus := embd.NewI2CBus(1)
controller, err := hd44780.NewI2C(
bus,
0x20,
hd44780.PCF8574PinMap,
hd44780.RowAddress20Col,
hd44780.TwoLine,
hd44780.BlinkOn,
)
if err != nil {
panic(err)
}
display := characterdisplay.New(controller, 20, 4)
defer display.Close()
display.Clear()
display.Message("Hello, world!\n@embd | characterdisplay")
time.Sleep(10 * time.Second)
display.BacklightOff()
}

53
samples/hd44780.go Normal file
View File

@ -0,0 +1,53 @@
// +build ignore
package main
import (
"flag"
"time"
"github.com/kidoman/embd"
"github.com/kidoman/embd/controller/hd44780"
_ "github.com/kidoman/embd/host/all"
)
func main() {
flag.Parse()
if err := embd.InitI2C(); err != nil {
panic(err)
}
defer embd.CloseI2C()
bus := embd.NewI2CBus(1)
hd, err := hd44780.NewI2C(
bus,
0x20,
hd44780.PCF8574PinMap,
hd44780.RowAddress20Col,
hd44780.TwoLine,
hd44780.BlinkOn,
)
if err != nil {
panic(err)
}
defer hd.Close()
hd.Clear()
message := "Hello, world!"
bytes := []byte(message)
for _, b := range bytes {
hd.WriteChar(b)
}
hd.SetCursor(0, 1)
message = "@embd | hd44780"
bytes = []byte(message)
for _, b := range bytes {
hd.WriteChar(b)
}
time.Sleep(10 * time.Second)
hd.BacklightOff()
}

6
spi.go
View File

@ -2,6 +2,10 @@
package embd
import (
"io"
)
const (
spiCpha = 0x01
spiCpol = 0x02
@ -21,6 +25,8 @@ const (
// SPIBus interface allows interaction with the SPI bus.
type SPIBus interface {
io.Writer
// TransferAndRecieveData transmits data in a buffer(slice) and receives into it.
TransferAndRecieveData(dataBuffer []uint8) error