I have successfuly used this driver in many projects.
I have two versions of this driver:
- one simpler to use driver (HD44780) but with blocking delays. This one can be used in not so "real-time" constrained projects.
MACROS for PIN access on my STM32 environment
#define LCD_D7(x) (x==(u8)0 ? (LCD_D7_PORT->ODR &= (~LCD_D7_PIN)) : (LCD_D7_PORT->ODR |= LCD_D7_PIN))
#define LCD_D6(x) (x==(u8)0 ? (LCD_D6_PORT->ODR &= (~LCD_D6_PIN)) : (LCD_D6_PORT->ODR |= LCD_D6_PIN))
#define LCD_D5(x) (x==(u8)0 ? (LCD_D5_PORT->ODR &= (~LCD_D5_PIN)) : (LCD_D5_PORT->ODR |= LCD_D5_PIN))
#define LCD_D4(x) (x==(u8)0 ? (LCD_D4_PORT->ODR &= (~LCD_D4_PIN)) : (LCD_D4_PORT->ODR |= LCD_D4_PIN))
#define LCD_EN(x) (x==(u8)0 ? (LCD_EN_PORT->ODR &= (~LCD_EN_PIN)) : (LCD_EN_PORT->ODR |= LCD_EN_PIN))
#define LCD_RS(x) (x==(u8)0 ? (LCD_RS_PORT->ODR &= (~LCD_RS_PIN)) : (LCD_RS_PORT->ODR |= LCD_RS_PIN))
Delay on my STM32 environment
static volatile u16 timer_tstamp, timer_delta;
/**
* @brief time delay in us unit
* @param n_us is how many us to delay
* @retval None
*/
void delay_us(u16 n_us)
{
timer_tstamp = TIMER_COUNTER;
while(1)
{
timer_delta = TIMER_COUNTER - timer_tstamp;
if(timer_delta >= n_us) break;
}
}
Usage from main
LCD_Initialize();
LCD_Clear();
LCD_WriteString(" SINUS ");
And then I have second version of the driver (HD44780-nonBlocking), with non blocking delays for projects where a stricter timely reaction is expected from the system.
I basically transformed the sequential code into a state machine which goes from one state to the other when the delay expired. After the state machine is processed it always returns, it never blocks. You have to periodically call the state machine.
In delay.c and h you can download the non blocking delay functions.
Before while(1) loop you have to call the LCD functions multiple times so the state machine behind them cycles through all states.
Usage in main before while(1) loop
while(!LCD_Initialize());
while(!LCD_Clear());
while(!LCD_Home());
But the real advantage is in the while(1), the loop cycle time speed increased dramatically.
Casetă text
while (1)
{
Func1_work();
Func2_work();
Func3_work();
if(LCD_Update && LCD_UPDATE_LIMIT_FLAG)
{
switch(step_Background_Task)
{
case 0:
{
if(LCD_Home())
{
step_Background_Task = 1;
}
break;
}
case 1:
{
if(Errors_IsError()) {
Convert2String_Errors();
}
else {
flag_LCD_Update_row1 = FALSE;
flag_LCD_Update_row2 = FALSE;
/* Max Duration: 26.5us, 1 step */
if(LCD_Update & LCD_Update_Current) {
Convert2String_Current(ImA);
flag_LCD_Update_row1 = TRUE;
}
if(LCD_Update & LCD_Update_Voltage) {
Convert2String_Voltage(UmV);
flag_LCD_Update_row1 = TRUE;
}
if(LCD_Update & LCD_Update_Power) {
Convert2String_Power(PowermW);
flag_LCD_Update_row2 = TRUE;
}
}
step_Background_Task = 2;
break;
}
case 2:
{
if(LCD_WriteString(lcd_row1)) {
step_Background_Task = 3;
}
break;
}
case 3:
{
if(LCD_WriteString(lcd_row2)) {
LCD_Update = LCD_Update_NO_UPDATE;
LCD_UPDATE_LIMIT_FLAG = FALSE;
step_Background_Task = 0;
}
break;
}
}
}
}
As you can see, there are 3 functions in the while(1) loop and after they are executed I have some kind of "background task" which takes care of LCD display update.
This "background task" is a state machine which cycles through the states every time it is called.
With the blocking delays version of the LCD driver, when the functions for LCD update were called, the while(1) loop cycle speed was delayed, was blocked for as much as a few miliseconds.
With the non blocking version, the loop cycle is CONSTANT and varies between some much more constrained limits which makes it suitable for reaction time sensitive applications.
For the while loop to be fast, the rest of the functions inside the loop should be non blocking. If they are large functions, they can be implemented like a state machine having one state executed at a time, and next time it is called, the next step is executed.
If we want to have a faster while loop, we will have to split the functionality of the functions in more states.
Having the functionality split along state machines and non blocking code we can create a "real-time" system with predictable maximum response time without having to use a preemptive operating system which can be a problem to implement for small projects.