Faster LCD screen communication

Status
Not open for further replies.
I'm looking for some guidance on writing to LCD screens. I am running an adafruit 16x02 screen on newer Teensy boards (see test examples below)

1.) LiquidCrystalFast seems to yield poor results with the 3.6 and 4.0. Are any updates planned, and/or is this more of an issue with the LCD hardware itself being too "slow"?

2.) I have a similar screen with the Adafruit LCD backpack (I2C & SPI) that I am testing with underwhelming results https://learn.adafruit.com/i2c-spi-lcd-backpack/overview. Is this also a "slow" screen issue? I understand that I am also reducing the number of pins to transmit the data, but shouldn't I2C and SPI communication be MUCH faster than was getting without the backpack? Are there better libraries out there (as far as speed goes) other than the Adafruit_LiquidCrystal?

3.) Are there other "minimal" screens out there that can be written to more quickly with the 3.6 or 4.0 that might cost more, but save some development time? I'd like to get to 1ms to send 32 characters. At the moment, this is more of an exercise than something critical to any particular program. In general I like the idea of being able to display real time status of the system/program using something other than a few indicator LEDs, and being able to do it in the sub millisecond range.

4.) Level shifters such as the https://www.adafruit.com/product/757 shouldn't be a big factor in slowing down communications to the point that I'm seeing below, correct?



Here is where I'm at with my testing using relevant code from the Benchmark example included with LiquidCrystalFast

Arduino Uno
standard 16x2 lcd
LiquidCrytstal = 386 milliseconds
LiquidCrystalFast = 213 millisecs

Uno with Adafruit LCD backpack via i2c
Adafruit_LiquidCrystal = 2032 milliseconds

Uno with Adafruit LCD backpack via SPI
Adafruit_LiquidCrystal = 2495 milliseconds
TeensyLC - Adafruit_LiquidCrystal = 766 milliseconds



The following Teensys were tested with bidirectional level shifters (Adafruit PID 757) on RS,RW,EN,D4-D7 pins or the correct i2c/SPI pins

Teensy LC
LiquidCrystal = 295 milliseconds
LiquidCrystalFast = 70 milliseconds
Adafruit_LiquidCrystal = 766 milliseconds with Adafruit LCD backpack via SPI
Adafruit_LiquidCrystal = 4885 milliseconds with Adafruit LCD backpack via i2c

Teensy3.6
LiquidCrystal = 274 milliseconds
LiquidCrystalFast = around 650-750 milliseconds (inconsistant!)
Adafruit_LiquidCrystal = 360 milliseconds with Adafruit LCD backpack via SPI
Adafruit_LiquidCrystal = 4366 milliseconds with Adafruit LCD backpack via i2c

Teensy 4.0
LiquidCrystal = 271 milliseconds
LiquidCrystalFast = gives me garbage characters
Adafruit_LiquidCrystal = 298 milliseconds with Adafruit LCD backpack via SPI
Adafruit_LiquidCrystal = 4530 milliseconds with Adafruit LCD backpack via i2c

Regards,
 
You haven't posted your code so we can't see where the problem is. You also need to show the connections you are using
as there's more than one way to connect and some are inherently slower. The particular module is also an issue as there
is a handshake per byte.

Level shifting is not affecting this.
 
I2C is a rather slow bus. You might be able to boost the speed of the bus by setting the I2C speed from its default setting of 100Khz by using the Wire.setClock function, but it depends on whether the device can go at a faster speed, how long the wires are in your entire I2C bus, what the resistance of those wires is, etc:

Note, many I2C devices have pull-up resistors between SDA/3.3v and SCL/3.3v. You need at least one pull-up resistor per pin, but having more pull-up resistors, or the resistors being higher than you need can prevent higher speed transfers. Also as I mentioned above, if you use shorter quality wires, it can help you achieve higher I2C speeds.

For SPI devices, you can also adjust the speed. What speed you set depends on the device and whether you have other devices on the SPI bus. I've had displays that use the same driver, but different displays from different manufactures had slightly different speeds. Like the I2C devices, you want wires as short as possible. It can help if you add a pull-up resistor on the CS line to get a higher speed if there are other devices on the SPI bus (better to not share the bus).

