Objective
Control a 16x2 LCD Display using a microcontroller.
Make the code as library since it is something that has to be done often and with different microcontrollers.
Make the library interrupt based so that use of resources is minimized.
The Arduino default library by contrast is blocking and uses wait to send comands to the display, making it painfully slow.
Architecture
This library is interrupt and queue based. The user calls API that write on a buffer. The driver is called periodically in interrupt and takes care of synchronizing the physical display with the buffer.This approach has the advantage of decoupling the API with the display.
This is useful for instance if the user calls the display update function inside a fast loop. The write operation does not impact the loop speed too much since it's just a memory write, and the actual IO operation will be handled in background. The system will ignore multiple older write on a location and update a character on the screen with only the latest entry.
Example
In the H file tell the library what pins are used and if the LCD is in 4-bit H or L mode or 8 bit mode.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Decide if i'm using the D0-D3 or D4-D7 of the LCD port in 4BIT mode | |
//NOTE: Enable only one of the following defines | |
//#define LCD_PORT_HIGH | |
#define LCD_PORT_LOW | |
//DATA - LCD DATA PORT | |
#define LCD_PORT PORTA | |
//RS - LCD Register Select | |
#define RS_PORT PORTA | |
#define RS_PIN PA4 | |
//EN - LCD Enable, Strobe 010 to execute an operation | |
#define EN_PORT PORTA | |
#define EN_PIN PA5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int init(void) | |
{ | |
//LCD Port Config | |
//PA0 : LCD_D4 | |
//PA1 : LCD_D5 | |
//PA2 : LCD_D6 | |
//PA3 : LCD_D7 | |
//PA4 : LCD_RS | |
//PA5 : LCD_EN | |
//PA6 : | |
//PA7 : | |
PORT_A_CONFIG('L','L','L','L','H','H','R','R'); | |
///LCD Init | |
//Turn OFF Delay | |
//This is meant to allow LCD display to safely power down and reset | |
//If it's too short, the LCD will bug out when quickly doing ON -> OFF -> ON | |
_delay_ms( 250.0 ); | |
//Power Up the LCD Display | |
CLEAR_BIT( PORTC, PC5 ); | |
//Turn ON Delay | |
//This is meant to give the LCD Display time to safely power Up | |
//Ifit's too short, The LCD will bug out when doing OFF -> ON -> COMMANDS | |
_delay_ms( 250.0 ); | |
//Initialise the display and the driver: Send the sequences that configure the display | |
lcd_init(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int main(void) | |
{ | |
//Main Loop | |
for EVER | |
{ | |
///---------------------------------------------------------------------- | |
/// LCD Display Driver | |
///---------------------------------------------------------------------- | |
// Sync the display content with the user structure | |
//If: LCD update flag | |
if (flags.lcd_update == 1) | |
{ | |
//Clear flag | |
flags.lcd_update = 0; | |
//Driver that sync the user structure with the LCD. | |
//This paradigm solve lots of timing problems of the direct call version. | |
//You can print a million time a second, and the driver still won't bug out | |
lcd_update(); | |
} //End If: LCD update flag | |
} //end main loop | |
return 0; | |
}//end main | |
Library
GitHub RepositoryLibrary Header
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef AT_LCD_H | |
//header envroiment variabile, is used to detect multiple inclusion | |
//of the same header, and can be used in the c file to detect the | |
//included library | |
#define AT_LCD_H | |
/**************************************************************************** | |
** ENVROIMENT VARIABILE | |
****************************************************************************/ | |
//Decide if i'm using the D0-D3 or D4-D7 of the LCD port in 4BIT mode | |
//NOTE: Enable only one of the following defines | |
//#define LCD_PORT_HIGH | |
#define LCD_PORT_LOW | |
/**************************************************************************** | |
** GLOBAL INCLUDE | |
** TIPS: you can put here the library common to all source file | |
****************************************************************************/ | |
/**************************************************************************** | |
** DEFINES | |
****************************************************************************/ | |
///---------------------------------------------------------------------- | |
/// DISPLAY DEFINITIONS | |
///---------------------------------------------------------------------- | |
//Number of physical Columns and Rows of the LCD DISPLAY | |
#define LCD_COL 16 | |
#define LCD_ROW 2 | |
//Length of the update vector, 1 bit for every display cell (LCD_COL*LCR_ROW/8) | |
#define LCD_UPDT 4 | |
///---------------------------------------------------------------------- | |
/// PIN DEFINITIONS | |
///---------------------------------------------------------------------- | |
//DATA - LCD DATA PORT | |
#define LCD_PORT PORTA | |
//RS - LCD Register Select | |
#define RS_PORT PORTA | |
#define RS_PIN PA4 | |
//EN - LCD Enable, Strobe 010 to execute an operation | |
#define EN_PORT PORTA | |
#define EN_PIN PA5 | |
///---------------------------------------------------------------------- | |
/// LCD TIMING DEFINITIONS | |
///---------------------------------------------------------------------- | |
//only used in the hardwired timed function during initialization | |
//[uS] Time in which EN will remain high during strobe | |
#define EN_STROBE_TIME 200 | |
//[mS] Time required by the LCD to execute the slowest operation. | |
#define LCD_LONG_WAIT 2.0 | |
///---------------------------------------------------------------------- | |
/// SETTINGS DEFINITIONS | |
///---------------------------------------------------------------------- | |
//Position of the ADJ flag in lcd_cfg_flags | |
#define LCD_ADJ_FLAG 0 | |
enum LCD_SETTINGS | |
{ | |
LCD_ADJ, //Number Adjust | |
LCD_ADJ_R, //Number Adjust Right eg. |123 | 1 (DEFAULT) | |
LCD_ADJ_L //Number Adjust Left eg. |123 |1 | |
}; | |
///---------------------------------------------------------------------- | |
/// ERRORS DEFINITIONS | |
///---------------------------------------------------------------------- | |
enum LCD_ERRORS | |
{ | |
LCD_OK = 0, //Default state, everything is fine | |
LCD_OOB = 10, //Out Of Bound, attempted accesso of a bad memory location | |
LCD_WTF = 99 //WhatTheFuck? Signal an algorithmic error | |
}; | |
#define MAX_DIGIT8 3 | |
/**************************************************************************** | |
** MACROS | |
****************************************************************************/ | |
///---------------------------------------------------------------------- | |
/// PIN MACROS | |
///---------------------------------------------------------------------- | |
//IF I'm using bit D4 to D7 of the LCD_PORT to interface with D4 to D7 of the LCD DISPLAY | |
#ifdef LCD_PORT_HIGH | |
//write the 4 LSB of data into the 4 MSB of the LCD port, leaving the other bit untouched | |
#define LCD_WRITE( data ) \ | |
LCD_PORT = ((LCD_PORT & 0x0f) | ((data & 0x0f) << 4)) | |
#endif | |
//IF I'm using bit D0 to D3 of the LCD_PORT to interface with D4 to D7 of the LCD DISPLAY | |
#ifdef LCD_PORT_LOW | |
//write the 4 MSB of data into the 4 MSB of the LCD port, leaving the other bit untouched | |
#define LCD_WRITE( data ) \ | |
LCD_PORT = ((LCD_PORT & 0xf0) | (data & 0x0f)) | |
#endif | |
//Strobe the enable pin, time specify the delay after each edge | |
#define STROBE_EN( time ) \ | |
SET_BIT( EN_PORT, EN_PIN ); \ | |
_delay_us( time ); \ | |
CLEAR_BIT( EN_PORT, EN_PIN ); \ | |
_delay_us( time ) | |
//Calculate the position of a digit in the lcd_show vector | |
#define LCD_POS( row, col ) \ | |
( (col) + (row) * LCD_COL) | |
///---------------------------------------------------------------------- | |
/// ERROR | |
///---------------------------------------------------------------------- | |
//called when errors are detected | |
#define LCD_ERROR( code ) \ | |
DPRINTF( "ERROR: %d\n", (code) ); \ | |
lcd_error = (code) | |
/**************************************************************************** | |
** TYPEDEF | |
****************************************************************************/ | |
typedef struct _Lcd_fsm_status Lcd_fsm_status; | |
/**************************************************************************** | |
** STRUCTURE | |
****************************************************************************/ | |
struct _Lcd_fsm_status | |
{ | |
U8 scan : 5; //current digit being scanned for update | |
U8 exe : 2; //state of execution | |
U8 adr : 1; //transmitting data/address | |
U8 h : 1; //transmitting MSB/LSB | |
U8 cursor : 5; //current cursor position (LCD display) | |
U8 : 2; //unused bits | |
}; | |
/**************************************************************************** | |
** PROTOTYPE: FUNCTION | |
****************************************************************************/ | |
///---------------------------------------------------------------------- | |
/// INTERNAL FUNCTIONS | |
///---------------------------------------------------------------------- | |
//Hardwired command send, ussed in lcd_init | |
extern void lcd_send_cmd( U8 cmd ); | |
//FSM that send data, it controls DATA and EN channels, but not RS | |
//xtern U8 lcd_write( U8 data ); | |
///---------------------------------------------------------------------- | |
/// LCD DRIVER FUNCTIONS | |
///---------------------------------------------------------------------- | |
//Initialise LCD Display | |
extern void lcd_init( void ); | |
//Main driver function. User should call this at a fixed frequency (10KHz) | |
//This function will automatically sync the content of lcd_show with the LCD display | |
extern void lcd_update( void ); | |
//This function configure certain behaviours of the print function | |
extern void lcd_config( U8 cfg, U8 val ); | |
///---------------------------------------------------------------------- | |
/// LCD PRINT FUNCTIONS | |
///---------------------------------------------------------------------- | |
//write a char in the lcd_show vector, update lcd_updt flags | |
extern void lcd_print_char( U8 pos, U8 data ); | |
//Print a string on screen | |
extern void lcd_print_str( U8 pos, U8 *str ); | |
//print a U8 number on screen | |
extern void lcd_print_u8( U8 pos, U8 num ); | |
//print a S8 number on screen | |
extern void lcd_print_s8( U8 pos, S8 num ); | |
/**************************************************************************** | |
** PROTOTYPE: GLOBAL VARIABILE | |
****************************************************************************/ | |
//LCD SHOW vector | |
// Print calls will update this vector. Driver will sync this vector with the display itself | |
extern U8 lcd_show[ LCD_ROW * LCD_COL ]; | |
//LCD CELLS UPDATE | |
// Print will rise the cell bit to '1', signaling the driver that content must be synced | |
extern U8 lcd_updt[ LCD_UPDT ]; | |
//Configuration flags for the print functions | |
extern U8 lcd_cfg_flags; | |
//current error code of the library, default state is LCD_OK | |
extern U8 lcd_error; | |
#else | |
#warning "multiple inclusion of the header file at_lcd.h" | |
#endif |
Library Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************** | |
** ORANGEBOT PROJECT | |
***************************************************************************** | |
** Library to make use of lcd display in 4bit or 8bit mode | |
** The library is interrupt based to reduce the MPU consumption | |
***************************************************************************** | |
** Author: Orso Eric | |
** Creation Date: | |
** Last Edit Date: | |
** Revision: | |
** Version: 3.0 | |
****************************************************************************/ | |
/**************************************************************************** | |
** HYSTORY VERSION | |
***************************************************************************** | |
** V3.0 | |
** >Added detailed description for the LCD DISPLAY hardware, timing and instructions | |
** >Cleaned function names from library V2.0 | |
** >Now the lcd_init has bit description of the initialization commands for the display | |
** To edit the settings you just need to uncomment or comment the right SET_BIT option | |
** Much like all my others init function for ATMEL works | |
** >Tested the init function in 4BIT_MSB mode (my bodule) | |
** pin and timing defines, lcd_send_cmd, lcd_init | |
** >Added lcd_error var, keep track of library error status. Default state is LCD_OK | |
** >Tested the lcd_print_char. The function now write into lcd_show, and rise a lcd_updt flag | |
** >Updated and tested LCD_BUF macros. Now i store bot and top at the end of the data vector | |
** >Rewritten and tested lcd_update(), the core function. It works like a charm! The refresh rate is majestic! | |
** A char take 12 lcd_update() calls for random access or 6 calls for sequential access | |
** >CleanUp: I was able to remove the command queque altogether | |
****************************************************************************/ | |
/**************************************************************************** | |
** USED PIN | |
** TIPS: compile this field before writing any code: it really helps! | |
***************************************************************************** | |
** >RS (Register Select) | |
** >RW (Read/#Write) | |
** >E (Enable) | |
** >D4 - D7 | |
****************************************************************************/ | |
/**************************************************************************** | |
** LCD DISPLAY | |
***************************************************************************** | |
** DATASHEET | |
** DISPLAY : CDM1602K | |
** CONTROLLER : KS0066U | |
** PINS | |
** >RS (Register Select) | |
** '0' | I'm sending an instruction | |
** '1' | I'm sending a data | |
** >RW (Read/#Write) | |
** '0' | I'm writing to the LCD | |
** '1' | I'm reading from the LCD (This driver will not perform reads) | |
** >E (Enable) | |
** Usually '0' | |
** Strobe '0'->'1'->'0' will execute an operation | |
** >D0 - D7 | |
** Bidirectionall data bus | |
** The LCD can operate in 4 bit mode, using only D4-D7 (This driver will only handle 4bit mode) | |
***************************************************************************** | |
** WRITE INSTRUCTIONS (R/#W = '0') | |
** Most instruction are fast, they take about 40uS. Slow instruction 1.5mS are marked | |
** RS | D0 D1 D2 D3 D4 D5 D6 D7 | Instruction Name and Details | |
** -------------------------------------------------------------------------- | |
** 0 | 1 0 0 0 0 0 0 0 | 0) Clean LCD (slow) - reset the content | |
** 0 | / 1 0 0 0 0 0 0 | 1) Reset cursor position, Reset display shift (Slow) | |
** 0 | S D 1 0 0 0 0 0 | 2) Set shift, Set cursor direction | |
** | S = '1' -> Shift | |
** | D = '1' -> Increment | |
** 0 | B C E 1 0 0 0 0 | 3) Enable Show Display, Show Cursor, Cursor Blink | |
** | B = '1' -> Cursor will blink | |
** | C = '1' -> Show Cursor | |
** | E = '1' -> Display ON (content will be shown | |
** 0 | / / F D 1 0 0 0 | 4) Cursor working (NOTE: not sure about this command) | |
** | S = '1' -> cursor will shift display | '0' normal operation | |
** | D = '1' -> advance backward | |
** 0 | / / F N W 1 0 0 | 5) Font type, Bus Width | |
** | F = '1' -> Font 5x10 | '0' -> Font 5x7 | |
** | N = '1' -> 2 lines | '0' -> 1 line | |
** | W = '1' -> 8bit mode | '0' -> 4bit mode D4-D7 | |
** 0 | A0 A1 A2 A3 A4 A5 1 0 | 6) Set CGRAM address (Charater Generator RAM) (Font) | |
** 0 | A0 A1 A2 A3 A4 A5 A6 1 | 7) Set DDRAM address (Data Display RAM) | |
** 1 | D0 D1 D2 D3 D4 D5 D6 D7 | Write data to CGRAM or DDRAM | |
** | Decided by the most recent Set Address operation | |
** | Entry mode will define the cursor address after the operation | |
***************************************************************************** | |
** 4BIT OPERATION | |
** the display will only use its 4 to 7 bit of the data port | |
** First send bit 4 to 7 (high), strobe EN, then send bit 0 to 3 (low) | |
** Send Command 5 first. Special care has to be taken, since the default mode is 8bit | |
** Special care has to be | |
****************************************************************************/ | |
/**************************************************************************** | |
** DESCRIPTION | |
***************************************************************************** | |
** >Print functions update lcd_show and lcd_update vectors | |
** >The driver will take care of syncing vectors with the LCD DISPLAY itself | |
** >The user must call the display_init function to setup the display | |
** >The user must call the display_update function at a fixed rate | |
** that is the function sync the vectors with the content | |
** >I only use 4bit mode | |
****************************************************************************/ | |
/**************************************************************************** | |
** KNOWN BUG | |
***************************************************************************** | |
** >BUG Architecture (SOLVED) | |
** The version V2 of the library had quequeing problems, since print calls | |
** directly quequed command for the display, if I called too many prints, | |
** the buffer would overflow. | |
** SOLUTION: Architecture Shift V2 -> V3 | |
** Print calls update the lcd_show vector which contains what the user wants to display | |
** The driver will read from there and send commands to the display at his | |
** defined refresh rate. This way user won't have to worry about calling | |
** too many prints. The function is also a lot more efficient | |
** >BUG Cursor (SOLVED) | |
** When I write in 0,15, the cursor moves to 0,16 (out of screen) NOT 1,0 | |
** SOLUTION: I disable address optimization for position 1,0 only in lcd_update() | |
****************************************************************************/ | |
/**************************************************************************** | |
** USED PIN | |
** TIPS: compile this field before writing any code: it really helps! | |
***************************************************************************** | |
** | |
****************************************************************************/ | |
/**************************************************************************** | |
** INCLUDE | |
****************************************************************************/ | |
#define F_CPU 20000000 | |
#include <stdint.h> | |
#include <avr/io.h> | |
#include <util/delay.h> | |
#include "at_utils.h" | |
#include "at_lcd.h" | |
#include "at_string.h" | |
/**************************************************************************** | |
** GLOBAL VARIABILE | |
****************************************************************************/ | |
//LCD SHOW vector | |
// Print calls will update this vector. Driver will sync this vector with the display itself | |
U8 lcd_show[ LCD_ROW * LCD_COL ]; | |
//LCD CELLS UPDATE | |
// Print will rise the cell bit to '1', signaling the driver that content must be synced | |
U8 lcd_updt[ LCD_UPDT ]; | |
//Configuration flags for the print functions | |
U8 lcd_cfg_flags; | |
//current error code of the library, default state is LCD_OK | |
U8 lcd_error; | |
/**************************************************************************** | |
** FUNCTIONS | |
****************************************************************************/ | |
/**************************************************************************** | |
***************************************************************************** | |
** DRIVER GROUP | |
***************************************************************************** | |
****************************************************************************/ | |
/**************************************************************************** | |
** LCD SEND CMD | |
***************************************************************************** | |
** This function send a command to the LCD display. Use hard wired delay | |
** Use 4 bit mode only | |
****************************************************************************/ | |
void lcd_send_cmd( U8 cmd ) | |
{ | |
//*********************************************************************** | |
// STATIC VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// LOCAL VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// BODY | |
//*********************************************************************** | |
// This routine is used to send a command. use hard wired delay. Used only during init | |
//write 4 bit of command to the LCD data port, define decide if low or high bits are written | |
//macro will always use the 4 LSB of the input | |
//Send the 4MSB of cmd first, I move them to LSB for the macro to send, the macro will move them to the appropriate position | |
LCD_WRITE( cmd >> 4 ); | |
//Clear RS: I'm sending an'instruction | |
CLEAR_BIT( RS_PORT, RS_PIN ); | |
//Strobe the enable pin, time specify the delay after each edge | |
STROBE_EN( EN_STROBE_TIME ); | |
//Now send the 4LSB of cmd | |
LCD_WRITE( cmd ); | |
//Strobe the enable pin, time specify the delay after each edge | |
STROBE_EN( EN_STROBE_TIME ); | |
//the slowest command take 1.5ms | |
_delay_ms( LCD_LONG_WAIT ); | |
//*********************************************************************** | |
// RETURN | |
//*********************************************************************** | |
return; | |
} //End function: lcd_send_cmd | |
/**************************************************************************** | |
** LCD INIT | |
***************************************************************************** | |
** This function will initialize the LCD display | |
** SETTINGS | |
** | |
****************************************************************************/ | |
void lcd_init( void ) | |
{ | |
//*********************************************************************** | |
// STATIC VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// LOCAL VARIABILE | |
//*********************************************************************** | |
U8 u8t; | |
//*********************************************************************** | |
// BODY | |
//*********************************************************************** | |
///---------------------------------------------------------------------- | |
/// COMMAND 5: Bus Width, Number of lines, Font Type | |
///---------------------------------------------------------------------- | |
//Clear the temp var | |
u8t = 0x00; | |
//Command 5 | |
SET_BIT( u8t, 5 ); | |
//Bus Width | |
// 0 | 4bit | |
// 1 | 8bit | |
//SET_BIT( u8t, 4 ); | |
//Number of lines | |
// 0 | 1 line | |
// 1 | 2 lines | |
SET_BIT( u8t, 3 ); | |
//Font type | |
// 0 | 5x10 | |
// 1 | 5x7 | |
//SET_BIT( u8t, 2 ); | |
//Don't Care | |
//SET_BIT( u8t, 1 ); | |
//Don't Care | |
//SET_BIT( u8t, 0 ); | |
//Send the command a special way (this time only) | |
//BUG: The default mode for the LCD is 8bit, the first command must be sent twice, and this way | |
lcd_send_cmd( u8t >> 4 ); | |
//Send the command | |
lcd_send_cmd( u8t ); | |
///---------------------------------------------------------------------- | |
/// COMMAND 0: Clear the display | |
///---------------------------------------------------------------------- | |
//Clear the temp var | |
u8t = 0x00; | |
//Command 0 | |
SET_BIT( u8t, 0 ); | |
//Send the command | |
lcd_send_cmd( u8t ); | |
///---------------------------------------------------------------------- | |
/// COMMAND 3: Show Display, Show Cursor, Blink Cursor | |
///---------------------------------------------------------------------- | |
//Clear the temp var | |
u8t = 0x00; | |
//Command 3 | |
SET_BIT( u8t, 3 ); | |
//Show Display Content | |
// 0 | Hidden | |
// 1 | Show | |
SET_BIT( u8t, 2 ); | |
//Show Cursor | |
// 0 | Hide Cursor | |
// 1 | Show Cursor | |
//SET_BIT( u8t, 1 ); | |
//Blink Cursor | |
// 0 | Don't Blink | |
// 1 | Blink | |
//SET_BIT( u8t, 0 ); | |
//Send the command | |
lcd_send_cmd( u8t ); | |
///---------------------------------------------------------------------- | |
/// INITIALIZE VARIABILES | |
///---------------------------------------------------------------------- | |
//Here, I initialize the lcd variabiles | |
//For every digit | |
for (u8t = 0;u8t < LCD_ROW*LCD_COL; u8t++) | |
{ | |
//initialize the show vector with spaces | |
lcd_show[ u8t ] = ' '; | |
} | |
//For every 8 digit bit | |
for (u8t = 0;u8t < LCD_UPDT; u8t++) | |
{ | |
//initialize the 8 update bit to 0 (0 = up to date) | |
lcd_updt[ u8t ] = 0; | |
} | |
//Clear configuration flags (defaults) | |
lcd_cfg_flags = 0x00; | |
//Error code | |
lcd_error = LCD_OK; | |
//*********************************************************************** | |
// RETURN | |
//*********************************************************************** | |
return; | |
} //End function: lcd_init | |
/**************************************************************************** | |
** LCD UPDATE | |
***************************************************************************** | |
** Main driver function. User should call this at a fixed frequency (10KHz) | |
** This function will automatically sync the content of lcd_show with the LCD display | |
** DESCRIPTION: | |
** I have two states of operations: | |
** >Send commands to display | |
** >Generate commands | |
** I only generate commands if the comand queque is empty and there are update flags rised | |
** If the command queque is empty, and there are no update flag rised, the function will do nothing (very efficient) | |
** A huge advantage of the command queque dont this way, is that now I can stop adding commands to the queque if the queque is full | |
** A bug of the version 2.0 of the library was that prints directly added command to the queque, so the user could simply overflow the buffer | |
** There was no way to solve i, because i could only ignore commands at best | |
** Now the update function is the one that decide what to upload to the queque | |
***************************************************************************** | |
** ALGORITHM | |
***************************************************************************** | |
** S0 is the IDLE state, when I'm S0 i search for a '1' in lcd_updt scanning forward | |
** If I find it, I'll have to send data to the LCD to sync it with the buffer | |
** I use two flags, ADR and H to signal S3 what to do | |
** If the lcd cursor is already in the right position I can skip sending the address, i wont' rise ADR flag | |
** S1), and S2)= are strightforward, they strobe EN | |
** S3) is the important final state. In here I use the ADR and H flag to send the LSB of the byte and to | |
** know if i have to send data or address information | |
** I follow it with S1 as long as I need to continue | |
** Once I'm done, i return to status S0, and I'm ready for the next flag search | |
** The last operation of S3 is to check if the sent data is still coherent with the buffer | |
** since the user might have already written something new. if it's coherent, i clear the update flag | |
** | |
** STATUS VAR | |
** exe(2) : i only needs 2 bits to encode the status | |
** adr(1) : signal S3 if i'm sending address informations | |
** h(1) : signal S3 if i have to send the LSB | |
** scan(5) : current scan position | |
** cursor(5) : current cursor position on the LCD display | |
** | |
** S0) - IDLE | |
** Search for a flag, did I find it? | |
** N: RET S0 //nothing to do, the LCD is synced with the buffer | |
** is the cursor in the right position? | |
** N: CLEAR RS, SET H, SET ADR, Send ADR(H) | |
** Y: SET RS, SET H, Send DATA(H) | |
** RET S1 | |
** S1) - SET EN | |
** SET EN | |
** RET S2 | |
** S2) - CLEAR EN | |
** CLEAR EN | |
** if ADR is clear && H is clear? | |
** I'm done, RET S0 | |
** RET S3 | |
** S3) | |
** if ADR is set && H is set? | |
** CLEAR H, Send ADR(L), UPDATE status.cursor, RET S1 | |
** if ADR is set && H is clear? | |
** CLEAR ADR, SET H, Send DATA(H), RET S1 | |
** if ADR is clear && H is set? | |
** CLEAR H, Send DATA(L), UPDATE lcd_updt flag, RET S1 | |
****************************************************************************/ | |
void lcd_update( void ) | |
{ | |
///************************************************************************** | |
/// STATIC VARIABILE | |
///************************************************************************** | |
//Status variabile for the LCD FSM | |
static Lcd_fsm_status status; | |
//Store the char being sent, it prevent glitches when the user update a char currently being scanned | |
static U8 data_buf; | |
///************************************************************************** | |
/// LOCAL VARIABILE | |
///************************************************************************** | |
U8 exe_temp; | |
U8 scan_temp; | |
U8 u8t, u8t1, u8t2; | |
///************************************************************************** | |
/// PARAMETER CHECK | |
///************************************************************************** | |
///************************************************************************** | |
/// BODY | |
///************************************************************************** | |
///Fetch | |
//Move status var to temp vars | |
exe_temp = status.exe; | |
scan_temp = status.scan; | |
///FSM | |
//If: S0 - IDLE | |
if (exe_temp == 0) | |
{ | |
///---------------------------------------------------------------------- | |
/// S0 - IDLE | |
///---------------------------------------------------------------------- | |
// If: I'm idle, I'm searching for the next rised flag using scan | |
// the flag are stored in 4 byte, every bit is a flag for one of 32 digit of the screen | |
// >First I scan the current byte pointed by scan, and see if there are rised update flag there | |
// >Then I scan the following bytes for at least one flag | |
// Y> I scan for the first bit, i will find it | |
// N> Return S0), there is nothing to do | |
// >Do I have to send the address? | |
// Y> rise address flag, send_data=address | |
// N> send_data = data | |
// | |
///First byte flag detection | |
//The byte I'm currently scanning, has a rised flag that I have yet to scan? | |
//ex1. 0 1 1 0 0 0 1 0 | ex2. 0 1 1 0 0 0 1 0 | |
//scan ^ | scan ^ | |
//mask 0 0 0 0 0 1 1 1 | mask 0 0 0 0 0 0 0 1 | |
//In the ex1. there is a 1 after the scan index, in ex2 no. | |
//what I do is to generate a mask with 1 after the scan index | |
//The & with the flag byte will return 1 if I have at least one rised flag to scan | |
//otherwise I will scan the next bytes | |
/// | |
//generate the mask, the position inside the byte is the 3LSB of scan | |
u8t = (0xff << (scan_temp & 0x07)); | |
//fetch the current flag byte, , the index of the vector is the 2MSB of scan | |
u8t1 = lcd_updt[ ((scan_temp & 0x18) >> 3) ]; | |
//If: The byte currently pointed by scan, still has unreaded update flag | |
if ((u8t & u8t1) != 0x00) | |
{ | |
//Here: I know there is at least one rised flag after scan position. I search for it. | |
//Create a mask with a 1 in scan | |
u8t = MASK( scan_temp & 0x07 ); | |
//While: mobile mask scan. shift up the bit and & with lcd_updt until I find the flag I'm searching for | |
while (u8t != 0x00) | |
{ | |
//If: I found the flag | |
if ((u8t & u8t1) != 0x00) | |
{ | |
//DEBUG PRINTF | |
DPRINTF("S0 - Found rised flag in first status.scan byte: %d\n", scan_temp); | |
//DPRINTF("S0 - >%2x< >%2x< >%2x<\n", u8t, u8t1, u8t&u8t1); | |
//break while | |
u8t = 0x00; | |
} // End If: I found the flag | |
//If: I did not find the flag | |
else | |
{ | |
//increase scan while I'm at it | |
scan_temp++; | |
//Scan the next bit. u8t will overflow to 0x00 once I'm done | |
u8t = u8t << 1; | |
//If: the mask overflowed | |
if (u8t == 0x00) | |
{ | |
//ERROR: The first check told me that there was a rised bit, but I didn't find it! Algorithmic error | |
LCD_ERROR( LCD_WTF ); | |
} | |
} //End If: I did not find the flag | |
} // End While: mobile mask scan. shift up the bit and & with lcd_updt until I find the flag I'm searching for | |
} //End If: The byte currently pointed by scan, still has unreaded update flag | |
//If: I have to search for an update flag in the following four bytes | |
else | |
{ | |
//I have to circularly scan the bytes of lcd_updt and scan four of them | |
//The fourth is the starting byte, but I have not scanned all bits of it | |
// 0 1 2 3 | |
// -> | -> scan | |
///Setup var | |
//clear the 3LSB of scan index, i will start bit search from the first bit of the byte | |
scan_temp = scan_temp & 0x18; | |
//Get the byte index | |
u8t1 = ((scan_temp & 0x18) >> 3); | |
//Setup increment | |
u8t = 1; | |
///Search | |
//While: Scan (LCD_UPDT) byte after the one pointed by scan for a rised flag | |
while (u8t <= LCD_UPDT) | |
{ | |
//Fetch the content of the byte I'm scanning. | |
u8t2 = lcd_updt[ u8t1 ]; | |
//If: the byte I'm scanning contain at least one rised flag | |
if (u8t2 != 0x00) | |
{ | |
//DEBUG PRINTF | |
//DPRINTF("S0 - Found a byte with risen flag: byte %d, scan %d\n", u8t1, scan_temp); | |
//break cycle | |
u8t = LCD_UPDT +1; | |
} | |
//If: the byte I'm scanning contain no rised flags | |
else | |
{ | |
//Advance relative increment | |
u8t++; | |
//Advance the absolute index | |
u8t1 = ((u8t1 +1) >= (LCD_UPDT))?(0):(u8t1+1); | |
//I update scan index while I'm at it, i advance by 8 bits | |
scan_temp = AT_CIRCULAR_SUM( scan_temp, 8, LCD_ROW *LCD_COL ); | |
//If: the search failed, it means that there are NO risen flags | |
if (u8t > LCD_UPDT) | |
{ | |
//It means I have nothing to do, I stay in S0-IDLE | |
DPRINTF("S0 - No flags found, nothing to do here: scan_temp: %d\n", scan_temp); | |
//I'm done | |
return; | |
} | |
} | |
} //End While: Scan (LCD_UPDT) byte after the one pointed by scan for a rised flag | |
///HERE: | |
//I have found a byte with a risen flag, I have to find the bit now | |
///Setup | |
//mask with LSB rised | |
u8t = 0x01; | |
//u8t1 has the index, I fetch the content instead. Scan temp already point to the first bit of that byte | |
u8t1 = lcd_updt[ u8t1 ]; | |
///Search | |
//While: I'm searching for a rised flag inside a byte | |
while (u8t != 0x00) | |
{ | |
//If: I find the flag I'm looking for | |
if ((u8t & u8t1) != 0x00) | |
{ | |
//break the while | |
u8t = 0x00; | |
} | |
//If: flag is clear | |
else | |
{ | |
//advance mask | |
u8t = u8t << 1; | |
//while I'm at it, I update the scan index as well | |
scan_temp++; | |
//If: I completed the scan and didn't find the flag | |
if (u8t == 0x00) | |
{ | |
//ERROR: The first check told me that there was a rised bit, but I didn't find it! Algorithmic error | |
LCD_ERROR( LCD_WTF ); | |
} | |
} | |
} //End While: I'm searching for a rised flag inside a byte | |
// | |
DPRINTF("S0 - Flag after first byte found: %d\n", scan_temp); | |
} //End If: I have to search for an update flag in the following four bytes | |
///HERE: | |
//scan_temp hold the position of a char to be processed | |
///LOAD DATA BUFFER, UPDATE STATUS VARS, JUMP | |
//BUG the cursor from 0,15 move out of screen to 0,16 NOT 1,0 For this position only, I always have to specify the address | |
//If: the cursor is already in the correct position | |
if (((scan_temp & 0x0f) != 0) && (scan_temp == status.cursor)) | |
{ | |
//I will skip sending the address | |
status.adr = 0; | |
//Load data into the buffer | |
u8t = lcd_show[ scan_temp ]; | |
data_buf = u8t; | |
//SET RS line, i'm sending data | |
SET_BIT( RS_PORT, RS_PIN ); | |
DPRINTF("S0 - Cursor is Up To Date, sending digit: %x\n", u8t); | |
} | |
//If: I have to move the cursor | |
else | |
{ | |
//I'm sending the address | |
status.adr = 1; | |
//Load data into the buffer | |
// B7='1' is signature for DDRAM write instruction | |
// B6='1' is address for the second row, i have to move B4 to B6 | |
// B3...B0 is the LSB of the address | |
u8t = MASK(7) | ((scan_temp & 0x10) << 2) | (scan_temp & 0x0f); | |
data_buf = u8t; | |
//CLEAR RS line, i'm sending an address | |
CLEAR_BIT( RS_PORT, RS_PIN ); | |
DPRINTF("S0 - Sending cursor position: scan: %d cursor: %d adr: %x\n", scan_temp, status.cursor, u8t); | |
} | |
//I'm sending the H part | |
status.h = 1; | |
//send the H part of the buffer (still stored in u8t) | |
LCD_WRITE( u8t >> 4 ); | |
//Jump to S1) | |
exe_temp = 1; | |
} //End If: S0 - IDLE | |
//If: S1 - SET EN | |
else if (exe_temp == 1) | |
{ | |
DPRINTF("S1 - SET_EN\n"); | |
SET_BIT( EN_PORT, EN_PIN ); | |
//Jump S2) | |
exe_temp = 2; | |
} //End If: S1 - SET EN | |
//If: S2 - CLEAR EN | |
else if (exe_temp == 2) | |
{ | |
// | |
CLEAR_BIT( EN_PORT, EN_PIN ); | |
//If I have sent DATA(L) | |
if ((status.adr == 0) && (status.h == 0)) | |
{ | |
DPRINTF("S2 - CLEAR_EN, DATA LSB was sent, I'm done\n"); | |
//I'm done, I don't have to execute the next state (no pins needs to be toggled | |
exe_temp = 0; | |
} | |
//default case | |
else | |
{ | |
DPRINTF("S2 - CLEAR_EN\n"); | |
//Jump S3) | |
exe_temp = 3; | |
} | |
} //End If: S2 - CLEAR EN | |
//If: S3 - | |
else if (exe_temp == 3) | |
{ | |
///---------------------------------------------------------------------- | |
/// S3 - | |
///---------------------------------------------------------------------- | |
// The final state, i use status.h, status.adr and data_buf | |
// Here i setup DATA and RS line, and jump to S1) to strobe EN line | |
//If: I have sent ADR(H) | |
if ((status.adr == 1) && (status.h == 1)) | |
{ | |
///Send ADR(L) | |
//Clear H flag | |
status.h = 0; | |
//load 4LSB of data buffer | |
u8t = data_buf; | |
//Send ADR(L) | |
LCD_WRITE( u8t & 0x0f ); | |
///Update cursor | |
//I have written ADR, I need to update the cursor | |
status.cursor = scan_temp; | |
DPRINTF("S3 - Send ADR LSB\n"); | |
} | |
//If: I have sent ADR(L) | |
else if ((status.adr == 1) && (status.h == 0)) | |
{ | |
///Send DATA(H) | |
//Set RS line (I'm sending data, not instructuins) | |
SET_BIT( RS_PORT, RS_PIN ); | |
//Clear ADR flag | |
status.adr = 0; | |
//Set H flag | |
status.h = 1; | |
//load digit into buffer | |
//Remember that user can async write into it, that's why I need a buffer in the first place | |
u8t = lcd_show[ scan_temp ]; | |
data_buf = u8t; | |
//Send DATA(H) | |
LCD_WRITE( u8t >> 4 ); | |
DPRINTF("S3 - Send DATA MSB\n"); | |
} | |
//If: I have sent DATA(H) | |
else if ((status.adr == 0) && (status.h == 1)) | |
{ | |
///Send DATA(L) | |
//Clear H flag | |
status.h = 0; | |
//load 4LSB of data buffer | |
u8t = data_buf; | |
//Send ADR(L) | |
LCD_WRITE( u8t & 0x0f ); | |
///Update lcd_updt | |
//If: the LCD is synced with lcd_show | |
if (u8t == lcd_show[ scan_temp ]) | |
{ | |
//I can clear the flag, B3 B4 address the byte inside the vector, B0-B2 address the bit inside the byte | |
CLEAR_BIT( lcd_updt[ ((scan_temp & 0x18) >> 3) ], (scan_temp & 0x07) ); | |
} | |
///Update cursor | |
//A successful write to the LCD will advance the cursor | |
u8t = status.cursor; | |
//circular increment of cursor | |
u8t = ((u8t +1) < (LCD_ROW*LCD_COL))?(u8t +1):(0); | |
//write back cursor | |
status.cursor = u8t; | |
DPRINTF("S3 - Send DATA LSB\n"); | |
} | |
//If: I have sent DATA(L), but I should have quit earlier (no need to strobe again). This is an error | |
else | |
{ | |
// | |
LCD_ERROR( LCD_WTF ); | |
} | |
//Jump to S1) I have setup DATA and RS lines, I need to strobe EN | |
exe_temp = 1; | |
} //End If: S3 - | |
//Default case (impossibile) | |
else | |
{ | |
//ERROR: This is an impossibile state, something went REALLY wrong | |
LCD_ERROR( LCD_WTF ); | |
} | |
///************************************************************************** | |
/// WRITE BACK | |
///************************************************************************** | |
//save updated status var | |
status.exe = exe_temp; | |
status.scan = scan_temp; | |
///************************************************************************** | |
/// RETURN | |
///************************************************************************** | |
return; | |
} //end function: display_update | |
/**************************************************************************** | |
** LCD CONFIG | |
***************************************************************************** | |
** This function configure certain behaviours of the print function | |
****************************************************************************/ | |
void lcd_config( U8 cfg, U8 val ) | |
{ | |
//*********************************************************************** | |
// STATIC VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// LOCAL VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// BODY | |
//*********************************************************************** | |
//Configure number alignment for the print functions | |
if (cfg == LCD_ADJ) | |
{ | |
//If the user wants left adjust (write MSB on pos and go right) | |
if (val == LCD_ADJ_L) | |
{ | |
SET_BIT( lcd_cfg_flags, LCD_ADJ_FLAG ); | |
} | |
//If the user wants right adjust (write LSB on pos and go left) (Default) | |
if (val == LCD_ADJ_R) | |
{ | |
CLEAR_BIT( lcd_cfg_flags, LCD_ADJ_FLAG ); | |
} | |
} | |
//*********************************************************************** | |
// RETURN | |
//*********************************************************************** | |
return; | |
} //End function: | |
/**************************************************************************** | |
***************************************************************************** | |
** PRINT GROUP | |
***************************************************************************** | |
****************************************************************************/ | |
/**************************************************************************** | |
** LCD PRINT CHAR | |
***************************************************************************** | |
** Write a char in the show vector, update the flag is required | |
** Input: | |
** >pos: Vector position | |
** >data: Display data | |
****************************************************************************/ | |
void lcd_print_char( U8 pos, U8 data ) | |
{ | |
///************************************************************************** | |
/// STATIC VARIABILE | |
///************************************************************************** | |
///************************************************************************** | |
/// LOCAL VARIABILE | |
///************************************************************************** | |
///************************************************************************** | |
/// PARAMETER CHECK | |
///************************************************************************** | |
if (pos > (LCD_ROW*LCD_COL)) | |
{ | |
//error, Out Of Bound write | |
LCD_ERROR( LCD_OOB ); | |
//I'm done | |
return; | |
} | |
///************************************************************************** | |
/// BODY | |
///************************************************************************** | |
// I only do something if I'm writing something different in lcd_show | |
// This already is a huge improvement from V2.0 which always sent commands | |
//If: I'm writing a different byte to a location | |
if (lcd_show[ pos ] != data ) | |
{ | |
//write the new data | |
lcd_show[ pos ] = data; | |
//Rise the update bit | |
// Location inside the vector is pos/8 | |
// Bit position inside the Location is the 3LSB of pos (reminder of division by 8) | |
SET_BIT( lcd_updt[ ((pos & 0x18) >> 3) ], (pos & 0x07) ); | |
} | |
///************************************************************************** | |
/// RETURN | |
///************************************************************************** | |
return; | |
} //end Function: lcd_print_char | |
/**************************************************************************** | |
** PRINT STR | |
***************************************************************************** | |
** Print a string starting from screen position | |
** If reach EOL, it starts from the following one | |
****************************************************************************/ | |
void lcd_print_str( U8 pos, U8 *str ) | |
{ | |
//*********************************************************************** | |
// STATIC VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// LOCAL VARIABILE | |
//*********************************************************************** | |
U8 t; | |
//*********************************************************************** | |
// PARAMETER CHECK | |
//*********************************************************************** | |
//If: NULL pointer | |
if (str == NULL) | |
{ | |
return; | |
} | |
//*********************************************************************** | |
// MAIN | |
//*********************************************************************** | |
//Initialise | |
t = 0; | |
//While: I have char or I have space on screen | |
while ( ((pos +t) <= (LCD_ROW *LCD_COL)) && (str[ t ] != '\0') ) | |
{ | |
//Print char on screen | |
lcd_print_char( pos +t, str[ t ] ); | |
//next char | |
t++; | |
} | |
//*********************************************************************** | |
// RETURN | |
//*********************************************************************** | |
return; | |
} //End function: print_str | |
/**************************************************************************** | |
** LCD PRINT U8 | |
***************************************************************************** | |
** Print a U8 number on screen | |
** I print the spaces in the empty digit too otherwise if | |
** I write a 1 digit number after a 3 digit one in the same position, | |
** the old numbers will still be there | |
** SETTINGS | |
** ALIGNMENT | |
** POS |v |v | |
** RIGHT |123 LEFT |123 | |
** | 1 |1 | |
****************************************************************************/ | |
void lcd_print_u8( U8 pos, U8 num ) | |
{ | |
//*********************************************************************** | |
// STATIC VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// LOCAL VARIABILE | |
//*********************************************************************** | |
//Temp string to accomodate the number | |
U8 str[ MAX_DIGIT8 +1 ]; | |
//return var of the number to string conversion | |
U8 ret; | |
//Temp vars | |
U8 u8t, u8t1; | |
//*********************************************************************** | |
// BODY | |
//*********************************************************************** | |
//convert the number | |
ret = u8_to_str( num, str ); | |
//For: every digit | |
for (u8t = 0;u8t < MAX_DIGIT8; u8t++) | |
{ | |
//If: left alignment | |
if (IS_BIT_ONE( lcd_cfg_flags, LCD_ADJ_FLAG )) | |
{ | |
//If: I'm outside the number | |
if (u8t >= ret) | |
{ | |
//I write spaces to blank older numbers | |
u8t1 = ' '; | |
} | |
else | |
{ | |
//Print number | |
u8t1 = str[ u8t ]; | |
} | |
} //End If: left alignment | |
//If: right alignment | |
else | |
{ | |
//If: I'm outside the number (yes, that's the condition,right adjustment is messy) | |
if ((u8t +ret) < MAX_DIGIT8) | |
{ | |
//I write spaces to blank older numbers | |
u8t1 = ' '; | |
} | |
//If: I'm writing a digit | |
else | |
{ | |
//print number, I have to write digit in a mesi order with this adjust | |
u8t1 = str[ u8t +ret -MAX_DIGIT8 ]; | |
} | |
} //End If: right alignment | |
//I want to call a single print, I have calculated the argument based on the settings of the print | |
lcd_print_char( pos +u8t, u8t1 ); | |
} //End For: every digit | |
//*********************************************************************** | |
// RETURN | |
//*********************************************************************** | |
return; | |
} //End function: lcd_print_u8 | |
/**************************************************************************** | |
** LCD PRINT S8 | |
***************************************************************************** | |
** Print a S8 number on screen | |
** I print the spaces in the empty digit too otherwise if | |
** I write a 1 digit number after a 3 digit one in the same position, | |
** the old numbers will still be there | |
** SETTINGS | |
** ALIGNMENT | |
** POS |v |v | |
** RIGHT |123 LEFT |123 | |
** | 1 |1 | |
****************************************************************************/ | |
void lcd_print_s8( U8 pos, S8 num ) | |
{ | |
//*********************************************************************** | |
// STATIC VARIABILE | |
//*********************************************************************** | |
//*********************************************************************** | |
// LOCAL VARIABILE | |
//*********************************************************************** | |
//Temp string to accomodate the number | |
U8 str[ MAX_DIGIT8 +1 ]; | |
//return var of the number to string conversion | |
U8 ret; | |
//Temp vars | |
U8 u8t, u8t1; | |
//*********************************************************************** | |
// BODY | |
//*********************************************************************** | |
//convert the number | |
ret = s8_to_str( num, str ); | |
//For: every digit | |
for (u8t = 0;u8t < (MAX_DIGIT8 +1); u8t++) | |
{ | |
//If: sign handling | |
if (u8t == 0) | |
{ | |
//Print sign | |
u8t1 = str[ u8t ]; | |
} //End If: sign handling | |
//If: left alignment | |
else if (IS_BIT_ONE( lcd_cfg_flags, LCD_ADJ_FLAG )) | |
{ | |
//If: I'm outside the number | |
if (u8t >= ret) | |
{ | |
//I write spaces to blank older numbers | |
u8t1 = ' '; | |
} | |
else | |
{ | |
//Print number | |
u8t1 = str[ u8t ]; | |
} | |
} //End If: left alignment | |
//If: right alignment | |
else | |
{ | |
//If: I'm outside the number (yes, that's the condition,right adjustment is messy) | |
if ((u8t +ret ) <= (MAX_DIGIT8 +1)) | |
{ | |
//I write spaces to blank older numbers | |
u8t1 = ' '; | |
} | |
//If: I'm writing a digit | |
else | |
{ | |
//print number, I have to write digit in a mesi order with this adjust | |
u8t1 = str[ u8t -1 +ret -MAX_DIGIT8 ]; | |
} | |
} //End If: right alignment | |
//I want to call a single print, I have calculated the argument based on the settings of the print | |
lcd_print_char( pos +u8t, u8t1 ); | |
} //End For: every digit | |
//*********************************************************************** | |
// RETURN | |
//*********************************************************************** | |
return; | |
} //End function: lcd_print_s8 |
No comments:
Post a Comment