Wednesday, August 24, 2016

Alphanumeric LCD module Interfacing 4 and 8 bit mode library for AVR ATMega328

OnLCDLib - LCD Library written in C for AVR Microcontrollers

I have written a library (OnLCDLib) for interfacing Standard Alpha Numeric LCD Modules with AVR microcontrollers, for those who don’t use Arduino for different reasons. This type of LCD modules come in many different sizes and I’ve tried to make the library work with many of them, such as 16x1, 16x2, 16x4, 20x4, 20x2, 32x2, 40x2 LCD displays.
Next I will give an example of how to connect ATmega328 microcontroller with an 16x2 LCD module. 

Hardware interfacing ATmega328 microcontroller with an 16x2 LCD module with PWM brightness control

This LCD modules can be connected in 4 bit mode or 8 bit mode. Using 4 bit mode is recommended because it uses less pins but the code is a bit more complex. In the following example I use 4 bit mode.

Hardware interfacing AVR ATmega328 with an 16x2 LCD module with PWM brightness control


LCD pins:

1 – VSS: power supply (GND)

2 – VDD: power supply (+5v)

3 – Vo or VEE on other schematics: contrast adjust. I have used a 5k variable resistor for adjusting the contrast. If you power up the LCD and don’t see anything, first check the contrast. The characters could be displayed but you can’t see them.

4 – RS (Register Select): set this LOW for sending instructions to LCD or HIGH for sending data (characters).

5 – R/W (Read/Write): set this to LOW to write to LCD module or HIGH to read from LCD.

6 – E (Enable Signal): signals to LCD that it can process the commands.

7 – 14 – DB0…7 (Data Bus Lines): used for sending binary data.

15 – A (Anode): power supply (+5v) for LCD backlight.

16 – K (Cathode): power supply (GND) for LCD backlight. If you don’t want to control the LCD backlight brightness using PWM, then connect this pin to ground through a 100 ohm resistor. Some LCD modules have a current limiting resistors some don’t. It doesn’t hurt if you put one.




Software interfacing AVR ATmega328 microcontroller with an 16x2 LCD module

If you want to use the library provided on this page, next I will show an example of how to implement it in your code.

First you need to tell the library what pins did you chose to connect the LCD and what type of LCD module do you use. This is easily done by opening the library and in the setup section do the following modifications:


// MCU IO Setup
#define LCD_DATA_DDR                              DDRC     // Data bus (DB0 to DB7 on LCD)
This is the data direction register on microcontroller where you have connected LCD data bus lines. DDRC is port C, DDRB is port B, etc.

 #define LCD_DATA_PORT                           PORTC
Set the port.

#define LCD_DATA_PIN                                PINC
If port is C set this PINC, if it is B set it PINB and so on.

#define LCD_DATA_START_PIN                 2
For flexibility you can connect DB4 on any pin on the MCU other than 0. In the above schematic pins PC0 and PC1 are used for something else, so DB4 through DB7 are connected starting from PC2. So the starting pin is 2.

#define LCD_RS_CONTROL_DDR               DDRD
#define LCD_RW_CONTROL_DDR              DDRD
#define LCD_E_CONTROL_DDR                  DDRD
Data direction register for control pins RS, RW, and E.

#define LCD_RS_CONTROL_PORT             PORTD
#define LCD_RW_CONTROL_PORT            PORTD
#define LCD_E_CONTROL_PORT                PORTD
Ports where control pins RS, RW, and E are connected.

#define LCD_RS_PIN                                      PD0
#define LCD_RW_PIN                                     PD1
#define LCD_E_PIN                                         PD2
Pin numbers where RS, RW and E are connected.

// Include the library
#include "OnLCDLib.h"

int main(void){
     // Initiate the library.
     // "cursorStyle" can be: LCD_CURSOR_BLINK, LCD_CURSOR_ULINE, LCD_CURSOR_NONE
     LCDSetup(cursorStyle);

    // Send a string to the LCD:
    "LCDWriteString(“Text on LCD”);
    // Display a string on a certain location. E.g. character 3, line 2
    LCDWriteStringXY(3, 2, “Text on LCD”);

    // For displaying numbers you need a different function: LCDWriteInt(number, nr_of_digits). 
    //E.g. display  number 120. First parameter is the number and second in number of digits 3 in this example.
    LCDWriteInt(120, 3);
    // To display a number anywhere on LCD use this function LCDWriteIntXY(x, y, number, nr_of_digits)
   LCDWriteIntXY(5, 1, 120, 3); // on line 1 starting from position 5 display number 120 which has 3 digits
   while(1){}
}

FLOATS (We all float down here). To display float numbers on LDC find

#define DISPLAY_FLOATS                 0 // default [0]
and make it equal to 1 then uncomment MATH_LIB = -lm in your Make file to reduce the loaded math library to half.
Disregard the code example that I left in one of the comments bellow, because it has some errors. Use the function in the updated library instead.

- Display a float number
LCDWriteFloat(float_nr, nr_of_digits);
float_nr: positive or negative float number
nr_of_digits: how many decimal places (after the point) the number will have

E.g:
float nr = 0.025;
LCDWriteFloat(nr, 3);
will display "0.025".
LCDWriteFloat(temperature, 4);
will display "0.0250"

 
 - Send a float number to a specific location:
LCDWriteFloatXY(x, y, float_nr, nr_of_digits);

Other functions you might use:

- Move cursor to a specific location:
                "LCDGotoXY(character_position, row_number)"
- Clear display and DDRAM:
                "LCDClear()"
- Go to character 1, line 1
                "LCDHome()"
