Unverified Commit c3dd27cf authored by Nathaniel Wesley Filardo's avatar Nathaniel Wesley Filardo Committed by GitHub
Browse files

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
parent 109f500b
......@@ -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)`
......
......@@ -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,
}
......
-- 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)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment