HD44870 AVR Driver, running into issues

Go To Last Post
32 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So I purchased a 20x4 LCD a little while ago, and got it running manually with just a breadboard (no MCU, just using switches/etc...). However im running into issues when trying to drive it with my Atmega 328p.

The datasheet specific for the model is this: https://cdn-shop.adafruit.com/da...

Im also using a generic datasheet for HD44870: https://cdn-shop.adafruit.com/da...

 

Right now im trying to simply initialize it (to enter entry mode).

(Code right now is of course super basic and is no way the final product, im simply trying to initialize it for testing to ensure this works). However here is what I have so far:

 

#define DB4  PD6
#define DB5  PD5
#define DB6  PD4
#define DB7  PD3
#define RW   PD2
#define RS   PD1
#define EN   PD0

#define LCD_PORT PORTD
#define LCD_DDR  DDRD


void initLCD(void){
    DDRD = 0xff;
    _delay_ms(15);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB4) | (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB4) | (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB4) | (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(5);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB7 ); /* Set Font :0 and Lines: 2 Line */
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB7 ); 
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB7 ); 
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB4 ); 
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    _delay_ms(4.1);
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    LCD_PORT |= (1 << DB5) | (1 << DB6); 
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
       
}

 

This uses the datasheet: https://cdn-shop.adafruit.com/da... (Pg. 46, Initializing by Instruction section for 4-bit mode)

 

Obviously the writing to the PORTD is pretty self explanatory, but I don't even see a blinking cursor or anything on the screen. My fear is that I have no properly timed "Enable" and the latching of data. From the timing diagram it looks like we latch when EN goes high. So to me...bring it low, then putting the data on PORTD and then bringing EN high should latch it. But maybe im screwing up somewhere with my boolean logic.

 

I have tried:

  1. Double/Triple Checking Connections
  2. Making sure LED variable resistor is actually SHOWING the screen correctly
  3. Adding Larger delays between nibbles
  4. Using a separate power supply for the LCD VCC/VSS (was originally just using what was coming on USBTiny)
  5. Attempting to send the "standard" instructions instead (see pg. 42 of 2nd datasheet)

 

 

One thing I do not understand (Maybe some HD44870 experts can chime in) is what exactly the first 3 commands mean? As in...why do we send the same thing 3 times? Or is that just how it's made? Also we are sending Function Set and setting the interface to 8-bits 3 times even on the 4-bit one? Which doesn't make any sense.

 

I also see different commands on Pg. 42 (Initialization by internal reset)...Writing this how exactly should I assume the "state" of the LCD?  (They seem to be similar, with the "Initializing By Instruction"  just sending a function set with 8 bits set 3 times for some reason at the beginning).

 

 

Sidenote: As far as writing the driver obviously what I have now is ugly but im just trying to get the bare basics going. But I imagine i'll want to break it out into functions like:

SendNibble (For sending whatever specific byte I need to send)

SendCommand (Probably specific commands, with definitions)

Also having representations of certain "configurables", IE: 5x8 vs 5x10 lines being 0x03 vs 0x4 (or whatever they may be) that way I can just use that as a parameter

 

