tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/hd44780/hd44780.go (about)

     1  // Package hd44780 provides a driver for the HD44780 LCD controller.
     2  //
     3  // Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
     4  package hd44780 // import "tinygo.org/x/drivers/hd44780"
     5  
     6  import (
     7  	"errors"
     8  	"io"
     9  	"machine"
    10  	"time"
    11  )
    12  
    13  const (
    14  	// These are the default execution times for the Clear and
    15  	// Home commands and everything else.
    16  	//
    17  	// These are used if RW is passed as machine.NoPin and ignored
    18  	// otherwise.
    19  	//
    20  	// They are set conservatively here and can be tweaked in the
    21  	// Config structure.
    22  	DefaultClearHomeTime = 80 * time.Millisecond
    23  	DefaultInstrExecTime = 80 * time.Microsecond
    24  )
    25  
    26  type Buser interface {
    27  	io.ReadWriter
    28  	SetCommandMode(set bool)
    29  	WriteOnly() bool
    30  }
    31  
    32  type Device struct {
    33  	bus          Buser
    34  	width        uint8
    35  	height       uint8
    36  	buffer       []uint8
    37  	bufferLength uint8
    38  
    39  	rowOffset  []uint8 // Row offsets in DDRAM
    40  	datalength uint8
    41  
    42  	cursor     cursor
    43  	busyStatus []byte
    44  
    45  	clearHomeTime time.Duration // time clear/home instructions might take
    46  	instrExecTime time.Duration // time all other instructions might take
    47  }
    48  
    49  type cursor struct {
    50  	x, y uint8
    51  }
    52  
    53  type Config struct {
    54  	Width         int16
    55  	Height        int16
    56  	CursorBlink   bool
    57  	CursorOnOff   bool
    58  	Font          uint8
    59  	ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default
    60  	InstrExecTime time.Duration // time all other instructions might take - use 0 for the default
    61  }
    62  
    63  // NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7
    64  //
    65  // If your device has RW set permanently to ground then pass in rw as machine.NoPin
    66  func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
    67  	const fourBitMode = 4
    68  	if len(dataPins) != fourBitMode {
    69  		return Device{}, errors.New("4 pins are required in data slice (D4-D7) when HD44780 is used in 4 bit mode")
    70  	}
    71  	return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_4BIT), nil
    72  }
    73  
    74  // NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7
    75  //
    76  // If your device has RW set permanently to ground then pass in rw as machine.NoPin
    77  func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
    78  	const eightBitMode = 8
    79  	if len(dataPins) != eightBitMode {
    80  		return Device{}, errors.New("8 pins are required in data slice (D0-D7) when HD44780 is used in 8 bit mode")
    81  	}
    82  	return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_8BIT), nil
    83  }
    84  
    85  // Configure initializes device
    86  func (d *Device) Configure(cfg Config) error {
    87  	d.busyStatus = make([]byte, 1)
    88  	d.width = uint8(cfg.Width)
    89  	d.height = uint8(cfg.Height)
    90  	if d.width == 0 || d.height == 0 {
    91  		return errors.New("width and height must be set")
    92  	}
    93  	d.clearHomeTime = cfg.ClearHomeTime
    94  	d.instrExecTime = cfg.InstrExecTime
    95  	memoryMap := uint8(ONE_LINE)
    96  	if d.height > 1 {
    97  		memoryMap = TWO_LINE
    98  	}
    99  	d.setRowOffsets()
   100  	d.ClearBuffer()
   101  
   102  	cursor := CURSOR_OFF
   103  	if cfg.CursorOnOff {
   104  		cursor = CURSOR_ON
   105  	}
   106  	cursorBlink := CURSOR_BLINK_OFF
   107  	if cfg.CursorBlink {
   108  		cursorBlink = CURSOR_BLINK_ON
   109  	}
   110  	if !(cfg.Font == FONT_5X8 || cfg.Font == FONT_5X10) {
   111  		cfg.Font = FONT_5X8
   112  	}
   113  
   114  	//Wait 15ms after Vcc rises to 4.5V
   115  	time.Sleep(15 * time.Millisecond)
   116  
   117  	d.bus.SetCommandMode(true)
   118  	d.bus.Write([]byte{DATA_LENGTH_8BIT})
   119  	time.Sleep(5 * time.Millisecond)
   120  
   121  	for i := 0; i < 2; i++ {
   122  		d.bus.Write([]byte{DATA_LENGTH_8BIT})
   123  		time.Sleep(150 * time.Microsecond)
   124  
   125  	}
   126  
   127  	if d.datalength == DATA_LENGTH_4BIT {
   128  		d.bus.Write([]byte{DATA_LENGTH_4BIT})
   129  	}
   130  
   131  	// Busy flag is now accessible
   132  	d.SendCommand(memoryMap | cfg.Font | d.datalength)
   133  	d.SendCommand(DISPLAY_OFF)
   134  	d.SendCommand(DISPLAY_CLEAR)
   135  	d.SendCommand(ENTRY_MODE | CURSOR_INCREASE | DISPLAY_NO_SHIFT)
   136  	d.SendCommand(DISPLAY_ON | uint8(cursor) | uint8(cursorBlink))
   137  	return nil
   138  }
   139  
   140  // Write writes data to internal buffer
   141  func (d *Device) Write(data []byte) (n int, err error) {
   142  	size := len(data)
   143  	if size > len(d.buffer) {
   144  		size = len(d.buffer)
   145  	}
   146  	d.bufferLength = uint8(size)
   147  	for i := uint8(0); i < d.bufferLength; i++ {
   148  		d.buffer[i] = data[i]
   149  	}
   150  	return size, nil
   151  }
   152  
   153  // Display sends the whole buffer to the screen at cursor position
   154  func (d *Device) Display() error {
   155  
   156  	// Buffer may contain less characters than its capacity.
   157  	// We must be sure that we will not send unassigned characters
   158  	// That would result in sending zero values of buffer slice and
   159  	// potentialy displaying some character.
   160  	var totalDisplayedChars uint8
   161  
   162  	var bufferPos uint8
   163  
   164  	for ; d.cursor.y < d.height; d.cursor.y++ {
   165  		d.SetCursor(d.cursor.x, d.cursor.y)
   166  
   167  		for ; d.cursor.x < d.width && totalDisplayedChars < d.bufferLength; d.cursor.x++ {
   168  			d.sendData(d.buffer[bufferPos])
   169  			bufferPos++
   170  			totalDisplayedChars++
   171  		}
   172  		if d.cursor.x >= d.width {
   173  			d.cursor.x = 0
   174  		}
   175  		if totalDisplayedChars >= d.bufferLength {
   176  			break
   177  		}
   178  
   179  	}
   180  	return nil
   181  }
   182  
   183  // SetCursor moves cursor to position x,y, where (0,0) is top left corner and (width-1, height-1) bottom right
   184  func (d *Device) SetCursor(x, y uint8) {
   185  	d.cursor.x = x
   186  	d.cursor.y = y
   187  	d.SendCommand(DDRAM_SET | (x + (d.rowOffset[y] * y)))
   188  }
   189  
   190  // SetRowOffsets sets initial memory addresses coresponding to the display rows
   191  // Each row on display has different starting address in DDRAM. Rows are not mapped in order.
   192  // These addresses tend to differ between the types of the displays (16x2, 16x4, 20x4 etc ..),
   193  // https://web.archive.org/web/20111122175541/http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html
   194  func (d *Device) setRowOffsets() {
   195  	switch d.height {
   196  	case 1:
   197  		d.rowOffset = []uint8{}
   198  	case 2:
   199  		d.rowOffset = []uint8{0x0, 0x40, 0x0, 0x40}
   200  	case 4:
   201  		d.rowOffset = []uint8{0x0, 0x40, d.width, 0x40 + d.width}
   202  	default:
   203  		d.rowOffset = []uint8{0x0, 0x40, d.width, 0x40 + d.width}
   204  
   205  	}
   206  }
   207  
   208  // SendCommand sends commands to driver
   209  func (d *Device) SendCommand(command byte) {
   210  	d.bus.SetCommandMode(true)
   211  	d.bus.Write([]byte{command})
   212  
   213  	for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
   214  	}
   215  }
   216  
   217  // sendData sends byte data directly to display.
   218  func (d *Device) sendData(data byte) {
   219  	d.bus.SetCommandMode(false)
   220  	d.bus.Write([]byte{data})
   221  
   222  	for d.busy(false) {
   223  	}
   224  }
   225  
   226  // CreateCharacter crates characters using data and stores it under cgram Addr in CGRAM
   227  func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
   228  	d.SendCommand(CGRAM_SET | cgramAddr)
   229  	for _, dd := range data {
   230  		d.sendData(dd)
   231  	}
   232  }
   233  
   234  // busy returns true when hd447890 is busy
   235  // or after the timeout specified
   236  func (d *Device) busy(longDelay bool) bool {
   237  	if d.bus.WriteOnly() {
   238  		// Can't read busy flag if write only, so sleep a bit then return
   239  		if longDelay {
   240  			// Note that we sleep like this so the default
   241  			// time.Sleep is time.Sleep(constant) as
   242  			// time.Sleep(variable) doesn't seem to work on AVR yet
   243  			if d.clearHomeTime != 0 {
   244  				time.Sleep(d.clearHomeTime)
   245  			} else {
   246  				time.Sleep(DefaultClearHomeTime)
   247  			}
   248  		} else {
   249  			if d.instrExecTime != 0 {
   250  				time.Sleep(d.instrExecTime)
   251  			} else {
   252  				time.Sleep(DefaultInstrExecTime)
   253  			}
   254  		}
   255  		return false
   256  	}
   257  	d.bus.SetCommandMode(true)
   258  	d.bus.Read(d.busyStatus)
   259  	return (d.busyStatus[0] & BUSY) > 0
   260  }
   261  
   262  // Busy returns true when hd447890 is busy
   263  func (d *Device) Busy() bool {
   264  	return d.busy(false)
   265  }
   266  
   267  // Size returns the current size of the display.
   268  func (d *Device) Size() (w, h int16) {
   269  	return int16(d.width), int16(d.height)
   270  }
   271  
   272  // ClearDisplay clears displayed content and buffer
   273  func (d *Device) ClearDisplay() {
   274  	d.SendCommand(DISPLAY_CLEAR)
   275  	d.ClearBuffer()
   276  }
   277  
   278  // ClearBuffer clears internal buffer
   279  func (d *Device) ClearBuffer() {
   280  	d.buffer = make([]uint8, d.width*d.height)
   281  }