LiquidCrystal robustness and test (#3369)

* LiquidCrystal I2C 4-bit robustness

- Fix up some formatting
- Initialization is now more conformant with the datasheet.
- Read-backs don't needlessly (or erroneously!) store back

While here, document some unexpected behaviour of read-back commands.

* liquidcrystal i2c 4bit NTest
This commit is contained in:
Nathaniel Wesley Filardo 2021-01-13 02:36:00 +00:00 committed by GitHub
parent 109f500be7
commit c3dd27cf9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 137 additions and 37 deletions

View File

@ -237,6 +237,12 @@ liquidcrystal:blink(true)
## liquidcrystal.busy
Get busy status of the LCD. When using GPIO backend without `rw` argument specification function does nothing.
!!! note
At least some HD44780s and/or interfaces have been observed to count polling
the busy flag as grounds for incrementing their position in memory. This is
mysterious, but software should restore the position after observing that the
busy flag is clear.
#### Syntax
`liquidcrystal.busy(self)`
@ -429,6 +435,11 @@ 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.
!!! note
At least some HD44780s and/or interfaces have been observed to count reading
the position as grounds for incrementing their position in memory. This is
mysterious, but software likely intends to restore the position anyway.
#### Syntax
`liquidcrystal.position(self)`

View File

@ -22,27 +22,27 @@ return function(bus_args)
-- 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
local function exchange(data, read)
local rv = data
i2c.start(busid)
i2c.address(busid, busad, i2c.TRANSMITTER)
i2c.write(busid, bit.set(data, en)) -- set data with en
if read then
i2c.start(busid) -- read 1 byte and go back to tx mode
i2c.address(busid, busad, i2c.RECEIVER)
rv = i2c.read(busid, 1):byte(1)
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
i2c.write(busid, data) -- lower en
i2c.stop(busid)
return rv
end
local function send4bitI2C(value, rs_en, rw_en)
local meta = 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)
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,
@ -51,11 +51,8 @@ return function(bus_args)
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)
hi = exchange(bit.bor(meta, hi), rw_en)
lo = exchange(bit.bor(meta, lo), rw_en)
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),
@ -66,36 +63,45 @@ return function(bus_args)
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)
-- init sequence from datasheet (Figure 24)
local function justsend(what)
i2c.start(busid)
i2c.address(busid, busad, i2c.TRANSMITTER)
i2c.write(busid, bit.set(what, en))
i2c.write(busid, what)
i2c.stop(busid)
end
local three = bit.bor(bit.bit(d4), bit.bit(d5))
justsend(three)
tmr.delay(5)
justsend(three)
tmr.delay(1)
justsend(three)
tmr.delay(1)
justsend(bit.bit(d5))
-- we are now primed for the FUNCTIONSET command from the liquidcrystal ctor
-- Return backend object
return {
fourbits = true,
command = function (_, cmd)
return send4bitI2C(cmd, false, false, false)
return send4bitI2C(cmd, false, false)
end,
busy = function(_)
local rv = send4bitI2C(0xff, false, true, true)
send4bitI2C(bit.bor(0x80, bit.clear(rv, 7)), false, false, false)
return bit.isset(rv, 7)
return bit.isset(send4bitI2C(0xff, false, true), 7)
end,
position = function(_)
local rv = bit.clear(send4bitI2C(0xff, false, true, true), 7)
send4bitI2C(bit.bor(0x80, rv), false, false, false)
return rv
return bit.clear(send4bitI2C(0xff, false, true), 7)
end,
write = function(_, value)
return send4bitI2C(value, true, false, false)
return send4bitI2C(value, true, false)
end,
read = function(_)
return send4bitI2C(0xff, true, true, true)
return send4bitI2C(0xff, true, true)
end,
backlight = function(_, on)
backlight = on
local rv = bit.clear(send4bitI2C(0xff, false, true, true), 7)
send4bitI2C(bit.bor(0x80, rv), false, false, false)
send4bitI2C(0, false, false) -- No-op
return on
end,
}

View File

@ -0,0 +1,83 @@
-- Run LiquidCrystal through some basic tests. Requires `liquidcrystal.lua`
-- and `l2-i2c4bit.lua` available available to `require`.
--
-- This file ought to be named "NTest_liquidcrystal_i2c4bit" or something,
-- but it has its current name due to our default SPIFFS filename length limit.
local N = ...
N = (N or require "NTest")("liquidcrystal-i2c4bit")
local metalcd
local metaback
local backend
local lcd
collectgarbage()
print("HEAP init", node.heap())
metalcd = require "liquidcrystal"
collectgarbage() print("HEAP constructor imported ", node.heap())
metaback = require "lc-i2c4bit"
collectgarbage() print("HEAP backend imported ", node.heap())
backend = metaback({
address = 0x27,
id = 0,
speed = i2c.SLOW,
sda = 2,
scl = 1,
})
collectgarbage() print("HEAP backend built", node.heap())
lcd = metalcd(backend, false, true, 20)
collectgarbage() print("HEAP lcd built", node.heap())
print("waiting for LCD to be unbusy before testing...")
while lcd:busy() do end
N.test("custom character", function()
local glyph = { 0x1F, 0x15, 0x1B, 0x15, 0x1F, 0x10, 0x10, 0x0 }
lcd:customChar(0, glyph)
ok(eq(glyph,lcd:readCustom(0)), "read back")
end)
N.test("draw and readback", function()
lcd:cursorMove(0)
lcd:write("abc")
lcd:cursorMove(10,1)
lcd:write("de")
lcd:cursorMove(10,2)
lcd:write("fg")
lcd:cursorMove(12,3)
lcd:write("hi\000")
lcd:cursorMove(18,4)
lcd:write("jk")
lcd:home() ok(eq(0x61, lcd:read()), "read back 'a'")
ok(eq(0x62, lcd:read()), "read back 'b'")
lcd:cursorMove(11,1) ok(eq(0x65, lcd:read()), "read back 'e'")
lcd:cursorMove(11,2) ok(eq(0x67, lcd:read()), "read back 'g'")
lcd:cursorMove(13,3) ok(eq(0x69, lcd:read()), "read back 'i'")
lcd:cursorMove(14,3) ok(eq(0x00, lcd:read()), "read back 0" )
lcd:cursorMove(19,4) ok(eq(0x6B, lcd:read()), "read back 'k'")
end)
N.test("update home", function()
lcd:home() lcd:write("l")
lcd:home() ok(eq(0x6C, lcd:read()))
end)
N.testasync("clear", function(next)
-- clear and poll busy
lcd:clear()
tmr.create():alarm(5, tmr.ALARM_SEMI, function(tp)
if lcd:busy() then tp:start() else next() end
end)
lcd:home() -- work around busy polling incrementing position (XXX)
ok(eq(0x20, lcd:read()), "is space")
ok(eq(1, lcd:position())) -- having just read 1 from home, we should be at 1
end)