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

     1  // Package hd44780i2c implements a driver for the Hitachi HD44780 LCD display module
     2  // with an I2C adapter.
     3  //
     4  // Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
     5  package hd44780i2c
     6  
     7  import (
     8  	"errors"
     9  	"time"
    10  
    11  	"tinygo.org/x/drivers"
    12  )
    13  
    14  // Device wraps an I2C connection to a HD44780 I2C LCD with related data.
    15  type Device struct {
    16  	bus             drivers.I2C
    17  	addr            uint8
    18  	width           uint8
    19  	height          uint8
    20  	cursor          cursor
    21  	backlight       uint8
    22  	displayfunction uint8
    23  	displaycontrol  uint8
    24  	displaymode     uint8
    25  }
    26  
    27  type cursor struct {
    28  	x, y uint8
    29  }
    30  
    31  // Config for HD44780 I2C LCD.
    32  type Config struct {
    33  	Width       uint8
    34  	Height      uint8
    35  	Font        uint8
    36  	CursorOn    bool
    37  	CursorBlink bool
    38  }
    39  
    40  // New creates a new HD44780 I2C LCD connection. The I2C bus must already be
    41  // configured.
    42  //
    43  // This function only creates the Device object, it does not touch the device.
    44  func New(bus drivers.I2C, addr uint8) Device {
    45  	if addr == 0 {
    46  		addr = 0x27
    47  	}
    48  	return Device{
    49  		bus:  bus,
    50  		addr: addr,
    51  	}
    52  }
    53  
    54  // Configure sets up the display. Display itself and backlight is default on.
    55  func (d *Device) Configure(cfg Config) error {
    56  
    57  	if cfg.Width == 0 || cfg.Height == 0 {
    58  		return errors.New("width and height must be set")
    59  	}
    60  	d.width = uint8(cfg.Width)
    61  	d.height = uint8(cfg.Height)
    62  
    63  	delayms(50)
    64  
    65  	d.backlight = BACKLIGHT_ON
    66  	d.expanderWrite(0)
    67  	delayms(1000)
    68  
    69  	d.write4bits(0x03 << 4)
    70  	delayus(4500)
    71  	d.write4bits(0x03 << 4)
    72  	delayus(4500)
    73  	d.write4bits(0x03 << 4)
    74  	delayus(150)
    75  	d.write4bits(0x02 << 4)
    76  
    77  	d.displayfunction = DATA_LENGTH_4BIT | ONE_LINE | FONT_5X8
    78  	if d.height > 1 {
    79  		d.displayfunction |= TWO_LINE
    80  	}
    81  	if cfg.Font != 0 && d.height == 1 {
    82  		d.displayfunction |= FONT_5X10
    83  	}
    84  	d.sendCommand(FUNCTION_MODE | d.displayfunction)
    85  
    86  	d.displaycontrol = DISPLAY_ON | CURSOR_OFF | CURSOR_BLINK_OFF
    87  	if cfg.CursorOn {
    88  		d.displaycontrol |= CURSOR_ON
    89  	}
    90  	if cfg.CursorBlink {
    91  		d.displaycontrol |= CURSOR_BLINK_ON
    92  	}
    93  	d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
    94  	d.ClearDisplay()
    95  
    96  	d.displaymode = CURSOR_INCREASE | DISPLAY_NO_SHIFT
    97  	d.sendCommand(ENTRY_MODE | d.displaymode)
    98  	d.Home()
    99  
   100  	return nil
   101  }
   102  
   103  // ClearDisplay clears all texts on the display and sets the cursor back to position (0, 0).
   104  func (d *Device) ClearDisplay() {
   105  	d.sendCommand(DISPLAY_CLEAR)
   106  	d.cursor.x = 0
   107  	d.cursor.y = 0
   108  	delayus(2000)
   109  }
   110  
   111  // Home sets the cursor back to position (0, 0).
   112  func (d *Device) Home() {
   113  	d.sendCommand(CURSOR_HOME)
   114  	d.cursor.x = 0
   115  	d.cursor.y = 0
   116  	delayus(2000)
   117  }
   118  
   119  // SetCursor sets the cursor to a specific position (x, y).
   120  //
   121  // For example, on 16x2 LCDs the range of x (column) is 0~15 and y (row) is 0~1.
   122  // if y is larger than actual rows, it would be set to 0 (restart from first row).
   123  func (d *Device) SetCursor(x, y uint8) {
   124  	rowOffset := []uint8{0x0, 0x40, 0x14, 0x54}
   125  	if y > (d.height - 1) {
   126  		y = 0
   127  	}
   128  	d.cursor.x = x
   129  	d.cursor.y = y
   130  	d.sendCommand(DDRAM_SET | (x + (rowOffset[y])))
   131  }
   132  
   133  // Print prints text on the display (started from current cursor position).
   134  //
   135  // It would automatically break to new line when the text is too long.
   136  // You can also use \n as line breakers.
   137  func (d *Device) Print(data []byte) {
   138  	for _, chr := range data {
   139  		if chr == '\n' {
   140  			d.newLine()
   141  		} else {
   142  			if d.cursor.x >= d.width {
   143  				d.newLine()
   144  			}
   145  			d.sendData(uint8(rune(chr)))
   146  			d.cursor.x++
   147  		}
   148  	}
   149  }
   150  
   151  // CreateCharacter crates custom characters (using data parameter)
   152  // and stores it under CGRAM address (using cgramAddr, 0x0-0x7).
   153  func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
   154  	cgramAddr &= 0x7
   155  	d.sendCommand(CGRAM_SET | cgramAddr<<3)
   156  	for _, dd := range data {
   157  		d.sendData(dd)
   158  	}
   159  	d.SetCursor(d.cursor.x, d.cursor.y)
   160  }
   161  
   162  // DisplayOn turns on/off the display.
   163  func (d *Device) DisplayOn(option bool) {
   164  	if option {
   165  		d.displaycontrol |= DISPLAY_ON
   166  	} else {
   167  		d.displaycontrol &= ^uint8(DISPLAY_ON)
   168  	}
   169  	d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
   170  }
   171  
   172  // CursorOn display/hides the cursor.
   173  func (d *Device) CursorOn(option bool) {
   174  	if option {
   175  		d.displaycontrol |= CURSOR_ON
   176  	} else {
   177  		d.displaycontrol &= ^uint8(CURSOR_ON)
   178  	}
   179  	d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
   180  }
   181  
   182  // CursorBlink turns on/off the blinking cursor mode.
   183  func (d *Device) CursorBlink(option bool) {
   184  	if option {
   185  		d.displaycontrol |= CURSOR_BLINK_ON
   186  	} else {
   187  		d.displaycontrol &= ^uint8(CURSOR_BLINK_ON)
   188  	}
   189  	d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
   190  }
   191  
   192  // BacklightOn turns on/off the display backlight.
   193  func (d *Device) BacklightOn(option bool) {
   194  	if option {
   195  		d.backlight = BACKLIGHT_ON
   196  	} else {
   197  		d.backlight = BACKLIGHT_OFF
   198  	}
   199  	d.expanderWrite(0)
   200  }
   201  
   202  func (d *Device) newLine() {
   203  	d.cursor.x = 0
   204  	d.cursor.y++
   205  	d.SetCursor(d.cursor.x, d.cursor.y)
   206  }
   207  
   208  func delayms(t uint16) {
   209  	time.Sleep(time.Millisecond * time.Duration(t))
   210  }
   211  
   212  func delayus(t uint16) {
   213  	time.Sleep(time.Microsecond * time.Duration(t))
   214  }
   215  
   216  func (d *Device) expanderWrite(value uint8) {
   217  	d.bus.Tx(uint16(d.addr), []uint8{value | d.backlight}, nil)
   218  }
   219  
   220  func (d *Device) pulseEnable(value uint8) {
   221  	d.expanderWrite(value | En)
   222  	delayus(1)
   223  	d.expanderWrite(value & ^uint8(En))
   224  	delayus(50)
   225  }
   226  
   227  func (d *Device) write4bits(value uint8) {
   228  	d.expanderWrite(value)
   229  	d.pulseEnable(value)
   230  }
   231  
   232  func (d *Device) write(value uint8, mode uint8) {
   233  	d.write4bits(uint8(value&0xf0) | mode)
   234  	d.write4bits(uint8((value<<4)&0xf0) | mode)
   235  }
   236  
   237  func (d *Device) sendCommand(value uint8) {
   238  	d.write(value, 0)
   239  }
   240  
   241  func (d *Device) sendData(value uint8) {
   242  	d.write(value, Rs)
   243  }