- Scroll a string from right to left. Needs to be uncommented in setup section:
                "LCDScrollText(aString)"

Controlling the LCD backlight brightness using PWM:

First do the following modifications in the setup section if you use other AVR microcontroller
#define LCD_BACKLIGHT // Uncomment this line to activate the function
#define LCD_PWM_DDR                                       DDRD
DDR where OC0B is located in case you use other AVR microcontroller
#define LCD_PWM_PORT                                      PORTD
The port where OC0B is located
                                                 
#define LCD_PWM_PIN                                          PD5
The pin where OC0B is located and that is connected to the base of a transistor.  

To dim the LCD backlight or turn it off use this function:
LCDBacklightPWM(brightness)
"brightness" can be between 0 - 100.
"0" will turn off the backlight and the LCD without clearing DDRAM thus saving power.
"100" will turn the backlight fully on and stop PWM.


Custom LCD digits - 3 characters wide digits:

3 characters double height custom LCD digits

The function used for displaying this digits is: LCDWriteIntBig3Chars(int16_t number, int8_t nrOfDigits)
- number is the number to be displayed.
- nrOfDigits how many digits the number has. Say 20 has two digits, 104 has 3 digits. If the number is let's say 1 or 5 or 8 and you pass the nrOfDigits to be 2 then the displayed number will be padded with 1 zero.

To use this function first open the OnLCDLib header file and uncomment these two lines:
#define CUSTOM_CHARS
#define BIG_DIGITS


Then download double_height_3_characters_round_digits_v1.0.h
and copy the content of "static const uint8_t LCD_custom_chars[]" array over the one in the library.


Here is an example of how to use the function. Tested on 16x2 LCD:
#include <avr/io.h>;
#include <util/delay.h>;
#include "OnLCDLib.h"

int main(void){
    uint8_t seconds = 0;
    uint8_t minutes = 0;
 
    LCDSetup(LCD_CURSOR_NONE);
 
    while(1){
        if(seconds > 59){
            minutes += 1;
            seconds = 0;
   
            if(minutes > 59) minutes = 0;
        }
  
        LCDHome();

        LCDWriteIntBig3Chars(minutes, 2);
        LCDWriteBigSeparator();
        LCDWriteIntBig3Chars(seconds, 2);
  
        seconds += 1;
        _delay_ms(1000);
    }
}


Custom LCD digits - sharp digits:


Same as above but replace LCDWriteIntBig3Chars with LCDWriteIntBig.
And instead of  double_height_3_characters_round_digits_v1.0.h use double_height_sharp_digits_v1.0.h

Download LCD Library:

Version 1.6:
- fixed some known bugs
- fixed the function that displays floats that couldn't display 0s after the point 

Update (version 1.5):
- new function for displaying floats
OnLCDLib v1.5

Download special LCD characters:

double_height_sharp_digits_v1.0.h
double_height_3_characters_round_digits_v1.0.h


Other resources:
If you want to learn more in depth about LCDs and their protocols used to communicate with a microcontroller I've provided some links bellow.

NewbieHack - Microcontroller Tutorial - A Beginners Guide (an youtube playlist about avr microcontrollers but from video 19 he talks about interfacing LCD with mcu and describes the protocol used)
https://www.8051projects.net/lcd-interfacing/introduction.php
http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html
http://web.alfredstate.edu/weimandn/lcd/lcd_initialization/lcd_initialization_index.html

5 comments:

  1. Give an example of a function please!
    LCDWriteIntBig(int16_t number, int8_t nrOfDigits);
    LCDWriteIntBig3Chars(int16_t number, int8_t nrOfDigits);
    LCDWriteBigSeparator(void);

    ReplyDelete
    Replies
    1. Sure. Sorry for the delay; i noticed it was a problem with the function when number 1 was displayed so i will update the library in a few hours.

      First you need to open the OnLCDLib header file and uncomment these two lines
      #define CUSTOM_CHARS
      #define BIG_DIGITS

      Then download double_height_3_characters_round_digits_v1.0.h
      and copy the content of static const uint8_t LCD_custom_chars[] array over the one in the library.

      Now the function LCDWriteIntBig3Chars() can be used. I've made an example that imitates a clock and i've tested it on a 16 characters 2 lines LCD.

      int main(void){
      uint8_t seconds = 0;
      uint8_t minutes = 0;

      LCDSetup(LCD_CURSOR_NONE);

      while(1){
      if(seconds > 59){
      minutes += 1;
      seconds = 0;

      if(minutes > 59) minutes = 0;
      }

      LCDHome();
      LCDWriteIntBig3Chars(minutes, 2); // 2 represents the number of digits to be displayed. If minutes is 1 digit only, then it will be padded with 1 zero
      LCDWriteBigSeparator();
      LCDWriteIntBig3Chars(seconds, 2);

      seconds += 1;
      _delay_ms(1000);
      }
      }

      I will update this post with an example for the other function but the principle is the same.

      Delete
    2. Thank you!

      Delete
  2. Replies
    1. I will see if I can implement this in the library, but for now here is a solution.

      // Extract integer part and fractional part and put them in two integer vars

      float aFloatNumber;
      uint16_t integer_part_1 = aFloatNumber;
      float fractional_part = aFloatNumber - integer_part;
      uint16_t integer_part_2 = (int)(fractional_part * 100);

      Now you can write it on LCD as follows
      LCDWriteInt(integer_part_1, 3);
      LCDWriteString(".");
      LCDWriteInt(integer_part_2, 3);

      I didn't have the time to test it but it should work.

      Delete