外设驱动库开发笔记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中定义的顺序一致才能正确操作。

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499316.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

滤波器开发之四:基于算术平均的中值滤波器

在信号采集系统中,除了我们感兴趣的数据外,难免会有一些来自于环境的干扰信号。但我们总希望我们得到的数据是纯净而真实的,为了达到这个目标,我们不得不想办法去除这些干扰信号,于是滤波器就成为我们必不可少的帮手。…

LwIP应用开发笔记之十一:LwIP带操作系统UDP服务器

我们已经实现了在FreeRTOS系统上的LwIP的移植工作,但只是简单的在系统平台上跑了起来。我们还希望能做更多的事情,这一节我们就在FreeRTOS系统上实现基于LwIP的UDP服务器。 1、UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议…

滤波器开发之五:基于算术平均的限幅滤波器

通过AD采集数据时,我们总是希望采集到的数据是纯净而真实的,而实际上环境中存在太多的干扰信号,为了让我们得到的数据尽可能地接近实际值,我们需要降低这些干扰信号的影响。所以软件实现的数字滤波器应运而生,这一篇我…

外设驱动库开发笔记34:OLED显示屏驱动

现在OLED显示屏在嵌入式系统中应用的越来越多。对于一些显示信息不太复杂,以显示信息为主的需求,我们一般会选择OLED显示屏。在这一篇中,我们将讨论OLED显示屏驱动的设计与实现。 1、功能概述 从使用的情况来说,较为常用的是0.96…

外设驱动库开发笔记35:迪文触摸屏驱动

有些时候嵌入式系统也需要显示更为复杂的图形,需要更丰富的数据展示。为此,我们需要更大,色彩更丰富,带触屏的显示屏,当然性价比更高就最好了。在我们的项目中遇到此类需求,我们有时会选择DWIN触摸屏。在本…

快速实现一个室内空气质量检测仪

冬天我们大多会关闭门窗,而依靠暖通空调设备来维持室内温度。而在保证居室温度的同时,我们也希望保持居室内大气环境的健康度。鉴于此,我们设计了一个简单的室内空气质量检测器。 1、系统概述 我们依靠暖通空调设备来维持室内温度、湿度和通…

外设驱动库开发笔记36:NTC负温度系数热电阻测温驱动

在嵌入式产品中,温度检测非常常见。在成本比较敏感而精度要求较低时,NTC电阻是个不错的选择。在这一篇中,我们将讨论如何和设计并实现一个通用的NTC驱动,以便在后续的项目中更方便的复用。 1、功能概述 NTC是指随温度上升电阻呈指…

外设驱动库开发笔记37:S1336-5BQ光敏二极管作为光度计驱动

光敏二极管能够实现很多应用,用于光度检测即是其一。我们在一些产品中就曾使用S1336-5BQ光敏二极管进行光度值检测。所以在本篇中,我们将讨论如何设计并实现S1336-5BQ光敏二极管用于光度检测的驱动。 1、功能概述 根据相关的资料,光电二极管…

PID控制器改进笔记之六:改进PID控制器之参数设定

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。这篇中我们来讨论改进PID控制…

软件设计开发笔记1:基于状态机的程序设计

在编码实现的过程中,我们会经常使用到条件判断结构,而且使用起来很方便。但是在需要转移的状态比较多,或是条件比较复杂时,我们就可能需要很长的条件判断结构来处理。不过,过于复杂的条件判断结构会给代码的编写和维护…

外设驱动库开发笔记38:RTD热电阻测温驱动

我们已经讨论过多种温度检测方式,但我们尚未关注热电阻温度检测,但热电阻测温在工业环境中是非常常见的。尽管有很多集成的数字式的热电阻接口元器件,但这些器件不但成本较高,灵活性也大打折扣。所以我们有时会使用更简单灵活的电…

外设驱动库开发笔记39:按键操作驱动

按键在我们的项目中是经常使用到的组件。一般来说,我们都是在用到按键时直接针对编码,但这样每次都做很多重复性的工作。所以在这里我们考虑做一般性抽象得到一个可应用于按键操作的通用性驱动程序。 1、功能概述 按键操作在我们的产品种经常用到&#…

外设驱动库开发笔记40:AT25xxx外部存储器驱动

我们在前面开发过AT24CXX系列EEPROM存储器,它使用的是I2C接口。不过有时候我们也会使用SPI接口的EEPROM存储器。在这一篇我们将来讨论AT25XXX系列EEPROM存储器的驱动设计、实现及使用。 1、功能概述 AT25XXX系列EEPROM存储器采用SPI接口,因其操作简单且…

闪存中的键值对:无文件系统 minINI

许多嵌入式系统应用需要以持久的方式存储某种数据:校准值、设置或日志信息。对于较少的数据量,使用外部存储器或文件系统是一种过度大材小用。在许多系统中,我使用minINI以“ini-file”的方式存储键值解析,但它需要使用某种文件系…

外设驱动库开发笔记41:ADS1256 ADC驱动

我们经常会碰到多通道AD采集的需求,有时候甚至需要高精度的ADC器件。本篇我们将来设计并实现ADS1256模数转换器的驱动。并简单讨论该驱动使用方式。 1、功能概述 ADS1256是TI公司推出的一款低噪声高分辨率的24位Sigma-Delta(E-v)模数转换器(ADC)。E-vADC与传统的逐…

PID参数自整定库之一:继电反馈整定算法

在前述的篇章中,我们实现了PID控制器并在后续对其进行了改进。但作为经典PID控制器还存在PID参数整定的问题。通常我们可以采取人工整定的办法,但人工整定涉及到比较专业的知识,而且找到合适的参数本身也不是一件容易的事,所以人们…

外设驱动库开发笔记42:DAC8552 DAC驱动

模拟信号输出是经常会遇到的应用需求,解决的办法应多种,但我们使用最多的还是数模转换。对于不同的数模转换器我们需要为其编写适用的驱动程序,在这一篇中我们就来考虑如何实现DAC8552高精度模数转换器的驱动程序。 1、功能概述 该DAC8552是…

软件设计开发笔记2:基于QT设计串口调试工具

串口通信是我们经常会遇到的问题。很多时候当我们设计一个串口应用时,我们希望有一个简便的、可视的方式来验证它。这一篇中我们就来基于QT设计一个串口调试工具。 1、概述 在开始软件设计之前,我们来简略地分析一下这样一个小软件其要包含的主要内容有…

外设驱动库开发笔记43:GPIO模拟SPI驱动

SPI总线是我们常用的串行设备接口,一般情况下我们都会适应硬件SPI接口,但有些时候当硬件端口不足时,我们也希望可以使用软件来模拟SPI硬件接口,特别是要求不是很高的时候。在这一篇中我们将来讨论如何使用GPIO和软件来模拟SPI通讯…

外设驱动库开发笔记44:DDC114 ADC驱动

在产品设计过程中,很多时候都会用到ADC器件,而在一些特殊场合还需要一些特别的ADC器件。我们在这篇中将讨论常用于医疗器件方面的,DDC114这款电流输入ADC,并为其设计一个驱动程序。 1、功能概述 模数转换器DDC114是一款电流输入型…