第27章 LCD1602显示

第二十七章 LCD1602液晶显示

1. 导入

在前面章节, 我们已经学习过几种显示装置, 例如静态数码管、 动态数码管、8*8LED 点阵、 串口通信。 使用它们可以直观显示一些字符数据, 但是它们也有各种局限性, 比如显示字符数据太少, 硬件设计复杂、 代码编写难度大等。

这一章就来介绍一种非常简单且常用的显示装置--LCD1602 液晶显示器, 使用它可以显示更多的字符数字。 我们开发板上集成了一个 LCD1602 液晶显示器接口, 将LCD1602 液晶对应插入即可。 本章要实现的功能是: 系统运行时, 在 LCD1602 液晶上显示字符信息。

2. LCD1602介绍

2.1 LCD1602简介

1602 液晶也叫 1602 字符型液晶, 它能显示 2 行字符信息, 每行又能显示 16个字符。

它是一种专门用来显示字母、 数字、 符号的点阵型液晶模块。 它是由若干个 5x7 或者 5x10 的点阵字符位组成, 每个点阵字符位都可以用显示一个字符,每位之间有一个点距的间隔, 每行之间也有间隔, 起到了字符间距和行间距的作用, 正因为如此, 所以它不能很好的显示图片。 其实物图如下所示:

在上图中可以看到有 16 个管脚孔, 从左至右管脚编号顺序是 1-16, 其功能定义如下所示:

屏幕截图 2024 06 14 103338

有几个重要的管脚需要我们分析

  • 3脚:VL, 液晶显示偏压信号, 用于调整 LCD1602 的显示对比度, 一般会外接电位器用以调整偏压信号, 注意此脚电压为 0 时可以得到最强的对比度。

  • 4脚:RS, 数据/命令选择端, 当此脚为高电平时, 可以对 1602 进行数据字节的传输操作, 而为低电平时, 则是进行命令字节的传输操作。 命令字节, 即是用对 LCD1602 的一些工作方式作设置的字节; 数据字节, 即使用以在 1602 上显示的字节。 值得一提的是, LCD1602 的数据是 8 位的。

  • 5脚:R/W, 读写选择端。 当此脚为高电平可对 LCD1602 进行读数据操作,反之进行写数据操作。

  • 6脚:E, 使能信号, 其实是 LCD1602 的数据控制时钟信号, 利用该信号的上升沿实现对 LCD1602 的数据传输。

  • 7~14脚:8 位并行数据口, 而 51 单片机一组 IO 也是 8 位, 使得对 LCD1602的数据读写大为方便。

在LCD1602内部含有80个字节的DDRAM,他是用来寄存显示字符的。其地址和屏幕的对应关系如下:

屏幕截图 2024 06 14 103803

从上图可知, 不是所有的地址都可以直接用来显示字符数据, 只有第一行中的 00-0F, 第二行中的 40-4F 才能显示, 其他地址只能用于存储。

要显示字符时要先输入显示字符地址, 也就是告诉模块在哪里显示字符, 例如第二行第一个字符的地址是 40H, 那么是否直接写入 40H 就可以将光标定位在第二行第一个字符的位置呢? 这样不行, 因为写入显示地址时要求最高位 D7 恒定为高电平 1所以实际写入的数据应该是 01000000B(40H) +10000000B(80H)=11000000B(C0H)。 在 1602 中我们就用前 16 个就行了。 第二行也一样用前 16 个地址。

2.2 LCD1602常用指令

  • 清屏指令

屏幕截图 2024 06 14 103955

功能:

  1. 清除液晶显示器, 即将 DDRAM 的内容全部填入"空白"的 ASCII 码 20H;

  2. 光标归位, 即将光标撤回液晶显示屏的左上方。

  3. 将地址计数器(AC)的值设为 0。

  • 模式设置指令

屏幕截图 2024 06 14 104738

功能:

  1. 设定每次写入 1 位数据后光标的移位方向, 并且设定每次写入的一个字符是否移动。

  2. 0=写入新数据后光标左移    1=写入新数据后光标右移

  3. 0=写入新数据后显示屏不移动    1=写入新数据后显示屏整体右移1个字符

  • 显示开关控制指令

