16x2 LCD interfacing
- The LCD interfacing can be done by first instantiating a
lcd16x2_8data
object.
lcd16x2_8data
specification
// Used for LCD 16x2 8 bit dataline object initialization
typedef struct lcd16x2_8data{
// data_port and control_port are pointer of the port register
// For example, if we define PORTD as data pins, then data_port is initialized with &PORTD
volatile unsigned char* data_port; // Data port
volatile unsigned char* control_port; // Control wire port
unsigned char rs_bit; // Register select bit location (0 to 7) with respect to control wire port
unsigned char rw_bit; // Read Write bit location (0 to 7) with respect to control wire port
unsigned char enable_bit; // Enable bit location (0 to 7) with respect to control wire port
} lcd16x2_8data;
- This
struct
is defined in thelcd16x2.h
file. Astruct
approach was taken as it will be easier for transacting data when calling functions.
- The
data_port
variable is a pointer that points to the port address (such as address of PORTA, PORTB, etc). So, we can use the de-reference operator*
to access the PORT’s data value. In this I have used 8bit data mode, so all 8 bits of data_port will be used for data line interfacing the LCD.
- The
control_port
is also a pointer that points to the address of that PORT that acts as the control wire. Since only 3 bits of control wires are required, we users_bit
,rw_bit
,enable_bit
to identify which locations the control wires are connected.
- The struct assumes that the tri-state buffers for the
data_port
andcontrol_port
are rightly initialised.
Available functions on lcd16x2_8data
void init_display(lcd16x2_8data* display); // Initialize the display
void command_write(lcd16x2_8data* display,unsigned char data); // Write Command to display
void data_write(lcd16x2_8data* display, unsigned char data); // Write Data to display
void blinky_onboard_led(); // Debug purpose (onboard led blinking)
An example of the command_write
function is explained below
// LCD Command write
void command_write(lcd16x2_8data* display, unsigned char data){
// * is used to de-reference and write to the value (and not the memory location)
*display->data_port = data; // Float the data in the data_port
*display->control_port &= (~(1<<display->rs_bit)); // Clear the Register Select bit
*display->control_port &= (~(1<<display->rw_bit)); // Clear the Read/Write bit
*display->control_port |= (1<<display->enable_bit); // Pull high the Enable bit
_delay_ms(50); // 50ms delay
*display->control_port &= (~(1<<display->enable_bit)); // Clear the Enable bit to write the data
_delay_ms(50); // 50ms delay
}
- We pass in the pointer of the structure display instead of the actual variable to prevent excess memory usage.
- As
display
structure is a pointer, we use the->
operator to access internal variables
- Internal to the structure,
data_port
andcontrol_port
are pointers, hence we use*
de-reference operator to write to the memory location. [I was previously making a mistake of not specifying the*
, which was very difficult to debug at first, but I was able to figure this error out easily thanks to VSCode Intellisense; A similar problem came when I initialised the structure with the Port directly for the data_port variable but I should have actually sent the reference or address of the Port!]
main.c
Source code
#include "./../inc/atmega328p/memorymap.h" // Register Memory map file
#include "./../inc/lib/lcd16x2/lcd16x2.h" // Functions for accessing LCD displays
#define RS_BIT 0 // Define the Register Select bit location in PortB
#define RW_BIT 1 // Define the Read Write bit location in PortB
#define ENBL 2 // Define the Enable bit location in PortB
int main(){
DDRD = 0xff; DDRB = 0xff; // PORTD and PORTB as output ports
PORTD = 0; PORTB = 0; // Initialize both to logic level 0
// Create a object of the struct
// We pass in address of PORTD and PORTB and not the value in PORTD or PORTB
lcd16x2_8data LCD_Display = {&PORTD,&PORTB,RS_BIT,RW_BIT,ENBL};
// As per datasheet we need to wait for 40ms until the voltage level stabilizes
_delay_ms(40);
// Call the initialize display function by passing the address of LCD_Display struct
init_display(&LCD_Display);
// An example text for displaying
unsigned char name[] = {"AVR Baremetal!"};
// We call this function as per the legnth of the array and display it
for(int i=0;name[i]!='\0';i++)
data_write(&LCD_Display,name[i]); // We pass in the struct and data we wish to print
blinky_onboard_led(); // Infinite loop while blinking the onboard LED
return 0;
}
lcd16x2_8data LCD_Display = {&PORTD, &PORTB, RS_BIT, RW_BIT, ENBL};
- Using this line, we create an object of lcd16x2_lcd structure. Notice that we pass in the object of PORTD and PORTB and not the PORTD, PORTB values!
- This object can then be easily used to pass for functions. Also we pass the address of this object created in main to prevent excess memory usages.
References
- https://www.vishay.com/docs/37484/lcd016n002bcfhet.pdf LCD datasheet