Any other tips for writing such a driver? (Obviously I know what I have no is super ugly and basic but im just trying to figure out what's going wrong using the most basic code)

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What value does LCD_PORT hold after your last line of code? More importantly, what value is on DB4 - DB7?

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HD44780 has been done to death.

Why re-invent the same old wheel again?

 

Floating point parameters in delay_ms() ???

 

If it worked with switches, but not with your code, then a likely cullprit is the timing.

Datasheet timings are based on the assumption the HD44780 runs on a certain clock frequency.

Start by slowing everything down by a factor of 10 or 100.

Take it from there.

 

Measure the logic signals, and compare the wavefroms with the reference from the datasheet.

Get a Logic Analyser, for example "24MHz 8ch" from Ali / Ebay / China and use it with Sigrok / Pulseview.

 

 

 

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Paulvdh wrote:

If it worked with switches, but not with your code, then a likely cullprit is the timing.

 

Really? I'd be more inclined to suspect the code.

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

LCD_PORT |= (1 << DB4) | (1 << DB5);

 

Why are you oring all the time? This is both ridiculous and dangerous.  Set the bits to exactly  you want...never assume the other bits are zero

 

use LCD_PORT = (1 << DB4) | (1 << DB5) and any other bits needed

 

 LCD_PORT |= (1 << DB5);   .......isn't DB4 STILL set from before???

 

 

 

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brian Fairchild wrote:

What value does LCD_PORT hold after your last line of code? More importantly, what value is on DB4 - DB7?

Good point, and like avrcandies mentioned as well I should probably be using = for everything, and then only use |= maybe when raising the EN bit. Should I be clearing everything maybe after each command? (I would think since im pulling EN down it shouldn't matter right? since it only works when EN is up?)

 

 

HD44780 has been done to death.

Why re-invent the same old wheel again?

 

Floating point parameters in delay_ms() ???

Good point about floating point in _delay_ms() I don't know why I did that lol. Also this is purely a learning exercise I realize HD44870 has been done a ton but writing libraries for myself helps me learn. I don't think it's a timing issue TBH because when I did this on a breadboard I was literally doing switches and moving stuff by hand (so super duper slow). I think my code is just not correct at all.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your LCD defaults to 8 bit mode, so try that first, once that is working to your satisfaction, then switch to 4 bit mode.

Let the learning begin....

 

I would take a look at known good LCD code (peter Fleury) to see how that works, and adapt your code from lessons learned.

 

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mercfh wrote:

Also this is purely a learning exercise I realize HD44870 has been done a ton but writing libraries for myself helps me learn.

I completely support you on this.  I think "it's been done to death, why are you doing it?" is a bizarre argument.  Blinking LEDs have been done to death too - so???

 

Here's some code I wrote that might be helpful.

 

https://www.embeddedrelated.com/...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kk6gm wrote:

Mercfh wrote:

Also this is purely a learning exercise I realize HD44870 has been done a ton but writing libraries for myself helps me learn.

I completely support you on this.  I think "it's been done to death, why are you doing it?" is a bizarre argument.  Blinking LEDs have been done to death too - so???

 

Here's some code I wrote that might be helpful.

 

https://www.embeddedrelated.com/...

Thanks, This explains what the "Initialization by Instruction" does too. So that should ALWAYS work. I'd imagine delays don't matter THAT much (as long as they are long enough) because I was doing it manually with multiple second delays between commands.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mercfh wrote:

Thanks, This explains what the "Initialization by Instruction" does too. So that should ALWAYS work. I'd imagine delays don't matter THAT much (as long as they are long enough) because I was doing it manually with multiple second delays between commands.

You're right, the delays only matter at the minimums.  Keep in mind too that the specified minimums are also affected by the range of specified device speeds, so the minimums for a given part can be as much as 42% greater than what is shown in the data sheet (190 kHz minimum internal clock, vs 270 kHz nominal clock).

 

I gave it a little more thought and I think if I were teaching an embedded class I would REQUIRE students to implement some basic HD44870 functionality.  It's a good example of how to start from a data sheet and end up with a functioning interface between micro and device.

 

1) Because the device, and the data sheet, is non-trivial (to put it mildly).  Nobody will get it right the first time.

 

2) Because there is a ton of online help and sample code out there.

 