屏幕截图 2024 06 14 105344

功能:

控制显示器开/关、 光标显示/关闭以及光标是否闪烁。

  1. D:0=显示功能关    1=显示功能开

  2. C:0=无光标 1=有光标

  3. B:0=光标闪烁 1=光标不闪烁

  • 功能设定指令

屏幕截图 2024 06 14 110016

功能:

设定数据总线位数、显示的行数及字符

  1. DL:0=数据总线为4位 1=数据总线为8位

  2. N:0=显示1行 1=显示2行

  3. 0=5 x 7 点阵/每字符 1=5 x 10 点阵/每字符

2.3 LCD1602使用

要使用 LCD1602, 首先需要对其初始化, 即通过写入一些特定的指令实现。然后选择要在 LCD1602 的哪个位置显示并将所要显示的数据发送到 LCD 的DDRAM。 使用 LCD1602 通常都是用于写数据进去, 很少使用读功能。 LCD1602 操作步骤如下所示:

  • 初始化

  • 写命令(RS=L),设置显示坐标

  • 写数据(RS=H)

在此, 不需要读出它的数据的状态或者数据本身。 所以只需要看两个写时序:

  • 当要写指令字, 设置 LCD1602 的工作方式时: 需要把 RS 置为低电平, RW置为低电平, 然后将数据送到数据口 D0~D7, 最后 E 引脚一个高脉冲将数据写入。

  • 当要写入数据字, 在 1602 上实现显示时: 需要把 RS 置为高电平, RW 置为低电平, 然后将数据送到数据口 D0~D7, 最后 E 引脚一个高脉冲将数据写入。

写指令和写数据, 差别仅仅在于 RS 的电平不一样而已。 以下是 LCD1602 的时序图:

屏幕截图 2024 06 14 113029

从上图可以看到, 以上给的时间参数全部是 ns 级别的, 而 51 单片机的机器周期是 1us, 指令周期是 2-4 个机器周期, 所以即便在程序里不加延时程序, 也可以很好的配合 LCD1602 的时序要求了。

当要写命令字节的时候, 时间由左往右, RS 变为低电平, R/W 变为低电平,注意看是 RS 的状态先变化完成。 然后这时, DB0~DB7 上数据进入有效阶段, 接着 E 引脚有一个整脉冲的跳变, 接着要维持时间最小值为 tpw=400ns 的 E 脉冲宽度。 然后 E 引脚负跳变, RS 电平变化, R/W 电平变化。 这样便是一个完整的 LCD1602写命令的时序。

更多关于LCD1602参考:【常用传感器】LCD1602液晶工作原理详解及例程代码_lcd1602工作原理-CSDN博客

透彻理解液晶显示模组LCD1602指令集与驱动编程(1) - 阳光&技术 - 博客园

3. 硬件设计

本实验使用到硬件资源如下:

  • LCD1602 液晶

开发板上集成了一个 LCD1602 液晶接口, 下面来看下开发板上 LCD1602 液晶接口电路, 如下图所示:

屏幕截图 2024 06 14 124719

从上图中可知, LCD1602 的 8 位数据口 DB0-DB7 与单片机的 P0.0-P0.7 管脚连接, LCD1602 的 RS、 RW、 E 脚与单片机的 P2.6、 P2.5、 P2.7 管脚连接。 RJ1是一个电位器, 用来调节 LCD1602 对比度即显示亮度。

4. 软件设计

本章所要实现的功能是:在LCD1602上显示字符信息

  • LCD1602驱动函数
#ifndef _lcd1602_H
#define _lcd1602_H

#include "public.h"

//LCD1602数据口4位和8位定义,若为1,则为LCD1602四位数据口驱动,反之为8位
#define LCD1602_4OR8_DATA_INTERFACE    0 // 默认使用8位数据口LCD1602

//管脚定义
sbit LCD1602_RS = P2^6; // 数据命令选择
sbit LCD1602_RW = P2^5; // 读写选择
sbit LCD1602_E  = P2^7; // 使能信号
#define LCD1602_DATAPORT P0    // 宏定义LCD1602数据端口


