Fast answer on LCD-I2C?

Hello,
I am looking for a way to print on an LCD-I2C, so that my sketch do not spend too much time to print on my LCD-I2C and to have time to check some GPIO input every few micro seconds.
My constrains are the followings:

  • I am using a RaspberryPi-Pico (used to replace an Arduino nano, for speed reason)
  • the answer should be as close as possible to a few us only.
    (On Raspberry site, I found things in Python, but unfortunalty much too slow for my project.)
    Do you have some idea how to proceed ? a specific library ?
    thank you

100kHz I2C is 10 000 bytes per second.
An I2C LCD may well be operating in 4 bit mode, effectively halving those 10000 bytes per second.

Do some back-of-a-beermat calculations.

thanks for your answer.
I am rearly not a expect and didn't spend time so far to understand how an I2C LCD is working.
Could you please elaborate?

No, based on what I know about your project, I don't think I could

basically, considering a simple sketch, as follow, the time to answer is very high - see comments included

#include <LCD_I2C.h>
LCD_I2C lcd(0x27, 20, 4); // SDA=A4 ; SCL=A5
long tps;
float t;

void setup()  {
  lcd.begin();
  lcd.backlight();
}

void loop()  {

  tps = micros();
//                               t= 0 micro sec
  lcd.setCursor(2, 0);
  lcd.print(micros() - tps);
  lcd.setCursor(8, 0);
  lcd.print("micro-sec");
//                               t= 1000 micro sec
  lcd.setCursor(2, 1);
  t = (micros() - tps) / 1000.00;
  lcd.print(t);
  lcd.setCursor(8, 1);
  lcd.print("milli-sec");
//                               t= 16 000 micro sec = 16 ms
  lcd.setCursor(2, 2);
  t = (micros() - tps) / 1000000.00;
  lcd.print(t);
  lcd.setCursor(8, 2);
  lcd.print("sec");
  lcd.setCursor(2, 3);
//                               t= 32 000 micro sec = 32 ms
  lcd.print("-------------");

  delay(1000);

}

The Arduino on Raspberry Pi Pico is on top of Mbed. I don't know if the Mbed I2C code disables the interrupts, but the Arduino code waits... and waits... and waits... until the I2C session has completely finished.

The well known Arduino Uno or Nano may be faster than a Raspberry Pi Pico.
For a Arduino Nano, the input signals should be connected to interrupt pins, and by using a software I2C library, you avoid to use the Wire library with its interrupt that might get in the way.

I don't know how Arduino supports interrupts on the Raspberry Pi Pico, I'm still learning to use the Raspberry Pi Pico.

I made a Wokwi project for you:

The Wokwi simulator has also a simulated Logic Analyzer.

Please tell us more about your project, give us a broader view.
Perhaps you need additional hardware to capture the fast signals.

thank you Koepel, I appreciate your support !

I have tested your sketch, but with one screen only. It works, but I am not sure to understand its interest ?
I can however see that the library LiquidCrystal_I2C provides roughly the same time for one loop : 1ms for the first line only => equivalent to my sketch/LCD_I2C.
I have also tested my sketch on Arduino Nano : times are very similar to what was recorded for Raspberrypi-Pico.

As far as my project is concerned: I need to capture the signals from 3 Hall sensors, which finally will provide me indications on the speeds on a machine. The average lag time is 20ms for each of the Hall sensors.
Moreover, I am using 2 rotary encoders for input data.
Finally, I compute the 3 speeds and the 2 encoders in order to drive the machine.
This is working well. At least when I am not using LCD.
However, when I want to include the LCD instead of the serial print, the systems does not work anymore, due to the huge time dedicated to this library. Refer to the sketch hereafter.

I would have liked to use Interrupts, but I don't know where to use it efficiently ...
I also imagine to use the multithread property of the RP. I still need to discover this field on my side :slight_smile:
Finally, I would really avoid to use two µcontroleurs for my project. !

Thank you for your proposals !

//                              Pour RaspberryPi-Pico
#include "Wire.h"
#include "LiquidCrystal_I2C.h"     // pins affected : G4=SDA ; G5=SCL ; V+ ; GND
LiquidCrystal_I2C LDC(0x27, 20, 4);

#include "RotaryEncoder.h"
RotaryEncoder encoder(14, 15);     // (A, B)

static int pos = 0;
int newPos;
const int BP = 7;                  // BP sur GP7
bool BPstate  = LOW;
long tps;

void setup() {
  pinMode(BP, INPUT_PULLUP);
  Serial.begin(115200);
  LDC.init();
  LDC.backlight();
}

void loop() {
  tps = millis();
  encoder.tick();
  newPos = encoder.getPosition();
  BPstate = digitalRead(BP);

//                                print in Serial
  if (pos != newPos) {
    Serial.println(newPos);
    pos = newPos;
  }

//                                print in LDC
    LDC.setCursor(2, 0);
    char buffer[20];
    sprintf( buffer, "newPos= %03d   ", newPos);
    LDC.print( buffer);

    LDC.setCursor(4, 1);
    sprintf( buffer, "BPstate = %02d   ", BPstate);
    LDC.print( buffer);

    LDC.setCursor(6, 2);
    sprintf( buffer, "DT(ms)= %04i   ", millis() - tps);
    LDC.print( buffer);
//                                        one loop with LDC ~ 45ms sur RP-Pico !
}

