PIC18F4550 + 16x2 LCD code query

Discussion in 'Embedded Systems and Microcontrollers' started by GrafZeppelin, Jun 1, 2016.

  1. GrafZeppelin

    Thread Starter New Member

    May 3, 2013
    11
    0
    I'm a beginner in MCU programming and also very eager to learn lots about it. Recently I have acquired the "Aptinex development board rev.6" which has many features already attached. I also bought a PIC18F4550 to plug it in the board. I've managed to understand how the basics of I/O operations work, but now I have stumbled on the LCD, which is attached to the development board. I found the code bellow in one of many tutorials pages all over google, and decided to try it out because it seemed simple enough.
    Code (Text):
    1. //File name 'LCD_Display_String_Original.c'
    2. #define _XTAL_FREQ 8000000
    3. #define RS LATD0
    4. #define EN LATD1
    5. #define D4 LATD2
    6. #define D5 LATD3
    7. #define D6 LATD4
    8. #define D7 LATD5
    9.  
    10. #include <xc.h>
    11. #include "lcd.h"
    12.  
    13. void delay_ms(unsigned int delay_value)
    14. {
    15. while(delay_value-- > 0)
    16. // While desired delay value is more than 0, decrement that value by 1 after each 10ms
    17. {
    18.     __delay_ms(1);
    19.     // Wait 10ms
    20. }                      
    21. }
    22.  
    23. int main()
    24. {
    25. TRISD = 0b00000000;
    26. Lcd_Init();
    27. while(1)
    28. {
    29.     Lcd_Clear();
    30.     Lcd_Set_Cursor(1,1);
    31.     Lcd_Write_String("Hello world!");
    32.     delay_ms(1000);
    33.  
    34. }
    35. }
    The question that I have is, could someone kindly please explain what is going on in the functionLcd_port (char a);, located in the lcd.h file bellow. Can't understand what conditions are tested and how they are true or false.
    Code (Text):
    1. //File name 'lcd.h'
    2. #include <xc.h>
    3. #include <stdio.h>
    4.  
    5. void Lcd_Port(char a)
    6. {
    7. if(a & 1)
    8.     D4 = 1;
    9. else
    10.     D4 = 0;
    11.  
    12. if(a & 2)
    13.     D5 = 1;
    14. else
    15.     D5 = 0;
    16.  
    17. if(a & 4)
    18.     D6 = 1;
    19. else
    20.     D6 = 0;
    21.  
    22. if(a & 8)
    23.     D7 = 1;
    24. else
    25.     D7 = 0;
    26. }
    27.  
    28. void Lcd_Cmd(char a)
    29. {
    30. RS = 0;             // => RS = 0
    31. Lcd_Port(a);
    32. EN  = 1;             // => E = 1
    33.     __delay_ms(4);
    34.     EN  = 0;             // => E = 0
    35. }
    36.  
    37. Lcd_Clear()
    38. {
    39. Lcd_Cmd(0);
    40. Lcd_Cmd(1);
    41. }
    42.  
    43. void Lcd_Set_Cursor(char a, char b)
    44. {
    45. char temp,z,y;
    46. if(a == 1)
    47. {
    48.   temp = 0x80 + b - 1;
    49.     z = temp>>4;
    50.     y = temp & 0x0F;
    51.     Lcd_Cmd(z);
    52.     Lcd_Cmd(y);
    53. }
    54. else if(a == 2)
    55. {
    56.     temp = 0xC0 + b - 1;
    57.     z = temp>>4;
    58.     y = temp & 0x0F;
    59.     Lcd_Cmd(z);
    60.     Lcd_Cmd(y);
    61. }
    62. }
    63.  
    64. void Lcd_Init()
    65. {
    66. Lcd_Port(0x00);
    67. __delay_ms(20);
    68. Lcd_Cmd(0x03);
    69. __delay_ms(5);
    70. Lcd_Cmd(0x03);
    71. __delay_ms(11);
    72. Lcd_Cmd(0x03);
    73.  
    74. Lcd_Cmd(0x02);
    75. Lcd_Cmd(0x02);
    76. Lcd_Cmd(0x08);
    77. Lcd_Cmd(0x00);
    78. Lcd_Cmd(0x0C);
    79. Lcd_Cmd(0x00);
    80. Lcd_Cmd(0x06);
    81. }
    82.  
    83. void Lcd_Write_Char(char a)
    84. {
    85. char temp,y;
    86. temp = a&0x0F;
    87. y = a&0xF0;
    88. RS = 1;             // => RS = 1
    89. Lcd_Port(y>>4);             //Data transfer
    90. EN = 1;
    91. __delay_us(40);
    92. EN = 0;
    93. Lcd_Port(temp);
    94. EN = 1;
    95. __delay_us(40);
    96. EN = 0;
    97. }
    98.  
    99. void Lcd_Write_String(char *a)
    100. {
    101. int i;
    102. for(i=0;a[i]!='\0';i++)
    103.    Lcd_Write_Char(a[i]);
    104. }
    105.  
    106. void Lcd_Shift_Right()
    107. {
    108. Lcd_Cmd(0x01);
    109. Lcd_Cmd(0x0C);
    110. }
    111.  
    112. void Lcd_Shift_Left()
    113. {
    114. Lcd_Cmd(0x01);
    115. Lcd_Cmd(0x08);
    116. }
    As mentioned before, the code works perfectly fine. I just really need someone to explain how that part works. Any help will be highly appreciated!
     
  2. dannyf

    Well-Known Member

    Sep 13, 2015
    1,771
    359
    It takes the lower four bits and output it in d3..d0.

    Since d3..d0 are specified by the user, this approach allows arbitrary arrangement between mcu dataa pins and LCDs data pins.

    I use an identical sgeme in my code as well.


    Also, it toggles direction register to output 1 and 0.
     
    GrafZeppelin likes this.
  3. GrafZeppelin

    Thread Starter New Member

    May 3, 2013
    11
    0
    Thanks for the answer dannyf ! I think I almost got it, just need a bit more clarity. So lets say:
    1. Lcd_Port() receives a value of 'a' = '0x03' from the Lcd_Cmd() function, binary 0b0011
    2. Decimal '1' is 0b0001
    3. After the logical AND operation the result is 0b0001, which makes the condition TRUE, because it is more than zero, thus setting the corresponding pin D4 high.
    And in this case, if 'a' was 0b1100, the condition would be false then, because 0b1100 AND 0b0001 would produce 0b0000, thus setting the pin D4 low. Is this the correct way to see the Lcd_Port() function?
     
  4. dannyf

    Well-Known Member

    Sep 13, 2015
    1,771
    359
    Yes. Effectively, the lowest four bits of lcd_port()'s input are being mapped to D7..D4. The beauty of this approach is that it allows you to define D7..D4 arbitrarily, even on different ports, greatly simplifying pcb layout.
     
    GrafZeppelin likes this.
  5. GrafZeppelin

    Thread Starter New Member

    May 3, 2013
    11
    0
    @ dannyf, thanks again mate! Couple of more questions (apologies for being such a newbie). In my understanding, and please correct me if I'm wrong, when working with the LCD in 4-bit mode, the data is first sent bit by bit (Bit8 > Bit7 > Bit6 > Bit5) or in our case (0>0>1>1) in 4-bit higher nibble (D7-MSB,D6,D5,D4-LSB) while masking (ignoring) the lower 4-bit nibble (D3-MSB, D2, D1, D0-LSB), and then sending the lower 4-bit nibble while masking the higher 4-bit nibble. What confuses me, is how are we masking those nibbles exactly? How are we influencing the bits that are not even connected to the MCU (D3,D2,D1 and D0). Also, I am not so sure if I'm right about the bits 8-4 and 5-1 being MSB and LSB, respectively.
    I hope my questions are clear. Could you please help me with this?
     
  6. dannyf

    Well-Known Member

    Sep 13, 2015
    1,771
    359
    A byte needs to be sent twice, the highest 4 bits and then the lowest 4 bits.

    You can take a look at Lcd_Write_Char() to see how it is done (quite inefficiently I may add).
     
    GrafZeppelin likes this.
  7. GrafZeppelin

    Thread Starter New Member

    May 3, 2013
    11
    0
    Understood. Could you suggest a more efficient way maybe?
     
  8. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    2,907
    2,164
    I don't see much use in 'masking' a into two temp upper/lower nibble variables before sending the correct nibble to Lcd_Port().
    The original XLCD code with direct 8 bit port writes needed to mask off the 4 bits (LOWER in this case) not used for LCD data from modification by the routines with something like this.

    Code (Text):
    1.  
    2. #define UPPER
    3. /* DATA_PORT defines the port to which the LCD data lines are connected */
    4. #define DATA_PORT PORTH
    5. ...
    6. data = some_char;
    7. ...
    8. DATA_PORT &= 0x0f; // set the upper bits to zeros and leave the lower ones alone
    9. DATA_PORT |= data & 0xf0; // OR in upper data
    10. ...
    11. DATA_PORT &= 0x0f;
    12. DATA_PORT |= ((data << 4)&0xf0); // OR in lower data after nibble shift
    13.  
    It looks like someone just left in unneeded code with the rewrite to indirect port bits in Lcd_Port();
     
    GrafZeppelin likes this.
  9. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    Since the Lcd_Port() function is bit testing bits 3 through 0 of the data, there's really no need to mask off the unused bits before calling the function. You just need to make sure the correct nibble is in the b3..b0 bit position. Also, if you need a delay to stretch the 'E' pulse width, 1-uS should be sufficient (ref: HD44780 datasheet).

    Good luck on your project. Cheerful regards, Mike
    Code (Text):
    1.  
    2. void Lcd_Write_Char(char a)
    3. { RS = 1;           // RS = 1 (data)
    4.   Lcd_Port(a>>4);   // send hi nibble
    5.   EN = 1;           // strobe 'E'
    6.   EN = 0;           //  "
    7.   Lcd_Port(a);      // send lo nibble
    8.   EN = 1;           // strobe 'E'
    9.   EN = 0;           //  "
    10. }
    11.  
     
    GrafZeppelin likes this.
  10. GrafZeppelin

    Thread Starter New Member

    May 3, 2013
    11
    0
    Thanks for the input. I was just about to ask something, but got an "Eureka!" moment just as I was writing the question. Thanks a lot!
     
  11. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    2,907
    2,164
    Another thing I might do is to convert the bit tests in Lcd_Port() to a direct assignment expression with the ternary operator instead of an if statement. The code generated by a good compiler will likely be the same but the C syntax is cleaner but maybe a bit more human-readable than repeated multi-line expressions.
    Code (Text):
    1.  
    2. // convert each if statement of this form
    3.     if (a & 1)
    4.         D4 = 1;
    5.     else
    6.         D4 = 0;
    7.  
    8.     // to
    9.  
    10.     D4 = (a & 1) ? 1 : 0;
    11.  
    12.  
     
    GrafZeppelin likes this.
  12. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    I believe the following is smaller and faster, as well as being isochronous...
    Code (Text):
    1.  
    2.     D4 = 0; if(a & 1) D4 = 1;    //
    3.  
     
    GrafZeppelin, nsaspook and jpanhalt like this.
  13. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    And I believe the following is smaller and faster, as well as being isochronous...

    Code (Text):
    1.    D4 = (a & 1);
     
  14. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    2,907
    2,164
    Let's see what XC8 'free' does with each.:)

    Code (Text):
    1.  
    2. 16:               if (a & 1)
    3. 7FD4  A001     BTFSS a, 0, ACCESS
    4. 7FD6  D002     BRA 0x7FDC
    5. 17:                   D4 = 1;
    6. 7FD8  848C     BSF LATD, 2, ACCESS
    7. 7FDA  D001     BRA 0x7FDE
    8. 18:               else
    9. 19:                   D4 = 0;
    10. 7FDC  948C     BCF LATD, 2, ACCESS
    11. 20:               // to
    12. 21:               D4 = (a & 1) ? 1 : 0;
    13. 7FDE  A001     BTFSS a, 0, ACCESS
    14. 7FE0  D002     BRA 0x7FE6
    15. 7FE2  848C     BSF LATD, 2, ACCESS
    16. 7FE4  D001     BRA 0x7FE8
    17. 7FE6  948C     BCF LATD, 2, ACCESS
    18. 22:               //
    19. 23:               D4 = 0; if(a & 1) D4 = 1;    //
    20. 7FE8  948C     BCF LATD, 2, ACCESS
    21. 7FEA  B001     BTFSC a, 0, ACCESS
    22. 7FEC  848C     BSF LATD, 2, ACCESS
    23. 24:               //
    24. 25:               D4 = (a & 1);
    25. 7FEE  A001     BTFSS a, 0, ACCESS
    26. 7FF0  D002     BRA 0x7FF6
    27. 7FF2  848C     BSF LATD, 2, ACCESS
    28. 7FF4  D001     BRA 0x7FF8
    29. 7FF6  948C     BCF LATD, 2, ACCESS
    30. 26:               //
    31.  
     
  15. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    Thanks, NSA'... I looked at the generated code for each method, too, instead of just guessing (grin)...
     
  16. nsaspook

    AAC Fanatic!

    Aug 27, 2009
    2,907
    2,164
    If there are no side-effects (like in a self-clocked data signal) from the extra bit flip edges with 1's in the 'a' data stream it works.
     
  17. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    Yeah, it really works, on real hardware (lol).

    Slightly off-topic... how about an 8-bit mode LCD backpack with a 2 pin interface where the shift register <clock> and <data> signals as well as the LCD 'RS' signal are derived from a single pin?

    K8LH 2-Pin Backpacks.png
    Code (Text):
    1.   /*                                                                *
    2.    *  K8LH 2-Pin 8-Bit 74HC595 LCD low level driver                 *
    3.    *                                                                */
    4.    void PutLCD(char work)       // write byte to 74HC595 & LCD
    5.    { char bitctr = 8;           //
    6.      do                         // load 74HC595 shift register
    7.      { if(!(work&128) clk = 0;  // if a '0', set clk = 0
    8.        _delay_us(9);            // charge or drain cap (3*tau)
    9.        clk = 0; clk = 1;        // clock bit into shift register
    10.        work <<= 1;              // shift next bit into b7
    11.      } while(--bitctr);         // until all 8 bits clocked out
    12.      if(rs == 0) clk = 0;       // make clk pin = rs flag
    13.      lat = 1; lat = 0;          // latch 595, pulse lcd 'E' pin
    14.      clk = 1;                   // leave clk pin high
    15.    }
    16.  
    It's a bit slow (about ~100-uS per character) but that's because the RC time constant was selected to allow operation on a PIC with a clock as low as 4-MHz. If you need a faster interface, you could get rid of the RC and use a 3 pin interface.

    rc timing.png
     
    Last edited: Jun 3, 2016
Loading...