HD44780U and derivatives bus timings - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: January 23, 2022, 10:01:05 am
content: I'm trying to write code to interface to a HD44780U compatible display. I'm having rather a lot of !fun with trying to get it to work. As stocks of serial displays are not available I pitched for the parallel ones not realizing just how ambiguous all the data out there is about the various controllers.I am looking to understand the bus timings rather than just copy other peoples code blindly and hope. As I wanted to avoid delays I settled on a rother slow clock/enable pulse of 100µs which can be driven by the systems main timer. this is because there is apparently a required 80µs delay before the busy flag can be read and presumably before it is otherwise worth considering the instruction has completed (yes it's a contradiction of the usually stated sub 40µs instruction time).Well I have not been successful but I see that most of the AVR code to be found around uses a delay of 1µs on the pulse. Indeed the minimum pulse duration on the datasheets is 500ns and the total cycle should be at least 1µs.So does having a 100µs pulse cause a problem? or is the start up time considerably longer than what the datasheets claim?

author: Ian.M
author_rank: Super Contributor
timestamp: January 23, 2022, 10:40:15 am
content: No. The E pulse can be *MUCH* longer than that and a HD44780U or bus compatible clone will still accept it. See part one of "How to use Intelligent L.C.D.s" by Julyan Ilett, originally published in EPE, for a tutorial that uses a manual pushbutton for E strobe! He adds debouncing for experiment 2 when it becomes apparent that randomly repeating commands and data bytes isn't helpful, and at that point the E strobe is active for as long as you hold the button, i.e. hundreds of ms to seconds!http://www.wizard.org/auction_support/lcd1.pdfhttp://www.wizard.org/auction_support/lcd2.pdf

author: ozcar
author_rank: Frequent Contributor
timestamp: January 23, 2022, 11:07:15 am
content: There are some much longer delays required after power-up, if that is what you mean by start up time.None of the data sheets I have seen give any maximum for the E cycle time nor for the high or low times. There is a maximum given for the rise time.But then if that "wait 80µs before reading the busy flag" part is to be believed, I'm not sure I'd call what you have a HD44780 derivative.

author: Simon
author_rank: Unknown
timestamp: January 23, 2022, 11:29:00 am
content: WQuote from: ozcar on January 23, 2022, 11:07:15 amWell yea, I am kind of thinking the same, it's an ST7066U.As I am trying to do this with timing rather than delays as I am using an ARM based MCU and would like to not block it up during refresh I decided to just have all timings on a 100µs cycle so that the display is just being updated continually with the rest of the code running. No delays rubbish.

author: DavidAlfa
author_rank: Super Contributor
timestamp: January 23, 2022, 01:20:34 pm
content: You only have 2 different execution times, most need ~100us, but CLEAR_DISPLAY and RETURN_HOME require more, around 1.5ms.Knowing that, I'd use a timer configured to overflow/reset after 100us (To ensure enough time has passed before reading BUSY flag).After any LCD operation, start the timer and exit, without waiting. You don't need to.Before sending anything to the LCD, check the timer.If the timer reset/overflow flag is not set, wait for it to ensure enough time has passed before reading BUSY. If done, check BUSY.That way you don't waste any CPU time unless your next LCD write comes too soon. Usually you don't update the LCD every 2ms, so it'll be much more efficient.Newer controllers like the ST7066 are faster, but if you want to ensure compatibility, use the timings from the original HD44780.Sure, 1us clock is perfectly fine.Basically:- Check the timer. If running, keep waiting until it resets- Check the BUSY flag, wait if necessary- Set DC to desired state- Write DATA to port- Wait 1us, set CLK high- Wait 1us, set CLK low.- Start the timer- ExitYou could also adjust the timer each time depending on the instruction, ex. 200us for normal instructions, 2ms for slow ones.As you're waiting more than needed, you can skip BUSY check.But the efficient way is to skip waiting after sending LCD data, only waiting when sending data multiple times within a very short time interval.

author: Simon
author_rank: Unknown
timestamp: January 23, 2022, 01:40:50 pm
content: Yes that is sort of what I am doing. I have a timer overflow interrupt that fires every 100µs, I've been crude so far in my setup code as nothing else need happen until the display is setup and I have a flag variable in the interrupt routine that is set to flag that the interrupt has fired so I check for that with a while loop that blocks code execution until 100µs have passed, do the next step and reset the flag and wait again.Once I am into my main code my plan is to refresh the entire display cyclically from an array so at every 100µs firing of the interrupt I can advance to the next step, run the main code (or do that at a slower pace with a counter variable) and wait for the interrupt again. This way the display code is not using delays that block the main code.

author: DavidAlfa
author_rank: Super Contributor
timestamp: January 23, 2022, 04:47:35 pm
content: If that timer use is just for the LCD, keeping it running every 100us is pretty expensive. That's 10K interrupts for nothing.Why don't simply use it as one-shot timer?After the LCD transaction, check if the instruction is any of the slow ones, adjust the timer counter/prescaler so it resets after 100us or 1.5ms, start it and go.Next LCD write, wait if the timer hasn't stopped yet. That's really simple.

author: Simon
author_rank: Unknown
timestamp: January 23, 2022, 06:02:03 pm
content: Well if I can run with a 100µs high and a 100µs low then it will be a 5kHz clock, I need to refresh the display of 80 characters at a rate that makes things appear to instantly update like 10 times a second which requires a 1.25kHz clock at least. Yes I can slow that down but actually 100µs between changing the clock line state or any other input state is not too much faster than it should be going.Rather than have to do so much checking all the time for what should I set the counter at, should I rearm the interrupt I thought it easier to just leave it cycling around the current display buffer page. The same interrupt can drive the programs overall timing. at 48MHz 100µs has nearly 4'000 clock cycles between firings, yes I can slow it down and have even more.

author: ozcar
author_rank: Frequent Contributor
timestamp: January 23, 2022, 08:16:22 pm
content: So this is a 40 character by 2 line display?Are you driving it in 4-bit or 8-bit mode?Does the display provide any way to adjust the LCD operating voltage?How does the problem manifest itself? EG nothing at all on the display, or darks blocks on one or both lines, or incorrect characters, orDo you have more than one of the displays to try with?Try creating a test program that avoids anything tricky, and for a start without trying to read the busy flag at all. Take the original HD44780 specification, including the suggested initialisation sequence. All you really need is a semi-trustworthy delay function, and in that regard you can err on the slow side. E rise and fall times aside, it should still work if you slow everything down by a factor of 10 or even 100 times.

author: Simon
author_rank: Unknown
timestamp: January 23, 2022, 08:33:50 pm
content: It's 4 lines of 20 characters so to the controller 2 40 char lines. Indeed when uninitialized I see the signature black rectangles on the first row only (1st and 3rd line).I decided to run in 8 bit to make life simple. So once I run my setup code I get a display where the 1st and 2nd row look the same. Checking the E line with my scope there is a train of pulses that spend 100µs high and low. I will have another look at extending the delay before start up. I have one working off an arduino so can also probe that.

author: mikerj
author_rank: Super Contributor
timestamp: January 23, 2022, 10:55:52 pm
content: Rather than a constant timer period you can put a basic state machine in the timer interrupt that reconfigures the next timer period according to the type of command. Many years ago I did this on the PIC, you just put data into a buffer, kicked the timer interrupt off and it did it's thing in the background. The E pulse can be as short at 140ns at 5v for the ST7066, so that could be handled directly in the interrupt without having to keep it asserted until the next interrupt is fired.

author: Simon
author_rank: Unknown
timestamp: January 24, 2022, 08:13:06 am
content: Yes this is what I am thinking and while at the start I was thinking of adjusting timings I also thought that given the "slow" times are fast enough for my main program I could just use the same timer to time the program. I also have a speed to measure so somewhere I need something accurate although I may do that with another counter.

author: mikerj
author_rank: Super Contributor
timestamp: January 24, 2022, 09:59:03 am
content: Yeah a fixed time will certainly work and might even make the state machine slightly simpler, but it does mean quite a lot more interrupts when executing the long commands like display clear or home. From memory I may not have even used the busy flag, instead used fixed times with plenty of margin for the fast and slow commands. I did have a look for the code but couldn't immediately find it, it was many years back so probably lurking on an old hard drive somewhere.I do remember using a non-printing ASCII character as an escape code to be able to write LCD commands via the normal print buffer (e.g. like switching the display on/off or programming the CG).

author: Simon
author_rank: Unknown
timestamp: January 24, 2022, 02:48:37 pm
content: Well i have it working on delays. I'll leave the init with delays and thing about how I write data cyclically.

author: Simon
author_rank: Unknown
timestamp: January 24, 2022, 06:24:39 pm
content: Well working as in I can print to the first line. Setting the current address seems to be the next challenge

author: DavidAlfa
author_rank: Super Contributor
timestamp: January 24, 2022, 09:17:18 pm
content: Very easy! Set address command is (0x80 | address).Address is:- 1st line: 0x00- 2nd line: 0x40- 3rd line: 1st line + char number per line (ex. 0x14 for 4x20 LCD)- 4th line: 2nd line + char number per line (ex. 0x54 for 4x20 LCD)Some simple code:Code:#define RS(n) LATB0=n#define RW(n) LATB1=n#define E(n) LATB2=n#define Data(n) LATC=n#define DATA 1#define CMD 0// Entry Mode Set Control Bits#define BIT_S_AUTOSCROLL_ON (1<<0) //!< Enable autoscroll. For use with Entry Mode Set command#define BIT_S_AUTOSCROLL_OFF 0 //!< Disable autoscroll. For use with Entry Mode Set command#define BIT_ID_INCREMENT_CURSOR (1<<1) //!< Increment cursor position after each char. For use with Entry Mode Set command#define BIT_ID_DECREMENT_CURSOR 0 //!< Decrement cursor position after each char. For use with Entry Mode Set command// Display On/Off Control Bits#define BIT_B_CURSOR_BLINK (1<<0)#define BIT_B_CURSOR_NO_BLINK 0#define BIT_C_CURSOR_ON (1<<1)#define BIT_C_CURSOR_OFF 0#define BIT_D_DISPLAY_ON (1<<2)#define BIT_D_DISPLAY_OFF 0// Cursor / Display Shift Control Bits#define BIT_RL_SHIFT_RIGHT (1<<2)#define BIT_RL_SHIFT_LEFT 0#define BIT_SC_SHIFT_DISPLAY (1<<3) //!< Seting this bit causes a display scroll#define BIT_SC_SHIFT_CURSOR 0 //!< Clearing this bits causes a cursor move// Function set Control Bits#define BIT_F_FONT_5_10 (1<<2)#define BIT_F_FONT_5_8 0#define BIT_N_DISP_LINES_2 (1<<3)#define BIT_N_DISP_LINES_1 0#define BIT_DL_DATALENGTH_8 (1<<4)#define BIT_DL_DATALENGTH_4 0enum enLcdCommands {E_CLEAR_DISPLAY = 0x01,E_RETURN_HOME = 0x02,E_ENTRY_MODE_SET = 0x04,E_DISPLAY_ON_OFF_CTRL = 0x08,E_CURSOR_DISPLAY_SHIFT = 0x10,E_FUNCTION_SET = 0x20,E_SET_CGRAM_ADDR = 0x40,E_SET_DDRAM_ADDR = 0x80,};enum enLCDCursorModes {E_LCD_CURSOR_OFF = 0x00,E_LCD_CURSOR_ON = 0x02,E_LCD_CURSOR_ON_BLINK = 0x03,};#define LCD_LEN 16enum enLCDLineAddr {E_LCD_LINE_1 = 0x00,E_LCD_LINE_2 = 0x40,E_LCD_LINE_3 = LCD_LEN,E_LCD_LINE_4 = 0x40 + LCD_LEN,};void lcd_write(uint8_t d, bool isData){RS(isData);Data(d);__delay_us(5);E(1);__delay_us(5);E(0);if(!isData){if(d==E_CLEAR_DISPLAY || d==E_RETURN_HOME){__delay_ms(2);}else if (d&E_FUNCTION_SET){__delay_us(250);}else{__delay_us(100);}}else{__delay_us(100);}}void lcd_init(void){__delay_ms(100);lcd_write(E_FUNCTION_SET | BIT_DL_DATALENGTH_8 , CMD);__delay_ms(10);lcd_write(E_FUNCTION_SET | BIT_DL_DATALENGTH_8 , CMD);__delay_us(200);lcd_write(E_FUNCTION_SET | BIT_DL_DATALENGTH_8 , CMD);lcd_write(E_FUNCTION_SET | BIT_DL_DATALENGTH_8 | BIT_N_DISP_LINES_2 | BIT_F_FONT_5_8, CMD);lcd_write(E_CLEAR_DISPLAY, CMD);lcd_write(E_DISPLAY_ON_OFF_CTRL | BIT_D_DISPLAY_ON | BIT_C_CURSOR_ON | BIT_B_CURSOR_BLINK, CMD);}void lcd_str(char* str){while(*str){lcd_write(*str++, DATA);}}void main(void){// Initialize the deviceSYSTEM_Initialize();RW(0);lcd_init();// Line 1lcd_write(E_SET_DDRAM_ADDR | E_LCD_LINE_1, CMD);lcd_str("Line 1");// Line 2lcd_write(E_SET_DDRAM_ADDR | E_LCD_LINE_2, CMD);lcd_str("Line 2");// Line 3lcd_write(E_SET_DDRAM_ADDR | E_LCD_LINE_3, CMD);lcd_str("Line 3");// Line 4lcd_write(E_SET_DDRAM_ADDR | E_LCD_LINE_4, CMD);lcd_str("Line 4");while (1){// Add your application code}}

author: mazurov
author_rank: Frequent Contributor
timestamp: January 25, 2022, 12:49:21 am
content: Quote from: DavidAlfa on January 23, 2022, 01:20:34 pmThey are also the first 2 commands in the table, so you can check if the command code is less than 3 set timer to 2ms, else set to 100us.

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 08:05:53 am
content: Quote from: DavidAlfa on January 24, 2022, 09:17:18 pmIndeed when I do not do my usual trick of putting "&" where I should put "|". That is running through an array on a constant cycle triggered the 100µs interrupt. I could make it longer if needs be.