@ -1,4 +1,4 @@
// Copyright 20 18 The Periph Authors. All rights reserved.
// Copyright 20 25 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
@ -14,216 +14,427 @@ import (
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/display"
"periph.io/x/conn/v3/gpio"
)
// lineTwo offset for the second line in the LCD buffer.
const lineTwo = 0x40
type writeMode bool
// Dev is the 4-bit addressing device for HD-44780
type Dev struct {
// data pins
dataPins [ ] gpio . PinOut
type ifMode byte
// register select pin
rsPin gpio . PinOut
const (
modeCommand writeMode = false
modeData writeMode = true
// enable pin
cmdByte byte = 0xfe
mode4Bit ifMode = 0x04
mode8Bit ifMode = 0x08
)
// HD44780 is an implementation that supports writing to LCD displays using a
// gpio.Group for the data pins, and discrete pins for the reset, enable, and
// and backlight pins.
//
// Implements periph.io/conn/x/display/TextDisplay and display.DisplayBacklight
type HD44780 struct {
dataPins gpio . Group
resetPin gpio . PinOut
enablePin gpio . PinOut
blMono display . DisplayBacklight
blRGB display . DisplayRGBBacklight
mode ifMode
rows int
cols int
on bool
cursor bool
blink bool
lastWrite int64
}
// New creates and initializes the LCD device
//
// data - references to data pins
// rs - rs pin
// e - strobe pin
func New ( data [ ] gpio . PinOut , rs , e gpio . PinOut ) ( * Dev , error ) {
if len ( data ) != 4 {
return nil , fmt . Errorf ( "expected 4 data pins, passed %d" , len ( data ) )
}
dev := & Dev {
dataPins : data ,
enablePin : e ,
rsPin : rs ,
}
if err := dev . Reset ( ) ; err != nil {
return nil , err
const (
delayCommand time . Duration = 2000
delayCharacter time . Duration = 200
)
var rowConstants = [ ] [ ] byte { { 0 , 0 , 64 } , { 0 , 0 , 64 , 20 , 84 } }
var clearScreen = [ ] byte { cmdByte , 0x01 }
var goHome = [ ] byte { cmdByte , 0x02 }
var setCursorPosition = [ ] byte { cmdByte , 0x80 }
// Return the row offset value
func getRowConstant ( row , maxcols int ) byte {
var offset int
if maxcols != 16 {
offset = 1
}
return dev, nil
return rowConstants[ offset ] [ row ]
}
// Reset resets the HC-44780 chipset, clears the screen buffer and moves cursor to the
// home of screen (line 0, column 0).
func ( r * Dev ) Reset ( ) error {
if err := r . clearBits ( ) ; err != nil {
return err
}
// NewHD44780 takes a GPIO group, and gpio.PinOut for reset and enable. It
// returns an HD44780 device in an initialized state and ready for use.
//
// The first 4 or 8 pins of the data group must be connected to the data lines
// To use 4 bit mode, you would connect lines D4-D7 on the display, and for 8
// bit mode, D0-D7. If dataPinGroup is 8 or more pins, then it's assumed the
// display is connected using all 8 pins.
//
// backlight should implement either display.DisplayBacklight or
// display.DisplayRGBBacklight. See GPIOMonoBacklight.
func NewHD44780 (
dataPinGroup gpio . Group ,
resetPin , enablePin gpio . PinOut ,
backlight any ,
rows , cols int ) ( * HD44780 , error ) {
delayMs ( 15 )
mode := mode4Bit
if len ( dataPinGroup . Pins ( ) ) >= 8 {
mode = mode8Bit
}
if err := r . rsPin . Out ( gpio . Low ) ; err != nil {
return err
lcd := & HD44780 {
dataPins : dataPinGroup ,
resetPin : resetPin ,
enablePin : enablePin ,
mode : mode ,
rows : rows ,
cols : cols ,
on : true ,
}
if err := r . enablePin . Out ( gpio . Low ) ; err != nil {
return err
switch bl := backlight . ( type ) {
case display . DisplayBacklight :
lcd . blMono = bl
case display . DisplayRGBBacklight :
lcd . blRGB = bl
}
return lcd , lcd . init ( )
}
if err := r . bulkSendData ( resetSequence , r . write4Bits ) ; err != nil {
return err
}
// Not supported by this device. Returns display.ErrNotImplemented
func ( lcd * HD44780 ) AutoScroll ( enabled bool ) error {
// TODO: Wrap
return display . ErrNotImplemented
}
return r . bulkSendData ( initSequence , r . writeInstruction )
// Clears the screen and moves the cursor to the first position.
func ( lcd * HD44780 ) Clear ( ) error {
_ , err := lcd . Write ( clearScreen )
return err
}
func ( r * Dev ) String ( ) string {
return "HD44870, 4 bit mode"
// Return the number of columns the display supports
func ( lcd * HD44780 ) Cols ( ) int {
return lcd . cols
}
// Halt clears the LCD screen
func ( r * Dev ) Halt ( ) error {
if err := r . writeInstruction ( 0x01 ) ; err != nil {
return err
// Set the cursor mode. You can pass multiple arguments.
// Cursor(CursorOff, CursorUnderline)
func ( lcd * HD44780 ) Cursor ( modes ... display . CursorMode ) ( err error ) {
var val = byte ( 0x08 )
if lcd . on {
val |= 0x04
}
delayMs ( 2 )
return nil
for _ , mode := range modes {
switch mode {
case display . CursorOff :
// lcd.Write(underlineCursorOff)
lcd . blink = false
lcd . cursor = false
case display . CursorBlink :
lcd . blink = true
lcd . cursor = true
val |= 0x01
case display . CursorUnderline :
lcd . cursor = true
lcd . blink = true
// lcd.Write(underlineCursorOn)
val |= 0x02
case display . CursorBlock :
lcd . cursor = true
lcd . blink = true
val |= 0x01
default :
err = fmt . Errorf ( "HD44780 - unexpected cursor: %d" , mode )
return
}
}
_ , err = lcd . Write ( [ ] byte { cmdByte , val & 0x0f } )
return err
}
// SetCursor positions the cursor
//
// line - screen line, 0-based
// column - column, 0-based
func ( r * Dev ) SetCursor ( line uint8 , column uint8 ) error {
return r . writeInstruction ( 0x80 | ( line * lineTwo + column ) )
// Move the cursor home (MinRow(),MinCol())
func ( lcd * HD44780 ) Home ( ) ( err error ) {
_ , err = lcd . Write ( goHome )
return err
}
// Print the data string
//
// data string to display
func ( r * Dev ) Print ( data string ) error {
for _ , v := range [ ] byte ( data ) {
if err := r . WriteChar ( v ) ; err != nil {
return err
}
}
return nil
// Return the min column position.
func ( lcd * HD44780 ) MinCol ( ) int {
return 1
}
// WriteChar writes a single byte (character) at the cursor position.
//
// data - character code
func ( r * Dev ) WriteChar ( data uint8 ) error {
if err := r . sendData ( ) ; err != nil {
return err
}
if err := r . write4Bits ( data >> 4 ) ; err != nil {
return err
}
if err := r . write4Bits ( data ) ; err != nil {
return err
// Return the min row position.
func ( lcd * HD44780 ) MinRow ( ) int {
return 1
}
// Move the cursor forward or backward.
func ( lcd * HD44780 ) Move ( dir display . CursorDirection ) ( err error ) {
var val byte = 0x10
switch dir {
case display . Backward :
case display . Forward :
val |= 0x04
case display . Down , display . Up :
fallthrough
default :
err = fmt . Errorf ( "hd44780: %w" , display . ErrNotImplemented )
return
}
delayUs ( 10 )
return nil
_ , err = lcd . Write ( [ ] byte { cmdByte , val } )
return
}
// service methods
// Move the cursor to arbitrary position.
func ( lcd * HD44780 ) MoveTo ( row , col int ) ( err error ) {
if row < lcd . MinRow ( ) || row > lcd . rows || col < lcd . MinCol ( ) || col > lcd . cols {
err = fmt . Errorf ( "HD44780.MoveTo(%d,%d) value out of range" , row , col )
return
}
var cmd = [ ] byte { cmdByte , setCursorPosition [ 1 ] }
cmd [ 1 ] |= getRowConstant ( row , lcd . cols ) + byte ( col - 1 )
_ , err = lcd . Write ( cmd )
return
}
var resetSequence = [ ] [ ] uint {
{ 0x03 , 50 } , // init 1-st cycle
{ 0x03 , 10 } , // init 2-nd cycle
{ 0x03 , 10 } , // init 3-rd cycle
{ 0x02 , 10 } , // init finish
// Return the number of rows the display supports.
func ( lcd * HD44780 ) Rows ( ) int {
return lcd . rows
}
var initSequence = [ ] [ ] uint {
{ 0x14 , 0 } , // 4-bit mode, 2 lines, 5x7 chars high
{ 0x10 , 0 } , // disable display
{ 0x01 , 2000 } , // clear screen
{ 0x06 , 0 } , // cursor shift right, no display move
{ 0x0c , 0 } , // enable display no cursor
{ 0x01 , 2000 } , // clear screen
{ 0x02 , 2000 } , // cursor home
// Return info about the dsiplay.
func ( lcd * HD44780 ) String ( ) string {
return fmt . Sprintf ( "HD44780 - Rows: %d, Cols: %d" , lcd . rows , lcd . cols )
}
func ( r * Dev ) bulkSendData ( seq [ ] [ ] uint , f func ( _data uint8 ) error ) error {
for _ , v := range seq {
if err := f ( uint8 ( v [ 0 ] ) ) ; err != nil {
return err
}
if v [ 1 ] > 0 {
delayUs ( v [ 1 ] )
}
// Turn the display on / off
func ( lcd * HD44780 ) Display ( on bool ) error {
lcd . on = on
val := byte ( 0x08 )
if on {
val |= 0x04
}
return nil
if lcd . blink {
val |= 0x01
}
if lcd . cursor {
val |= 0x02
}
_ , err := lcd . Write ( [ ] byte { cmdByte , val } )
return err
}
func ( r * Dev ) clearBits ( ) error {
for _ , v := range r . dataPins {
if err := v . Out ( gpio . Low ) ; err != nil {
return err
}
// Write a set of bytes to the display.
func ( lcd * HD44780 ) Write ( p [ ] byte ) ( n int , err error ) {
if len ( p ) == 0 {
return
}
if p [ 0 ] == cmdByte {
n = len ( p ) - 1
err = lcd . sendCommand ( p [ 1 : ] )
return
}
lcd . delayWrite ( delayCommand )
err = lcd . resetPin . Out ( gpio . Level ( modeData ) )
if err != nil {
return
}
return nil
}
func ( r * Dev ) write4Bits ( data uint8 ) error {
for i , v := range r . dataPins {
if data & ( 1 << uint ( i ) ) > 0 {
if err := v . Out ( gpio . High ) ; err != nil {
return err
for _ , byteVal := range p {
lcd . lastWrite = time . Now ( ) . UnixMicro ( )
if lcd . mode == mode4Bit {
err = lcd . write4Bits ( byteVal >> 4 )
if err == nil {
err = lcd . write4Bits ( byteVal & 0x0f )
}
} else {
if err := v . Out ( gpio . Low ) ; err != nil {
return err
}
err = lcd . write8Bits ( byteVal )
}
if err != nil {
return
}
n += 1
time . Sleep ( delayCharacter * time . Microsecond )
}
return r . strobe ( )
lcd . lastWrite = time . Now ( ) . UnixMicro ( )
return
}
func ( r * Dev ) sendInstruction ( ) error {
if err := r . rsPin . Out ( gpio . Low ) ; err != nil {
return err
}
return r . enablePin . Out ( gpio . Low )
// Write a string output to the display.
func ( lcd * HD44780 ) WriteString ( text string ) ( int , error ) {
return lcd . Write ( [ ] byte ( text ) )
}
func ( r * Dev ) sendData ( ) error {
if err := r . rsPin . Out ( gpio . High ) ; err != nil {
return err
// Halt clears the display, turns the backlight off, and turns the display off.
// Halt() is called for the data pins gpio.Group.
func ( lcd * HD44780 ) Halt ( ) error {
_ = lcd . Clear ( )
_ = lcd . Backlight ( 0 )
_ = lcd . Display ( false )
return lcd . dataPins . Halt ( )
}
// Set the backlight intensity.
func ( lcd * HD44780 ) Backlight ( intensity display . Intensity ) error {
if lcd . blMono != nil {
return lcd . blMono . Backlight ( intensity )
} else if lcd . blRGB != nil {
return lcd . blRGB . RGBBacklight ( intensity , intensity , intensity )
}
return r . enablePin . Out ( gpio . Low )
return display. ErrNotImplemented
}
func ( r * Dev ) writeInstruction ( data uint8 ) error {
if err := r . sendInstruction ( ) ; err != nil {
return err
// For units that have an RGB Backlight, set the backlight color/intensity.
// The range of the values is 0-255.
func ( lcd * HD44780 ) RGBBacklight ( red , green , blue display . Intensity ) error {
if lcd . blRGB != nil {
return lcd . blRGB . RGBBacklight ( red , green , blue )
} else if lcd . blMono != nil {
return lcd . blMono . Backlight ( red | green | blue )
}
// write high 4 bits
if err := r . write4Bits ( data >> 4 ) ; err != nil {
return err
return display . ErrNotImplemented
}
// delayWrite looks at the time of the last LCD write and if the specified
// microseconds period has not elapsed, it invokes time.Sleep() with the
// difference.
//
// Some I/O methods, like direct GPIO on a Pi are very fast, while other methods
// like i2c take longer. Without delays, on very fast I/O paths, the LCD will
// display garbage. The correct way to handle this would be to read the Busy
// flag on the LCD display. However, some backpacks don't have the capability to
// check the Busy flag because the R/W pin isn't connected. So, we can't
// correctly handle io delays. This handles the very fast interfaces, while not
// penalizing the slower ones with unnecessary delays.
//
// The value of lcd.lastWrite is updated to the current time by the call.
func ( lcd * HD44780 ) delayWrite ( microseconds time . Duration ) {
diff := microseconds - time . Duration ( time . Now ( ) . UnixMicro ( ) - lcd . lastWrite )
if diff > 0 {
time . Sleep ( time . Duration ( diff ) * time . Microsecond )
}
// write low bits
if err := r . write4Bits ( data ) ; err != nil {
return err
lcd . lastWrite = time . Now ( ) . UnixMicro ( )
}
// Init the display. The HD44780 has a fairly complex initialization cycle
// with variations for 4 and 8 pin mode.
func ( lcd * HD44780 ) init ( ) error {
/ *
This is the startup sequence for the Hitachi HD44780U chip as
documented in the Datasheet .
* /
lcd . lastWrite = time . Now ( ) . UnixMicro ( )
if lcd . mode == mode4Bit {
var lineMode byte = 0x20
if lcd . rows > 1 {
lineMode |= 0x08
}
err := lcd . resetPin . Out ( gpio . Level ( modeCommand ) )
if err != nil {
return err
}
err = lcd . enablePin . Out ( gpio . Low )
if err != nil {
return err
}
err = lcd . write4Bits ( 0x03 )
if err != nil {
return err
}
time . Sleep ( 4100 * time . Microsecond )
_ = lcd . write4Bits ( 0x03 )
_ = lcd . write4Bits ( 0x03 )
_ = lcd . write4Bits ( 0x02 )
_ = lcd . sendCommand ( [ ] byte { lineMode } )
} else {
// Init the display for 8 pin operation.
lineMode := byte ( 0x30 ) // Set the line mode and interface to 8 bits
if lcd . rows > 1 {
lineMode |= 0x08
}
err := lcd . resetPin . Out ( gpio . Level ( modeCommand ) )
if err != nil {
return err
}
err = lcd . enablePin . Out ( gpio . Low )
if err != nil {
return err
}
_ = lcd . write8Bits ( 0x03 << 4 ) // Get it's attention
time . Sleep ( 4100 * time . Microsecond )
_ = lcd . write8Bits ( 0x03 << 4 )
_ = lcd . write8Bits ( 0x03 << 4 )
_ = lcd . write8Bits ( lineMode )
_ = lcd . write8Bits ( 0x4 ) // set entry mode
}
delayUs ( 50 )
_ = lcd . Cursor ( display . CursorOff )
_ = lcd . Display ( true )
_ = lcd . Clear ( )
_ = lcd . Home ( )
// If there's not a backlight, ignore the error.
_ = lcd . Backlight ( 0xff )
return nil
}
func ( r * Dev ) strobe ( ) error {
if err := r . enablePin . Out ( gpio . High ) ; err != nil {
func ( lcd * HD44780 ) sendCommand ( commands [ ] byte ) error {
lcd . delayWrite ( delayCommand )
err := lcd . resetPin . Out ( gpio . Level ( modeCommand ) )
if err != nil {
return err
}
delayUs ( 2 )
return r . enablePin . Out ( gpio . Low )
for _ , command := range commands {
if lcd . mode == mode4Bit {
err = lcd . write4Bits ( byte ( command >> 4 ) )
if err == nil {
err = lcd . write4Bits ( byte ( command ) )
}
} else {
err = lcd . write8Bits ( command )
}
if err != nil {
break
}
}
lcd . lastWrite = time . Now ( ) . UnixMicro ( )
return err
}
func ( lcd * HD44780 ) write4Bits ( value byte ) error {
return lcd . writeBits ( gpio . GPIOValue ( value ) , 0x0f )
}
func delayUs ( ms uint ) {
time . Sleep ( time . Duration ( ms ) * time . Microsecond )
func ( lcd * HD44780 ) write8Bits ( value byte ) error {
return lcd . writeBits ( gpio . GPIOValue ( value ) , 0xff )
}
func delayMs ( ms int ) {
time . Sleep ( time . Duration ( ms ) * time . Millisecond )
func ( lcd * HD44780 ) writeBits ( value , mask gpio . GPIOValue ) error {
err := lcd . dataPins . Out ( value , mask )
if err != nil {
return err
}
err = lcd . enablePin . Out ( gpio . High )
if err == nil {
time . Sleep ( 2 * time . Microsecond )
err = lcd . enablePin . Out ( gpio . Low )
}
return err
}
var _ conn . Resource = & Dev { }
var _ display . TextDisplay = & HD44780 { }
var _ display . DisplayBacklight = & HD44780 { }
var _ display . DisplayRGBBacklight = & HD44780 { }
var _ conn . Resource = & HD44780 { }