Maker Pro
Maker Pro

PIC18F4550 + 16x2 LCD code query

GiedriusStasiulis

Jun 1, 2016
5
Joined
Jun 1, 2016
Messages
5
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. The code works without problems.

Code:
//File name 'LCD_Display_String_Original.c'
#define _XTAL_FREQ 8000000
#define RS LATD0
#define EN LATD1
#define D4 LATD2
#define D5 LATD3
#define D6 LATD4
#define D7 LATD5

#include <xc.h>
#include "lcd.h"

void delay_ms(unsigned int delay_value)
{
while(delay_value-- > 0)
// While desired delay value is more than 0, decrement that value by 1 after each 10ms
{
    __delay_ms(1);
    // Wait 10ms
}                       
}

int main()
{
TRISD = 0b00000000;
Lcd_Init();
while(1)
{
    Lcd_Clear();
    Lcd_Set_Cursor(1,1);
    Lcd_Write_String("Hello world!");
    delay_ms(1000);

}
}

The question that I have is, could someone kindly please explain what is going on in the function Lcd_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:
//File name 'lcd.h'
#include <xc.h>
#include <stdio.h>

void Lcd_Port(char a)
{
if(a & 1)
    D4 = 1;
else
    D4 = 0;

if(a & 2)
    D5 = 1;
else
    D5 = 0;

if(a & 4)
    D6 = 1;
else
    D6 = 0;

if(a & 8)
    D7 = 1;
else
    D7 = 0;
}

void Lcd_Cmd(char a)
{
RS = 0;             // => RS = 0
Lcd_Port(a);
EN  = 1;             // => E = 1
    __delay_ms(4);
    EN  = 0;             // => E = 0
}

Lcd_Clear()
{
Lcd_Cmd(0);
Lcd_Cmd(1);
}

void Lcd_Set_Cursor(char a, char b)
{
char temp,z,y;
if(a == 1)
{
  temp = 0x80 + b - 1;
    z = temp>>4;
    y = temp & 0x0F;
    Lcd_Cmd(z);
    Lcd_Cmd(y);
}
else if(a == 2)
{
    temp = 0xC0 + b - 1;
    z = temp>>4;
    y = temp & 0x0F;
    Lcd_Cmd(z);
    Lcd_Cmd(y);
}
}

void Lcd_Init()
{
Lcd_Port(0x00);
__delay_ms(20);
Lcd_Cmd(0x03);
__delay_ms(5);
Lcd_Cmd(0x03);
__delay_ms(11);
Lcd_Cmd(0x03);

Lcd_Cmd(0x02);
Lcd_Cmd(0x02);
Lcd_Cmd(0x08);
Lcd_Cmd(0x00);
Lcd_Cmd(0x0C);
Lcd_Cmd(0x00);
Lcd_Cmd(0x06);
}

void Lcd_Write_Char(char a)
{
char temp,y;
temp = a&0x0F;
y = a&0xF0;
RS = 1;             // => RS = 1
Lcd_Port(y>>4);             //Data transfer
EN = 1;
__delay_us(40);
EN = 0;
Lcd_Port(temp);
EN = 1;
__delay_us(40);
EN = 0;
}

void Lcd_Write_String(char *a)
{
int i;
for(i=0;a[i]!='\0';i++)
   Lcd_Write_Char(a[i]);
}

void Lcd_Shift_Right()
{
Lcd_Cmd(0x01);
Lcd_Cmd(0x0C);
}

void Lcd_Shift_Left()
{
Lcd_Cmd(0x01);
Lcd_Cmd(0x08);
}

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!
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,722
Joined
Nov 17, 2011
Messages
13,722
I really like uncommented code. It is so refreshing to have something to think really hard about ;)

Obviously the LCD's data port is connected to port pins D4...D7. To output the character 'a' on D4...D7 it is analyzed for the bit values at positions bi0...bit3.
Let's look at this set of instructions:
Code:
if(a & 1)
    D4 = 1;
else
    D4 = 0;
if (a & 1) is a logical AND of 'a' and the number 00000001b (b for binary, for simplicity only 8 position used).
A character 'a' is represented (typically) as an 8 bit code a7 a6 a5 a4 a3 a2 a1 a0.
The logical AND gives (a7 a6 a5 a4 a3 a2 a1 a0) & (00000001) = (0 0 0 0 0 0 0 a0)
The value of a0 is then used to either set or reset pin D4. Note that any value >0 is regarded as logically true.

The next set of instruction:
Code:
if(a & 2)
    D5 = 1;
else
    D5 = 0;
is equivalent to:
(a7 a6 a5 a4 a3 a2 a1 a0) & (00000010) = (0 0 0 0 0 0 a1 0)´
and D5 is set or reset accordingly.

And so on.

I could imagine a more elegant way of performing this, but as it works and probably is much faster than accessingh the LCD anyway, there's no need to optimize the code.
 

GiedriusStasiulis

Jun 1, 2016
5
Joined
Jun 1, 2016
Messages
5
@Herald Kapp, thanks mate for a great explanation! 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, which in binary is 0b0011
  2. Decimal 1 is 0b0001
  3. After the logical AND operation the result is 0b0001, which makes the condition TRUE, because it is not equal to 0, thus setting the corresponding pin D4 high. Then it goes to evaluate the next pin and so on.
  4. After all pins are evaluated, the output is, in this case D4(LATD2) = 1, D5(LATD3) = 1, D6(LATD4) = 0, D7(LATD5) = 0, thus 0b0011, which is the same as the initial command 0x03 (0b0011)
  5. 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?
 
Top