//函数声明
void lcd1602_init(void); // 初始化
void lcd1602_clear(void); // 清屏指令
void lcd1602_show_string(unsigned char x,unsigned char y,unsigned char *str); // 字符显示函数

#endif
#include "lcd1602.h"

// 1602写入函数
#if (LCD1602_4OR8_DATA_INTERFACE == 0) // 8位LCD
void lcd1602_write_cmd(unsigned char cmd)
{
    LCD1602_RS = 0; // 选择命令
    LCD1602_RW = 0; // 选择写
    LCD1602_E = 0;  // 使能
    LCD1602_DATAPORT = cmd; // 准备命令
    delay_ms(1);

    LCD1602_E = 1; // 使能脚E先上升沿写入
    delay_ms(1);

    LCD1602_E=0; // 使能脚E后负跳变完成写入    
}
#else    // 4位LCD
void lcd1602_write_cmd(unsigned char cmd)
{
    LCD1602_RS = 0;//选择命令
    LCD1602_RW = 0;//选择写
    LCD1602_E = 0;
    LCD1602_DATAPORT = cmd;//准备命令
    delay_ms(1);

    LCD1602_E = 1;//使能脚E先上升沿写入
    delay_ms(1);

    LCD1602_E = 0;//使能脚E后负跳变完成写入

    LCD1602_DATAPORT=cmd<<4; // 准备命令
    delay_ms(1);

    LCD1602_E = 1; // 使能脚E先上升沿写入
    delay_ms(1);

    LCD1602_E = 0;//使能脚E后负跳变完成写入    
}
#endif

// 写入数据函数
#if (LCD1602_4OR8_DATA_INTERFACE == 0) // 8位LCD
void lcd1602_write_data(unsigned char dat) 
{
    LCD1602_RS = 1; // 选择数据
    LCD1602_RW = 0; // 选择写
    LCD1602_E = 0;

    LCD1602_DATAPORT = dat; // 准备数据
    delay_ms(1);

    LCD1602_E = 1; // 使能脚E先上升沿写入
    delay_ms(1);

    LCD1602_E = 0; // 使能脚E后负跳变完成写入        
}
#else
void lcd1602_write_data(unsigned char dat) // 4位
{
    LCD1602_RS = 1; // 选择数据
    LCD1602_RW = 0; // 选择写
    LCD1602_E = 0;

    LCD1602_DATAPORT = dat;//准备数据
    delay_ms(1);

    LCD1602_E = 1; // 使能脚E先上升沿写入
    delay_ms(1);

    LCD1602_E = 0; // 使能脚E后负跳变完成写入

    LCD1602_DATAPORT=dat<<4; // 准备数据
    delay_ms(1);

    LCD1602_E = 1; // 使能脚E先上升沿写入
    delay_ms(1);

    LCD1602_E = 0; // 使能脚E后负跳变完成写入        
}
#endif

// 1602初始化函数
#if (LCD1602_4OR8_DATA_INTERFACE == 0) // 8位LCD
void lcd1602_init(void)
{
    lcd1602_write_cmd(0x38); // 数据总线8位,显示2行,5*7点阵/字符
    lcd1602_write_cmd(0x0c); // 显示功能开,无光标,光标闪烁
    lcd1602_write_cmd(0x06); // 写入新数据后光标右移,显示屏不移动
    lcd1602_write_cmd(0x01); // 清屏    
}
#else
void lcd1602_init(void) // 4位
{
    lcd1602_write_cmd(0x28); // 数据总线4位,显示2行,5*7点阵/字符
    lcd1602_write_cmd(0x0c); // 显示功能开,无光标,光标闪烁
    lcd1602_write_cmd(0x06); // 写入新数据后光标右移,显示屏不移动
    lcd1602_write_cmd(0x01); // 清屏    
}
#endif

// 1602清屏函数
void lcd1602_clear(void)
{
    lcd1602_write_cmd(0x01);    
}