Normal keys can be done by polling in almost all projects.
A rotary encoder that someone presses is slow, but sometimes interrupts are used. See : https://www.pjrc.com/teensy/td_libs_Encoder.html
A rpm sensor for a motor is something for interrupts.

The Arduino Uno has 2 real interrupts, and PCINT interrupts on the rest of the pins. There are libraries for rpm, but those can often only use those 2 real interrupts.
There are Arduino boards with real interrupts on every pin.

I'm not an expert for Arduino on the Raspberry Pi Pico, I just happen to like it a lot.

Try to avoid multiple boards. Those projects might never finish.

Can I have just a little fun with Wokwi :stuck_out_tongue_winking_eye:

ok I will use interrupts for the rotary encoder. thank you.

However, if the concern between the rotary encoder and the LCD can be solved thanks to interrupts, that does not solve the fact that the LCD library takes to much time to proceed !

Basically, my initial question is related to huge time to print on LCD. Has this concern already being discussed / treated ? where/how ?

thank you

for those interested in the workaround, I have used the 2 cores of my RP2040 in //. Result is really statisfying to me ! see sketch hereafter.
I will use that solution for my project, even if I am still a bit frustrated to not find another solution to speed up (significantly) the LCD print !

//                              Pour RaspberryPi-Pico
#include "Wire.h"
#include "LiquidCrystal_I2C.h"     // pins affected : G4=SDA ; G5=SCL ; V+ ; GND
LiquidCrystal_I2C LDC(0x27, 20, 4);

#include "RotaryEncoder.h"
RotaryEncoder encoder(14, 15);     // (A, B)

static int pos = 0;
int newPos;
const int BP = 7;                  // BP sur GP7
bool BPstate  = LOW;
long tps0;
long tps1;
int DT0;

//                                RUNNING ON CORE0
void setup() {
  pinMode(BP, INPUT_PULLUP);
//  Serial.begin(115200);
}

void loop() {
  tps0 = micros();
  encoder.tick();
  newPos = encoder.getPosition();
  BPstate = digitalRead(BP);

  if (pos != newPos) {
    //  Serial.println(newPos);
    pos = newPos;
  }
  DT0 = micros() - tps0;
}

//                                RUNNING ON CORE1
void setup1() {
  LDC.init();
  LDC.backlight();
}

void loop1() {
  tps1 = millis();

  LDC.setCursor(2, 0);
  char buffer[20];
  sprintf( buffer, "newPos= %03d   ", newPos);
  LDC.print( buffer);

  LDC.setCursor(4, 1);
  sprintf( buffer, "BPstate = %02d   ", BPstate);
  LDC.print( buffer);

  LDC.setCursor(6, 2);
  sprintf( buffer, "DT0(us)= %03i   ", DT0);
  LDC.print( buffer);

  LDC.setCursor(6, 3);
  sprintf( buffer, "DT1(ms)= %03i   ", millis() - tps1);
  LDC.print( buffer);

  delay(250);

}

Question 1 : I am updating a LCD display every time in the loop() and that takes too much time. I need a fast answer.
Answer 1 : Don't do that.

Is that your question and does that solve your problem ?
The liquid crystals in a LCD display are slow. Updating the LCD display about 4 times per second might be good enough.
Use a millis-timer, see the Blink Without Delay. That is standard for a Arduino Uno.
On a Pico, you can use the Arduino Schedular or use the multitasking rtos of the Mbed directly. Just add a task and use delay().

Question 2 : The I2C bus is so slow.
Answer 2 : Don't use the I2C bus or make it faster.

Is the slow I2C bus the problem ?
A LCD display without I2C can be faster. A 400kHz I2C bus is faster than a 100kHz I2C bus. A software I2C library does not use interrupts that might interfere with other interrupts. Using multiple tasks on a Pico can prevent most problems. You don't need multiple cores, since the Mbed on the Pico is already a preemtive multitasking system.

While I was typing this, you posted the answer above, but I will post this anyway. Can you try to explain the problem once more ?

I have a question for you:
A 5V LCD display has a 5V I2C bus and your Raspberry Pi Pico has a 3.3V I2C bus. How do you solve that problem ?

Recommended: Remove the 5 V pull-ups on the I²C backpack and provide pull-ups to 3.3 V instead.

Well, my I2C-2004A is connected to 3.3V only from Pico, at it works :slight_smile: To be honest, I just initially read the LCD 2004A specification for which the range is 3V to 5.5V.

thanks Paul_B for the recommendation. Right now, I don't know how to proceed ... apparently I2C bus can manage the 3.3V. Is it border line ?

