1. 程式人生 > 其它 >15-CubeMx+Keil+Proteus模擬STM32 - LCD1602

15-CubeMx+Keil+Proteus模擬STM32 - LCD1602

本文例子參考《STM32微控制器開發例項——基於Proteus虛擬模擬與HAL/LL庫》
原始碼:https://github.com/LanLinnet/STM33F103R6

寫在前面

在前面幾節的基礎上,我們已經基本瞭解了STM32F103的GPIO、外部中斷、定時器、串列埠通訊和一些片內外設,接下來幾節都將對其常用的獨立外設進行介紹。

專案要求

掌握LCD1602的驅動方法,要求在螢幕第一行顯示“Hello World!”。

硬體設計

  1. 第一節的基礎上,在Proteus中新增電路如下圖所示。其中我們添加了一個LCD1602液晶顯示器LM016L

  2. LCD1602:
    1)簡介:LCD1602液晶顯示屏能夠顯示2行,每行16字元,共32個5x7或5x11的點陣字元。
    2)引腳:LCD1602採用標準的16腳介面,每個引腳的功能如下表所示。

    3)儲存器:

    • DDRAM-指示顯示字元的位置,其地址與字元顯示位置的關係如下表所示;
    • CGRAM-使用者自定義字模;
    • CGROM-內建160個常用字模,包括ASCII碼、日文假名和希臘字母,本專案使用ASCII碼顯示。

    4)控制指令:LCD1602共有11條控制指令,如下表所示

  3. 開啟CubeMX,建立工程。設定PA1-PA3、PC0-PC7均為GPIO_Output,點選“Categories”中的“GPIO”,將"GPIO Output level"設定為High,並設定“User Label”如下圖所示。

  4. 點選“Generator Code”生成Keil工程。

