-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Raspberry and I2C LCD1602 with PCF8574T controller does not show text but only squares #1639
Comments
I don't know if this can help solve the problem, I just tried this tutorial to see if the LCD works and to make it work I had to replace I2C_ADDR = 0x3f with I2C_ADDR = 0x27 done the screen shows the writing but if I try the script nodejs continues to show nothing now not even the squares anymore. |
I tried this script #1314 (comment) but it still doesn't work
|
Hi @ciarlystreet, after a 1602 LCD is powered on, the top row will display solid squares and the bottom row will be blank. This is the expected behaviour and indicates the LCD is being powered correctly. The programs shown above also look like they should work. What is the complete output of the following command when run from the command line on the Raspberry Pi? i2cdetect -y -r 1 If When the Johnny-Five program is running what output does it display? Are any error messages displayed? A lot of work was done on raspi-io recently and there are currently issues with I2C that are being resolved with this PR. The issue that you are seeing may be related to the current I2C issues in raspi-io. It may be possible to workaround the issue by directly editing the file this.io.i2cWrite(this.address, this.memory); with this: this.io.i2cWrite(this.address, [this.memory]); If it still doesn't work after modifying |
Hi @fivdi,
This is the output:
Thanks 🙌 |
@ciarlystreet I think the recent work on raspi-io may result in timing issues that were not there in the past so I created this issue. These timing issues are likely to be the cause of the issues that you are seeing with raspi-io. I don't have an I2C LCD so I'll get one to see if I can figure out what the problem is with pi-io. beaglebone-io is likely to have the same issue. |
@nebrius ^^ |
I realized what's happening and did a writeup at nebrius/j5-io#12 (comment). tl;dr the To fix this, we'll need to rewrite this method to use a mechanism that does not block the event loop, probably with an async function and a new sleep method that looks something like: async function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
} and then we can
What do you all think? |
@ciarlystreet I had forgotten that The following should work: const five = require('johnny-five');
const PiIO = require('pi-io');
const board = new five.Board({
io: new PiIO()
});
board.on("ready", () => {
const lcd = new five.LCD({
controller: "PCF8574T"
});
lcd.useChar("heart");
lcd.cursor(0, 0);
lcd.print("Hello :heart:");
}); |
From the source 🤣 /**
* This atrocity is unfortunately necessary.
* If any other approach can be found, patches
* will gratefully be accepted.
*/ Sleep uses microseconds or milliseconds so the solution will be a little trickier than just wrapping setTimeout. Maybe use setTimeout for ms calls, but for us calls we will need to check the time on every pass through the event loop. This has the side effect of pegging the CPU, but hopefully these are 5 - 50 microsecond bursts of intensity and are unlikely to set anything on fire.
p.s. There are some supported platforms that don't make it through the event loop anywhere near fast enough for us to have sub-millisecond delays. On old pi's for example, we may end up waiting 20ms for a 5us call to complete. We need to check the device docs and make sure that's not a problem if, for example, we are needing to rapidly toggle a pin. |
Thanks @fivdi 🙌 |
🤣 😭 Hmm...what about something like this? async function sleepus(duration) {
const startTime = process.hrtime();
let deltaTime;
let usWaited = 0;
return new Promise((resolve) => {
function tick() {
if (usDelay > usWaited) {
deltaTime = process.hrtime(startTime);
usWaited = (deltaTime[0] * 1E9 + deltaTime[1]) / 1000;
process.nextTick(tick) // IIRC, this runs as fast as the event loop runs, and can be sub millisecond in some cases
} else {
resolve();
}
}
});
} EDIT: when I get some free time, I'll start experimenting. |
OK, I tested and that approach is...not great. After bug fixes, I could only get to a highly-variable minimum delay time of 500-1000us. So what about this: We updated the IO plugin API so that all of these I2C read/write methods return a Promise. In J5, we could then I'm not familiar with this part of the codebase, so do you see any gotcha's with this approach @dtex? EDIT: FYI I just tested and |
My apologies for intruding on the conversation here. One gotcha is that To be honest, I think this is a difficult problem to solve and it goes beyond what we have looked at so far, for example, I have just realized that if the below code (which is unrelated to LCDs) is run using raspi-io it is highly likely that the this.io.i2cWrite(address, [0x00, 0]);
this.io.i2cWrite(address, [0x01, 0]);
this.io.digitalWrite(pin, this.io.LOW); On the other hand, the IO Plugins Specification specifies that data writing operations must be executed in order of instruction. |
Good points, hmmm... I'm thinking now that I should switch from using manager specific queues (one for Serial, a different one for I2C, etc) to using a single global queue. This will fix the ordering issue. Oh!! I could create a bigger boundary for this using worker threads. Each method exposed by J5-IO just puts a queue message into a queue running in the background thread (or separate process even), so that blocking the event loop won't stop Raspi IO from working properly. Honestly this makes it more firmata like anyways. |
I would expect both of these proposals to result in more timing and synchronous/asynchronous issues. KISS and all will be good. |
In this case KISS means Keep it synchronous, stupid 😄 |
Do keep in mind that every firmata operation is asynchronous. That some Raspi IO operations are/were synchronous is actually a deviation from the Arduino and other firmata based systems, even though the API would suggest otherwise. It’ll be a chunk of work to convert to threads, but it will conceptually make the Raspberry Pi more in line with the other platforms, not less. |
Sure, I'll keep that in mind. It's also important to keep in mind that some J5 operations are orders of magnitude faster on a Raspberry Pi than on Notebook connected to an Arduino Uno via a serial port running at 57600 baud which is the default baud rate if I remember correctly. This results in very different timing characteristics. But rather than speculating about what may or may not be the case I'd prefer if we took a look at a specific piece of Johnny-Five code. The below code reads the temperature and humidity from a SHT31D sensor. var readCycle = function() {
// Page 10, Table 8
// Send high repeatability measurement command
io.i2cWrite(address, [
this.REGISTER.MEASURE_HIGH_REPEATABILITY >> 8,
this.REGISTER.MEASURE_HIGH_REPEATABILITY & 0xFF,
]);
setTimeout(function() {
io.i2cReadOnce(address, READLENGTH, function(data) {
computed.temperature = uint16(data[0], data[1]);
computed.humidity = uint16(data[3], data[4]);
this.emit("data", computed);
readCycle();
}.bind(this));
}.bind(this), 16);
}.bind(this); If you switch to an implementation based on worker threads, how will you guarantee that the |
There's no guarantee of that on any platform, so I don't think it matters. Indeed, it almost certainly will not have been sent to the SHT31D on an Arduino before setTimeout has been called |
As mentioned above I'd prefer not to speculate.
Fair enough 😄, then let me rephrase the question and ask you why is it likely to work? |
OK, I'll rephrase: I guarantee that it won't have happened. For the value to have been sent to the sensor, these things have to happen:
That is, at minimum, 13 asynchronous steps that occur after
Because firmata itself is a message passing protocol that sends messages from one thread (the main Node.js thread) to another "thread" (firmata running on the Arduino) over a serialized communication mechanism. The transmission protocol is different, as there are a lot fewer steps on the RPi than the Arduino (as outlined in the steps above) because there were fewer systems in play, but we're closer to simulating this because all operations are serialized and sent over a (virtual) wire. This approach normalizes all operations and enforces an order of operations (because they sit in a receive queue), which was missing before. |
Yes, this is what will happen, setTimeout starts waiting after step 1 or 2, and after the 16 milliseconds i2cRead will go through even more steps as it needs to send information back and everything automagically works. It's quite fascinating actually. I guess, I'll have to wait and see how the threads based implementation goes on the Pi. Good Luck 🍀 |
I hit an architectural brick wall with the threads approach...super tl;dr another architectural decision I made (and don't regret and want to keep) makes using threads impossible, since they don't have true shared memory. I'm going to dig into the J5 code and see if there's a way to get rid of the blocking code entirely in it. |
OK, I've done some digging and gotten a greater understanding of how I think the best approach now is to convert the LCD class to use a command queue system like Raspi IO uses under the hood. This will remove the need for I'll start working on that today |
throws in towel Looks like updating the LCD class isn't straightfoward, and would require a pretty hefty rewrite. I give, I rewrote j5-io to use synchronous I2C writes. CPU usage increased by about 5-10% in my integration test app (I noticed it while reading from an MCP9808 sensor and writing to an SSD1306 LCD), but better for it to work than be fast. @ciarlystreet can you try running 'npm update' and test again to see if it works? |
@ciarlystreet if it's still not working after updating please manually modify @nebrius if J5 calls i2cWrite with two numbers like this: this.io.i2cWrite(this.address, this.memory); it looks like i2cWrite in j5-io will ignore it or do you see this differently? |
Hmmm, yeah that does appear to be the case. This signature isn't documented though in the IO Plugin spec, and AFAIK Raspi IO never supported it. |
Related firmata/firmata.js#198 |
@nebrius @fivdi I tried this script:
with:
And I kept seeing the squares. Replacing, in the file
with this:
it works.
|
@ciarlystreet That's good news. We're nearly there. Thanks for the feedback.
Unfortunately the documentation is incomplete.
Raspi IO supported it up until at least [email protected] here. The use case mentioned by @dtex which, if I'm not mistaken, is "write a single byte to the specified register" was supported up until at least [email protected] here. |
Huh, I always thought that was a hack for Remote IO or something, since firmata.js doesn't call Raspi IO when used directly in J5. TIL. Turns out, that specific signature is in the code, but it's written as: i2cWrite(address: number, register: number): void; But the second argument is the payload, not the register, and the proper signature is: i2cWrite(address: number, byte: number): void; This is why I love TypeScript: it makes all this explicit and intentional, and it's impossible to have undocumented signatures 😁 I just published another version of j5-io with this fix. @ciarlystreet can you run 'npm update' again and test to ensure this fix is correct? |
I tested this with the latest version of Raspi IO and everything now works as expected. I think this issue can be closed. |
Awesome! Since @fivdi tested amd we haven't heard from @ciarlystreet in a while, I'm going to go ahead and close it. Feel free to re-open if necessary. |
Thanks for testing @fivdi! |
Hi,
I'm probably doing something wrong, this is the connection scheme:
and this is the code:
but in the LCD I see only:
Can you help me understand if I'm wrong or if there is something wrong with the LCD?
Any advice is welcome 🙌
The text was updated successfully, but these errors were encountered: