-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add LiquidCrystal lua module #2974
Conversation
I have done limited testing with a 4x20 4-bit/I2C LCD module I have laying around and it seems workable. I've been using a forked version of https://github.com/dvv/nodemcu-thingies/blob/master/lcd1602.lua in my projects, but having something in the NodeMCU tree itself seems like a grand plan. The things I think would be welcome improvements are...
But I don't think any of those are blockers for this going into the tree. |
Oh, actually, one thing that might be important before merging: documenting exactly what the |
bed02fb
to
cb601be
Compare
@nwf Do you mean that someone might want to use I2C GPIO expander other then this one? |
Some people apparently use the same PCF8574P I2C GPIO expander with different wiring for the LCD itself; the suggested schematic in http://lcdproc.sourceforge.net/docs/lcdproc-0-5-5-user.html#hd44780-i2c, for example, uses different pinouts. The |
Added the following definition to local LCD_RS = 0
local LCD_RW = 1
local LCD_EN = 2
local LCD_BACKLIGHT = 3
local LCD_D4 = 4
local LCD_D5 = 5
local LCD_D6 = 6
local LCD_D7 = 7 This way it is easy enough to tune for different pinouts and |
I was just about to commit this, because it's been languishing too long, but then I realized... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, as said, the png
's gotta go or have a license indicating its redistributability.
This review is to block future-me from accidentally committing.
@nwf Done |
Thanks for paying attention to copyright! Instead of redistributing an image one could either link to one (e.g. https://www.google.ch/search?q=HD44780+char+codes&tbm=isch&source=univ) or link to a specific page in the PDF like so |
128906a
to
88efab1
Compare
Had some static analyzer warnings there... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some very minor additional requests. If you'd rather not, I think I'm happy to merge this as is (thank you again for all the work put into it!) and I'll submit a follow-up PR.
5f49da7
to
04ce84f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some additional thoughts, around memory saving, for you to ponder. Thanks again!
self._bus_args = bus_args | ||
if bus == "I2C" then | ||
assert(fourbitmode, "8bit mode not supported") | ||
self._backend = dofile "i2c4bit.lua" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating backends like this is going to result in a new object per LCD, even if they all use the same backend. That's fine, and could even be what you want, except that the backends here are coded to be immutable collections of functions. So I'd suggest either:
- switch to
require
-ing the backends, so that they only get loaded once (and are subsequently cached) - redesign the backends to be factories that produce functions that close over their configuration parameters and state, rather than look them up in dictionaries. These factories can return (or directly set, if that's more your thing) functions that end up being
self._command
,self._backlight
,self._write
and friends.
Either approach should help with memory usage. I think doing both will have the maximum impact, as then the function bodies will be shared and only the closures will vary per LCD.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current code works with both SPIFFS and LFS. require
will not find module if it is stored on LFS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really should just make package.loaders
search LFS by default at this point, but thus far the guidance has been for programmers to enable that themselves: https://nodemcu.readthedocs.io/en/master/lfs/#accessing-lfs-functions-and-loading-lfs-modules
Anyway, require
can be taught to search LFS and it's the recommended configuration.
Failing that, you could push the onus of finding and constructing the backend to the user. So rather than :init(bus, bus_args, ...)
, it'd be more like :init((require "backend")(args), ...)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some time to draft up what I had in mind in hopes of tamping down on memory requirements by using constructors and closures rather than tables. I've also found out that it is possible to read back from these LCDs and so gone and written a first pass at an automated test harness for LiquidCrystal.
Give https://github.com/nwf/nodemcu-firmware/tree/lcd-draft a look over and take anything that's useful. Please note that it the draft there is incomplete.
@nwf I'm not very familiar with Lua internals. Does it really require less memory to have such constructors with local functions? Don't all functions internally have metatables associated with them? Or |
I will not pretend to be an expert at Lua internals, by any means. But, as I understand it, the function bodies themselves are constant and are shared values across all constructor invocations. The bits that aren't shared are the "closure" entries that point at the code blocks and the captured "upval" variables. These are not generic (meta)tables, but slightly specialized storage structures for each function. Section 3 of https://pdfs.semanticscholar.org/73a2/e3c03f799956aa5a3188e4eb35c90977a471.pdf seems to do an OK job describing what's going on. Even with the juggling I did, instantiating a LCD still seems to occupy 3-4KB of RAM, which seems... high. I mean to investigate further as time permits. |
I've got these results for (keep in mind that closure version has extra feature read from lcd implemented):
Maybe this ought to be a C module? |
That's a good idea, and nicely decouples the issue from this PR. Go ahead and leave it as you have, with |
Actually I suspect that it's not too hard to detect constants automatically without any keywords |
The whole idea of considering a c preprocessor for embedded Lua is definitely the wong way to go IMO, and just not needed. Accessing Upvals is cheap and the storage overhead is minimal. In general it takes less RAM to use scalar upvals than table ones. Also remember that binding upvals is only done once when the closure is itself bound, and the cost of accessing a upval local less than a global or table entry. In an LFS usecase, don't worry too much about the true constants and Lua code since this is in flash anyway. If in doubt then |
@TerryE: the particular concern is that there isn't a way to define a symbolic name for a constant in Lua. That's the motivation for wanting either to rewrite the Lua before handing it off to luac, especially in combination with LFS or to push constants into a C rotable. Neither option is very attractive, and I would, ordinarily, agree that neither should be considered, were it not for.... In my experiment on this PR, I found that inlining all the That concern then spilled out more generally: should we generally be trying to minimize the number of upvals referenced by closures by referring to tables instead? I think the answer is no, because unless there is truly an exorbitant number of closures, the overhead of creating the table outweighs the overhead of the upvals, but I don't know for sure. |
Lua has some strengths for embedded use but unlike JavaScript and PHP, there isn't the concept of symbolic constants in the language.
I accept your figure of 1Kb, which is regrettable, but then again this is only a few % of the total heap at boot. This shouldn't be an issue with the code in LFS. The whole point of LFS is that we can move the Lua code of RAM, so we can be a little more relaxed about creating variables.
I think that the main goals in any code should be correctness, clarity and simplicity. |
This code may be used to test gpio8bit on esp32 (since sep8266 does not have enough gpio pins): local gpio = gpio
return {
read = gpio.read,
write = gpio.write,
mode = function(pin, mode)
gpio.config{gpio={pin}, dir=mode}
end,
INPUT = gpio.IN,
OUTPUT = gpio.OUT,
HIGH = 1,
LOW = 0,
} |
Thank you for all your hard work on this! I believe this PR is good to go live, but I'll give it a day or so for others to chime in. Yes, I think the tests will have to wait until we've decided how we want to do things; for |
Ah, one more favor to ask: can you rename your backends from (e.g.) |
Er, no, one more still, I'm afraid. While |
@nwf added missing functionality, renamed backends, fixed some 10 dot mode bugs. When this will be ready for merge I will squash the commits. |
LGTM! |
Thanks, but there's no need. GitHub will do that for us when we merge 😄 |
@seregaxvm I'm ready to push the button if you are. :) |
Merge when you are ready. I don't have anything to add |
This module provides functionality similar to Arduino LiquidCrystal library - control LCD text screens based on the Hitachi HD44780 chip. It supports 4bit and 8bit GPIO; 4bit I2C interfaces (via GPIO expander). I2C version was tested in
i2c.SLOW
andi2c.FAST
speed modes.Some functionality of the LCD chip was not implemented: reading text from internal memory and adding custom characters. These functions are exotic and are not needed for most applications.
dev
branch rather than formaster
.docs/*
.