40x4 LCD on Teensy 4.1

wassabi

Member
I am having a rough time getting my 40x4 lcd to init on the teensy 4.1.
I am using the 3.3 Volt resistor mod described on this help page: https://www.pjrc.com/teensy/td_libs_LiquidCrystal.html

  • Works ok-ish using LiquidCrystal, but I can only init half the display.
  • Works perfect on my 5V tolerant teensy 3.2 without 1k resistors on the lcd data pins.

Currently, I suspect the 3.3 Volt resistor mod doesn't work on the teensy 4.1. Is that true?

edit: After some googling, it seems that the 1k series resistor method relies on the teensy chip to have internal protection diodes on it's inputs. source
Does the teensy 4.x use clamp diodes for esd?


Nothing special in my code, is the example from the LiquidCrystalFast library.

Code:
// include the library code:
#include <LiquidCrystalFast.h>

// initialize the library with the numbers of the interface pins
LiquidCrystalFast lcd( 2,  3,  0, 1, 7, 6, 5, 4);
         // LCD pins: RS RW EN1 EN2 D4 D5 D6 D7

void setup(){
    // set up the LCD's number of rows and columns: 
  lcd.begin(40, 4);
  // initialize the serial communications:
  Serial.begin(9600);
}

void loop()
{
  // when characters arrive over the serial port...
  if (Serial.available()) {
    // wait a bit for the entire message to arrive
    delay(100);
    // clear the screen
    lcd.clear();
    // read all the available characters
    while (Serial.available() > 0) {
      // display each character to the LCD
      lcd.write(Serial.read());
    }
  }
}
 
Last edited:
Although I have not used the T4.x in my projects ISTR it has 3.3v logic levels so no need to level shift them.
I wish that was true. The LCD is a 5V device, and the 40x4 version has two controller chips that require both write and reads on the data pins to initialize.

At this point, I feel like I have the problem decently figured out. I just need confirmation from the IMXRT1060 Datasheet. I can't find a description of the esd protections on the inputs. Closest mention is a hint about the presence of a "ESD network".

From my understanding... The inline 1k resistor on the data pins works as a voltage shifter, because when an IO pin is set to input an internal diode is connected. Clamping the input voltage.

Who should I email/PM to report an outdated help page?
 
The resistor is there to limit fault currents when the clamp diode turns on. (5V signal to 3.3V device) Most devices list a current limit (a few mA) in their data sheet but I couldn't find one here. Exceed that limit and you risk latchup.

There is a limit of Vdd+0.3V on inputs.

You can use character LCDs in a write only manner and skip the resistors at the cost of slower operation. You have to insert delays at various points instead of reading the device status. It still does require care to meet setup and hold times of course.
 
You can use character LCDs in a write only manner and skip the resistors at the cost of slower operation. You have to insert delays at various points instead of reading the device status. It still does require care to meet setup and hold times of course.

I would love to know more. I have been unable to init my 40x4 lcd in write only mode.

As far as I know, the 40x4 lcd is only supported by the LiquidCrystalFast library and requires the R/W pin used. Here all the inits from the library, the "8 pin connection (fast): 4x40 LCD, two HD44780 controller chips" is the only one that supports the two enable pins required for the 40x4 lcd.

Code:
	// 6 pin connection (slow): normal LCD, single HD44780 controller
	LiquidCrystalFast(uint8_t rs, uint8_t enable,
	  uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) {
		init(rs, 255, enable, 255, d4, d5, d6, d7);
	}
	// 7 pin connection (fast): normal LCD, single HD44780 controller
	LiquidCrystalFast(uint8_t rs, uint8_t rw, uint8_t enable,
	  uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) {
		init(rs, rw, enable, 255, d4, d5, d6, d7);
	}
	// 8 pin connection (fast): 4x40 LCD, two HD44780 controller chips
	LiquidCrystalFast(uint8_t rs, uint8_t rw, uint8_t enable1, uint8_t enable2,
	  uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) {
		init(rs, rw, enable1, enable2, d4, d5, d6, d7);
	}
	// 10 pin connection - not recommended, for backwards compatibility only
	LiquidCrystalFast(uint8_t rs, uint8_t enable,
	  uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
	  uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) {
		init(rs, 255, enable, 255, d4, d5, d6, d7);
	}
	// 11 pin connection - not recommended, for backwards compatibility only
	LiquidCrystalFast(uint8_t rs, uint8_t rw, uint8_t enable,
	  uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
	  uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) {
		init(rs, rw, enable, 255, d4, d5, d6, d7);
	}

UhClem, sounds like I need to go hunting for a new library. One that supports a second enable pin and doesn't require RW. Hmm
 
I know nothing about that library code. I have only used character LCDs with the MSP430 and writing my own code. Various versions over the years: 4 bit, 8 bit, and with or without read.

Before writing new code, look at what you have and see if you can find the problem. With a copy of the LCD datasheet in hand of course. I took a look and I can't say I like it. First is the writing each data bit individually as though they could be spread out over multiple ports. Ugh.