well, since I am really not confortable with the all the microcontroleur stuff (this is clearly not my day-to-day activity !! - I am actually in the mechanical/aerodynamical field - not really the same world :slight_smile: ), I would have liked to use the LCD the same way (or so) I was using the Serial port.
I appears that it's not possible ...
My objective is really this one : to be able to compute multiple external data and send information to the machine, every ~1ms. And to be able to pilot the behavior of the machine with 2 rotary encoders and visualisation via LCD.
thanks.

Really? What do you mean by "connected to 3.3V only"?

Is the "2004" display - and its backpack - not powered by 5 V?

Which specification is this? All the "2004" displays I have seen operate specifically at 5 V. :grimacing:

What do you mean by "manage"?

Unless I am very mistaken, the I²C backpack includes two 4k7 pull-up resistors to 5 V. Because I²C is an open-collector bus, this means that when not pulled down, the I²C lines are pulled up to 5 V and that is connected to the 3.3 V processor. This will inject 230 µA into the protection diodes on that processor. It is a somewhat debatable point, but many people consider this to be a "Bad Thing".

Well, they are very different things - a 20 by 4 display which you have to program to put the characters in the places you want compared with a serial port which connects to something with a complete terminal emulation including control codes such as carriage return, line feed, backspace etc. as well as line wrap and scrolling.

you are certainly right on all counts!
However, I bought the LDC 2004A, with I2C attached on the back. The four pins of the I2C are just connected to the Pico, therefore the VCC from I2C connected to 3.3V from Pico. And that works ...
However, if this configuration were to lead to a in-service issue (not tested yet in real condition), then I could supply that 5V to that I2C.
As far as the LCD vs Serial port is concerned, I understand now better the situation.
In term of multiple tasks synchronized activities, do you think my sketch above optimized (to get fast answer, I mean) ?
Thank you

and as far as the spec is concerned, I took this one:

I don't believe you. Most cheap LCDs are designed for 5V. The LCD controller works fine at 3.3V. The backpack works fine at 3.3V.

However the contrast will not adjust correctly at 3.3V
So the LCD has all the correct pixels in all the correct places. But a human can't see them !

Look at the back of the main LCD pcb. You will see some empty footprints on 5V modules. But all chips will be mounted on a 3V compatible module.

Regarding speed. HD44780.h library will be much faster than whichever random LiquidCrystal_I2C.h you are using.

Human eyes can only read steady pictures. LCD response is very SLOW. If you change pixels too fast it just looks smeary.

Regarding your other application code. The I2C backpack uses interrupts. So it will not interfere with your other code (within reason).

A 1ms timer interrupt will be fine. Don't expect to run a 10us Timer interrupt.

In life you want monitor your rotary encoder at a sensible frequency. The display is updated only when something changes.

David.

Correction : I confirm that the complete LCD is just connected to my Pico ;
however I connected it to VBUS, which is 5V, and not 3.3V. My mistake, sorry for that !

My intent is not to update the LCD every ms, but to print/send information on LCD without loosing time to process the other tasks of the sketch which require a fast running.

Thank you

If those are true requirements, then you can stop looking as there is no way to get there regardless of library and speed of processor.
As @anon73444976 said, do some math to see the theoretical timing.

And keep in mind that it takes much more than a single byte transfered over the i2c bus to the device to get a single byte transferred to the LCD.
You will have a minimum of 4 i2c bytes per LCD byte plus i2c address byte plus i2c start/stop overhead.
So you can count on 6 byte transfer times to calculate a maximum theoretical transfer time per byte.
And then many libraries like LiquidCrystal_I2C are transfering even more bytes per byte transfer so the total is closer to about double that.

For some realworld numbers:

If you use the hd44780 library (the fastest library for PCF8574 based i2c backpacks), it takes about 500us to transfer a byte to the LCD using a 100kHz bus speed. It is 1500 us using the the LiquidCrystal_I2C library.

The Arduino Print class is blocking so if you send a string of characters, your sketch will be blocked until all of them have been transferred to the LCD.

Time to rethink the overall design.

--- bill

Some thoughts on this that might or might not help.

Some(?) / most(?) / all(?) I2C libraries use blocking code to service the I2C hardware. I2C is slow compared to most processors, even at 400kHz. You call an I2C function and it does not return until it's finished doing what it does, which for a display can take ages and ages. I do not know if there are any I2C libraries that just deal with 1 byte then return leaving the I2C hardware to do its thing, then do another byte when the hardware is free again. If you really want to solve the problem with I2C behaving like this you have to write your own driver for the hardware, which is possible but you need to study the data sheet for the processor very carefully to find out how to drive the I2C hardware.

You could just use a separate micro-controller to drive the display and send the data to it via serial. Serial does not block as long as you don't over fill the buffer. If you do it this way you send the data to the serial Tx on your main controller and leave it to get on with the job. The micro-controller driving the display can then take as long as it likes to update the display.

Another thought is to be very careful when you send data by I2C, for example you mention:

What is the minimum time? If you make sure that after getting an input from a Hall sensor only then do you send the I2C data you are allowing the maximum possible time for the I2C function to do its thing, if it is quicker than the changes from the Hall sensors it will have completed before the next change from one of them.