15-CubeMx+Keil+Proteus仿真STM32 - LCD1602
本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》
源代码:https://github.com/LanLinnet/STM32F103R6
写在前面
在前面几节的基础上,我们已经基本了解了STM32F103的GPIO、外部中断、定时器、串口通信和一些片内外设,接下来几节都将对其常用的独立外设进行介绍。
项目要求
掌握LCD1602的驱动方法,要求在屏幕第一行显示“Hello World!”。
硬件设计
-
在第一节的基础上,在Proteus中添加电路如下图所示。其中我们添加了一个LCD1602液晶显示器
LM016L
。
-
LCD1602:
1)简介:LCD1602液晶显示屏能够显示2行,每行16字符,共32个5x7或5x11的点阵字符。
2)引脚:LCD1602采用标准的16脚接口,每个引脚的功能如下表所示。
3)存储器:- DDRAM-指示显示字符的位置,其地址与字符显示位置的关系如下表所示;
- CGRAM-用户自定义字模;
- CGROM-内置160个常用字模,包括ASCII码、日文假名和希腊字母,本项目使用ASCII码显示。
4)控制指令:LCD1602共有11条控制指令,如下表所示
- DDRAM-指示显示字符的位置,其地址与字符显示位置的关系如下表所示;
-
打开CubeMX,建立工程。设置PA1-PA3、PC0-PC7均为
GPIO_Output
,点击“Categories”中的“GPIO”,将"GPIO Output level"设置为High
,并设置“User Label”如下图所示。
-
点击“Generator Code”生成Keil工程。
软件编写
-
考虑到代码的可移植性,这里将LCD1602相关的功能代码全部封装成函数并归入头文件“LCD1602.h”。我们可以先在
...\15_LCD1602\Core\Src
文件夹中建立该头文件,此时Keil可能找不到对应文件,可以直接将文件拽入Keil中进行编辑,然后再在“main.c”文件中进行include。
-
点击“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_
-
随后我们需要在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 */
联合调试
- 点击运行,生成HEX文件。
- 在Proteus中加载相应HEX文件,点击运行。可以看到LCD1602的第一行显示了“Hello World!”。