From 6499387039308449c9a11c50cfaf940d94137e58 Mon Sep 17 00:00:00 2001 From: seregaxvm Date: Sat, 18 Apr 2020 17:35:14 +0300 Subject: [PATCH] add LiquidCrystal lua module (#2974) --- docs/lua-modules/liquidcrystal.md | 559 ++++++++++++++++++++ lua_modules/liquidcrystal/lc-gpio4bit.lua | 83 +++ lua_modules/liquidcrystal/lc-gpio8bit.lua | 80 +++ lua_modules/liquidcrystal/lc-i2c4bit.lua | 103 ++++ lua_modules/liquidcrystal/liquidcrystal.lua | 183 +++++++ mkdocs.yml | 1 + 6 files changed, 1009 insertions(+) create mode 100644 docs/lua-modules/liquidcrystal.md create mode 100644 lua_modules/liquidcrystal/lc-gpio4bit.lua create mode 100644 lua_modules/liquidcrystal/lc-gpio8bit.lua create mode 100644 lua_modules/liquidcrystal/lc-i2c4bit.lua create mode 100644 lua_modules/liquidcrystal/liquidcrystal.lua diff --git a/docs/lua-modules/liquidcrystal.md b/docs/lua-modules/liquidcrystal.md new file mode 100644 index 00000000..12db0525 --- /dev/null +++ b/docs/lua-modules/liquidcrystal.md @@ -0,0 +1,559 @@ +# LiquidCrystal Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2019-12-01 | [Matsievskiy Sergey](https://github.com/seregaxvm) | [Matsievskiy Sergey](https://github.com/seregaxvm) | [liquidcrystal.lua](../../lua_modules/liquidcrystal/liquidcrystal.lua) [i2c4bit.lua](../../lua_modules/liquidcrystal/lc-i2c4bit.lua) [gpio4bit.lua](../../lua_modules/liquidcrystal/lc-gpio4bit.lua) [gpio8bit.lua](../../lua_modules/liquidcrystal/lc-gpio8bit.lua) | + +This Lua module provides access to [Hitachi HD44780](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) based LCDs. It supports 4 bit and 8 bit GPIO interface, 4 bit [PCF8574](https://www.nxp.com/docs/en/data-sheet/PCF8574_PCF8574A.pdf) based I²C interface. + +!!! note + This module requires `bit` C module built into firmware. Depending on the interface, `gpio` or `i2c` module is also required. + +## Program example + +In this example LED screen is connected using I²C GPIO expander. +Program defines five custom characters and prints text. + +```lua +backend_meta = require "lc-i2c4bit" +lc_meta = require "liquidcrystal" + +-- create display object +lc = lc_meta(backend_meta{sda=1, scl=2}, false, true, 20) +backend_meta = nil +lc_meta = nil +-- define custom characters +lc:customChar(0, {0,14,31,31,4,4,5,2}) +lc:customChar(1, {4,6,5,5,4,12,28,8}) +lc:customChar(2, {14,31,17,17,17,17,17,31}) +lc:customChar(3, {14,31,17,17,17,17,31,31}) +lc:customChar(4, {14,31,17,17,31,31,31,31}) +lc:customChar(5, {14,31,31,31,31,31,31,31}) +lc:clear() -- clear display +lc:blink(true) -- enable cursor blinking +lc:home() -- reset cursor position +lc:write("hello", " ", "world") -- write string +lc:cursorMove(1, 2) -- move cursor to second line +lc:write("umbrella", 0, 32, "note", 1) -- mix text strings and characters +lc:cursorMove(1, 3) +lc:write("Battery level ", 2, 3, 4, 5) +lc:home() +lc:blink(false) +for i=1,20 do print(lc:read()) end -- read back first line +lc:home() +for _, d in ipairs(lc:readCustom(0)) do print(d) end -- read back umbrella char +for _, d in ipairs(lc:readCustom(1)) do print(d) end -- read back note char +``` + +### Require +```lua +i2c4bit_meta = require("lc-i2c4bit") +gpio4bit_meta = require("lc-gpio4bit") +gpio8bit_meta = require("lc-gpio8bit") +lc_meta = require("liquidcrystal") +``` + +### Release +```lua +package.loaded["lc-i2c4bit"] = nil +package.loaded["lc-gpio4bit"] = nil +package.loaded["lc-gpio8bit"] = nil +package.loaded["liquidcrystal"] = nil +``` + +## Initialization + +Liquidcrystal module is initialized using closure, which takes backend object as an argument. + +### I²C backend + +Loading I²C backend module returns initialization closure. +It configures I²C backend and returns backend object. + +#### Syntax +`function({[sda=sda_pin] [, scl=scl_pin] [, busid=id] [, busad=address] [, speed = spd] [, rs = rs_pos] [, rw = rw_pos] [, en = en_pos] [, bl = bl_pos] [, d4 = d4_pos] [, d5 = d5_pos] [, d6 = d6_pos] [, d7 = d7_pos]})` + +!!! note + In most cases only `sda` and `scl` parameters are required + +#### Parameters +- `sda`: I²C data pin. If set to `nil`, I²C bus initialization step via [`i2c.setup`](https://nodemcu.readthedocs.io/en/master/modules/i2c/#i2csetup) will be skipped +- `scl`: I²C clock pin. If set to `nil`, I²C bus initialization step via [`i2c.setup`](https://nodemcu.readthedocs.io/en/master/modules/i2c/#i2csetup) will be skipped +- `busid`: I²C bus ID. Defaults to `0` +- `busad`: chip I²C address. Defaults to `0x27` (default PCF8574 address) +- `speed`: I²C speed. Defaults to `i2c.SLOW` +- `rs`: bit position assigned to `RS` pin in I²C word. Defaults to 0 +- `rw`: bit position assigned to `RW` pin in I²C word. Defaults to 1 +- `en`: bit position assigned to `EN` pin in I²C word. Defaults to 2 +- `bl`: bit position assigned to backlight pin in I²C word. Defaults to 3 +- `d4`: bit position assigned to `D4` pin in I²C word. Defaults to 4 +- `d5`: bit position assigned to `D5` pin in I²C word. Defaults to 5 +- `d6`: bit position assigned to `D6` pin in I²C word. Defaults to 6 +- `d7`: bit position assigned to `D7` pin in I²C word. Defaults to 7 + +#### Returns +- backend object + +#### Example +```lua +backend_meta = require "lc-i2c4bit" +backend = backend_meta{sda=1, scl=2 ,speed=i2c.FAST} +``` + +### GPIO 4 bit backend + +Loading GPIO 4 bit backend module returns initialization closure. +It configures GPIO 4 bit backend and returns backend object. + +#### Syntax +`function({[, rs = rs_pos] [, rw = rw_pos] [, en = en_pos] [, bl = bl_pos] [, d4 = d4_pos] [, d5 = d5_pos] [, d6 = d6_pos] [, d7 = d7_pos]})` + +#### Parameters +- `rs`: GPIO pin connected to `RS` pin. Defaults to 0 +- `rw`: GPIO pin connected to `RW` pin. If set to `nil` then `busy`, `position` and `readChar` functions will not be available. Note that `RW` pin must be pulled to the ground if not connected to GPIO +- `en`: GPIO pin connected to `EN` pin. Defaults to 1 +- `bl`: GPIO pin controlling backlight. It is assumed, that high level turns backlight on, low level turns backlight off. If set to `nil` then backlight function will not be available +- `d4`: GPIO pin connected to `D4` pin. Defaults to 2 +- `d5`: GPIO pin connected to `D5` pin. Defaults to 3 +- `d6`: GPIO pin connected to `D6` pin. Defaults to 4 +- `d7`: GPIO pin connected to `D7` pin. Defaults to 5 + +#### Returns +- backend object + +#### Example +```lua +backend_meta = require "lc-gpio4bit" +backend = backend_meta{rs=0, rw=1, en=4, d4=5, d5=6, d6=7, d7=8} +``` + +### GPIO 8 bit backend + +Loading GPIO 8 bit backend module returns initialization closure. +It configures GPIO 8 bit backend and returns backend object. + +#### Syntax +`function({[, rs = rs_pos] [, rw = rw_pos] [, en = en_pos] [, bl = bl_pos] [, d0 = d0_pos] [, d1 = d1_pos] [, d2 = d2_pos] [, d3 = d3_pos] [, d4 = d4_pos] [, d5 = d5_pos] [, d6 = d6_pos] [, d7 = d7_pos]})` + +#### Parameters +- `rs`: GPIO pin connected to `RS` pin. Defaults to 0 +- `rw`: GPIO pin connected to `RW` pin. If set to `nil` then `busy`, `position` and `readChar` functions will not be available. Note that `RW` pin must be pulled to the ground if not connected to GPIO +- `en`: GPIO pin connected to `EN` pin. Defaults to 1 +- `bl`: GPIO pin controlling backlight. It is assumed, that high level turns backlight on, low level turns backlight off. If set to `nil` then backlight function will not be available +- `d0`: GPIO pin connected to `D0` pin. Defaults to 2 +- `d1`: GPIO pin connected to `D1` pin. Defaults to 3 +- `d2`: GPIO pin connected to `D2` pin. Defaults to 4 +- `d3`: GPIO pin connected to `D3` pin. Defaults to 5 +- `d4`: GPIO pin connected to `D4` pin. Defaults to 6 +- `d5`: GPIO pin connected to `D5` pin. Defaults to 7 +- `d6`: GPIO pin connected to `D6` pin. Defaults to 8 +- `d7`: GPIO pin connected to `D7` pin. Defaults to 9 + +#### Returns +- backend object + +#### Example +```lua +backend_meta = require "lc-gpio8bit" +backend = backend_meta{rs=15, rw=2, en=5, d0=23, d1=13, d2=33, d3=32, d4=18, d5=19, d6=21, d7=22} +``` + +### Liquidcrystal initialization + +Loading Liquidcrystal module returns initialization closure. +It requires backend object and returns LCD object. + +#### Syntax +`function(backend, onelinemode, eightdotsmode, column_width)` + +#### Parameters +- `backend`: backend object +- `onelinemode`: `true` to use one line mode, `false` to use two line mode +- `eightdotsmode`: `true` to use 5x8 dot font, `false` to use 5x10 dot font +- `column_width`: number of characters in column. Used for offset calculations in function `cursorMove`. If set to `nil`, functionality of `cursorMove` will be limited. For most displays column width is `20` characters + +#### Returns +screen object + +#### Example +```lua +lc_meta = require "liquidcrystal" +lc = lc_meta(backend, true, true, 20) +``` + +## liquidcrystal.autoscroll +Autoscroll text when printing. When turned off, cursor moves and text stays still, when turned on, vice versa. + +#### Syntax +`liquidcrystal.autoscroll(self, on)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `on`: `true` to turn on, `false` to turn off + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:autoscroll(true) +``` + +## liquidcrystal.backlight +Control LCDs backlight. When using GPIO backend without `bl` argument specification function does nothing. + +#### Syntax +`liquidcrystal.backlight(self, on)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `on`: `true` to turn on, `false` to turn off + +#### Returns +- backlight status + +#### Example +```lua +liquidcrystal:backlight(true) +``` + +## liquidcrystal.blink +Control cursors blink mode. + +#### Syntax +`liquidcrystal.blink(self, on)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `on`: `true` to turn on, `false` to turn off + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:blink(true) +``` + +## liquidcrystal.busy +Get busy status of the LCD. When using GPIO backend without `rw` argument specification function does nothing. + +#### Syntax +`liquidcrystal.busy(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- `true` if device is busy, `false` if device is ready to receive commands + +#### Example +```lua +while liquidcrystal:busy() do end +``` + +## liquidcrystal.clear +Clear LCD screen. + +#### Syntax +`liquidcrystal.clear(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:clear() +``` + +## liquidcrystal.cursorLeft +Move cursor one character to the left. + +#### Syntax +`liquidcrystal.cursorLeft(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:cursorLeft() +``` + +## liquidcrystal.cursorMove +Move cursor to position. If `row` not specified, move cursor to address `col`. + +!!! note + Note that column and row indexes start with 1. However, when omitting `row` parameter, cursor addresses start with 0. + +#### Syntax +`liquidcrystal.cursorMove(self, col, row)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `col`: new cursor position column. If `row` not specified, new cursor position address +- `row`: new cursor position row or `nil` + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:cursorMove(5, 1) +liquidcrystal:cursorMove(10, 4) +liquidcrystal:cursorMove(21) +``` + +## liquidcrystal.cursor +Control cursors highlight mode. + +#### Syntax +`liquidcrystal.cursor(self, on)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `on`: `true` to turn on, `false` to turn off + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:cursor(true) +``` + +## liquidcrystal.cursorRight +Move cursor one character to the right. + +#### Syntax +`liquidcrystal.cursorRight(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:cursorRight() +``` + +## liquidcrystal.customChar +Define new custom char. Up to 8 custom characters with indexes 0 to 7 may be defined in eight dot mode. +They are accessed via `write` function by index. +In ten dot mode only 4 custom characters may be used. +They are numbered from 0 to 7 with half of them being aliases to each other (0 to 1, 2 to 3 etc). + +!!! note + Upon redefinition of a custom character all its instances will be updated automatically. + + This function resets cursor position to home if `liquidcrystal.position` function is not available. + + There are web services ([1](https://omerk.github.io/lcdchargen/), [2](https://www.quinapalus.com/hd44780udg.html)) and [desktop applications](https://pypi.org/project/lcdchargen/) that help create custom characters. + +#### Syntax +`liquidcrystal.customChar(self, index, bytes)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `index`: custom char index in range from 0 to 7 +- `bytes`: array of 8 bytes in eight bit mode or 11 bytes in ten bit mode (eleventh line is a cursor line that can also be used) that defines new char bitmap line by line + +#### Returns +`nil` + +#### Example +```lua +liquidcrystal:customChar(5, {14,31,31,31,31,31,31,31}) +liquidcrystal:write(5) +``` + +## liquidcrystal.display +Turn display on and off. Does not affect display backlight. Does not clear the display. + +#### Syntax +`liquidcrystal.display(self, on)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `on`: `true` to turn on, `false` to turn off + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:display(true) +``` + +## liquidcrystal.home +Reset cursor and screen position. + +#### Syntax +`liquidcrystal.home(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:home() +``` + +## liquidcrystal.leftToRight +Print text left to right (default). + +#### Syntax +`liquidcrystal.leftToRight(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:leftToRight() +``` + +## liquidcrystal.position +Get current position of the cursor. Position is 0 indexed. When using GPIO backend without `rw` argument specification function does nothing. + +#### Syntax +`liquidcrystal.position(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- 0 indexed position of the cursor + +#### Example +```lua +local pos = liquidcrystal:position() -- save position +-- some code +liquidcrystal:cursorMove(pos) -- restore position +``` + +## liquidcrystal.read +Return current character numerical representation. +When using GPIO backend without `rw` argument specification function does nothing. + +#### Syntax +`liquidcrystal.read(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- numerical representation of the current character + +#### Example +```lua +liquidcrystal:home() -- goto home +local ch = liquidcrystal:read() -- read char +liquidcrystal:cursorMove(1, 2) -- move to the second line +for i=ch,ch+5 do lc:write(i) end -- print 6 chars starting with ch +``` + +## liquidcrystal.readCustom +Return custom char byte array. +When using GPIO backend without `rw` argument specification function returns zeros. + +#### Syntax +`liquidcrystal.readCustom(self, index)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `index`: custom char index in range from 0 to 7 + +#### Returns +- table of size 8 in eight dot mode or 11 in ten dot mode. Each 8 bit number represents a character dot line + +#### Example +```lua +lc:customChar(0, {0,14,31,31,4,4,5,2}) -- define custom character +for _, d in ipairs(lc:readCustom(0)) do print(d) end -- read it back +``` + +## liquidcrystal.rightToLeft +Print text right to left. + +#### Syntax +`liquidcrystal.rightToLeft(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:rightToLeft() +``` + +## liquidcrystal.scrollLeft +Move text to the left. + +#### Syntax +`liquidcrystal.scrollLeft(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:scrollLeft() +``` + +## liquidcrystal.scrollRight +Move text to the right. + +#### Syntax +`liquidcrystal.scrollRight(self)` + +#### Parameters +- `self`: `liquidcrystal` instance + +#### Returns +- sent data + +#### Example +```lua +liquidcrystal:scrollRight() +``` + +## liquidcrystal.write +Print text. + +#### Syntax +`liquidcrystal.write(self, ...)` + +#### Parameters +- `self`: `liquidcrystal` instance +- `...`: strings or char codes. For the list of available characters refer to [HD44780 datasheet](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf#page=17) + +#### Returns +`nil` + +#### Example +```lua +liquidcrystal:write("hello world") +liquidcrystal:write("hello yourself", "!!!", 243, 244) +``` + + diff --git a/lua_modules/liquidcrystal/lc-gpio4bit.lua b/lua_modules/liquidcrystal/lc-gpio4bit.lua new file mode 100644 index 00000000..290a96ee --- /dev/null +++ b/lua_modules/liquidcrystal/lc-gpio4bit.lua @@ -0,0 +1,83 @@ +local gpio, bit = gpio, bit + +return function(bus_args) + local rs = bus_args.rs or 0 + local rw = bus_args.rw + local en = bus_args.en or 1 + local bl = bus_args.backlight + local d4 = bus_args.d4 or 2 + local d5 = bus_args.d5 or 3 + local d6 = bus_args.d6 or 4 + local d7 = bus_args.d7 or 5 + + for _, d in pairs({rs,rw,en,bl}) do + if d then + gpio.mode(d, gpio.OUTPUT) + end + end + + local function setGPIO(mode) + for _, d in pairs({d4, d5, d6, d7}) do + gpio.mode(d, mode) + end + end + + setGPIO(gpio.OUTPUT) + + local function send4bitGPIO(value, rs_en, rw_en, read) + local function exchange(data) + local rv = 0 + if rs then gpio.write(rs, rs_en and gpio.HIGH or gpio.LOW) end + if rw then gpio.write(rw, rw_en and gpio.HIGH or gpio.LOW) end + gpio.write(en, gpio.HIGH) + for i, d in ipairs({d4, d5, d6, d7}) do + if read and rw then + if gpio.read(d) == 1 then rv = bit.set(rv, i-1) end + else + gpio.write(d, bit.isset(data, i-1) and gpio.HIGH or gpio.LOW) + end + end + gpio.write(en, gpio.LOW) + return rv + end + local hi = bit.rshift(bit.band(value, 0xf0), 4) + local lo = bit.band(value, 0xf) + if read then setGPIO(gpio.INPUT) end + hi = exchange(hi) + lo = exchange(lo) + if read then setGPIO(gpio.OUTPUT) end + return bit.bor(bit.lshift(hi, 4), lo) + end + + -- init sequence from datasheet + send4bitGPIO(0x33, false, false, false) + send4bitGPIO(0x32, false, false, false) + + -- Return backend object + return { + fourbits = true, + command = function (screen, cmd) + return send4bitGPIO(cmd, false, false, false) + end, + busy = function(screen) + if rw == nil then return false end + return bit.isset(send4bitGPIO(0xff, false, true, true), 7) + end, + position = function(screen) + if rw == nil then return 0 end + return bit.clear(send4bitGPIO(0xff, false, true, true), 7) + end, + write = function(screen, value) + return send4bitGPIO(value, true, false, false) + end, + read = function(screen) + if rw == nil then return nil end + return send4bitGPIO(0xff, true, true, true) + end, + backlight = function(screen, on) + if (bl) then gpio.write(bl, on and gpio.HIGH or gpio.LOW) end + return on + end, + } + +end diff --git a/lua_modules/liquidcrystal/lc-gpio8bit.lua b/lua_modules/liquidcrystal/lc-gpio8bit.lua new file mode 100644 index 00000000..d61a06f9 --- /dev/null +++ b/lua_modules/liquidcrystal/lc-gpio8bit.lua @@ -0,0 +1,80 @@ +local gpio, bit = gpio, bit + +return function(bus_args) + local rs = bus_args.rs or 0 + local rw = bus_args.rw + local en = bus_args.en or 1 + local bl = bus_args.backlight + local d0 = bus_args.d0 or 2 + local d1 = bus_args.d1 or 3 + local d2 = bus_args.d2 or 4 + local d3 = bus_args.d3 or 5 + local d4 = bus_args.d4 or 6 + local d5 = bus_args.d5 or 7 + local d6 = bus_args.d6 or 8 + local d7 = bus_args.d7 or 9 + + for _, d in pairs({rs,rw,en,bl}) do + if d then + gpio.mode(d, gpio.OUTPUT) + end + end + + local function setGPIO(mode) + for _, d in pairs({d0, d1, d2, d3, d4, d5, d6, d7}) do + gpio.mode(d, mode) + end + end + + setGPIO(gpio.OUTPUT) + + local function send8bitGPIO(value, rs_en, rw_en, read) + local function exchange(data) + local rv = 0 + if rs then gpio.write(rs, rs_en and gpio.HIGH or gpio.LOW) end + if rw then gpio.write(rw, rw_en and gpio.HIGH or gpio.LOW) end + gpio.write(en, gpio.HIGH) + for i, d in ipairs({d0, d1, d2, d3, d4, d5, d6, d7}) do + if read and rw then + if gpio.read(d) == 1 then rv = bit.set(rv, i-1) end + else + gpio.write(d, bit.isset(data, i-1) and gpio.HIGH or gpio.LOW) + end + end + gpio.write(en, gpio.LOW) + return rv + end + if read then setGPIO(gpio.INPUT) end + value = exchange(value) + if read then setGPIO(gpio.OUTPUT) end + return value + end + + -- Return backend object + return { + fourbits = false, + command = function (screen, cmd) + return send8bitGPIO(cmd, false, false, false) + end, + busy = function(screen) + if rw == nil then return false end + return bit.isset(send8bitGPIO(0xff, false, true, true), 7) + end, + position = function(screen) + if rw == nil then return 0 end + return bit.clear(send8bitGPIO(0xff, false, true, true), 7) + end, + write = function(screen, value) + return send8bitGPIO(value, true, false, false) + end, + read = function(screen) + if rw == nil then return nil end + return send8bitGPIO(0xff, true, true, true) + end, + backlight = function(screen, on) + if (bl) then gpio.write(bl, on and gpio.HIGH or gpio.LOW) end + return on + end, + } + +end diff --git a/lua_modules/liquidcrystal/lc-i2c4bit.lua b/lua_modules/liquidcrystal/lc-i2c4bit.lua new file mode 100644 index 00000000..2b299481 --- /dev/null +++ b/lua_modules/liquidcrystal/lc-i2c4bit.lua @@ -0,0 +1,103 @@ +local i2c, bit = i2c, bit + +return function(bus_args) + local busid = bus_args.id or 0 + local busad = bus_args.address or 0x27 + local speed = bus_args.speed or i2c.SLOW + + local rs = bus_args.rs or 0 + local rw = bus_args.rw or 1 + local en = bus_args.en or 2 + local bl = bus_args.backlight or 3 + local d4 = bus_args.d4 or 4 + local d5 = bus_args.d5 or 5 + local d6 = bus_args.d6 or 6 + local d7 = bus_args.d7 or 7 + + -- Convenience I2C setup if a pin configuration is given + if bus_args.sda ~= nil and bus_args.scl ~= nil then + i2c.setup(busid, bus_args.sda, bus_args.scl, speed) + end + + -- The onus is on us to maintain the backlight state + local backlight = true + + local function send4bitI2C(value, rs_en, rw_en, read) + local function exchange(data, unset_read) + local rv = data + i2c.start(busid) + i2c.address(busid, busad, i2c.TRANSMITTER) + i2c.write(busid, bit.set(data, en)) + if read then + i2c.start(busid) + i2c.address(busid, busad, i2c.RECEIVER) + rv = i2c.read(busid, 1):byte(1) + i2c.start(busid) + i2c.address(busid, busad, i2c.TRANSMITTER) + if unset_read then data = bit.bor(bit.bit(rs), + bit.bit(rw), + backlight and bit.bit(bl) or 0) end + i2c.write(busid, bit.set(data, en)) + end + i2c.write(busid, bit.clear(data, en)) + i2c.stop(busid) + return rv + end + local lo = bit.bor(bit.isset(value, 0) and bit.bit(d4) or 0, + bit.isset(value, 1) and bit.bit(d5) or 0, + bit.isset(value, 2) and bit.bit(d6) or 0, + bit.isset(value, 3) and bit.bit(d7) or 0) + local hi = bit.bor(bit.isset(value, 4) and bit.bit(d4) or 0, + bit.isset(value, 5) and bit.bit(d5) or 0, + bit.isset(value, 6) and bit.bit(d6) or 0, + bit.isset(value, 7) and bit.bit(d7) or 0) + local cmd = bit.bor(rs_en and bit.bit(rs) or 0, + rw_en and bit.bit(rw) or 0, + backlight and bit.bit(bl) or 0) + hi = exchange(bit.bor(cmd, hi), false) + lo = exchange(bit.bor(cmd, lo), true) + return bit.bor(bit.lshift(bit.isset(lo, d4) and 1 or 0, 0), + bit.lshift(bit.isset(lo, d5) and 1 or 0, 1), + bit.lshift(bit.isset(lo, d6) and 1 or 0, 2), + bit.lshift(bit.isset(lo, d7) and 1 or 0, 3), + bit.lshift(bit.isset(hi, d4) and 1 or 0, 4), + bit.lshift(bit.isset(hi, d5) and 1 or 0, 5), + bit.lshift(bit.isset(hi, d6) and 1 or 0, 6), + bit.lshift(bit.isset(hi, d7) and 1 or 0, 7)) + end + + -- init sequence from datasheet + send4bitI2C(0x33, false, false, false) + send4bitI2C(0x32, false, false, false) + + -- Return backend object + return { + fourbits = true, + command = function (screen, cmd) + return send4bitI2C(cmd, false, false, false) + end, + busy = function(screen) + local rv = send4bitI2C(0xff, false, true, true) + send4bitI2C(bit.bor(0x80, bit.clear(rv, 7)), false, false, false) + return bit.isset(rv, 7) + end, + position = function(screen) + local rv = bit.clear(send4bitI2C(0xff, false, true, true), 7) + send4bitI2C(bit.bor(0x80, rv), false, false, false) + return rv + end, + write = function(screen, value) + return send4bitI2C(value, true, false, false) + end, + read = function(screen) + return send4bitI2C(0xff, true, true, true) + end, + backlight = function(screen, on) + backlight = on + local rv = bit.clear(send4bitI2C(0xff, false, true, true), 7) + send4bitI2C(bit.bor(0x80, rv), false, false, false) + return on + end, + } + +end diff --git a/lua_modules/liquidcrystal/liquidcrystal.lua b/lua_modules/liquidcrystal/liquidcrystal.lua new file mode 100644 index 00000000..d711e57a --- /dev/null +++ b/lua_modules/liquidcrystal/liquidcrystal.lua @@ -0,0 +1,183 @@ +local bit = 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 diff --git a/mkdocs.yml b/mkdocs.yml index 0a7d78c3..a63b90a0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,7 @@ pages: - 'hdc1000': 'lua-modules/hdc1000.md' - 'httpserver': 'lua-modules/httpserver.md' - 'imap': 'lua-modules/imap.md' + - 'liquidcrystal': 'lua-modules/liquidcrystal.md' - 'lm92': 'lua-modules/lm92.md' - 'mcp23008': 'lua-modules/mcp23008.md' - 'redis': 'lua-modules/redis.md'