外設驅動庫開發筆記33:LCD1602液晶顯示屏驅動
LCD1602是一種工業字元型液晶,能夠同時顯示16x02即32個字元。LCD1602液晶顯示的原理是利用液晶的物理特性,通過電壓對其顯示區域進行控制,即可以顯示出圖形。在這一章我們就來討論LCD1602液晶顯示屏驅動的設計與實現。
1、功能概述
LCD1602液晶又被稱作1602字元型液晶,這是一種只用來顯示字母、數字、符號等的點陣型液晶模組。LCD1602裡面儲存器一般有三種:CGROM、CGRAM、DDRAM。其中DDRAM(Display Data RAM)就是顯示資料RAM,用來寄存待顯示的字元程式碼。共80個位元組,其地址和螢幕的對應關係如下如圖所示:
LCD1602使用三條控制線:EN、RW、RS。 其中EN的作用其實就是中線的功能,RW和RS指示了讀、它寫的是寫的方向和內容。在讀資料(或者Busy標誌)期間,EN線必須保持高電平;而在寫指令(或者資料)過程中,EN線上必須送出一個正脈衝。RW、RS的組合一共有四種情況,分別對應四種操作:
(1)、RS=0、RW=0——表示向LCD寫入指令。
(2)、RS=0、RW=1——表示讀取Busy標誌。
(3)、RS=1、RW=0——表示向LCD寫入資料。
(4)、RS=1、RW=1——表示從LCD讀取資料。
LCD1602利用指令碼來區分不同的操作,主要的有兩類:一是用於初始化配置的指令碼;二是用於資料控制的指令碼。第一類用於LCD初始化配置的指令碼基本上都是在系統啟動時,用於對LCD1602的一次性配置。而第二類資料操作的指令碼主要用於設定資料指標的位置,現實資訊的實現與清楚等。這兩類指令碼從使用上並無太大區別,後續我們將詳細說明。
2、驅動設計與實現
我們已經瞭解了LCD1602的基本情況,接下來我們將給予對LCD1602的基本瞭解設計LCD602的驅動程式。
2.1、物件定義
在使用一個物件之前我們需要獲得一個物件。同樣的我們想要LCD1602液晶顯示屏就需要先定義LCD1602液晶顯示屏的物件。
2.1.1、物件的抽象
我們要得到LCD1602液晶顯示屏物件,需要先分析其基本特性。一般來說,一個物件至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下LCD1602液晶顯示屏的物件。
先來考慮屬性,作為屬性肯定是用於標識或記錄物件特徵的東西。我們來考慮LCD1602液晶顯示屏物件屬性。對於LCD1602顯示屏,它主要的功能就是顯示資訊,為了標識當前的狀態,我們將狀態暫存器的值作為物件的屬性。
接著我們還需要考慮LCD1602液晶顯示屏物件的操作問題。首先我們需要控制LCD1602的3個控制引腳以實現對LCD1602的控制,但這些控制引腳的操作都與具體的操作平臺相關,所以我們將其作為物件的操作來實現。同樣的我們還需要向LCD1602傳送命令和資料以及從LCD1602獲取訊息,而讀取和傳送都是依賴於具體的操作平臺的所以我們將其作為LCD1602的兩個操作。我們對LCD1602進行操作,免不了要進行時序控制,所以我們需要有延時操作,但我們都明白演示操作依賴於具體的軟硬體平臺,所以我們將延時處理函式也作為物件的操作。
根據上述我們對LCD1602液晶顯示屏的分析,我們可以定義LCD1602液晶顯示屏的物件型別如下:
/* 定義LCD1602的物件型別 */
typedef struct LCD1602Object {
uint8_t status;
LCD1602PinSetType *PinHandle;
void(*SendByte)(uint8_t data);
uint8_t(*GetByte)(void);
void (*Delayus)(volatile uint32_t period); //微秒延時函式
void (*Delayms)(volatile uint32_t nTime); //毫秒秒延時函式
}LCD1602ObjectType;
2.1.2、物件初始化
我們知道,一個物件僅作宣告是不能使用的,我們需要先對其進行初始化,所以這裡我們來考慮LCD1602液晶顯示屏物件的初始化函式。一般來說,初始化函式需要處理幾個方面的問題。一是檢查輸入引數是否合理;二是為物件的屬性賦初值;三是對物件作必要的初始化配置。據此我們設計LCD1602液晶顯示屏物件的初始化函式如下:
/*對顯示屏作初始化配置*/
void LCD1602Initialization(LCD1602ObjectType *lcd, //LCD1602物件指標
LCD1602PinSetType *PinHandle, //控制引腳操作函式指標陣列
LCD1602SendByteType sendByte, //傳送一個位元組函式指標
LCD1602GetByteType getByte, //讀取一個位元組函式指標
LCD1602DelayType delayus, //微秒延時函式指標
LCD1602DelayType delayms //毫秒延時函式指標
)
{
if((lcd==NULL)||(PinHandle==NULL)||(sendByte==NULL)||(getByte==NULL)||(delayus==NULL)||(delayms==NULL))
{
return;
}
lcd->PinHandle=PinHandle;
lcd->SendByte=sendByte;
lcd->GetByte=getByte;
lcd->Delayus=delayus;
lcd->Delayms=delayms;
lcd->Delayus(15);
WriteCommandToLCD1602(lcd,0x38);
lcd->Delayms(5);
WriteCommandToLCD1602(lcd,0x38);
lcd->Delayms(5);
WriteCommandToLCD1602(lcd,0x38);
/*後續需要檢測BUSY,等待10Mms*/
lcd->Delayms(10);
WriteCommandToLCD1602(lcd,0x38);//顯示模式設定
lcd->Delayms(10);
WriteCommandToLCD1602(lcd,0x08);//顯示關閉
lcd->Delayms(10);
WriteCommandToLCD1602(lcd,0x01);//顯示清屏
lcd->Delayms(10);
WriteCommandToLCD1602(lcd,0x06);//顯示游標移動位置
lcd->Delayms(10);
WriteCommandToLCD1602(lcd,0x0C);//顯示開及游標設定
lcd->PinHandle[LCD1602_EN](Low);
lcd->status=ReadStatusFromLCD1602(lcd);
}
2.2、物件操作
我們已經完成了LCD1602液晶顯示屏物件型別的定義和物件初始化函式的設計。但我們的主要目標是獲取物件的資訊,接下來我們還要實現面向LCD1602液晶顯示屏的各類操作。
2.2.1、讀資料操作
我們需要從LCD1602液晶顯示屏獲取一定的資料,包括讀取狀態資訊和資料資訊,唯一的區別只是RS控制引腳的電平,其他的操作都一樣,讀取資料的時序圖如下所示:
根據我們前面的描述及上面的時序圖,我們可以實現獲取狀態資訊及資料的操作函式如下:
/*從LCD1602讀狀態*/
static uint8_t ReadStatusFromLCD1602(LCD1602ObjectType *lcd)
{
uint8_t status;
lcd->PinHandle[LCD1602_RS](Low);
lcd->PinHandle[LCD1602_RW](High);
lcd->PinHandle[LCD1602_EN](High);
lcd->Delayus(20);
status=lcd->GetByte();
lcd->PinHandle[LCD1602_EN](Low);
lcd->Delayus(5);
return status;
}
/*從LCD1602讀資料*/
static uint8_t ReadDataFromLCD1602(LCD1602ObjectType *lcd)
{
uint8_t data;
lcd->PinHandle[LCD1602_RS](High);
lcd->PinHandle[LCD1602_RW](High);
lcd->PinHandle[LCD1602_EN](High);
lcd->Delayus(20);
data=lcd->GetByte();
lcd->PinHandle[LCD1602_EN](Low);
lcd->Delayus(5);
return data;
}
2.2.2、寫資料操作
我們想要在LCD1602顯示屏上顯示我們想要的訊息就需要向LCD1602顯示屏傳送命令和資料。傳送資料和傳送命令的區別僅是RS控制引腳的操作電平不同,具體的操作時序如下所示:
/*向LCD1602寫指令*/
static void WriteCommandToLCD1602(LCD1602ObjectType *lcd,uint8_t command)
{
lcd->PinHandle[LCD1602_RS](Low);
lcd->PinHandle[LCD1602_RW](Low);
lcd->SendByte(command);
lcd->PinHandle[LCD1602_EN](High);
lcd->Delayus(20);
lcd->PinHandle[LCD1602_EN](Low);
lcd->Delayus(5);
}
/*向LCD1602寫資料*/
static void WriteDatatoLCD1602(LCD1602ObjectType *lcd,uint8_t data)
{
lcd->PinHandle[LCD1602_RS](High);
lcd->PinHandle[LCD1602_RW](Low);
lcd->SendByte(data);
lcd->PinHandle[LCD1602_EN](High);
lcd->Delayus(20);
lcd->PinHandle[LCD1602_EN](Low);
lcd->Delayus(5);
}
3、驅動的使用
我們已經實現了LCD1602液晶顯示屏驅動程式,在接下來我們還需要設計一個簡單的應用驗證這一驅動設計是否正確。
3.1、宣告並初始化物件
使用基於物件的操作我們需要先得到這個物件,所以我們先要使用前面定義的LCD1602液晶顯示屏物件型別宣告一個LCD1602液晶顯示屏物件變數,具體操作格式如下:
LCD1602ObjectType lcd;
聲明瞭這個物件變數並不能立即使用,我們還需要使用驅動中定義的初始化函式對這個變數進行初始化。這個初始化函式所需要的輸入引數如下:
LCD1602ObjectType *lcd, //LCD1602物件指標
LCD1602PinSetType *PinHandle, //控制引腳操作函式指標陣列
LCD1602SendByteType sendByte, //傳送一個位元組函式指標
LCD1602GetByteType getByte, //讀取一個位元組函式指標
LCD1602DelayType delayus, //微秒延時函式指標
LCD1602DelayType delayms //毫秒延時函式指標
對於這些引數,物件變數我們已經定義了。主要的是我們需要定義幾個函式,並將函式指標作為引數。這幾個函式的型別如下:
/*定義引腳操作函式指標型別*/
typedef void (*LCD1602PinSetType)(uint8_t value);
/*定義傳送一個位元組操作函式指標*/
typedef void(*LCD1602SendByteType)(uint8_t data);
/*定義讀取一個位元組操作函式指標*/
typedef uint8_t(*LCD1602GetByteType)(void);
/*定義延時操作函式指標*/
typedef void (*LCD1602DelayType)(volatile uint32_t time);
對於這幾個函式我們根據樣式定義就可以了,具體的操作可能與使用的硬體平臺有關係。控制引腳的操作函式實際是3個,組成一個函式指標陣列,分別對應RS、RW、EN控制引腳。具體函式定義如下:
LCD1602PinSetType pinSets[3]={RsPinOperation,RwPinOperation,EnPinOperation};
/*RS控制引腳操作*/
static void RsPinOperation(uint8_t value)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_7,(GPIO_PinState)value);
}
/*RW控制引腳操作*/
static void RwPinOperation(uint8_t value)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_8,(GPIO_PinState)value);
}
/*EN控制引腳操作*/
static void EnPinOperation(uint8_t value)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_9,(GPIO_PinState)value);
}
/*從LCD1602讀一個位元組*/
static uint8_t ReadByteFromLCD(void)
{
uint8_t data=0;
data=(uint8_t)(GPIOD->IDR);
return data;
}
/*向LCD1602寫一個位元組*/
static void WriteByteToLCD(uint8_t data)
{
uint16_t value=GPIOD->ODR;
value=(value&&0xFF00)||data;
GPIOD->ODR=value;
}
對於延時函式我們可以採用各種方法實現。我們採用的STM32平臺和HAL庫,所以毫秒延時函式可以直接使用HAL_Delay()函式。微秒延時函式採用我們編寫的delayus於是我們可以呼叫初始化函式如下:
LCD1602Initialization(&lcd, //LCD1602物件指標
pinSets, //控制引腳操作函式指標陣列
WriteByteToLCD, //傳送一個位元組函式指標
ReadByteFromLCD, //讀取一個位元組函式指標
Delayus, //微秒延時函式指標
HAL_Delay //毫秒延時函式指標
);
3.2、基於物件進行操作
我們定義了物件變數並使用初始化函式給其作了初始化。接著我們就來考慮操作這一物件獲取我們想要的資料。我們在驅動中已經將獲取資料並轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用例項。
/*在LCD1602中顯示資料*/
void LCD1602Display(void)
{
float temp=20.5;
float pres=101.35;
float humi=34.6;
LCD1602DisplayClear(&lcd,LCD1602_AllLine),
Lcd1602ContentDisplay(&lcd,0x80, "T%0.1fC,P%0.1fKPa,H%0.1f%%", temp,pres, humi);
Lcd1602ContentDisplay(&lcd,0xC0, "T%0.1fC,P%0.1fKPa,H%0.1f%%", temp,pres, humi);
}
我們將顯示器清屏,然後再每一行都顯示溫度、壓力和溼度資料。
4、應用總結
我們已經設計並實現了LCD1602的驅動程式,並在此基礎上設計了簡單的驗證應用。我們可以正常讀寫LCD1602顯示屏,並且在LCD1602顯示屏正確心事我們想要的資訊,說明我們的驅動設計是沒有問題的。
在使用驅動時,有一點需要注意。因為在初始化函式中,對控制引腳的操作採用的時函式指標陣列,但這個陣列元素的順序不是隨意的,而是必須與列舉型別LCD1602PinType中定義的順序一致才能正確操作。
歡迎關注:
如果閱讀這篇文章讓您略有所得,還請點選下方的【好文要頂】按鈕。
當然,如果您想及時瞭解我的部落格更新,不妨點選下方的【關注我】按鈕。
如果您希望更方便且及時的閱讀相關文章,也可以掃描上方二維碼關注我的微信公眾號【木南創智】