15-CubeMx+Keil+Proteus仿真STM32 - LCD1602

本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》
源代码:https://github.com/LanLinnet/STM32F103R6

写在前面

在前面几节的基础上,我们已经基本了解了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!”。
posted @ 2022-05-23 11:36  Sheepeach  阅读(3501)  评论(0编辑  收藏  举报