Microcontroller - A Beginners Guide - Displaying Numbers on the LCD Display

Microcontroller - A Beginners Guide - Displaying Numbers on the LCD Display

So, we know how to put characters and even strings of information to the LCD. The most useful thing to display on the LCD would be a number. Now, I'm sure you are thinking, well, we could just pass a number in quotes, like this: "632" using the Send_A_String command (that we created in the last tutorial ! Well, sure, but what if it is a number from a sensor stored in a variable, or something like that! We actually do want the number in those quotes, but how we get it there is where the magic comes in... and it is REALLY simple.

If we want the numbers to be displayed at the same locations every time (and not just fill the entire screen with a list of numbers), we will need to move the cursor to the desired locations every time we want to send the number to the LCD. This can also be used to locate strings and characters.

The magic of moving the cursor to any position on the LCD display is in the 0x80 number. That number represents the first location on the display, at the top left corner. If you've been paying attention to the binay tutorial, you would know that 0x80 represents 0b10000000 in binary. The "1" is in the 8th place, leaving a full 7 digits left for something wonderful. 7 binary digits can hold 128 numbers (0-127).

As you already know, LCD screens are divided up into many columns and rows. For the LCD in the tutorial, there are 4 lines (rows) and 20 columns, hence the 20x4 that is usually shown in the description. Add them all up and you have 80 total positions on the LCD. Hmm... 80 is less than 128. This tells us that there may be LCDs out there that have more lines and columns, becuase there are that many possibilities using this command.

If you sent a command using Send_A_Command and inserted 0x80 to get processed by the LCD, the cursor position would appear at the top left. If you inserted 0x8A, then the cursor would be positioned 10 spaces right of the top left corner. It would actually be in the middle, since the LCD is 20 columns wide. But there is a catch! One would think that if this number kept increasing, the cursor would wrap to the next line, but it doesn't. The cursor wraps to the third line instead. This is due to how it uses the 128 reserved spaces. Because of the various LCD displays out there with different line and column counts, the first line will start from "0" and fill lines 1 and three up to number 63, which is 1 minum half of 128. Now it is starting to come together, isn't it. The 2nd and fourth line start from 64 and go on towards the end. So half of the 128 is reserved for the first and third line, and the other half is reserved for the second and fourth line. That way, all LCD will start from 0 on the first line, and start from 64 on the second line, even on 2 line LCDs.

This makes great sense, because if you have an LCD with only two lines that were 64 columns long (using all of the 128 reserved positions), then the LCD would still be compatible with an LCD with 2 lines but with only 20 or so columns. You could expect to see the same information on the LCD, at least as far as the selected LCD.

However, trying to put the cursor in a desired location using a number within 0 to 64 and 64 to 128 not really knowing which line you may be affecting is as easy as it could be. We can create a new routine that allows you to simply state the x and y coordinate locations would be much easier. If you wanted to display something on the 4th line and 6 spaces from the start of the line could be written link GotoLocation(6, 4); That's easier than stating the location in this form: Send_A_Command(90); 90 (in decimal, not hex) just happens to be that position on a 20x4 LCD display. 90 is derived this way: 64 is the 2nd line. The 2nd line wraps to the 4th line by adding 20 to 64 (20+64 = 84). knowing that you are on the 4th line, just add 6 and you have the numer 90. But forget all that, because we will implement the easy way.

Creating a routine that you can just enter the X and Y component is pretty easy. First, you will need to create a variable that stores all of the initial column numbers. In the case of the 20x4 LCD, the numbers are 0 for the first line, 64 for the second line, 20 for the third line and 84 for the forth line. The variable declaration might look like this:

char firstColumnPositionsForMrLCD[4] = {0, 64, 20, 84};

I am using the "char" data type because the numbers are not that big. The "char" data type can range from 0 to 128. The variable name "firstColumnPositionsForMrLCD" is LONG. This is actually intentional. First, for best programming practice and readability, and second, so later on, the possibility for this name to be used for another use would be very unlikely. Remember, we will have this varialble in another file as a library and we wont see it (while working in our main .c file), but it will be present. The numbers between the squiggly brackets are the initial positions for each line. This way, all we need to do is put the "Y" component in this array and it will tell us the initial position for that line. Then we add the "X" component to that number and we will have the correct position on the LCD.

The array could also be a bit more systematic and could look like this:

#define numberOfColumns 20;
char firstColumnPositionsForMrLCD[4] = {0, 64, numberOfColumns , 64 + numberOfColumns };

This way, all you would need to do is specify the LCD's number of columns and you would not need to go to that obfuscatious data sheet (or should I say data BOOK). So, armed with these glorious instructions and new knowledge that you thought you would never know, how might the actual GotoLocation routine look like? Well, here you go:

void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}

Notice the 0x80... That still needs to reside in the Send_A_Command since it is the command that represents the first position. then the array calls up the correct number and then the "X" is added. You may have also noticed the y-1 and x-1. This is added because the array is indexed from 0 and the screen positions are also indexed from 0 and not 1. The small subrraction is just to be able to call line #1 "1" instead of "0" and line #2 "2" instead of "1", and so on. For the "X", i like calling the first column "1" not "0". It's like being in Paris, all of the first floors are considered the ground, or "0" floor and the floor just above is considered the first floor. Not that it is wrong, but I'm just not accustomed to that. If you like your first column and first line to be "0", simple remove the subtraction and you are set.

Now, for the desert. Making a numeric variable appear on the LCD screen. The secret to doing this is to have a function that converts a number (numeric variable) into a string. We already know ow to put a string to the LCD, now all we need is to have a string that represents the number in a variable. Fortunately, there is a function that is already made for this. It's called itoaand dtostrf. The "i" in itoa means integer. The "d" in dtostrf means double. You already know what an integer is. If not, its just a number that doesn't have any decimal places. You might have guessed that double is a number that can contain decimal places. Double refers to double precision. Both of these functions convert a value to a string and they require the stdlib.h library to be included ( #include ). The following shows the proper usage:

itoa(integer value, string that will store the numbers, base);

dtostr(double precision value, width, precision, string that will store the numbers);

Value is either a direct value plugged into this place, or a variable to contains a value.

Width that is used with dtostrf is the number of characters in the number that includes the negative sign (-). For instance, if the number is -532.87, the width would be 7 including the negative sign and the decimal point.

Precision is how many numbers would be after the decimal point in the dtostrf usage.

Base is the maximum number of values per digit. For instance, 2 is for binary representation (2 possible values for each digit - 0 or 1); 10 is for the common human number system (10 possible values for each digit - 0, 1, 2, 3, 4, 5, 6, 7, 8, or 9); 16 is for hexadecimal where there are 16 possible values for each digit - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, or F. It should be understood that if the base is larger, then the alphabet will just get larger.

String is the variable that accepts alphanumeric characters such as a char array. After the function is executed, the string will have the number stored within the array. For instance, if an integer variable contains the number 643, then the string variable will contain "634".

This declaration would be somewhere in program.

char aNumberAsString[4];
int x = 432;

This would be where you needed to convert the number to the string:

itoa(x, aNumberAsString, 10);

This would be where you would want to display the number to the LCD:

Send_A_String(aNumberAsString);

Here is the entire program that pretty much has all of the routines that we need for the LCD:

#include
#include
#include

#define MrLCDsCrib PORTB
#define DataDir_MrLCDsCrib DDRB
#define MrLCDsControl PORTD
#define DataDir_MrLCDsControl DDRD
#define LightSwitch 5
#define ReadWrite 7
#define BiPolarMood 2

char firstColumnPositionsForMrLCD[4] = {0, 64, 20, 84};
void Check_IF_MrLCD_isBusy(void);
void Peek_A_Boo(void);
void Send_A_Command(unsigned char command);
void Send_A_Character(unsigned char character);
void Send_A_String(char *StringOfCharacters);
void GotoMrLCDsLocation(uint8_t x, uint8_t y);

int main(void)
{
DataDir_MrLCDsControl |= 1< _delay_ms(15);

Send_A_Command(0x01); //Clear Screen 0x01 = 00000001
_delay_ms(2);
Send_A_Command(0x38);
_delay_us(50);
Send_A_Command(0b00001110);
_delay_us(50);

char positionString[4];

while(1)
{
for(int y = 1; y<=4; y++)
{
for(int x = 1;x<=20;x++)
{

itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String("X = ");
Send_A_String(positionString);

itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String("Y = ");
Send_A_String(positionString);

GotoMrLCDsLocation(x, y);
Send_A_String("x");

_delay_ms(50);

GotoMrLCDsLocation(x, y);
Send_A_String(" ");

itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String(" ");
itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String(" ");
}
}
}
}

void Check_IF_MrLCD_isBusy()
{
DataDir_MrLCDsCrib = 0;
MrLCDsControl |= 1< MrLCDsControl &= ~1<
while (MrLCDsCrib >= 0x80)
{
Peek_A_Boo();
}

DataDir_MrLCDsCrib = 0xFF; //0xFF means 0b11111111
}

void Peek_A_Boo()
{
MrLCDsControl |= 1< asm volatile ("nop");
asm volatile ("nop");
MrLCDsControl &= ~1< }

void Send_A_Command(unsigned char command)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = command;
MrLCDsControl &= ~ ((1< Peek_A_Boo();
MrLCDsCrib = 0;
}

void Send_A_Character(unsigned char character)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = character;
MrLCDsControl &= ~ (1< MrLCDsControl |= 1< Peek_A_Boo();
MrLCDsCrib = 0;
}

void Send_A_String(char *StringOfCharacters)
{
while(*StringOfCharacters > 0)
{
Send_A_Character(*StringOfCharacters++);
}
}

void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}
Back to blog

Leave a comment

Please note, comments need to be approved before they are published.