// 1602显示字符串函数(xy代表坐标,str代表字符串)
void lcd1602_show_string(unsigned char x,unsigned char y,unsigned char *str)
{
    unsigned char i=0;

    if(y>1 || x>15)
        return; // 行列参数不对则强制退出

    if(y < 1) // 第1行显示
    {    
        while(*str != '\0')//字符串是以'\0'结尾,只要前面有内容就显示
        {
            if(i <16 - x)//如果字符长度超过第一行显示范围,则在第二行继续显示
            {
                lcd1602_write_cmd(0x80+i+x); // 第一行显示地址设置    
            }
            else
            {
                lcd1602_write_cmd(0x40+0x80+i+x-16); // 第二行显示地址设置    
            }
            lcd1602_write_data(*str); // 显示内容

            str++; // 指针递增
            i++;    
        }    
    }
    else // 第2行显示
    {
        while(*str != '\0')
        {
            if(i < 16 - x) //如果字符长度超过第二行显示范围,则在第一行继续显示
            {
                lcd1602_write_cmd(0x80+0x40+i+x);    
            }
            else
            {
                lcd1602_write_cmd(0x80+i+x-16);    
            }
            lcd1602_write_data(*str);

            str++;
            i++;    
        }    
    }                
}
  • 主函数
#include "public.h"
#include "lcd1602.h"

void main()
{    
    lcd1602_init();//LCD1602初始化

    lcd1602_show_string(0,0,"Hello hazy1k"); // 第一行显示字符
    lcd1602_show_string(0,1,"0123456789"); // 第二行显示数字

    while(1)
    {

    }    
}

与前面一样,难点在于配置,可以看到主函数倒是很简单,这个代码可以不用很深理解,但是还是要理解一些重要部分:

在代码中可以看到有如下格式:

#if (LCD1602_4OR8_DATA_INTERFACE==0)
...( 省略内容 1)
#else
...( 省略内容 2)
#endif

当初学习C语言基础的时候应该没有遇到过,这是 C 语言中的条件编译, 如果 LCD1602_4OR8_DATA_INTERFACE 值等于 0,则执行省略内容 1 部分, 否则执行省略内容 2 部分, 这个和 if...else 是类似的。而 LCD1602_4OR8_DATA_INTERFACE 是在 lcd1602.h 中定义好的宏, 用于选择使用的 LCD1602 是 8 位数据口还是 4 位数据口, 通过这个宏就可以兼容 8 位和 4 位 2款 LCD1602 驱动, 非常方便。 如果大家觉得代码过长, 可以将兼容的一块去掉,这样代码就简短明了。

lcd1602_write_cmd 函数是 LCD1602 写命令, lcd1602_write_data 函数是LCD1602 写数据, 它们之间的区别仅在于控制脚 RS 的值, 如果为 0 则写命令,为 1 则写数据。 对于 8 位数据和 4 位数据口的驱动, 不同之处在于 8 位数据是一次性传输到端口, 而 4 位数据口需要将一个字节数据分两次传输, 高 4 位和低 4位

lcd1602_init 函数是 LCD1602 初始化函数, 根据其命令设置相应的值, 开启LCD1602 显示。
    lcd1602_clear 函数是 LCD1602 清屏函数, 实际上也是调用lcd1602_write_cmd 函数写 0X01 命令。

cd1602_show_string 函数是 LCD1602 显示字符函数, 该函数有 3 个入口参数, x 和 y 用来确定显示的坐标, x 范围是 0-15, y 范围是 0-1。 str 参数是一个指针类型变量, 用于传递显示的内容。 函数内首先判断 xy 坐标是否有效, 无效则强制退出, 然后根据 xy 值在对应的坐标位置显示内容。

5. 小结

这一章看起来赶紧难,但是我们完全可以只简单了解一下,后面要用到这个1602显示就只需要封装起来就行了,直接使用不用理解,但是主函数中的内容还是要理解哈

总得来说,就是一个配置的事,网上都有现成的copy,或者直接复制我的也行,如果学有余力可以尝试自己编写配置函数。


2024.7.24 第一次修订

2024.8.24 第二次修订,后期不再维护

posted @ 2024-08-24 10:04  hazy1k  阅读(31)  评论(0编辑  收藏  举报