軟體編寫

  1. 考慮到程式碼的可移植性,這裡將LCD1602相關的功能程式碼全部封裝成函式並歸入標頭檔案“LCD1602.h”。我們可以先在...\15_LCD1602\Core\Src

    資料夾中建立該標頭檔案,此時Keil可能找不到對應檔案,可以直接將檔案拽入Keil中進行編輯,然後再在“main.c”檔案中進行include。

  2. 點選“Open Project”在Keil中開啟工程,開啟“LCD1602.h”,新增程式碼如下。

    #ifndef INC_LCD1602_H_
    #define INC_LCD1602_H_
    #include "main.h"
    
    //選擇資料暫存器
    #define RS_DataR() HAL_GPIO_WritePin(GPIOA, RS_Pin, GPIO_PIN_SET)
    #define RS_InstructionR() HAL_GPIO_WritePin(GPIOA, RS_Pin, GPIO_PIN_RESET)
    
    //選擇指令暫存器
    //讀操作
    #define RW_Read() HAL_GPIO_WritePin(GPIOA, RW_Pin, GPIO_PIN_SET)
    //寫操作
    #define RW_Write() HAL_GPIO_WritePin(GPIOA, RW_Pin, GPIO_PIN_RESET)
    
    //Enable操作:高電平-讀取資訊;下降沿-執行指令
    #define E_Set() HAL_GPIO_WritePin(GPIOA, E_Pin, GPIO_PIN_SET)
    #define E_Rst() HAL_GPIO_WritePin(GPIOA, E_Pin, GPIO_PIN_RESET)
    
    /*************************************自定義函式****************************************/
    //D0-D7設定方向:I-輸入;O-輸出
    void DataDir(char dir)
    {
    	GPIO_InitTypeDef GPIO_InitStruct = {0};
    	HAL_GPIO_WritePin(GPIOC, D0_Pin|D1_Pin|D2_Pin|D3_Pin|D4_Pin|D5_Pin|D6_Pin|D7_Pin, GPIO_PIN_SET);
    	GPIO_InitStruct.Pin = D0_Pin|D1_Pin|D2_Pin|D3_Pin|D4_Pin|D5_Pin|D6_Pin|D7_Pin;
    	GPIO_InitStruct.Pull = GPIO_PULLUP;
    	if(dir == 'I')
    	{
    		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    	}
    	else if(dir == 'O')
    	{
    		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    	}
    	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    }
    
    //D0-D7讀資料
    uint8_t ReadData()
    {
    	uint8_t dat=0;
    	//DataDir('I');
    	if(HAL_GPIO_ReadPin(GPIOC, D0_Pin)==GPIO_PIN_SET) dat|=0x01;
    	if(HAL_GPIO_ReadPin(GPIOC, D1_Pin)==GPIO_PIN_SET) dat|=0x02;
    	if(HAL_GPIO_ReadPin(GPIOC, D2_Pin)==GPIO_PIN_SET) dat|=0x04;
    	if(HAL_GPIO_ReadPin(GPIOC, D3_Pin)==GPIO_PIN_SET) dat|=0x08;
    	if(HAL_GPIO_ReadPin(GPIOC, D4_Pin)==GPIO_PIN_SET) dat|=0x10;
    	if(HAL_GPIO_ReadPin(GPIOC, D5_Pin)==GPIO_PIN_SET) dat|=0x20;
    	if(HAL_GPIO_ReadPin(GPIOC, D6_Pin)==GPIO_PIN_SET) dat|=0x40;
    	if(HAL_GPIO_ReadPin(GPIOC, D7_Pin)==GPIO_PIN_SET) dat|=0x80;
    	return dat;
    }
    
    //D0-D7寫資料
    void WriteData(uint8_t dat)
    {
    	uint16_t Set_Pins = 0, Rst_Pins = 0;
    	//DataDir('O');
    	if(dat & 0x01) Set_Pins |= D0_Pin;
    	else Rst_Pins |= D0_Pin;
    	if(dat & 0x02) Set_Pins |= D1_Pin;
    	else Rst_Pins |= D1_Pin;
    	if(dat & 0x04) Set_Pins |= D2_Pin;
    	else Rst_Pins |= D2_Pin;
    	if(dat & 0x08) Set_Pins |= D3_Pin;
    	else Rst_Pins |= D3_Pin;
    	if(dat & 0x10) Set_Pins |= D4_Pin;
    	else Rst_Pins |= D4_Pin;
    	if(dat & 0x20) Set_Pins |= D5_Pin;
    	else Rst_Pins |= D5_Pin;
    	if(dat & 0x40) Set_Pins |= D6_Pin;
    	else Rst_Pins |= D6_Pin;
    	if(dat & 0x80) Set_Pins |= D7_Pin;
    	else Rst_Pins |= D7_Pin;
    	
    	HAL_GPIO_WritePin(GPIOC, Set_Pins, GPIO_PIN_SET);
    	HAL_GPIO_WritePin(GPIOC, Rst_Pins, GPIO_PIN_RESET);
    }
    
    //LCD忙等待
    void LCD_Busy_Wait()
    {
    	uint8_t status;
    	DataDir('I');
    	RS_InstructionR();
    	RW_Read();
    	do
    	{
    		E_Set();
    		__NOP();
    		status = ReadData();
    		E_Rst();
    	}
    	while(status & 0x80);
    }
    
    //寫LCD指令
    void LCD_Write_Cmd(uint8_t cmd)
    {
    	DataDir('O');
    	WriteData(cmd);
    	RS_InstructionR();
    	RW_Write();
    	E_Rst();
    	RS_InstructionR();
    	RW_Write();
    	E_Set();
    	__NOP();
    	E_Rst();
    	LCD_Busy_Wait();
    }
    
    //寫LCD資料暫存器
    void LCD_Write_Data(uint8_t dat)
    {
    	DataDir('O');
    	WriteData(dat);
    	RS_DataR();
    	RW_Write();
    	E_Set();
    	__NOP();
    	E_Rst();
    	LCD_Busy_Wait();
    }
    
    //LCD初始化
    void LCD_Init()
    {
    	LCD_Write_Cmd(0x38);
    	HAL_Delay(2);
    	LCD_Write_Cmd(0x01);
    	HAL_Delay(2);
    	LCD_Write_Cmd(0x06);
    	HAL_Delay(2);
    	LCD_Write_Cmd(0x0c);
    	HAL_Delay(2);
    }
    
    //在x行(0-1),y列(0-15)顯示字串
    void LCD_ShowString(uint8_t x, uint8_t y, char *str)
    {
    	uint8_t i=0;
    	//設定顯示起始位置
    	if(x == 0)
    		LCD_Write_Cmd(0x80|y);
    	else if(x == 1)
    		LCD_Write_Cmd(0xc0|y);
    	//輸出字串
    	for(i=0; i<16 && str[i]!='\0'; i++)
    	{
    		LCD_Write_Data(str[i]);
    		HAL_Delay(2);
    	}
    }
    #endif //INC_LCD1602_H_
    
    
  3. 隨後我們需要在main.c檔案中的最前面引入我們自定義的標頭檔案

    /* USER CODE BEGIN Includes */
    #include “LCD1602.h”
    /* USER CODE END Includes */
    

    在main函式中定義需要在LCD中顯示的字串

    /* USER CODE BEGIN 1 */
    char str[]="Hello World!";  //輸出字串內容設定
    /* USER CODE END 1 */
    

    最後,呼叫我們自定義的函式對LCD進行操作

    /* USER CODE BEGIN 2 */
    LCD_Init();  //初始化LCD1602
    LCD_ShowString(0,0,str);  //LCD 顯示設定字串
    /* USER CODE END 2 */
    

聯合除錯

  1. 點選執行,生成HEX檔案。
  2. 在Proteus中載入相應HEX檔案,點選執行。可以看到LCD1602的第一行顯示了“Hello World!”。