A lot of SPI devices are graphics displays instead of text displays. This might mean you are transmitting more data, than a text oriented display like the I2C 16x2 displays.

For SPI devices there are some displays that have special optimizations, depending on the display driver, and what special pins you use for the CS and D/C pins. Also some of these drivers support DMA output that can improve performance. But it might be helpful if one of the display driver maintainers can chime in on the details.

There are some displays that take 8/16 pins in parallel. I haven't used them.

The digole devices also can use serial UARTs instead of I2C/SPI and you can push the baud rate up to 406,800 baud.
 
You haven't posted your code so we can't see where the problem is. You also need to show the connections you are using
as there's more than one way to connect and some are inherently slower. The particular module is also an issue as there
is a handshake per byte.

Level shifting is not affecting this.

See code below for the I2C/SPI setup. It is currently set for I2C, you can see the SPI pins used below the I2C comments. There isn't much to it, all the work is happening in the library:

Code:
/*
 Demonstration sketch for Adafruit i2c/SPI LCD backpack
 using 74HC595 SPI expander
 ( http://www.ladyada.net/products/i2cspilcdbackpack/index.html )

 This sketch prints "Hello World!" to the LCD
 and shows the time.
*/

#include "Wire.h"
#include "Adafruit_LiquidCrystal.h"


// Connect via i2c, default address #0 (A0-A2 not jumpered)
Adafruit_LiquidCrystal lcd(0);

// CLK to Analog #5 / D19 / SCL0
// DAT to Analog #4 / D18 / SDA0

// Connect via SPI. Data pin is #3, Clock is #2 and Latch is #4
// I used...
//Adafruit_LiquidCrystal lcd(3, 2, 4); //on Teensy LC & 4.0
//Adafruit_LiquidCrystal lcd(22, 23, 21);  //on Teensy 3.6

const int nRows = 2;      //number of rows on LCD
const int nColumns = 16;  //number of columns

const int length = nRows * nColumns;
char text[length+1];
char blanks[length+1];

void setup(void) {
  lcd.begin(nColumns,nRows);
  lcd.setCursor(0,0);
  char c = 'A';
  for (int i=0; i<length; i++) {
    text[i] = c++;
    blanks[i] = ' ';
    if (c > 'Z') c = 'A';
  }
  text[length] = 0;
  blanks[length] = 0;
  unsigned long startTime=millis();
  byte repetitions = 20;
  while (repetitions--) {
    lcd.setCursor(0,0);  // fill every screen pixel with text
    lcd.print(text);
    lcd.setCursor(0,0);  // then fill every pixel with blanks and repeat
    lcd.print(blanks);
  }
  unsigned long endTime = millis();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Benchmark ");
  lcd.print(nColumns, DEC);
  lcd.write('x');
  lcd.print(nRows, DEC);
  lcd.setCursor(0,1);
  lcd.print(endTime - startTime);
  lcd.print(" millisecs.");
}

void loop() {
}
 
You haven't posted your code so we can't see where the problem is. You also need to show the connections you are using
as there's more than one way to connect and some are inherently slower. The particular module is also an issue as there
is a handshake per byte.

Level shifting is not affecting this.

I just switched between using LiquidCrystalFast and LiquidCrystal for the 2 types of pin configurations below:


Code:
#include <LiquidCrystalFast.h>

// initialize the library with the numbers of the interface pins
LiquidCrystalFast lcd(23, 22, 21, 38, 37, 36, 35);  // pins used for Teensy 3.6
         // LCD pins: RS  RW  EN  D4 D5 D6 D7

// to see the speed of the original library, comment out the
// LiquidCrystalFast line above, and uncomment these 2 lines.
//#include <LiquidCrystal.h>
//LiquidCrystal lcd(12, 10, 11, 5, 4, 3, 2);   //pins used on Teensy LC, 4.0

const int nRows = 2;      //number of rows on LCD
const int nColumns = 16;  //number of columns