Then there is this bit of code in write4bits:
Code:
	digitalWrite(en, HIGH);   // enable pulse must be >450ns
	digitalWrite(en, LOW);

On slower processors this shouldn't be much of a problem but on a Teensy 4 you have to add some delay unless digitalWrite() is really slow. At least the requirement is acknowledged even if nothing is done. There is also a 1320ns requirement from enable to reading data.
 
Good points, the LiquidCrystalFast library isn't huge and is well commented and the init sequence is well documented by others.

I did find another library that attempts to address the read back issue.

Code:
#define set_data_pins_to_read pinMode(2,INPUT);pinMode(5,INPUT);pinMode(4,INPUT);pinMode(3,INPUT);   //data pins 5,4,3,2 
#define set_data_pins_to_write pinMode(2,OUTPUT);pinMode(5,OUTPUT);pinMode(4,OUTPUT);pinMode(3,OUTPUT); // digitalWrite etc gives some board independence
#define set_EN_high digitalWrite(11,HIGH);    //port B3 pin 11
#define set_EN_low digitalWrite(11,LOW);
#define set_EN2_high digitalWrite(11,HIGH);    //port B3 pin 11
#define set_EN2_low digitalWrite(11,LOW);
#define set_RW_high digitalWrite(10,HIGH);
#define set_RW_low digitalWrite(10,LOW);    //port B bit 2 pin10
#define set_RS_high digitalWrite(12,HIGH);    //port B bit 4  pin 12
#define set_RS_low digitalWrite(12,LOW);
#define read_busy digitalRead(2);        //portD2, pin 2
*/
void checkBusyFlag(int8_t chip) {
 uint8_t busy; // = 0x04;
 set_data_pins_to_read;
 set_RW_high;             //RW to read
 set_RS_low;
 if (chip == 0) {  //the if and else can be eliminated if only one hd44780 chip
 do {
   set_EN_high;
   delayMicroseconds(1);
   busy = read_busy;    // read busy flag
   set_EN_low;
   delayMicroseconds(1);
   set_EN_high; 
   delayMicroseconds(1);  //pulse the second nibble--discard it;
   set_EN_low; 
 }while (busy);
} else {
   do {
   set_EN2_high;
   delayMicroseconds(1);
   busy = read_busy;    // read busy flag
   set_EN2_low;
   delayMicroseconds(1);
   set_EN2_high; 
   delayMicroseconds(1);  //pulse the second nibble--discard it;
   set_EN2_low;
 } while (busy);
}
 set_data_pins_to_write; // data pins to write
 set_RW_low;              //RW to write
 set_RS_high;
}

This gives me an idea, to read back on a different set of pins. Thus allowing me to use a normal resistor divider for the 5V reading.

Here is a quick mockup

voltage_divider_for_teensy41.ong.png
 
Last edited:
I have it working now. Waited until I had a bidirectional shift register on hand so I could eliminate any hardware issue as the cause.

UhClem, you were close. Was actually the while (busy == HIGH) loop that had a timing issue.
This is a tight loop that toggles the enable pin to refresh the busy data bit. I played around with it and found the minimal delays needed for a functioning lcd.

Here is my basic modification to LiquidCrystalFast.cpp for a functioning 4x40 LCD using a teensy 4.

Code:
do {
	digitalWrite(en, HIGH);
	delayMicroseconds(1);
	busy = digitalRead(_data_pins[3]);
	digitalWrite(en, LOW);
	delayMicroseconds(1);
	digitalWrite(en, HIGH);
	delayMicroseconds(1);
	digitalWrite(en, LOW);
	delayMicroseconds(1);
} while (busy == HIGH);

Digging deeper, in the Arduino\hardware\teensy\avr\libraries\LiquidCrystal\src\LiquidCrystal.cpp, this code is used for toggling the enable pin.

Code:
void LiquidCrystal::pulseEnable(void) {
  digitalWrite(_enable_pin, LOW);
  delayMicroseconds(1);    
  digitalWrite(_enable_pin, HIGH);
  delayMicroseconds(1);    // enable pulse must be >450ns
  digitalWrite(_enable_pin, LOW);
  delayMicroseconds(100);   // commands need > 37us to settle
}

I wasn't using the default library because it doesn't support larger 4x40 lcd. Whomp whomp

Final version of the working library is attached. I modified LiquidCrystalFast to use the pulseEnable function from the teensy LiquidCrystal library.
 
UhClem, you were close. Was actually the while (busy == HIGH) loop that had a timing issue.

Which is exactly what I was refering to with that 1320us delay when reading data.

Do not use that slow version of pulseEnable with its 100us delay. The whole point of checking the busy flag is so you can avoid that delay.

The 1us delay just before the function return is pointless. It is wildly unlikely that you will reach a point where enable is set again before the cycle time requirement is met.
Setting enable low at the beginning of that pulse enable function (and its delay) is also a waste of time unless you foolishly have some function which leaves enable high.
 
Back
Top