local bit = bit --luacheck: read globals bit -- metatable local LiquidCrystal = {} LiquidCrystal.__index = LiquidCrystal -- commands local LCD_CLEARDISPLAY = 0x01 local LCD_RETURNHOME = 0x02 local LCD_ENTRYMODESET = 0x04 local LCD_DISPLAYCONTROL = 0x08 local LCD_CURSORSHIFT = 0x10 local LCD_FUNCTIONSET = 0x20 local LCD_SETCGRAMADDR = 0x40 local LCD_SETDDRAMADDR = 0x80 -- flags for display entry mode -- local LCD_ENTRYRIGHT = 0x00 local LCD_ENTRYLEFT = 0x02 local LCD_ENTRYSHIFTINCREMENT = 0x01 -- local LCD_ENTRYSHIFTDECREMENT = 0x00 -- flags for display on/off control local LCD_DISPLAYON = 0x04 -- local LCD_DISPLAYOFF = 0x00 local LCD_CURSORON = 0x02 -- local LCD_CURSOROFF = 0x00 local LCD_BLINKON = 0x01 -- local LCD_BLINKOFF = 0x00 -- flags for display/cursor shift local LCD_DISPLAYMOVE = 0x08 local LCD_CURSORMOVE = 0x00 local LCD_MOVERIGHT = 0x04 local LCD_MOVELEFT = 0x00 -- flags for function set local LCD_8BITMODE = 0x10 local LCD_4BITMODE = 0x00 local LCD_2LINE = 0x08 local LCD_1LINE = 0x00 local LCD_5x10DOTS = 0x04 local LCD_5x8DOTS = 0x00 function LiquidCrystal:autoscroll(on) if on then self._displaymode = bit.bor(self._displaymode, LCD_ENTRYSHIFTINCREMENT) else self._displaymode = bit.band(self._displaymode, bit.bnot(LCD_ENTRYSHIFTINCREMENT)) end return self:_command(bit.bor(LCD_ENTRYMODESET, self._displaymode)) end function LiquidCrystal:blink(on) if on then self._displaycontrol = bit.bor(self._displaycontrol, LCD_BLINKON) else self._displaycontrol = bit.band(self._displaycontrol, bit.bnot(LCD_BLINKON)) end return self:_command(bit.bor(LCD_DISPLAYCONTROL, self._displaycontrol)) end function LiquidCrystal:clear() return self:_command(LCD_CLEARDISPLAY) end function LiquidCrystal:cursorLeft() return self:_command(bit.bor(LCD_CURSORSHIFT, LCD_CURSORMOVE, LCD_MOVELEFT)) end function LiquidCrystal:cursorMove(col, row) return self:_command(bit.bor(LCD_SETDDRAMADDR, col + (row and (self._offsets[row] - 1) or 0))) end function LiquidCrystal:cursor(on) if on then self._displaycontrol = bit.bor(self._displaycontrol, LCD_CURSORON) else self._displaycontrol = bit.band(self._displaycontrol, bit.bnot(LCD_CURSORON)) end return self:_command(bit.bor(LCD_DISPLAYCONTROL, self._displaycontrol)) end function LiquidCrystal:cursorRight() return self:_command(bit.bor(LCD_CURSORSHIFT, LCD_CURSORMOVE, LCD_MOVERIGHT)) end function LiquidCrystal:customChar(index, bytes) local pos = self:position() self:_command(bit.bor(LCD_SETCGRAMADDR, bit.lshift(bit.band(self._eightdots and index or bit.clear(index, 0), 0x7), 3))) for i=1,(self._eightdots and 8 or 11) do self:_write(bytes[i] or 0) end self:cursorMove(pos) end function LiquidCrystal:display(on) if on then self._displaycontrol = bit.bor(self._displaycontrol, LCD_DISPLAYON) else self._displaycontrol = bit.band(self._displaycontrol, bit.bnot(LCD_DISPLAYON)) end return self:_command(bit.bor(LCD_DISPLAYCONTROL, self._displaycontrol)) end function LiquidCrystal:home() return self:_command(LCD_RETURNHOME) end function LiquidCrystal:leftToRight() self._displaymode = bit.bor(self._displaymode, LCD_ENTRYLEFT) return self:_command(bit.bor(LCD_ENTRYMODESET, self._displaymode)) end function LiquidCrystal:readCustom(index) local pos = self:position() local data = {} self:_command(bit.bor(LCD_SETCGRAMADDR, bit.lshift(bit.band(self._eightdots and index or bit.clear(index, 0), 0x7), 3))) for i=1,(self._eightdots and 8 or 11) do data[i] = self:read() end self:cursorMove(pos) return data end function LiquidCrystal:rightToLeft() self._displaymode = bit.band(self._displaymode, bit.bnot(LCD_ENTRYLEFT)) self:_command(bit.bor(LCD_ENTRYMODESET, self._displaymode)) end function LiquidCrystal:scrollLeft() return self:_command(bit.bor(LCD_CURSORSHIFT, LCD_DISPLAYMOVE, LCD_MOVELEFT)) end function LiquidCrystal:scrollRight() return self:_command(bit.bor(LCD_CURSORSHIFT, LCD_DISPLAYMOVE, LCD_MOVERIGHT)) end function LiquidCrystal:write(...) for _, x in ipairs({...}) do if type(x) == "number" then self:_write(x) end if type(x) == "string" then for i=1,#x do self:_write(string.byte(x, i)) end end end end return function (backend, onelinemode, eightdotsmode, column_width) local self = {} setmetatable(self, LiquidCrystal) -- copy out backend functions, to avoid a long-lived table self._command = backend.command self.busy = backend.busy self.position = backend.position self._write = backend.write self.read = backend.read self.backlight = backend.backlight -- defaults self._displaycontrol = 0 self._displaymode = 0 self._eightdots = eightdotsmode self._offsets = {0, 0x40} if column_width ~= nil then self._offsets[3] = 0 + column_width self._offsets[4] = 0x40 + column_width end self:_command(bit.bor(LCD_FUNCTIONSET, bit.bor( backend.fourbits and LCD_4BITMODE or LCD_8BITMODE, onelinemode and LCD_1LINE or LCD_2LINE, eightdotsmode and LCD_5x8DOTS or LCD_5x10DOTS))) self:_command(bit.bor(LCD_ENTRYMODESET, self._displaymode)) self:display(true) self:clear() return self end