3) Because the results are very gratifying - much more so than a blinking LED.  Good for student morale!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
 LCD_PORT |= (1 << DB4) | (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    

The average AVR running at over a couple of MHz will easily violate setup and hold times for the LCD. The HD44780 is dog slow by today's standards so you need a good sprinkling of NOP()s and/or delays to ensure you meet the timing requirements.

 

Some of the 'usual' problems people encounter:

1. start up delay. "it works when I run from the debugger, but not when I power up!"

2. setup and hold timing. Results vary from not working to sometimes working.

3. Instruction delays. 

4. not following the initialisation sequence. Don't ask why it is weird, just follow it.

5. These things have an RC oscillator. There's a bit of tolerance in the timing. A little more delay is better than too little.

 

A common question is do I poll the busy bit or just have an adequate delay? If you poll the busy bit, make sure you have a timeout. These LCDs are sensitive to ESD and will lock up. If you decide to poll the busy bit and the LCD locks up, then hopefully the watchdog will bail you out if you don't implement a timeout. Using delays means the lcd stops working but doesn't kill your app. Choose your poison.

 

The code is simple but the devil is in the details. Violate a spec and it will punish you!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

 LCD_PORT |= (1 << DB4) | (1 << DB5);
    LCD_PORT |= (1 << EN); /* Bring EN High) */
    LCD_PORT &= ~(1 << EN); /* Bring EN Low) */
    

The average AVR running at over a couple of MHz will easily violate setup and hold times for the LCD. The HD44780 is dog slow by today's standards so you need a good sprinkling of NOP()s and/or delays to ensure you meet the timing requirements.

 

Some of the 'usual' problems people encounter:

1. start up delay. "it works when I run from the debugger, but not when I power up!"

2. setup and hold timing. Results vary from not working to sometimes working.

3. Instruction delays. 

4. not following the initialisation sequence. Don't ask why it is weird, just follow it.

5. These things have an RC oscillator. There's a bit of tolerance in the timing. A little more delay is better than too little.

 

A common question is do I poll the busy bit or just have an adequate delay? If you poll the busy bit, make sure you have a timeout. These LCDs are sensitive to ESD and will lock up. If you decide to poll the busy bit and the LCD locks up, then hopefully the watchdog will bail you out if you don't implement a timeout. Using delays means the lcd stops working but doesn't kill your app. Choose your poison.

 

The code is simple but the devil is in the details. Violate a spec and it will punish you!

 

Thanks, do you think adding the nops before or after pulling enable up/down? or both?

as in:

  1. Set "Data" bits (DB4-DB7)
  2. nop
  3. pull en high
  4. nop
  5. pull en low
  6. nop
  7. ?
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Read the datasheet!
From memory, E high is 500ns. What is your clock speed?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mercfh wrote:
Thanks, do you think adding the nops before or after pulling enable up/down? or both?

 

As stated above, looking at the Fluery code as an example that works....

/* toggle Enable Pin to initiate write */
static void toggle_e(void)
{
    lcd_e_high();
    lcd_e_delay();
    lcd_e_low();
}

Jim

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To find some background information on the initialization routines and the addressing scheme, and also some example code (not a library) go to http://web.alfredstate.edu/faculty/weimandn/  and follow the appropriate links. 

 

I retired a few decades ago but the college is still hosting these web pages for me.  The underlying code has migrated from the original 8085 version to the 68HC11 and now to the ATmega 328p.  I think I have some old PIC code around here as well. 

 

Don

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Enable must be high at least around 250ns  (4 MHz)

 

better to use a sub each time, so you don't need nops all over th place

 


ldi XL, your value
rcall send_out   ;sends byte XL, with needed enable timing

ldi XL, your value
rcall send_out   ;sends byte XL, with needed enable timing

 

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Thu. Jan 3, 2019 - 04:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
Read the datasheet! From memory, E high is 500ns. What is your clock speed?

1 Mhz. Unfortunately the exact datasheet from Adafruit for that particular model didn't go into delays too much.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wow thanks, these do a great job at explaining a lot the datasheet doesn't (especially how the memory/pixels are mapped)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I managed to write a character to the LCD screen, so at least partial success. I increased my delays and used = instead of |= for the EN rising (that way I wouldn't get leftover DB7/etc... bits. Now to actually formulate functions to make it cleaner!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Understand the difference between |= of one bit and more than one bit. If you are only doing one bit, this will most likely get translated into a SBI instruction, conversely and one bit clear will be a CBI. Multiple bits become a read/modify/write.

When writing to the lcd, the setup and hold times are related to the falling edge of E. So be very clear of whether you do an =, |_ or &= as it makes a difference. If your AVR is running at 1MHz, then you can be a bit sloppy with the timing. However if you’re running at 16MHz, then you need to be more careful - 1 clock is 62.5ns and it is easy to violate the timing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
These LCDs are sensitive to ESD and will lock up. If you decide to poll the busy bit and the LCD locks up, then hopefully the watchdog will bail you out if you don't implement a timeout. Using delays means the lcd stops working but doesn't kill your app. Choose your poison.

 

I've experienced this a few times.  Has anyone ever worked out a way to recover other than cycling power to the LCD?

Apologies to the OP for the OT.

Letting the smoke out since 1978

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
Understand the difference between |= of one bit and more than one bit. If you are only doing one bit, this will most likely get translated into a SBI instruction, conversely and one bit clear will be a CBI. Multiple bits become a read/modify/write. When writing to the lcd, the setup and hold times are related to the falling edge of E. So be very clear of whether you do an =, |_ or &= as it makes a difference. If your AVR is running at 1MHz, then you can be a bit sloppy with the timing. However if you’re running at 16MHz, then you need to be more careful - 1 clock is 62.5ns and it is easy to violate the timing.

 

Im not super familiar with the AVR assembly instructions (my next topic of learning). How does the SBI/CBI vs read/modify/write instruction exactly cause problems with E? im assuming they just take longer or?

 

or I just need to make sure the writing is "done" before dropping E I assume is what you mean (apologies if this is incorrect)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

They don't cause problems - unless you use them incorrectly. My point was to understand the differences and to apply them correctly. SBI/CBI take two clock cycles, but will only change 1 bit and not necessarily work on all ports. 

 

With sequential digital logic there's two main parameters - setup and hold time. Setup is the time where the data needs to be stable before the clock. Hold is the time the data needs to remain stable after the clock. The clock in this instance is the E signal. Both these parameters are reasonably long on the HD44780. Long in relation to an avr clocked at a few MHz or more. So for a write cycle, you need to ensure your control and data pins have the required states and you have allowed enough time before dropping E. On a 1MHz AVR you probably don't need to give this much attention as the execution rate pretty well ensures the required timing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

They don't cause problems - unless you use them incorrectly. My point was to understand the differences and to apply them correctly. SBI/CBI take two clock cycles, but will only change 1 bit and not necessarily work on all ports. 

 

With sequential digital logic there's two main parameters - setup and hold time. Setup is the time where the data needs to be stable before the clock. Hold is the time the data needs to remain stable after the clock. The clock in this instance is the E signal. Both these parameters are reasonably long on the HD44780. Long in relation to an avr clocked at a few MHz or more. So for a write cycle, you need to ensure your control and data pins have the required states and you have allowed enough time before dropping E. On a 1MHz AVR you probably don't need to give this much attention as the execution rate pretty well ensures the required timing.

Ah I see, yeah I took a look at those timing parameters (still my weakest link) and they make more sense. They are on the order of ns but I imagine on a really fast chip you would want to account for these.

 

Right now im spending time writing definitions for the different instructions. So something like this:

/* Display On/Off Control */
#define LCD_DISPLAY_CONTROL        0x08    
#define DISPLAY_ON                0x04
#define DISPLAY_OFF                0x00
#define CURSOR_ON                0x02
#define CURSOR_OFF                0x00    
#define BLINK_ON                0x01
#define BLINK_OFF                0x00
/**************************************/

That way when someone wants to send a command they can say "Oh I want to send an LCD_DISPLAY_CONTROL command and then just OR the options, IE: DISPLAY_ON|CURSOR_ON|BLINK_ON. If im thinking right that should create the right binary value (which i will then split into nibbles when doing 4 bit mode...somehow, haven't quite figured that out yet). Hopefully this is a decent way to do this.

 

One partial confusion I have is writing to CGRAM vs DDRAM. From what I can gather on the datasheet the DDRAM represents the actual positions on the screen. So writing to 0x07 will take the cursor to the 8th display position.

 

CGRAM lets us make our own character codes from what the datasheet says....but when looking at the "sample operations" (pg 40 of https://cdn-shop.adafruit.com/da...) it specifically says "Write data to CGRAM/DDRAM" which it's using the command for "Set CGRAM address" (pg 24). So the DDRAM is set but the "Shift" naturally (assuming you set it to shift)....so were saying "Look at this CGRAM address, which might hold an "H" or whatever" and it gets applied to the DDRAM address that has been set.

 

But then there is CGROM and then making "Custom" CGRAM characters. So I think im confusing CGRAM vs CGROM and im not entirely sure what CGROM refers to (Since the datasheet says "look at table 4". But then "Commands" used in the examples are using the "Set CGRAM address", so that's confusing me a bit.

 

Table 5 confuses me even more by saying " Relationship between CGRAM Addresses, Character Codes (DDRAM) and Character Patterns (CGRAM Data)".....which had me until "Chracter Codes (DDRAM).....I thought that's where the "screen positions" were defined. Im going to take a guess based on this table though:

 

DDRAM ADDRESS (Assuming 5x8): Bits 0 to 2 represent the LINE of the LCD display area (8 lines, 0 to 2 has 8 values, makes sense, 7 lines for pixels + cursor)

CGRAM ADDRESS: Bits 0 to 2 represent the "line" of CGRAM Data that would be filled out, 3 to 5 I can't quite tell...it seems like it would change the address for CGRAM but there are only 3 bits here....and way more than 7 different character types

CGRAM DATA: 0 to 4 represent whether the pixel would be on or not.

 

Obviously the data types and what does what I still need some studying on. It's interesting learning these things though.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In 4 bit mode, an ESD strike normally resets the LCD so you need to re-initialise it. The problem is if you use write only mode, there is no easy way to determine if it went west. My workaround is to regularly re- init it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you're overthinking things a little. You have 8 programmable characters. These are the first 8 character codes. To program these characters you write to the CGRAM. So to set the first programmable character (0x00) you'd issue the command SET CGRAM ADDRESS with the address of 0, the next character the address is 8, next 16 and so on. Nevertheless, implementing programmable characters is one of the last features to add. You've got many hurdles to conquer before that.

 

For the most part - putting stuff onto the display is via the DDRAM. Depending on your actual display, the addresses for each line are not sequential. The first line starts at 0, whereas the second line might start at 0x40 (my memory is weak here - its been years since I've needed to write code for these displays). So use SET DDRAM ADDRESS to the position where you want to start writing. Then write to DDRAM with the character data.

 

The code usually works out simple - you have the initialisation function, then a write function. If you use four bit mode, the write function splits the byte and does the two nibble operations. Then you add a write command and write data function that calls the write function. Everything else sits on top of this.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

In 4 bit mode, an ESD strike normally resets the LCD so you need to re-initialise it. The problem is if you use write only mode, there is no easy way to determine if it went west. My workaround is to regularly re- init it.

Ah. Interesting. Do you re-init after a certain command usually? (like after every character write?) or something else specifically. I may try to work some reads in there but I haven't quite gotten that far (specifically reading for a busy flag).

 

Also I finally figured out the CGRAM vs CGROM thing, the datasheet makes it sort of confusing slightly (imo) but I realized from other websites you use CGRAM writes to store the 8 possible ASCII custom created characters. And CGROM simply just holds the pre-generated ones.

 

To me it was weird because we were using CGRAM writes (according to the table that defines the different type of instructions) to use the CGROM. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So far so good with the driver (it's not great but it's something):

 

/*
 * HD44780_LCD.h
 *
 * Created: 12/27/2018 10:54:00 PM
 *  Author: smith
 */ 

#include <util/delay.h>
#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>

#define DB4			4
#define DB5			5
#define DB6			6
#define DB7			7
#define RS			0
#define RW			1
#define EN			2

#define LCD_DDR		DDRD
#define LCD_PORT	PORTD

enum mode {
	INSTRUCTION_REGISTER,
	DATA_REGISTER
	};

/* Delays */
/* All Delays noted in microseconds */
#define LCD_ENABLE_PULSE_DELAY			1000
#define LCD_POWER_ON_DELAY				100000
#define LCD_POWER_ON_FUNCTION_SET_DELAY	5000
#define LCD_FUNCTION_SET_SPECIAL_DELAY	150
#define LCD_FUNCTION_SET_DELAY			60
#define LCD_DISPLAY_CONTROL_DELAY		60
#define LCD_CLEAR_DISPLAY_DELAY			3500
#define LCD_ENTRY_MODE_SET_DELAY		60
/**************************************/

/* The following represent the hex values for instruction options as well as the value for the instruction itself */

/* Clear Display */
#define LCD_DISPLAY_CLEAR		0x01
/**************************************/

/* Return Home */
#define LCD_RETURN_HOME			0x02
/**************************************/

/* Display On/Off Control */
#define LCD_DISPLAY_CONTROL		0x08
#define DISPLAY_ON				0x04
#define DISPLAY_OFF				0x00
#define CURSOR_ON				0x02
#define CURSOR_OFF				0x00
#define BLINK_ON				0x01
#define BLINK_OFF				0x00
/**************************************/

/* Entry Mode Set */
#define LCD_ENTRY_MODE			0x04
#define INC_ADDRESS				0x02
#define	DEC_ADDRESS				0x00
/*
Note: If using SHIFT_DISPLAY_X, you do NOT want to set INC_ADDRESS or DEC_ADDRESS also as it changes I/D as well
S (Shift) being 0 assumes no shift, so this assumes Shift is (1) and I/D is set with it.
You can use INC_ADDRESS or DEC_ADDRESS on it's own however if we are assuming no shifting
*/
#define SHIFT_DISPLAY_RIGHT		0x01
#define SHIFT_DISPLAY_LEFT		0x03
/**************************************/

/* Function Set */
#define LCD_FUNCTION_SET		0x20
#define	MODE_4BIT				0x00
#define MODE_8BIT				0x08
#define DISPLAY_2_LINE			0x08
#define DISPLAY_1_LINE			0x00
#define FONT_5x8_DOTS			0x00
#define	FONT_5x10_DOTS			0x04
/* Note: Cannot display two lines for 5x10 dot character font */
/**************************************/

/* Cursor/Display Shift */
#define LCD_CURSOR_SHIFT		0x10
#define CURSOR_SHIFT_LEFT		0x00
#define	CURSOR_SHIFT_RIGHT		0x04
#define DISPLAY_SHIFT_LEFT		0x08
#define DISPLAY_SHIFT_RIGHT		0X0C
/**************************************/

void initLCD(void);
void sendNibble(uint8_t data, uint8_t mode);
void toggleEnable(void);
void putChar(char c);
void writeString(char *s);
void lcdInstruction(uint8_t cmd);

// Helper Functions
void lcd_clear(void);
void lcd_home(void);
void lcd_cursor_right(void);
void lcd_cursor_left(void);

 

/*
 * HD44780_LCD.c
 *
 * Created: 12/27/2018 10:54:09 PM
 *  Author: smith
 */
#include "HD44780_LCD.h"

void toggleEnable(void) {
		LCD_PORT |= (1 << EN);
		_delay_us(5000);
		LCD_PORT &= ~(1 << EN);
}

void sendNibble(uint8_t data, uint8_t mode) {
	mode ? (LCD_PORT |= (1 << RS)) : (LCD_PORT &= ~(1 << RS));
	if(data & 0x80) LCD_PORT |= (1 << DB7);
	if(data & 0x40) LCD_PORT |= (1 << DB6);
	if(data & 0x20) LCD_PORT |= (1 << DB5);
	if(data & 0x10) LCD_PORT |= (1 << DB4);
	toggleEnable();
	LCD_PORT = 0x00;
	mode ? (LCD_PORT |= (1 << RS)) : (LCD_PORT &= ~(1 << RS));
	if(data & 0x08) LCD_PORT |= (1 << DB7);
	if(data & 0x04) LCD_PORT |= (1 << DB6);
	if(data & 0x02) LCD_PORT |= (1 << DB5);
	if(data & 0x01) LCD_PORT |= (1 << DB4);
	toggleEnable();
	LCD_PORT = 0x00;
	_delay_us(60);
}

void lcdInstruction(uint8_t cmd) {
	sendNibble(cmd, INSTRUCTION_REGISTER);
}

void putChar(char c) {
	sendNibble(c, DATA_REGISTER);
}

void writeString(char *s) {
	while(*s != '\0') {
		putChar(*s);
		s++;
	}
}

void initLCD(void) {
	LCD_DDR |= (1 << RS) | (1 << RW) | (1 << EN) | (1 << DB4) | (1 << DB5) | (1 << DB6) | (1 << DB7); /* Set all to output */

	/* Initial Special Case Function Sets */
	/* DB0-DB3 are ignored, however we are not sending 2 separate nibbles */
	_delay_ms(100); /* Wait 20ms after VDD > 4.5v */
	LCD_PORT = (1 << DB5) | (1 << DB4);
	toggleEnable();
	_delay_us(LCD_POWER_ON_FUNCTION_SET_DELAY);

	LCD_PORT = (1 << DB5) | (1 << DB4);
	toggleEnable();
	_delay_us(LCD_FUNCTION_SET_SPECIAL_DELAY);

	LCD_PORT = (1 << DB5) | (1 << DB4);
	toggleEnable();
	_delay_us(LCD_FUNCTION_SET_SPECIAL_DELAY);

	LCD_PORT = (1 << DB5);
	toggleEnable();
	_delay_us(LCD_FUNCTION_SET_SPECIAL_DELAY);
	/***********************************************************************/

	/* Real Function Set (Number of Lines/Font */
	sendNibble(LCD_FUNCTION_SET|MODE_4BIT|DISPLAY_2_LINE,INSTRUCTION_REGISTER);
	/***********************************************************************/

	/* Display On/Off Control (All turned off initially)  */
	sendNibble(LCD_DISPLAY_CONTROL|DISPLAY_OFF|CURSOR_OFF|BLINK_OFF,INSTRUCTION_REGISTER);
	/***********************************************************************/

	/* Clear Display  */
	sendNibble(LCD_DISPLAY_CLEAR,INSTRUCTION_REGISTER);
	/***********************************************************************/

	/* Entry Mode Set  */
	sendNibble(LCD_ENTRY_MODE|INC_ADDRESS,INSTRUCTION_REGISTER);
	/***********************************************************************/

	/* Initialization Ends */
}

/***********************************************************************/
/* Helper Functions */
void lcd_clear(void) {
	lcdInstruction(LCD_DISPLAY_CLEAR);
}

void lcd_home(void) {
	lcdInstruction(LCD_RETURN_HOME);
}

void lcd_cursor_right(void) {
	lcdInstruction(LCD_CURSOR_SHIFT|CURSOR_SHIFT_RIGHT);
}

void lcd_cursor_left(void) {
	lcdInstruction(LCD_CURSOR_SHIFT|CURSOR_SHIFT_LEFT);
}

/***********************************************************************/

 

One thing I noticed is the enable pulse needs to be much longer (for me at least) than most guides say. I don't know if it's because my chip is slower so setting it to a "nop" short delay just does not work. (It tripped me up at why it wasn't working, until I extended the delay. It's probably longer than it needs to be (at 5000 microseconds or 5 milliseconds now). But i've seen people get away with literally a nop pulse. So...not too sure on that.

 

Otherwise all the functions work. Im sure some of my coding practices are bad, and there is still work to be done. BUT it does indeed print characters on the screen! and the "helper functions" do their job. Suggestions are of course welcome! (It's only 4-bit mode so far)

Last Edited: Wed. Jan 16, 2019 - 03:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mercfh wrote:
(It's only 4-bit mode so far)

That's all you need, why burn more pins then needed. 

Other alternatives is using a backpack for spi or i2c for even fewer pins.

 

Doing this as a learning exercise is ok, most freaks would use Peter Fleury's LCD driver and get on with the project.

http://homepage.hispeed.ch/peter...

 

Good luck with your project

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:

Mercfh wrote:
(It's only 4-bit mode so far)

That's all you need, why burn more pins then needed. 

Other alternatives is using a backpack for spi or i2c for even fewer pins.

 

Doing this as a learning exercise is ok, most freaks would use Peter Fleury's LCD driver and get on with the project.

http://homepage.hispeed.ch/peter...

 

Good luck with your project

Jim

 

 

I agree (With the 4-pin idea at least). And yeah this is purely a learning project, it's actually taught me a fair amount since it's my first "real" library even if it's a simple one. Code organization and so forth (especially the header file and dealing with configuration).

 

Honestly I gotta say I think it's actually a really excellent first time (or maybe 2nd/3rd time) Embedded Project/Library. Since it's relatively simple as far as just sending the right data, but you still need to worry some about timings and need to learn to read a datasheet well enough to know what to send. Although im curious how others got away with the shorter enable's than mine. Maybe since im doing this through breadboard it just has a bad connection or something.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

http://web.alfredstate.edu/facul...

The above reference was mentioned before, you may find it helpful.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

ki0bk wrote:

http://web.alfredstate.edu/facul...

The above reference was mentioned before, you may find it helpful.

 

Jim

 

That's actually what I used for my timings, they also increased them a bit across the board to be safe. I'll just have to experiment a bit with my enable (it still works, but just seems to require a longer one). I went ahead and increased all of the timings about 25% or so even on top of his (Which in microseconds, is still really small). Either way im pretty happy with the progress so far. Im sure there are better ways to do what im doing and am always looking for tips. For now at least the basics are down.