From dac729e4fd643dbee2d18355a6bdbe0cb0bd19d9 Mon Sep 17 00:00:00 2001 From: Matthew Dale Date: Sat, 27 Dec 2014 00:28:15 -0800 Subject: [PATCH 01/23] controller: add a lib for the HD44780 character display controller the hd44780 package supports HD44780 character display controllers connected by either a 4-bit GPIO bus or an I2C bus it also includes a high-level wrapper for easily printing messages --- controller/hd44780/character_display.go | 212 +++++++ controller/hd44780/character_display_test.go | 85 +++ controller/hd44780/hd44780.go | 572 +++++++++++++++++++ controller/hd44780/hd44780_test.go | 255 +++++++++ samples/hd44780.go | 43 ++ 5 files changed, 1167 insertions(+) create mode 100644 controller/hd44780/character_display.go create mode 100644 controller/hd44780/character_display_test.go create mode 100644 controller/hd44780/hd44780.go create mode 100644 controller/hd44780/hd44780_test.go create mode 100644 samples/hd44780.go diff --git a/controller/hd44780/character_display.go b/controller/hd44780/character_display.go new file mode 100644 index 0000000..6c23099 --- /dev/null +++ b/controller/hd44780/character_display.go @@ -0,0 +1,212 @@ +package hd44780 + +import ( + "github.com/golang/glog" + "github.com/kidoman/embd" +) + +// DefaultModes are the default initialization modes for a CharacterDisplay. +var DefaultModes []ModeSetter = []ModeSetter{ + FourBitMode, + OneLine, + Dots5x8, + EntryIncrement, + EntryShiftOff, + DisplayOn, + CursorOff, + BlinkOff, +} + +// CharacterDisplay represents an abstract character display and provides a +// convenience layer on top of the basic HD44780 library. +type CharacterDisplay struct { + *HD44780 + Cols int + Rows int + p *position +} + +type position struct { + col int + row int +} + +// NewGPIOCharacterDisplay creates a new CharacterDisplay connected by a 4-bit GPIO bus. +func NewGPIOCharacterDisplay( + rs, en, d4, d5, d6, d7, backlight interface{}, + blPolarity Polarity, + cols, rows int, + modes ...ModeSetter, +) (*CharacterDisplay, 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 + } + hd, err := NewGPIO( + pins[0], + pins[1], + pins[2], + pins[3], + pins[4], + pins[5], + pins[6], + blPolarity, + append(DefaultModes, modes...)..., + ) + if err != nil { + return nil, err + } + return NewCharacterDisplay(hd, cols, rows) +} + +// NewI2CCharacterDisplay creates a new CharacterDisplay connected by an I²C bus. +func NewI2CCharacterDisplay( + i2c embd.I2CBus, + addr byte, + pinMap I2CPinMap, + cols, rows int, + modes ...ModeSetter, +) (*CharacterDisplay, error) { + hd, err := NewI2C(i2c, addr, pinMap, append(DefaultModes, modes...)...) + if err != nil { + return nil, err + } + return NewCharacterDisplay(hd, cols, rows) +} + +// NewCharacterDisplay creates a new character display abstraction for an +// HD44780-compatible controller. +func NewCharacterDisplay(hd *HD44780, cols, rows int) (*CharacterDisplay, error) { + display := &CharacterDisplay{ + HD44780: hd, + Cols: cols, + Rows: rows, + p: &position{0, 0}, + } + err := display.BacklightOn() + if err != nil { + return nil, err + } + return display, nil +} + +// Home moves the cursor and all characters to the home position. +func (disp *CharacterDisplay) Home() error { + disp.currentPosition(0, 0) + return disp.HD44780.Home() +} + +// Clear clears the display, preserving the mode settings and setting the correct home. +func (disp *CharacterDisplay) Clear() error { + disp.currentPosition(0, 0) + err := disp.HD44780.Clear() + if err != nil { + return err + } + err = disp.SetMode() + if err != nil { + return err + } + if !disp.isLeftToRight() { + return disp.SetCursor(disp.Cols-1, 0) + } + return nil +} + +// Message prints the given string on the display. +func (disp *CharacterDisplay) 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 + } + if disp.isLeftToRight() { + disp.p.col++ + } else { + 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 *CharacterDisplay) Newline() error { + var col int + if disp.isLeftToRight() { + col = 0 + } else { + col = disp.Cols - 1 + } + return disp.SetCursor(col, disp.p.row+1) +} + +func (disp *CharacterDisplay) isLeftToRight() bool { + // EntryIncrement and EntryShiftOn is right-to-left + // EntryDecrement and EntryShiftOn is left-to-right + // EntryIncrement and EntryShiftOff is left-to-right + // EntryDecrement and EntryShiftOff is right-to-left + return disp.EntryIncrementEnabled() != disp.EntryShiftEnabled() +} + +// SetCursor sets the input cursor to the given position. +func (disp *CharacterDisplay) SetCursor(col, row int) error { + if row >= disp.Rows { + row = disp.Rows - 1 + } + disp.currentPosition(col, row) + return disp.HD44780.SetCursor(byte(col) + disp.lcdRowOffset(row)) +} + +func (disp *CharacterDisplay) lcdRowOffset(row int) byte { + // Offset for up to 4 rows + if row > 3 { + row = 3 + } + switch disp.Cols { + case 16: + // 16-char line mappings + return []byte{0x00, 0x40, 0x10, 0x50}[row] + default: + // default to the 20-char line mappings + return []byte{0x00, 0x40, 0x14, 0x54}[row] + } +} + +func (disp *CharacterDisplay) currentPosition(col, row int) { + disp.p.col = col + disp.p.row = row +} + +// Close closes the underlying HD44780 controller. +func (disp *CharacterDisplay) Close() error { + return disp.HD44780.Close() +} diff --git a/controller/hd44780/character_display_test.go b/controller/hd44780/character_display_test.go new file mode 100644 index 0000000..e2eca42 --- /dev/null +++ b/controller/hd44780/character_display_test.go @@ -0,0 +1,85 @@ +package hd44780 + +import ( + "testing" + + "github.com/kidoman/embd" +) + +const ( + rows = 20 + cols = 4 +) + +func TestNewGPIOCharacterDisplay_initPins(t *testing.T) { + var pins []*mockDigitalPin + for i := 0; i < 7; i++ { + pins = append(pins, newMockDigitalPin()) + } + NewGPIOCharacterDisplay( + pins[0], + pins[1], + pins[2], + pins[3], + pins[4], + pins[5], + pins[6], + Negative, + cols, + rows, + ) + 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) { + displayGPIO, _ := NewGPIOCharacterDisplay( + newMockDigitalPin(), + newMockDigitalPin(), + newMockDigitalPin(), + newMockDigitalPin(), + newMockDigitalPin(), + newMockDigitalPin(), + newMockDigitalPin(), + Negative, + cols, + rows, + ) + displayI2C, _ := NewI2CCharacterDisplay( + newMockI2CBus(), + testAddr, + MJKDZPinMap, + cols, + rows, + ) + + for idx, display := range []*CharacterDisplay{displayGPIO, displayI2C} { + if display.EightBitModeEnabled() { + t.Errorf("Display %d: Expected display to be initialized in 4-bit mode", idx) + } + if display.TwoLineEnabled() { + t.Errorf("Display %d: Expected display to be initialized in one-line mode", idx) + } + if display.Dots5x10Enabled() { + t.Errorf("Display %d: Expected display to be initialized in 5x8-dots mode", idx) + } + if !display.EntryIncrementEnabled() { + t.Errorf("Display %d: Expected display to be initialized in entry increment mode", idx) + } + if display.EntryShiftEnabled() { + t.Errorf("Display %d: Expected display to be initialized in entry shift off mode", idx) + } + if !display.DisplayEnabled() { + t.Errorf("Display %d: Expected display to be initialized in display on mode", idx) + } + if display.CursorEnabled() { + t.Errorf("Display %d: Expected display to be initialized in cursor off mode", idx) + } + if display.BlinkEnabled() { + t.Errorf("Display %d: Expected display to be initialized in blink off mode", idx) + } + } +} diff --git a/controller/hd44780/hd44780.go b/controller/hd44780/hd44780.go new file mode 100644 index 0000000..a977054 --- /dev/null +++ b/controller/hd44780/hd44780.go @@ -0,0 +1,572 @@ +/* +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. + +Character Display + +The CharacterDisplay type provides a convenience layer on top of the basic +HD44780 library. It includes functions for easier message printing and abstracts +some of the quirky behaviors of 4-row displays. + +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 Polarity bool +type entryMode byte +type displayMode byte +type functionMode byte + +const ( + Negative Polarity = false + Positive Polarity = 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 +} + +// NewGPIO creates a new HD44780 connected by a 4-bit GPIO bus. +func NewGPIO( + rs, en, d4, d5, d6, d7, backlight embd.DigitalPin, + blPolarity Polarity, + modes ...ModeSetter, +) (*HD44780, error) { + pins := []embd.DigitalPin{rs, en, d4, d5, d6, d7, backlight} + 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(rs, en, d4, d5, d6, d7, backlight, blPolarity), + modes..., + ) +} + +// NewI2C creates a new HD44780 connected by an I²C bus. +func NewI2C(i2c embd.I2CBus, addr byte, pinMap I2CPinMap, modes ...ModeSetter) (*HD44780, error) { + return New(NewI2CConnection(i2c, addr, pinMap), modes...) +} + +// New creates a new HD44780 connected by a Connection bus. +func New(bus Connection, modes ...ModeSetter) (*HD44780, error) { + controller := &HD44780{ + Connection: bus, + eMode: 0x00, + dMode: 0x00, + fMode: 0x00, + } + err := controller.lcdInit() + if err != nil { + return nil, err + } + err = controller.SetMode(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) +} + +// 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) + time.Sleep(clearDelay) + return err +} + +// SetCursor sets the input cursor to the given bye. +func (hd *HD44780) SetCursor(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 Polarity +} + +// NewGPIOConnection returns a new Connection based on a 4-bit GPIO bus. +func NewGPIOConnection( + rs, en, d4, d5, d6, d7, backlight embd.DigitalPin, + blPolarity Polarity, +) *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 Polarity +} + +var ( + // 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, + } + // 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() +} diff --git a/controller/hd44780/hd44780_test.go b/controller/hd44780/hd44780_test.go new file mode 100644 index 0000000..a061140 --- /dev/null +++ b/controller/hd44780/hd44780_test.go @@ -0,0 +1,255 @@ +package hd44780 + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/kidoman/embd" +) + +const testAddr byte = 0x20 + +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 (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 { + be := &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 <-be.en.values == embd.Low && + <-be.en.values == embd.High && + <-be.en.values == embd.Low { + rs = <-be.rs.values + b |= byte(<-be.d4.values) << 4 + b |= byte(<-be.d5.values) << 5 + b |= byte(<-be.d6.values) << 6 + b |= byte(<-be.d7.values) << 7 + } + // wait for EN low,high,low then read low nibble + if <-be.en.values == embd.Low && + <-be.en.values == embd.High && + <-be.en.values == embd.Low { + b |= byte(<-be.d4.values) + b |= byte(<-be.d5.values) << 1 + b |= byte(<-be.d6.values) << 2 + b |= byte(<-be.d7.values) << 3 + be.writes = append(be.writes, instruction{rs, b}) + } + } + }() + return be +} + +func (be *mockGPIOConnection) pins() []*mockDigitalPin { + return []*mockDigitalPin{be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.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) { + be := newMockGPIOConnection() + NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative) + for idx, pin := range be.pins() { + if pin.direction != embd.Out { + t.Errorf("Pin %d not set to direction Out", idx) + } + } +} + +func TestInitialize4Bit_lcdInit(t *testing.T) { + be := newMockGPIOConnection() + NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative) + instructions := []instruction{ + instruction{embd.Low, lcdInit}, + instruction{embd.Low, lcdInit4bit}, + instruction{embd.Low, byte(lcdSetEntryMode)}, + instruction{embd.Low, byte(lcdSetDisplayMode)}, + instruction{embd.Low, byte(lcdSetFunctionMode)}, + } + + if !reflect.DeepEqual(instructions, be.writes) { + t.Errorf( + "\nExpected\t%s\nActual\t\t%+v", + printInstructionsAsBinary(instructions), + printInstructionsAsBinary(be.writes)) + } +} + +func TestGPIOConnectionClose(t *testing.T) { + be := newMockGPIOConnection() + bus, _ := NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative) + bus.Close() + for idx, pin := range be.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") + } +} diff --git a/samples/hd44780.go b/samples/hd44780.go new file mode 100644 index 0000000..3307a4c --- /dev/null +++ b/samples/hd44780.go @@ -0,0 +1,43 @@ +// +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) + + display, err := hd44780.NewI2CCharacterDisplay( + bus, + 0x20, + hd44780.PCF8574PinMap, + 20, + 4, + hd44780.TwoLine, + hd44780.BlinkOn, + ) + if err != nil { + panic(err) + } + defer display.Close() + + display.Clear() + display.Message("Hello, world!\n@embd") + time.Sleep(10 * time.Second) + display.BacklightOff() +} From c78563a34160998cbb8a772dfd45be990ec63383 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 02:43:28 +0530 Subject: [PATCH 02/23] ci: update versions of go we build against --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 795fd2c..14c3702 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,9 @@ branches: - go-rpi go: - - 1.2 - - 1.2.1 + - 1.2.2 + - 1.3.3 + - 1.4 - tip script: From b6f5d8d64000618de8c3315eb4b9e84d78a05b3d Mon Sep 17 00:00:00 2001 From: Ben Delarre Date: Tue, 13 Jan 2015 13:43:23 -0800 Subject: [PATCH 03/23] spi: added write method to allow writing without transfer delays --- host/generic/spibus.go | 7 +++++++ spi.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/host/generic/spibus.go b/host/generic/spibus.go index e00150c..f682daa 100644 --- a/host/generic/spibus.go +++ b/host/generic/spibus.go @@ -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() diff --git a/spi.go b/spi.go index e6aecb0..e7b9671 100644 --- a/spi.go +++ b/spi.go @@ -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 From 6003f2249d2b1c1454e5608f8798a32fe612ab64 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 05:35:10 +0530 Subject: [PATCH 04/23] gpio: consistent naming --- host/generic/interrupt.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/host/generic/interrupt.go b/host/generic/interrupt.go index e2ebfa8..4052283 100644 --- a/host/generic/interrupt.go +++ b/host/generic/interrupt.go @@ -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 } From 05c03968d7e9064b3da7e4082e65e314a7900dae Mon Sep 17 00:00:00 2001 From: Kunal Powar Date: Thu, 15 Jan 2015 05:38:13 +0530 Subject: [PATCH 05/23] doc: updated contributors list --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9c5ffef..f44d830 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -5,3 +5,4 @@ Marco P. Monteiro Nikesh Vora Steve Beaulac Al S-M +Ben Delarre From e6d0ea4225393d4bc6e2a8fdca7c8b0994c841e5 Mon Sep 17 00:00:00 2001 From: Ben Delarre Date: Tue, 13 Jan 2015 13:55:11 -0800 Subject: [PATCH 06/23] gpio: added pinmap accessor for introspection usage --- gpio.go | 3 +++ gpiodriver.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/gpio.go b/gpio.go index 17badd2..d86122a 100644 --- a/gpio.go +++ b/gpio.go @@ -125,6 +125,9 @@ type PWMPin interface { // GPIODriver implements a generic GPIO driver. type GPIODriver interface { + // Returns the pinmap for this GPIODriver + PinMap() PinMap + // Unregister unregisters the pin from the driver. Should be called when the pin is closed. Unregister(string) error diff --git a/gpiodriver.go b/gpiodriver.go index 07fceff..be2f8d8 100644 --- a/gpiodriver.go +++ b/gpiodriver.go @@ -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 { From 9aaadba2e3cdcd666e4db62377fa8e43d10be853 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 05:59:03 +0530 Subject: [PATCH 07/23] gpio: better docs --- gpio.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpio.go b/gpio.go index d86122a..26bd866 100644 --- a/gpio.go +++ b/gpio.go @@ -125,7 +125,7 @@ type PWMPin interface { // GPIODriver implements a generic GPIO driver. type GPIODriver interface { - // Returns the pinmap for this GPIODriver + // PinMap returns the pinmap for this driver. PinMap() PinMap // Unregister unregisters the pin from the driver. Should be called when the pin is closed. From bf6e647c1d100ac046004bf1777429f6f67f9dbd Mon Sep 17 00:00:00 2001 From: Ben Delarre Date: Tue, 13 Jan 2015 14:15:25 -0800 Subject: [PATCH 08/23] detect host based on cpuinfo --- detect.go | 55 +++++++++++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/detect.go b/detect.go index 07cf61e..c0ae7c4 100644 --- a/detect.go +++ b/detect.go @@ -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,33 @@ 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 + } + if strings.HasPrefix(fields[0], "Revision") { + rev, err := strconv.ParseInt(fields[1], 16, 8) + if err != nil { + continue + } + revision = int(rev) + } else if strings.HasPrefix(fields[0], "Hardware") { + hardware = fields[1] + } else if 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 +109,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"): + 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 } From f54c1104453f132010de01a0c067be6b21efaccb Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 06:36:01 +0530 Subject: [PATCH 09/23] support detection on rpi b+ --- detect.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/detect.go b/detect.go index c0ae7c4..6658d5d 100644 --- a/detect.go +++ b/detect.go @@ -83,15 +83,17 @@ func cpuInfo() (model, hardware string, revision int, err error) { if len(fields) < 1 { continue } - if strings.HasPrefix(fields[0], "Revision") { - rev, err := strconv.ParseInt(fields[1], 16, 8) + switch { + case strings.HasPrefix(fields[0], "Revision"): + revs := strings.TrimSpace(fields[1]) + rev, err := strconv.ParseInt(revs, 16, 8) if err != nil { continue } revision = int(rev) - } else if strings.HasPrefix(fields[0], "Hardware") { + case strings.HasPrefix(fields[0], "Hardware"): hardware = fields[1] - } else if strings.HasPrefix(fields[0], "model name") { + case strings.HasPrefix(fields[0], "model name"): model = fields[1] } } From de0ad53f3fda269de5ed7c74a8eff3069f21ae6a Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 06:55:55 +0530 Subject: [PATCH 10/23] rpi: add support for rpi a+/b+ --- README.md | 4 ++-- host/rpi/rpi.go | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ed8ade3..5213924 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,9 @@ 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/)) * [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** diff --git a/host/rpi/rpi.go b/host/rpi/rpi.go index 372835b..940a4c2 100644 --- a/host/rpi/rpi.go +++ b/host/rpi/rpi.go @@ -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,29 @@ 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 + pins := rev3Pins + if rev < 16 { + pins = rev2Pins + } if rev < 4 { pins = rev1Pins } From 714179b86b092537a778b5a158b463699acc1bcf Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 06:56:14 +0530 Subject: [PATCH 11/23] cli: print the rev number in hex --- embd/detect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embd/detect.go b/embd/detect.go index 8d268e6..6659980 100644 --- a/embd/detect.go +++ b/embd/detect.go @@ -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{ From 69dfee5f752b79451498c3bfc49aeb94f20ecc16 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Thu, 15 Jan 2015 07:28:59 +0530 Subject: [PATCH 12/23] rpi: add link to revision number details --- host/rpi/rpi.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/host/rpi/rpi.go b/host/rpi/rpi.go index 940a4c2..0a82a4b 100644 --- a/host/rpi/rpi.go +++ b/host/rpi/rpi.go @@ -74,6 +74,8 @@ var ledMap = embd.LEDMap{ func init() { embd.Register(embd.HostRPi, func(rev int) *embd.Descriptor { + // Refer to http://elinux.org/RPi_HardwareHistory#Board_Revision_History + // for details. pins := rev3Pins if rev < 16 { pins = rev2Pins From 6af1796f35a0957d4b783917da1c3bb446b981c1 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Tue, 17 Feb 2015 17:04:09 +0530 Subject: [PATCH 13/23] support detection of rpi 2 --- detect.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/detect.go b/detect.go index 6658d5d..a92ff4e 100644 --- a/detect.go +++ b/detect.go @@ -86,7 +86,7 @@ func cpuInfo() (model, hardware string, revision int, err error) { switch { case strings.HasPrefix(fields[0], "Revision"): revs := strings.TrimSpace(fields[1]) - rev, err := strconv.ParseInt(revs, 16, 8) + rev, err := strconv.ParseInt(revs, 16, 32) if err != nil { continue } @@ -119,9 +119,9 @@ func DetectHost() (host Host, rev int, err error) { switch { case strings.Contains(model, "ARMv7") && (strings.Contains(hardware, "AM33XX") || strings.Contains(hardware, "AM335X")): return HostBBB, rev, nil - case strings.Contains(hardware, "BCM2708"): + case strings.Contains(hardware, "BCM2708") || strings.Contains(hardware, "BCM2709"): return HostRPi, rev, nil default: - 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 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) } } From 20fc71efcf95132dd8e9d3ab9f09cba9a6fb34b9 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Tue, 17 Feb 2015 17:05:10 +0530 Subject: [PATCH 14/23] doc: rpi 2 is now supported --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5213924..e994dea 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Join the [mailing list](https://groups.google.com/forum/#!forum/go-embd) ## Platforms Supported * [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 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** From bba94cdae23718f035ab815de82e8bbe78489be2 Mon Sep 17 00:00:00 2001 From: Karan Misra Date: Wed, 18 Feb 2015 19:49:50 +0530 Subject: [PATCH 15/23] doc: sostronk as sponsor --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e994dea..75157cc 100644 --- a/README.md +++ b/README.md @@ -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) From 915b3b76a757075500bc585c6a008cefdf233745 Mon Sep 17 00:00:00 2001 From: Matthew Dale Date: Sat, 21 Feb 2015 22:50:56 -0800 Subject: [PATCH 16/23] interface: add an abstraction layer for character displays the characterdisplay package is an abstraction layer for controlling character displays also includes refactors to the hd44780 package to fit the characterdisplay Controller interface --- controller/hd44780/character_display.go | 212 ------------------ controller/hd44780/character_display_test.go | 85 ------- controller/hd44780/hd44780.go | 121 +++++++--- controller/hd44780/hd44780_test.go | 131 ++++++++--- .../characterdisplay/characterdisplay.go | 106 +++++++++ .../characterdisplay/characterdisplay_test.go | 129 +++++++++++ samples/characterdisplay.go | 45 ++++ samples/hd44780.go | 24 +- 8 files changed, 489 insertions(+), 364 deletions(-) delete mode 100644 controller/hd44780/character_display.go delete mode 100644 controller/hd44780/character_display_test.go create mode 100644 interface/display/characterdisplay/characterdisplay.go create mode 100644 interface/display/characterdisplay/characterdisplay_test.go create mode 100644 samples/characterdisplay.go diff --git a/controller/hd44780/character_display.go b/controller/hd44780/character_display.go deleted file mode 100644 index 6c23099..0000000 --- a/controller/hd44780/character_display.go +++ /dev/null @@ -1,212 +0,0 @@ -package hd44780 - -import ( - "github.com/golang/glog" - "github.com/kidoman/embd" -) - -// DefaultModes are the default initialization modes for a CharacterDisplay. -var DefaultModes []ModeSetter = []ModeSetter{ - FourBitMode, - OneLine, - Dots5x8, - EntryIncrement, - EntryShiftOff, - DisplayOn, - CursorOff, - BlinkOff, -} - -// CharacterDisplay represents an abstract character display and provides a -// convenience layer on top of the basic HD44780 library. -type CharacterDisplay struct { - *HD44780 - Cols int - Rows int - p *position -} - -type position struct { - col int - row int -} - -// NewGPIOCharacterDisplay creates a new CharacterDisplay connected by a 4-bit GPIO bus. -func NewGPIOCharacterDisplay( - rs, en, d4, d5, d6, d7, backlight interface{}, - blPolarity Polarity, - cols, rows int, - modes ...ModeSetter, -) (*CharacterDisplay, 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 - } - hd, err := NewGPIO( - pins[0], - pins[1], - pins[2], - pins[3], - pins[4], - pins[5], - pins[6], - blPolarity, - append(DefaultModes, modes...)..., - ) - if err != nil { - return nil, err - } - return NewCharacterDisplay(hd, cols, rows) -} - -// NewI2CCharacterDisplay creates a new CharacterDisplay connected by an I²C bus. -func NewI2CCharacterDisplay( - i2c embd.I2CBus, - addr byte, - pinMap I2CPinMap, - cols, rows int, - modes ...ModeSetter, -) (*CharacterDisplay, error) { - hd, err := NewI2C(i2c, addr, pinMap, append(DefaultModes, modes...)...) - if err != nil { - return nil, err - } - return NewCharacterDisplay(hd, cols, rows) -} - -// NewCharacterDisplay creates a new character display abstraction for an -// HD44780-compatible controller. -func NewCharacterDisplay(hd *HD44780, cols, rows int) (*CharacterDisplay, error) { - display := &CharacterDisplay{ - HD44780: hd, - Cols: cols, - Rows: rows, - p: &position{0, 0}, - } - err := display.BacklightOn() - if err != nil { - return nil, err - } - return display, nil -} - -// Home moves the cursor and all characters to the home position. -func (disp *CharacterDisplay) Home() error { - disp.currentPosition(0, 0) - return disp.HD44780.Home() -} - -// Clear clears the display, preserving the mode settings and setting the correct home. -func (disp *CharacterDisplay) Clear() error { - disp.currentPosition(0, 0) - err := disp.HD44780.Clear() - if err != nil { - return err - } - err = disp.SetMode() - if err != nil { - return err - } - if !disp.isLeftToRight() { - return disp.SetCursor(disp.Cols-1, 0) - } - return nil -} - -// Message prints the given string on the display. -func (disp *CharacterDisplay) 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 - } - if disp.isLeftToRight() { - disp.p.col++ - } else { - 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 *CharacterDisplay) Newline() error { - var col int - if disp.isLeftToRight() { - col = 0 - } else { - col = disp.Cols - 1 - } - return disp.SetCursor(col, disp.p.row+1) -} - -func (disp *CharacterDisplay) isLeftToRight() bool { - // EntryIncrement and EntryShiftOn is right-to-left - // EntryDecrement and EntryShiftOn is left-to-right - // EntryIncrement and EntryShiftOff is left-to-right - // EntryDecrement and EntryShiftOff is right-to-left - return disp.EntryIncrementEnabled() != disp.EntryShiftEnabled() -} - -// SetCursor sets the input cursor to the given position. -func (disp *CharacterDisplay) SetCursor(col, row int) error { - if row >= disp.Rows { - row = disp.Rows - 1 - } - disp.currentPosition(col, row) - return disp.HD44780.SetCursor(byte(col) + disp.lcdRowOffset(row)) -} - -func (disp *CharacterDisplay) lcdRowOffset(row int) byte { - // Offset for up to 4 rows - if row > 3 { - row = 3 - } - switch disp.Cols { - case 16: - // 16-char line mappings - return []byte{0x00, 0x40, 0x10, 0x50}[row] - default: - // default to the 20-char line mappings - return []byte{0x00, 0x40, 0x14, 0x54}[row] - } -} - -func (disp *CharacterDisplay) currentPosition(col, row int) { - disp.p.col = col - disp.p.row = row -} - -// Close closes the underlying HD44780 controller. -func (disp *CharacterDisplay) Close() error { - return disp.HD44780.Close() -} diff --git a/controller/hd44780/character_display_test.go b/controller/hd44780/character_display_test.go deleted file mode 100644 index e2eca42..0000000 --- a/controller/hd44780/character_display_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package hd44780 - -import ( - "testing" - - "github.com/kidoman/embd" -) - -const ( - rows = 20 - cols = 4 -) - -func TestNewGPIOCharacterDisplay_initPins(t *testing.T) { - var pins []*mockDigitalPin - for i := 0; i < 7; i++ { - pins = append(pins, newMockDigitalPin()) - } - NewGPIOCharacterDisplay( - pins[0], - pins[1], - pins[2], - pins[3], - pins[4], - pins[5], - pins[6], - Negative, - cols, - rows, - ) - 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) { - displayGPIO, _ := NewGPIOCharacterDisplay( - newMockDigitalPin(), - newMockDigitalPin(), - newMockDigitalPin(), - newMockDigitalPin(), - newMockDigitalPin(), - newMockDigitalPin(), - newMockDigitalPin(), - Negative, - cols, - rows, - ) - displayI2C, _ := NewI2CCharacterDisplay( - newMockI2CBus(), - testAddr, - MJKDZPinMap, - cols, - rows, - ) - - for idx, display := range []*CharacterDisplay{displayGPIO, displayI2C} { - if display.EightBitModeEnabled() { - t.Errorf("Display %d: Expected display to be initialized in 4-bit mode", idx) - } - if display.TwoLineEnabled() { - t.Errorf("Display %d: Expected display to be initialized in one-line mode", idx) - } - if display.Dots5x10Enabled() { - t.Errorf("Display %d: Expected display to be initialized in 5x8-dots mode", idx) - } - if !display.EntryIncrementEnabled() { - t.Errorf("Display %d: Expected display to be initialized in entry increment mode", idx) - } - if display.EntryShiftEnabled() { - t.Errorf("Display %d: Expected display to be initialized in entry shift off mode", idx) - } - if !display.DisplayEnabled() { - t.Errorf("Display %d: Expected display to be initialized in display on mode", idx) - } - if display.CursorEnabled() { - t.Errorf("Display %d: Expected display to be initialized in cursor off mode", idx) - } - if display.BlinkEnabled() { - t.Errorf("Display %d: Expected display to be initialized in blink off mode", idx) - } - } -} diff --git a/controller/hd44780/hd44780.go b/controller/hd44780/hd44780.go index a977054..3531230 100644 --- a/controller/hd44780/hd44780.go +++ b/controller/hd44780/hd44780.go @@ -3,12 +3,6 @@ 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. -Character Display - -The CharacterDisplay type provides a convenience layer on top of the basic -HD44780 library. It includes functions for easier message printing and abstracts -some of the quirky behaviors of 4-row displays. - Resources This library is based three other HD44780 libraries: @@ -25,14 +19,24 @@ import ( "github.com/kidoman/embd" ) -type Polarity bool type entryMode byte type displayMode byte type functionMode byte +// RowAddress defines the DDRAM address of the first column of each row, up to 4 rows. +type RowAddress [4]byte + +var ( + RowAddress16Col RowAddress = [4]byte{0x00, 0x40, 0x10, 0x50} // row addresses for 16-column displays + RowAddress20Col RowAddress = [4]byte{0x00, 0x40, 0x14, 0x54} // row addresses for 20-column displays +) + +// BacklightPolarity defines the polarity of the backlight switch, either positive or negative +type BacklightPolarity bool + const ( - Negative Polarity = false - Positive Polarity = true + Negative BacklightPolarity = false + Positive BacklightPolarity = true writeDelay = 37 * time.Microsecond pulseDelay = 1 * time.Microsecond @@ -84,18 +88,38 @@ const ( // HD44780 represents an HD44780-compatible character LCD controller. type HD44780 struct { Connection - eMode entryMode - dMode displayMode - fMode functionMode + 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 embd.DigitalPin, - blPolarity Polarity, + rs, en, d4, d5, d6, d7, backlight interface{}, + blPolarity BacklightPolarity, + rowAddr RowAddress, modes ...ModeSetter, ) (*HD44780, error) { - pins := []embd.DigitalPin{rs, en, d4, d5, d6, d7, backlight} + 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 @@ -107,29 +131,45 @@ func NewGPIO( } } return New( - NewGPIOConnection(rs, en, d4, d5, d6, d7, backlight, blPolarity), + 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, modes ...ModeSetter) (*HD44780, error) { - return New(NewI2CConnection(i2c, addr, pinMap), modes...) +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, modes ...ModeSetter) (*HD44780, error) { +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(modes...) + err = controller.SetMode(append(DefaultModes, modes...)...) if err != nil { return nil, err } @@ -146,6 +186,18 @@ func (controller *HD44780) lcdInit() error { return controller.WriteInstruction(lcdInit4bit) } +// DefaultModes are the default initialization modes for an HD44780. +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) @@ -312,12 +364,29 @@ func (hd *HD44780) Home() error { // 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) - return err + // have to set mode here because clear also clears some mode settings + return hd.SetMode() } -// SetCursor sets the input cursor to the given bye. -func (hd *HD44780) SetCursor(value byte) error { +// 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) } @@ -357,13 +426,13 @@ type GPIOConnection struct { RS, EN embd.DigitalPin D4, D5, D6, D7 embd.DigitalPin Backlight embd.DigitalPin - BLPolarity Polarity + 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 Polarity, + blPolarity BacklightPolarity, ) *GPIOConnection { return &GPIOConnection{ RS: rs, @@ -480,7 +549,7 @@ type I2CPinMap struct { RS, RW, EN byte D4, D5, D6, D7 byte Backlight byte - BLPolarity Polarity + BLPolarity BacklightPolarity } var ( diff --git a/controller/hd44780/hd44780_test.go b/controller/hd44780/hd44780_test.go index a061140..b05dddc 100644 --- a/controller/hd44780/hd44780_test.go +++ b/controller/hd44780/hd44780_test.go @@ -9,7 +9,13 @@ import ( "github.com/kidoman/embd" ) -const testAddr byte = 0x20 +const ( + testAddr byte = 0x20 + cols = 20 + rows = 4 +) + +var testRowAddr RowAddress = RowAddress20Col type mockDigitalPin struct { direction embd.Direction @@ -60,6 +66,11 @@ type instruction struct { 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)) } @@ -73,7 +84,7 @@ func printInstructionsAsBinary(ins []instruction) string { } func newMockGPIOConnection() *mockGPIOConnection { - be := &mockGPIOConnection{ + conn := &mockGPIOConnection{ rs: newMockDigitalPin(), en: newMockDigitalPin(), d4: newMockDigitalPin(), @@ -87,32 +98,32 @@ func newMockGPIOConnection() *mockGPIOConnection { var b byte = 0x00 var rs int = 0 // wait for EN low,high,low then read high nibble - if <-be.en.values == embd.Low && - <-be.en.values == embd.High && - <-be.en.values == embd.Low { - rs = <-be.rs.values - b |= byte(<-be.d4.values) << 4 - b |= byte(<-be.d5.values) << 5 - b |= byte(<-be.d6.values) << 6 - b |= byte(<-be.d7.values) << 7 + 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 <-be.en.values == embd.Low && - <-be.en.values == embd.High && - <-be.en.values == embd.Low { - b |= byte(<-be.d4.values) - b |= byte(<-be.d5.values) << 1 - b |= byte(<-be.d6.values) << 2 - b |= byte(<-be.d7.values) << 3 - be.writes = append(be.writes, instruction{rs, b}) + 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 be + return conn } -func (be *mockGPIOConnection) pins() []*mockDigitalPin { - return []*mockDigitalPin{be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight} +func (conn *mockGPIOConnection) pins() []*mockDigitalPin { + return []*mockDigitalPin{conn.rs, conn.en, conn.d4, conn.d5, conn.d6, conn.d7, conn.backlight} } type mockI2CBus struct { @@ -156,9 +167,9 @@ func printBytesAsBinary(bytes []byte) string { } func TestInitialize4Bit_directionOut(t *testing.T) { - be := newMockGPIOConnection() - NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative) - for idx, pin := range be.pins() { + 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) } @@ -166,29 +177,29 @@ func TestInitialize4Bit_directionOut(t *testing.T) { } func TestInitialize4Bit_lcdInit(t *testing.T) { - be := newMockGPIOConnection() - NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative) + 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(lcdSetEntryMode)}, - instruction{embd.Low, byte(lcdSetDisplayMode)}, - instruction{embd.Low, byte(lcdSetFunctionMode)}, + 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, be.writes) { + if !reflect.DeepEqual(instructions, mock.writes) { t.Errorf( "\nExpected\t%s\nActual\t\t%+v", printInstructionsAsBinary(instructions), - printInstructionsAsBinary(be.writes)) + printInstructionsAsBinary(mock.writes)) } } func TestGPIOConnectionClose(t *testing.T) { - be := newMockGPIOConnection() - bus, _ := NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative) + 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 be.pins() { + for idx, pin := range mock.pins() { if !pin.closed { t.Errorf("Pin %d was not closed", idx) } @@ -253,3 +264,55 @@ func TestI2CConnectionClose(t *testing.T) { 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") + } +} diff --git a/interface/display/characterdisplay/characterdisplay.go b/interface/display/characterdisplay/characterdisplay.go new file mode 100644 index 0000000..ca866d9 --- /dev/null +++ b/interface/display/characterdisplay/characterdisplay.go @@ -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 +} diff --git a/interface/display/characterdisplay/characterdisplay_test.go b/interface/display/characterdisplay/characterdisplay_test.go new file mode 100644 index 0000000..4bd6c39 --- /dev/null +++ b/interface/display/characterdisplay/characterdisplay_test.go @@ -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) +} diff --git a/samples/characterdisplay.go b/samples/characterdisplay.go new file mode 100644 index 0000000..7a054aa --- /dev/null +++ b/samples/characterdisplay.go @@ -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() +} diff --git a/samples/hd44780.go b/samples/hd44780.go index 3307a4c..418fa7e 100644 --- a/samples/hd44780.go +++ b/samples/hd44780.go @@ -22,22 +22,32 @@ func main() { bus := embd.NewI2CBus(1) - display, err := hd44780.NewI2CCharacterDisplay( + hd, err := hd44780.NewI2C( bus, 0x20, hd44780.PCF8574PinMap, - 20, - 4, + hd44780.RowAddress20Col, hd44780.TwoLine, hd44780.BlinkOn, ) if err != nil { panic(err) } - defer display.Close() + defer hd.Close() - display.Clear() - display.Message("Hello, world!\n@embd") + 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) - display.BacklightOff() + hd.BacklightOff() } From ae26e1d7f02b5702585ae8669a193f902ecd1a6f Mon Sep 17 00:00:00 2001 From: Ben Schwartz Date: Sun, 29 Mar 2015 09:02:10 -0500 Subject: [PATCH 17/23] adding stepper motor example --- samples/28bjy-48.go | 111 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 samples/28bjy-48.go diff --git a/samples/28bjy-48.go b/samples/28bjy-48.go new file mode 100644 index 0000000..1e61d2a --- /dev/null +++ b/samples/28bjy-48.go @@ -0,0 +1,111 @@ +// +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" + "github.com/kidoman/embd" + _ "github.com/kidoman/embd/host/rpi" + "os" + "os/signal" + "time" +) + +func main() { + stepWait := 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 + // 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 func() { + if err := pin.SetDirection(embd.In); err != nil { + panic(err) + } + }() + + stepPins[i] = pin + } + + // Define advanced sequence + // as shown in manufacturers 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 + + // Start main loop + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, os.Kill) + defer signal.Stop(quit) + + stepCounter := 0 + for { + select { + case <-time.After(time.Duration(*stepWait) * time.Millisecond): + 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 + } + } + +} From 2e3e23a155c07a2486a9627a7d0bacba1a031484 Mon Sep 17 00:00:00 2001 From: Ben Schwartz Date: Mon, 30 Mar 2015 19:10:19 -0500 Subject: [PATCH 18/23] style updates and using ticker to time steps --- samples/28bjy-48.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/samples/28bjy-48.go b/samples/28bjy-48.go index 1e61d2a..f2dfbe6 100644 --- a/samples/28bjy-48.go +++ b/samples/28bjy-48.go @@ -14,15 +14,16 @@ package main import ( "flag" "fmt" - "github.com/kidoman/embd" - _ "github.com/kidoman/embd/host/rpi" "os" "os/signal" "time" + + "github.com/kidoman/embd" + _ "github.com/kidoman/embd/host/rpi" ) func main() { - stepWait := flag.Int("step-delay", 10, "milliseconds between steps") + stepDelay := flag.Int("step-delay", 10, "milliseconds between steps") flag.Parse() if err := embd.InitGPIO(); err != nil { @@ -30,7 +31,7 @@ func main() { } defer embd.CloseGPIO() - // Physical pins 11,15,16,18 + // Physical pins 11,15,16,18 on rasp pi // GPIO17,GPIO22,GPIO23,GPIO24 stepPinNums := []int{17, 22, 23, 24} @@ -57,8 +58,7 @@ func main() { stepPins[i] = pin } - // Define advanced sequence - // as shown in manufacturers datasheet + // Define sequence described in manufacturer's datasheet seq := [][]int{ []int{1, 0, 0, 0}, []int{1, 1, 0, 0}, @@ -73,15 +73,19 @@ func main() { stepCount := len(seq) - 1 stepDir := 2 // Set to 1 or 2 for clockwise, -1 or -2 for counter-clockwise - // Start main loop quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, os.Kill) defer signal.Stop(quit) - stepCounter := 0 + // Start main loop + ticker := time.NewTicker(time.Duration(*stepDelay) * time.Millisecond) + + var stepCounter int for { select { - case <-time.After(time.Duration(*stepWait) * time.Millisecond): + 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) @@ -103,9 +107,10 @@ func main() { } else if stepCounter < 0 { stepCounter = stepCount } + case <-quit: + ticker.Stop() return } } - } From 38897e416bd5c54049230542f2a09179ccae0ebd Mon Sep 17 00:00:00 2001 From: Ben Schwartz Date: Tue, 31 Mar 2015 18:16:02 -0500 Subject: [PATCH 19/23] style updates, removing error checking from defer, and closing timer channel --- samples/28bjy-48.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/samples/28bjy-48.go b/samples/28bjy-48.go index f2dfbe6..8eb8882 100644 --- a/samples/28bjy-48.go +++ b/samples/28bjy-48.go @@ -49,11 +49,7 @@ func main() { if err := pin.Write(embd.Low); err != nil { panic(err) } - defer func() { - if err := pin.SetDirection(embd.In); err != nil { - panic(err) - } - }() + defer pin.SetDirection(embd.In) stepPins[i] = pin } @@ -69,7 +65,6 @@ func main() { []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 @@ -79,6 +74,7 @@ func main() { // Start main loop ticker := time.NewTicker(time.Duration(*stepDelay) * time.Millisecond) + defer timer.Close() var stepCounter int for { @@ -96,7 +92,6 @@ func main() { if err := pin.Write(embd.Low); err != nil { panic(err) } - } } stepCounter += stepDir From 333194b96ff94c99a8120170090476113ed46162 Mon Sep 17 00:00:00 2001 From: Ben Schwartz Date: Tue, 31 Mar 2015 20:16:06 -0500 Subject: [PATCH 20/23] style updates, removing time.Close --- samples/28bjy-48.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/samples/28bjy-48.go b/samples/28bjy-48.go index 8eb8882..955f2cb 100644 --- a/samples/28bjy-48.go +++ b/samples/28bjy-48.go @@ -74,13 +74,11 @@ func main() { // Start main loop ticker := time.NewTicker(time.Duration(*stepDelay) * time.Millisecond) - defer timer.Close() 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 { From 3574e65c8dd408825c723f9fa42831abff10ce06 Mon Sep 17 00:00:00 2001 From: Ben Schwartz Date: Tue, 31 Mar 2015 20:22:47 -0500 Subject: [PATCH 21/23] moving ticker.Stop to defer statement when creating ticker --- samples/28bjy-48.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/28bjy-48.go b/samples/28bjy-48.go index 955f2cb..456b7db 100644 --- a/samples/28bjy-48.go +++ b/samples/28bjy-48.go @@ -74,6 +74,7 @@ func main() { // Start main loop ticker := time.NewTicker(time.Duration(*stepDelay) * time.Millisecond) + defer ticker.Stop() var stepCounter int for { @@ -102,7 +103,6 @@ func main() { } case <-quit: - ticker.Stop() return } } From 3254f15093cbe9566be45c3940c9f4522c9c0866 Mon Sep 17 00:00:00 2001 From: Matthew Dale Date: Thu, 9 Apr 2015 00:52:27 -0700 Subject: [PATCH 22/23] controller: add more descriptive docs to the hd44780 exported variables --- controller/hd44780/hd44780.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/controller/hd44780/hd44780.go b/controller/hd44780/hd44780.go index 3531230..88032d5 100644 --- a/controller/hd44780/hd44780.go +++ b/controller/hd44780/hd44780.go @@ -23,19 +23,25 @@ type entryMode byte type displayMode byte type functionMode byte -// RowAddress defines the DDRAM address of the first column of each row, up to 4 rows. +// 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 RowAddress = [4]byte{0x00, 0x40, 0x10, 0x50} // row addresses for 16-column displays - RowAddress20Col RowAddress = [4]byte{0x00, 0x40, 0x14, 0x54} // row addresses for 20-column displays + // 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 defines the polarity of the backlight switch, either positive or negative +// 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 @@ -187,6 +193,7 @@ func (controller *HD44780) lcdInit() error { } // 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, @@ -553,14 +560,14 @@ type I2CPinMap struct { } var ( - // Standard pin mapping for an MJKDZ-based I²C backpack. + // 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, } - // Standard pin mapping for a PCF8574-based I²C backpack. + // 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, From 5c00ad4d5811359df59f79f6fc58b21f80fa9ab7 Mon Sep 17 00:00:00 2001 From: adeschamps Date: Sat, 9 May 2015 20:44:15 -0400 Subject: [PATCH 23/23] Update README.md Fixed link for BH1750FVI documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75157cc..f37b5b5 100644 --- a/README.md +++ b/README.md @@ -225,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