Thread: PIC16F684 Interfacing with Hitachi 44780

  1. #1
    Deleted Account
    Join Date
    Nov 2010
    Posts
    16

    Unhappy PIC16F684 Interfacing with Hitachi 44780

    Hi guys!

    I'm a finalist university student on a Product Design and Technology course and I'm currently completing an assessment where we are required to design a circuit using a PIC16F684 to interface to a Hitachi 44780 LCD controller.

    We've been given some standard code in order to output a sample message to the LCD, which we're intending to add to once we've programmed in the additional functionality of the circuit.

    The code is as follows:

    Code:
    #include <pic.h>
    /*  cLCD.c - Write a String to a 4 Bit Hitachi 44780 LCD I/F
    
    This Program Initializes Hitachi 44780 Based LCD in 4 Bit Mode
      and then writes a simple string to it.  The simulator was used
      to time delay values.  
    
    RC3:RC0 - LCD I/O D7:D4 (Pins 14:11)
    RC4     - LCD E Clocking Pin
    RC5     - LCD R/S Pin
    
    */
    __CONFIG(INTIO & WDTDIS & PWRTEN & MCLRDIS & UNPROTECT \
      & UNPROTECT & BORDIS & IESODIS & FCMDIS);
    
    int i, j, k, n;                 //  Use Global Variables for Debug
    
    //                         1234567890123456
    const char TopMessage[] = "  Hello World   ";
    const char BotMessage[] = "  Hear me roar  ";
    
    #define E  RC4                  //  Define the LCD Control Pins
    #define RS RC5
    
    const int Twentyms = 1250;      //  Declare a Constant for 20 ms Delay
    const int Fivems = 300;
    const int TwoHundredus = 10;
    
    LCDWrite(int LCDData, int RSValue)
    {
    
        PORTC = (LCDData >> 4) & 0x0F;  //  Get High 4 Bits for Output
        RS = RSValue;
        E = 1;  E = 0;              //  Toggle the High 4 Bits Out
    
        PORTC = LCDData & 0x0F;     //  Get Low 4 Bits for Output
        RS = RSValue;
        E = 1;  E = 0;              //  Toggle the Low 4 Bits Out
    
        if ((0 == (LCDData & 0xFC)) && (0 == RSValue))
            n = Fivems;             //  Set Delay Interval
        else
            n = TwoHundredus;
    
        for (k = 0; k < n; k++);    //  Delay for Character
    
    }  //  End LCDWrite
    
    main()
    {
    
        PORTC = 0;                  //  Start with Everything Low
        CMCON0 = 7;                 //  Turn off Comparators
        ANSEL = 0;                  //  Turn off ADC
        TRISC = 0;                  //  All of PORTC are Outputs
    
    //  Initialize LCD 
        j = Twentyms;
        for (i = 0; i < j; i++);    //  Wait for LCD to Power Up
    
        PORTC = 3;                  //  Start Initialization Process
        E = 1;  E = 0;              //  Send Reset Command
        j = Fivems;
        for (i = 0; i < j; i++);
    
        E = 1;  E = 0;              //  Repeat Reset Command
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        E = 1;  E = 0;              //  Repeat Reset Command Third Time
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        PORTC = 2;                  //  Initialize LCD 4 Bit Mode
        E = 1;  E = 0;
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        LCDWrite(0b00101000, 0);    //  LCD is 4 Bit I/F, 2 Line
    
        LCDWrite(0b00000001, 0);    //  Clear LCD 
    
        LCDWrite(0b00000110, 0);    //  Move Cursor After Each Character
    
        LCDWrite(0b00001110, 0);    //  Turn On LCD and Enable Cursor
    
        for (i = 0; TopMessage[i] != 0; i++)
            LCDWrite(TopMessage[i], 1);
    
        LCDWrite(0b11000000, 0);    //  Move Cursor to the Second Line
    
        for (i = 0; BotMessage[i] != 0; i++)
            LCDWrite(BotMessage[i], 1);
    
        while(1 == 1);              //  Finished
    
    }  //  End cLCD
    We are manufacturing the circuits on a PCB and, to get all of the necessary connection on a single side of copper, we reversed the data bus connections to the Hitachi. Normally, the output of PORTC is defined such that pin RC3 goes to DB7, RC2 goes to DB6, RC1 goes to DB5 and RC0 goes to DB4. However, in our reconfigured circuit DB4 is connected to RC3, DB5 is connected to RC2, DB6 is connected to RC1 and DB7 is connected to RC0. i.e. the data connections are reversed (sorry if this is lengthy, wanted to be absolutely clear about what we've done).

    We made these modifications under the assumption that it would be a simple matter of changing the code to account for this change of connections, since the output of PORTC on the PIC is simply governed by its programming.

    We've tried a number of code modifications on our breadboarded circuit, but so far no success.

    Does anybody have any thoughts as to how I would reverse the order of outputs from PORTC pins 0 through 3?

    Sorry if any of this is unclear, if you need further information I'd be happy to provide it.

    At this stage, if we're unable to modify the code we're going to have to cut through some of the tracks on the PCB and solder some bits in using wires. Unfortunately, we'll get marked down for this.

    Thanks for any help anybody may be able to give.

    JT
    Last edited by Salem; 12-07-2010 at 12:48 PM. Reason: OP requested redaction

  2. #2
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    I haven't spent a lot of time studying this but it looks like you need to reverse the lower 4 bits of LCDData to make this work... i.e. bit0 = bit3, bit1 = bit2, bit2 = bit1, bit3 = bit0

    Code:
    char FlipBits(char LCDData)
     { 
        char LCDRev = 0;
    
         LCDRev |= (LCDData & 1) * 8;
         LCDRev |= (LCDData & 2) * 2;
         LCDRev |= (LCDData & 4)  / 2;
         LCDRev |= (LCDData & 8)  / 8;
    
         return  LCDRev;
      }
    This may not be *exactly* it, but it should give you the idea.

  3. #3
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    I imagine you've tried this change:
    Code:
    RC3:RC0 - LCD I/O D7:D4 (Pins 14:11)
    to this:
    Code:
    RC0:RC3 - LCD I/O D7:D4 (Pins 14:11)
    but I couldn't resist offering it anyway.

    Good luck!

  4. #4
    Registered User
    Join Date
    May 2009
    Posts
    4,183
    Looks like CommonTater FlipBits code should work.
    Remember this needs done inside LCDWrite where the PORTC value is set.
    And, it needs done any where PORTC is written with a non-zero value.
    Tim S.
    Last edited by stahta01; 11-30-2010 at 02:33 PM.

  5. #5
    Registered User
    Join Date
    Oct 2008
    Location
    TX
    Posts
    2,059
    If your compiler lets you write inline assembly, put the RRF (Rotate Right) instruction in the program.
    Alternatively use the code provided by CommonTater (but use bit shifts instead of multiply / divide):
    Code:
    return (((LCDData & 1)<<3) | ((LCDData & 2)<<1) | ((LCDData & 4)>>1) | ((LCDData & 8)>>3)));

  6. #6
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    Have you thought about mounting the chip on the bottom layer? (only works if it's DIP)

    That would reverse the pins for you (but of course, your PCB needs to be designed for that).

  7. #7
    Deleted Account
    Join Date
    Nov 2010
    Posts
    16
    Hi guys!

    Thanks very much for all of your responses. We've had a look at the code suggestions, and I can sort of understand how they work in principle, but we still can't get anything useful out of our display.

    We've altered our code to:

    Code:
    #include <pic.h>
    /*  cLCD.c - Write a String to a 4 Bit Hitachi 44780 LCD I/F
    
    This Program Initializes Hitachi 44780 Based LCD in 4 Bit Mode
      and then writes a simple string to it.  The simulator was used
      to time delay values.  
    
    RC3:RC0 - LCD I/O D7:D4 (Pins 14:11)
    RC4     - LCD E Clocking Pin
    RC5     - LCD R/S Pin
    
    */
    __CONFIG(INTIO & WDTDIS & PWRTEN & MCLRDIS & UNPROTECT \
      & UNPROTECT & BORDIS & IESODIS & FCMDIS);
    
    int i, j, k, n;                 //  Use Global Variables for Debug
    
    //                         1234567890123456
    const char TopMessage[] = "  Hello world     ";
    const char BotMessage[] = " Hear me roar ";
    
    #define E  RC4                  //  Define the LCD Control Pins
    #define RS RC5
    
    const int Twentyms = 1250;      //  Declare a Constant for 20 ms Delay
    const int Fivems = 300;
    const int TwoHundredus = 10;
    
    LCDWrite(int LCDData, int RSValue)
    {
    LCDData=(((LCDData & 1)<<3) | ((LCDData & 2)<<1) | ((LCDData & 4)>>1) | ((LCDData & 8)>>3));
    
        PORTC = (LCDData >> 4) & 0x0F;  //  Get High 4 Bits for Output
        RS = RSValue;
        E = 1;  E = 0;              //  Toggle the High 4 Bits Out
    
        PORTC = LCDData & 0x0F;     //  Get Low 4 Bits for Output
        RS = RSValue;
        E = 1;  E = 0;              //  Toggle the Low 4 Bits Out
    
        if ((0 == (LCDData & 0xFC)) && (0 == RSValue))
            n = Fivems;             //  Set Delay Interval
        else
            n = TwoHundredus;
    
        for (k = 0; k < n; k++);    //  Delay for Character
    
    
    }  //  End LCDWrite
    
    main()
    {
    
        PORTC = 0;                  //  Start with Everything Low
        CMCON0 = 7;                 //  Turn off Comparators
        ANSEL = 0;                  //  Turn off ADC
        TRISC = 0;                  //  All of PORTC are Outputs
    
    //  Initialize LCD 
        j = Twentyms;
        for (i = 0; i < j; i++);    //  Wait for LCD to Power Up
    
        PORTC = 12;                  //  Start Initialization Process
        E = 1;  E = 0;              //  Send Reset Command
        j = Fivems;
        for (i = 0; i < j; i++);
    
        E = 1;  E = 0;              //  Repeat Reset Command
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        E = 1;  E = 0;              //  Repeat Reset Command Third Time
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        PORTC = 4;                  //  Initialize LCD 4 Bit Mode
        E = 1;  E = 0;
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        LCDWrite(0b00101000, 0);    //  LCD is 4 Bit I/F, 2 Line
    
        LCDWrite(0b00000001, 0);    //  Clear LCD 
    
        LCDWrite(0b00000110, 0);    //  Move Cursor After Each Character
    
        LCDWrite(0b00001110, 0);    //  Turn On LCD and Enable Cursor
    
        for (i = 0; TopMessage[i] != 0; i++)
            LCDWrite(TopMessage[i], 1);
    
        LCDWrite(0b11000000, 0);    //  Move Cursor to the Second Line
    
        for (i = 0; BotMessage[i] != 0; i++)
            LCDWrite(BotMessage[i], 1);
    
        while(1 == 1);              //  Finished
    
    }  //  End cLCD
    But we're still not getting anything.

    Please, feel free to treat me like a total novice, I am woeful with C!

    Thanks for any help you might be able to give,

    JT

  8. #8
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    Which compiler are you using?

    > for (i = 0; i < j; i++);
    Even fairly modest compilers will see this loop does nothing and may optimise it away.
    Check that you do get loops.
    Code:
    $ cat foo.c
    int main(int argc, char *argv[])
    {
      int   i;
      for( i = 0 ; i < 10 ; i++ );
      return 0;
    }
    $ gcc -c -S foo.c
    $ cat foo.s
    	.file	"foo.c"
    	.text
    .globl main
    	.type	main, @function
    main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$16, %esp
    	movl	$0, -4(%ebp)
    	jmp	.L2
    .L3:
    	addl	$1, -4(%ebp)
    .L2:
    	cmpl	$9, -4(%ebp)
    	jle	.L3
    	movl	$0, %eax
    	leave
    	ret
    Obviously, this is x86, but you should be able to get the general idea from looking at small samples of your compiler output.


    Read the manual -> http://ww1.microchip.com/downloads/e...202f-print.pdf
    This says it is clocked at 20Mhz, with 200 ns instruction cycle.

    > const int TwoHundredus = 10;
    In 200uS, expect to execute 1000 instructions.
    Look at the above - how many instructions per loop do you see?

    I think your counters are about two orders of magnitude too quick, and the show is over before the LCD has even woken up.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  9. #9
    Registered User
    Join Date
    May 2009
    Posts
    4,183
    Code:
    LCDData=(((LCDData & 1)<<3) | ((LCDData & 2)<<1) | ((LCDData & 4)>>1) | ((LCDData & 8)>>3));
    Doing it all in one line MEANS you must re-write the code that checks for how long the time delay must be based on the instruction being sent. Also, the code above only swaps 4 bits; doing the change where you did it requires all 8 bits to be swapped correctly.

    The line below tests which time delay is to be used; it will need fixed using your non working solution.
    Code:
    if ((0 == (LCDData & 0xFC)) && (0 == RSValue))
    Tim S.

    Salem raises a good question what is the clock speed you are using.
    Note: On this size PIC, Clock speed/4 normally gives the instruction cycle speed.
    I would guess 8MHz Internal Clock with 2MHz or 0.5 us instruction cycle time.
    The manual says defaults to 4MHz Internal Clock that means 1MHz/1us instruction cycle.
    Per section 3.5.4 on about page 27.
    Last edited by stahta01; 12-01-2010 at 01:05 PM. Reason: 4MHZ clock is internal default

  10. #10
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    More reliable way to do delay in MCU is to start a timer, and check for interrupt bit (you don't actually need an interrupt service routine - just check the bit manually).

    If you don't have a spare timer (it doesn't look like you are using any), you can also do inline assembly. A bunch of nop's looped.

    If you really want to do empty for loop, you should at least mark the variable volatile. That's a hacky use of the volatile keyword, but I believe it does work by the standard.

  11. #11
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    I think you have a problem with the following line. It looks like it throws away the top 4 bits:

    Code:
    LCDData=(((LCDData & 1)<<3) | ((LCDData & 2)<<1) | ((LCDData & 4)>>1) | ((LCDData & 8)>>3));
    Try:
    Code:
    LCDData=(LCDData & 0xF0) | (((LCDData & 1)<<3) | ((LCDData & 2)<<1) | ((LCDData & 4)>>1) | ((LCDData & 8)>>3));
    Also, just a sanity check that your E and RS are tied to the right pins and your edge trigger is correct (falling edge). Also, is your edge trigger too fast for the LCD to pickup? Do you maybe need a small delay between E = 1 and E = 0?

  12. #12
    Deleted Account
    Join Date
    Nov 2010
    Posts
    16

    Thanks

    Thanks for all the input guys. Unfortunately, we've not had any luck on this end in getting the altered connections to work. At this point, we've decided to admit defeat and just cut the tracks on the PCB and fix it by soldering in some extra wires.

    Sorry to be a pain, but do you think any of you might be able to help with another issue we're having?

    Basically, we want to define an integer called "noofsteps" that is incremented when a tiltswitch connection is made.

    We can probably work out how to get that to work:

    I'm guessing something along the lines of:
    Code:
    IF (TILT_SWITCH_INPUT==1)
    {
    noofsteps++;
    }
    However, we're having nightmare trying to do the simple task of printing the value of this integer to the screen.

    We've tried

    Code:
    int noofsteps = 35;
    
    char TopMessage[]
    char BotMessage[] = "  Hear me roar  "
    sprintf(TopMessage, "%d", noofsteps);
    But depending on the combination of "'s, ;s and )s we just get %d displayed on the screen, or the compiler fails to build as we're changing variable type from integer to character.

    If I explain the general functionality of the program, it might help:

    The program is to be a Pedometer that counts the number of steps that the user makes. It will display this, the distance travelled, the calories burned, and also the temperature of the user (read in through a thermistor through an ADC) in a series of display screens that the user can cycle through by pressing a button.

    This is how we thought about doing the program:

    Code:
    int noofsteps = 0     // This variable, "noofsteps" is a running count of the number of steps that the user has taken. It is to be incremented every time a tiltswitch makes a connection.
    int stridelength = 70 // This variable, "stridelength" is the stride length of the user. At the start of the program, the user can press the forward button or the backward button to increment or decrement "stridelength" by 5 cm.
    int distancetravelled = 0     // This variable will be used to calculate the distance that the user has travelled by multiplying "noofsteps" by "stridelength".
    int caloriesburned = 0     // This variable stores the running total calores burned by mutiplying "distancetravelled" by various factors (can't remember off the top of my head but say for the sake of argument 2000).
    int bodytemp = 0      // This variable will be written to using the ADC and will output the user's body temperature as they exercise.
    
    int screenmode = 0     // This variable controls which message is read from both of the arrays and displayed on the LCD screen.
    
    ARRAY_1[]     // This array will contain all of the messages that will be displayed on the top line of the LCD, one for each display screen.
    
    "You have walked"
    "You have travelled"
    "You have burned"
    "You are at"
    
    ARRAY_2[]     // This array will contain all of the messages that will be displayed on the bottom line of the LCD, one for each display screen.
    
    "noofsteps"
    "distancetravelled"
    "caloriesburned"
    "bodytemp"
    I don't know how to make it clear here that the screen should write the VALUE of the integer 'noofsteps', not just write the word "noofsteps". Is there some sort of identifier that goes in front of noofsteps to tell the program that it's a variable, and not just the word?

    I then have the functions that I had originally:

    Code:
    LCDWrite(int LCDData, int RSValue)
    {
    
        PORTC = (LCDData >> 4) & 0x0F;  //  Get High 4 Bits for Output
        RS = RSValue;
        E = 1;  E = 0;              //  Toggle the High 4 Bits Out
    
        PORTC = LCDData & 0x0F;     //  Get Low 4 Bits for Output
        RS = RSValue;
        E = 1;  E = 0;              //  Toggle the Low 4 Bits Out
    
        if ((0 == (LCDData & 0xFC)) && (0 == RSValue))
            n = Fivems;             //  Set Delay Interval
        else
            n = TwoHundredus;
    
        for (k = 0; k < n; k++);    //  Delay for Character
    
    }  //  End LCDWrite
    main()
    {
    
        PORTC = 0;                  //  Start with Everything Low
        CMCON0 = 7;                 //  Turn off Comparators
        ANSEL = 0;                  //  Turn off ADC
        TRISC = 0;                  //  All of PORTC are Outputs
    
    //  Initialize LCD 
        j = Twentyms;
        for (i = 0; i < j; i++);    //  Wait for LCD to Power Up
    
        PORTC = 3;                  //  Start Initialization Process
        E = 1;  E = 0;              //  Send Reset Command
        j = Fivems;
        for (i = 0; i < j; i++);
    
        E = 1;  E = 0;              //  Repeat Reset Command
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        E = 1;  E = 0;              //  Repeat Reset Command Third Time
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        PORTC = 2;                  //  Initialize LCD 4 Bit Mode
        E = 1;  E = 0;
        j = TwoHundredus;
        for (i = 0; i < j; i++);
    
        LCDWrite(0b00101000, 0);    //  LCD is 4 Bit I/F, 2 Line
    
        LCDWrite(0b00000001, 0);    //  Clear LCD 
    
        LCDWrite(0b00000110, 0);    //  Move Cursor After Each Character
    
        LCDWrite(0b00001110, 0);    //  Turn On LCD and Enable Cursor
    
        for (i = 0; TopMessage[i] != 0; i++)
            LCDWrite(TopMessage[i], 1);
    
        LCDWrite(0b11000000, 0);    //  Move Cursor to the Second Line
    
        for (i = 0; BotMessage[i] != 0; i++)
            LCDWrite(BotMessage[i], 1);
    
        while(1 == 1);              //  Finished
    
    }  //  End cLCD
    Basically, I want to pull a message out of one array, pull a stored number out of the other array and put them on the screen. I anticipated that we'd be able to do this using the integer "screenmode" which gets incremented when the user presses the forward button. I thought about trying:

    Code:
        for (i = 0; TopMessage[screenmode] != 0; screenmode++)
            LCDWrite(TopMessage[screenmode], 1);
    
        LCDWrite(0b11000000, 0);    //  Move Cursor to the Second Line
    
        for (i = 0; BotMessage[screenmode] != 0; screenmode)
            LCDWrite(BotMessage[screenmode], 1)
    but I'm pretty sure that's wrong. From my LIMITED understanding of the code, I think the above code takes the first character of the message " Hello world", converts the 'H' into a binary number, then sends that to the LCD. I'm suspicious that changing the 'i's to 'screenmode's in the above snippet will cripple it.

    Arg!

    Sorry if this sounds like gibberish, it's been a LONG and frustrating day. I don't have a point of help for this project at the university or any lectures on C programming, so the coursework basically involves teaching myself C. It's not as if it's even a programming related course (it's practically art and design!) so I'm having a hard time getting my noggin around it.

    If any other information would help you guys (a flow diagram, circuit, datasheets, etc.) please let me know and I'll try and get them up.

    Thanks for any help any of you may be able to give. Sorry if any of these questions are stupid, I have less than basic knowledge of C.

    JT

  13. #13
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    I think this should answer your first question. I'll take a longer look at the second part soon (if nobody else beats me to it).
    We've tried

    Code:

    int noofsteps = 35;

    char TopMessage[]
    char BotMessage[] = " Hear me roar "
    sprintf(TopMessage, "%d", noofsteps);

    But depending on the combination of "'s, ;s and )s we just get %d displayed on the screen, or the compiler fails to build as we're changing variable type from integer to character.
    If you don't specify a size for TopMessage, and don't provide an initializer, it defaults to size 1. Your also missing a couple semicolons. Try this:
    Code:
    char TopMessage[11];
    char BotMessage[] = "  Hear me roar  ";
    sprintf(TopMessage, "%d", noofsteps);
    Last edited by anduril462; 12-03-2010 at 01:42 PM. Reason: Removed stupid mistake

  14. #14
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Ignore the part about ssprintf. Don't know what I was thinking there. sprintf is the right function, hopefully giving TopMessage a size will fix it.

  15. #15
    Deleted Account
    Join Date
    Nov 2010
    Posts
    16

    Thanks Anduril!

    Thanks very much for your help. I find this kind of resource amazing, that people are actually willing to help out total strangers just as an act of kindness. It's great to see

    As I said, I am a complete novice when it comes to C, so let me get this straight:

    Code:
    char TopMessage[11];
    This is defining a character string named 'TopMessage' which has a length of eleven characters?

    Code:
    sprintf(TopMessage, "%d", noofsteps);
    The sprintf function writes the value of the integer named 'noofsteps' into the character variable named 'TopMessage'. I.e. take the integer value of 'noofsteps' (say, 14,351 steps for example) then write this to 'TopMessage' as a character string i.e. binary code for 1, binary code for 4, binary code for 3, binary code for 5, binary code for 1. Is that what's going on here?

    Sorry to ask such basic questions, and thank you to everybody above for taking the time to respond.

    JT

Popular pages Recent additions subscribe to a feed