My current embedded project uses a 16x2 LCD controlled by the HD44780 standard controller. My PIC18 speaks to the LCD via the Adafruit LCD serial backpack (schematic link). I chose the SPI interface.
The HD44780 is controlled through various instruction writes to 8 data pins (DB0-7), a read/write pin (R/W), a register select pin (RS), and an enable pin (E). Link to the instruction set.
The instructions are composed of bits that indicate certain settings... configuration parameters for lack of a better term.
Everything is working as expected, but I am uncertain about my design. In order to best organize my code for readability and flexibility I have tried to follow the following approach:
- Assign each HD44780 configuration parameter via #define directive in the header file
- Build an array of char type for each instruction and load it with the appropriate configuration parameters
- Execute instructions by passing the instruction array via pointer and using shift operations to construct the SPI output in the order expected by serial backpack
This is all well and good, however, I have then nagging feeling that storing the instructions as arrays is not the best approach. I would appreciate any advice on how you would approach this issue with a bent towards clarity and efficiency. My header and .c files are below, main.c is not included but calls LCD_send via the instruction aliases.
HEADER FILE
/*
* File: LCD_SPI_16x2.h
* Author: rbs
* Comments:
* Revision history:
*/
/*Hardware configuration:
*
* Adafruit LCD serial backpack bit configuration:
* [DB4 | DB5 | DB6 | DB7 | E | RS | RW | LITE]
*
* HD44780 instruction set bit configuration (4-bit mode):
* [RS | RW | DB7 | DB6 | DB5 | DB4]
*
* HD44780 instruction set bit configuration (8-bit mode):
* [RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0]
*
* PIC18 MSSP1 SPI peripheral:
* LSB first, clock = FOSC/4, SCK idle = low, TX on SCK low -> high, SDI not used
*/
// This is a guard condition so that contents of this file are not included
// more than once.
#ifndef LCD_SPI_16X2_V2_H
#define LCD_SPI_16X2_V2_H
#include <xc.h> // include processor files - each processor file is guarded.
//function prototypes
//void LCD_init(void);
void LCD_send(char *cmd);
void LCD_spi_out(char data);
//void LCD_send_string(const char * str);
//void LCD_set_cursr(int row, int col);
#define _E 0x08 //alias for HD44780 ENABLE bit
//HD44780 LCD parameters
#define _BL 1 //1-> backlight on 0-> backlight off
#define _ID 1 // 1-> increment 0-> decrement
#define _S 0 // 1-> display shift 0-> no shift
#define _D 0 // 1-> display on 0-> display off
#define _C 0 // 1-> cursor on 0-> cursor off
#define _B 0 // 1-> cursor blinks 0-> cursor does not blink
#define _SC 0 // 1-> shift display 0-> shift cursor
#define _RL 0 // 1-> shift to the left 0-> shift to the right
#define _N 1 // 1-> 2 lines 0-> 1 line
#define _F 0 // 1-> 5x10 dot font 0-> 5x8 dot font
#define _DL8 1 // 8-bit mode
#define _DL4 0 // 4-bit mode
//HD44780 LCD instruction aliases
#define _Clear LCD_send(Clear_display)
#define _Home LCD_send(Home)
#define _Entry_mode LCD_send(Entry_mode)
#define _Display_onOff LCD_send(Display_onOff))
#define _Cursor_shift LCD_send(Cursor_shift)
#define _Function8 LCD_send(Function_set_8bit)
#define _Function4 LCD_send(Function_set_4bit)
//HD44780 LCD instruction set, see format note above
char Clear_display[10] = {0,0,0,0,0,0,0,0,0,1};
char Home[10] = {0,0,0,0,0,0,0,0,1,0};
char Entry_mode[10] = {0,0,0,0,0,0,0,1,_ID,_S};
char Display_onOff[10] = {0,0,0,0,0,0,1,_D,_C,_B};
char Cursor_shift[10] = {0,0,0,0,0,1,_SC,_RL,0,0};
char Function_set_8bit[10] = {0,0,0,0,1,_DL8,_N,_F,0,0};
char Function_set_4bit[10] = {0,0,0,0,1,_DL4,_N,_F,0,0};
char Cursor_pos[10] = {0,0,1,0,0,0,0,0,0,0};
#endif /* LCD_SPI_16X2_V2_H */
.C FILE
/*
* File: LCD_SPI_16x2.c
* Author: rbs
*
* Created on April 4, 2017, 10:00 PM
*/
#include <xc.h>
#include "system_initialize.h"
#include "LCD_SPI_16x2_v2.h"
void LCD_init(){
//not completed yet
}
void LCD_send(char *cmd){
/* Adafruit LCD serial backpack bit configuration:
* UPPER:
* [DB4 | DB5 | DB6 | DB7 | E | RS | RW | LITE]
* LOWER:
* [DB0 | DB1 | DB2 | DB3 | E | RS | RW | LITE]
* HD44780 incoming instruction configuration:
* [RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0]
*/
char Upper = 0x00;
char Lower = 0x00;
Upper |= (*(cmd))<<2; //set RS bit
Upper |= (*(cmd+1))<<1; //set RW bit
Upper |= (*(cmd+5))<<7; //set data bits for upper 4bits of command
Upper |= (*(cmd+4))<<6;
Upper |= (*(cmd+3))<<5;
Upper |= (*(cmd+2))<<4;
Upper |= _BL; //set back light bit
Lower |= (*(cmd))<<2; //set RS bit
Lower |= (*(cmd+1))<<1; //set RW bit
Lower |= (*(cmd+9))<<7; //set data bits for lower 4bits of command
Lower |= (*(cmd+8))<<6;
Lower |= (*(cmd+7))<<5;
Lower |= (*(cmd+6))<<4;
Lower |= _BL; //set back light bit
LCD_spi_out(Upper);
LCD_spi_out(Lower);
}
void LCD_spi_out(char data){
_CS = 0; //chip select = 0
SSP1BUF = (data|_E); //send out data with E pin high
__delay_ms(2);
_CS = 1; //clock data out of LCD backpack shift register
__delay_ms(2); //delay for HD44780 to process
_CS = 0; //chip select = 0
SSP1BUF = data; //send out data with E pin low to set HD44780
__delay_ms(2);
_CS = 1; //clock data out of LCD backpack shift register
__delay_ms(2);
}