const int length = nRows * nColumns;
char text[length+1];
char blanks[length+1];

void setup(void) {
	lcd.begin(nColumns,nRows);
	lcd.setCursor(0,0);
	char c = 'A';
	for (int i=0; i<length; i++) {
		text[i] = c++;
		blanks[i] = ' ';
		if (c > 'Z') c = 'A';
	}
	text[length] = 0;
	blanks[length] = 0;
	unsigned long startTime=millis();
	byte repetitions = 20;
	while (repetitions--) {
		lcd.setCursor(0,0);  // fill every screen pixel with text
		lcd.print(text);
		lcd.setCursor(0,0);  // then fill every pixel with blanks and repeat
		lcd.print(blanks);
	}
	unsigned long endTime = millis();
	lcd.clear();
	lcd.setCursor(0,0);
	lcd.print("Benchmark ");
	lcd.print(nColumns, DEC);
	lcd.write('x');
	lcd.print(nRows, DEC);
	lcd.setCursor(0,1);
	lcd.print(endTime - startTime);
	lcd.print(" millisecs.");
}

void loop() {
}
 
First a word of caution. These displays usually run with 5V power. Of the 32 bit Teensy models, only 3.2 and 3.5 are 5V tolerant.

The 6 signal mode is safe, because all 6 signals are always in the Teensy-to-LCD direction. Almost all those 5V displays have TTL level inputs which properly recognize a 3.3V signal at logic high. So the slow 6 pin mode is good.

But the 7 pin mode is dangerous with Teensy LC, 3.6, 4.0 and 4.1, or any other modern 3.3V board which is not 5V tolerant. In 7 pin mode, the extra RW pin is added. When Teensy drives that pin high, the LCD transmits back to Teensy. I don't recall which pins actually transmit, but I seem to remember it isn't all 4 of the data pins, only 1 or 2 of them. The transmitted signal tells Teensy whether the display is busy performing the previous command. In 7 pin mode, LiquidCrystalFast uses that to minimize the wait time.

In 6 pin mode, you would connect RW to GND, so the LCD can't ever transmit anything back to Teensy. Also, if you use the 7 pin mode with the slow LiquidCrystal library, the very simple unoptimized code in that library just drives RW low all the time. It never drives RW high, so 7 pin mode is exactly the same as 6 pin mode with the slow LiquidCrystal. Only LiquidCrystalFast actually makes use of RW.

But the really sad reality is even with RW, these ancient LCDs are very slow. They're also not so commonly used in modern times, which is why nobody (as far as I know) has put work into developing a nice non-blocking library. Nothing can make the LCD actually run faster, but with a lot of work, someone could create a library that does all the waiting "in the background" without stalling the rest of your program.
 
Thanks,

I used a level shifter for all the pins to be safe, but it is good to know that only 1 level shifter is essential for the RW pin. Am I correct in my assumption that there are some compatibility issues with the LiquidCrystalFast library and the 3.6 and 4.0 Teensys? I was just trying to get similar performance to the Teensy LC.

I'm exploring Michael's WireSetClock advice now...
 
I don't know. I haven't tested these ancient LCDs in quite some time.

I wrote LiquidCrystalFast over 10 years ago, when we only had slow 8 bit boards. It was designed to get the best performance on that hardware. With some other fast bitbang, like ShiftPWM and the shiftIn() and shiftOut() functions, we ran into problems where they would run too fast on Teensy 4. I don't know if that's happening with LiquidCrystalFast, but it's a possibility.

On higher clock speeds with the Wire library, make sure you have appropriate pullup resistors. If the resistor is too high, the waveforms will be too slow. You probably want 1K or maybe even 680 or 470 ohm resistors to run at much higher I2C clock speeds.
 
I did notice somewhere in the LiquidCrystalFast code something like:
Code:
  digitalWrite (pin, HIGH) ;  // pulse for at least 450ns
  digitalWrite (pin, LOW) ;
which will obviously break on the T4 which can do those calls in about no time at all!
 
Status
Not open for further replies.
Back
Top