一 . 51 单片机与 LCD1602 的通信代码概述
LCD1602 是一种字符型液晶显示器,可显示 2 行 16 列字符,51 单片机与它的通信基于并行接口(8 位数据总线),通过控制引脚和时序配合实现数据传输。
1. 硬件连接(代码中定义的引脚)
- 数据总线(8 位):通过P0 口与LCD的8pin数据总线连接,用于传输命令或数据.
- 控制引脚(将单片机特定IO口定义为一下形式):
LCD_RS(寄存器选择):0 表示传输命令(如初始化、清屏),1 表示传输数据(如字符)。LCD_RW(读写选择):0 表示写操作(单片机→LCD),1 表示读操作(LCD→单片机,代码中未使用,固定为 0)。LCD_EN(使能信号):高电平有效,下降沿时 LCD 锁存总线上的数据 / 命令(核心控制信号)。
// 引脚配置(硬件连接定义) sbit LCD_RS = P2^6; // RS引脚接P2.6(寄存器选择:0=命令,1=数据) sbit LCD_RW = P2^5; // RW引脚接P2.5(读写选择:0=写,1=读) #define LCD_DataPort P0 // 数据口接P0(8位数据总线) sbit LCD_EN = P2^7; // EN引脚接P2.7(使能信号:高脉冲锁存数据)2. 通信时序(写操作流程)
51 单片机向 LCD1602 写入数据 / 命令的核心流程如下(以写命令为例):
- 设置 RS 和 RW:
LCD_RS=0(传输命令)、LCD_RW=0(写)。 - 输出数据 / 命令:将 8 位命令通过
LCD_DataPort(P0 口)输出到 LCD。 - 产生使能脉冲:
LCD_EN=1(LCD 准备接收)→ 延时(确保数据稳定)→LCD_EN=0(下降沿锁存数据,LCD 开始处理)。 - 等待处理完成:延时一段时间(
LCD_Delay),确保 LCD 完成命令 / 数据的处理(避免连续操作导致错误)。
void LCD_WriteCommand(unsigned char Command) { LCD_RS = 0; // RS=0:表示传输命令 LCD_RW = 0; // RW=0:表示写操作 LCD_DataPort = Command; // 命令通过数据口输出 LCD_EN = 1; // EN=1:使能信号有效,LCD准备锁存数据 LCD_Delay(); // 延时等待数据稳定 LCD_EN = 0; // EN=0:下降沿锁存命令,完成写入 LCD_Delay(); // 延时等待LCD处理命令 }写数据的流程与写命令完全相同,仅需将LCD_RS设为 1。
3. 初始化流程
LCD1602 上电后需先初始化才能正常工作,代码中LCD_Init函数按以下步骤传输命令:
0x38:功能设置→8 位数据接口、2 行显示、5×7 点阵字符(适配 LCD1602 硬件特性)。0x0c:显示控制→开启显示、关闭光标、光标不闪烁(根据需求配置显示状态)。0x06:输入方式→数据写入后光标自动右移,屏幕不滚动(确保后续字符连续显示)。0x01:清屏→清除所有显示内容,光标复位到左上角(初始化完成后准备显示).
void LCD_Init() { LCD_WriteCommand(0x38); // 功能设置:8位数据接口,2行显示,5×7点阵字符 LCD_WriteCommand(0x0c); // 显示控制:显示开,光标关,光标不闪烁 LCD_WriteCommand(0x06); // 输入方式设置:数据写入后光标自动右移,屏幕不滚动 LCD_WriteCommand(0x01); // 清屏命令:清除所有显示内容,光标复位到原点 }4.字符显示
LCD1602 内部有字符发生器 ROM(CGROM),存储了常用 ASCII 码字符的点阵数据。当单片机发送字符的 ASCII 码(如'A'的 0x41),LCD 会自动从 CGROM 中读取对应点阵并显示。
5.光标定位
通过函数LCD_SetCursor用于设置 LCD1602 液晶显示器的光标位置,以便从光标所在位置开始并往后写入字符或字符串或数据。下面结合 LCD1602 的硬件特性和代码逻辑详细解释:
1. 函数功能与参数
- 功能:将 LCD1602 的光标定位到指定的行(
Line)和列(Column),后续的写数据操作会从该位置开始显示。 - 参数:
Line:行位置,范围1~2(LCD1602 通常为 2 行显示)。Column:列位置,范围1~16(LCD1602 每行可显示 16 个字符)。
2. 核心原理:LCD1602 的地址映射
LCD有2行16列一共32个方格,每个方格显示一个字符,每个方格都是一个5×7的点阵.
LCD1602 内部有一块DDRAM(数据显示存储器),每个方格在 DDRAM 中存在一个唯一地址。要在指定位置显示字符,需先通过命令将光标定位到特定方格。
- DDRAM 地址规则:
- 第 1 行所有方格的地址范围为
0x00 ~ 0x0F(对应列 1~16)。 - 第 2 行方格的地址范围为
0x40 ~ 0x4F(对应列 1~16)。
- 第 1 行所有方格的地址范围为
- 设置 DDRAM 地址的命令格式:
0x80 | 地址值(0x80是固定前缀,用于告知 LCD 这是一个 “设置 DDRAM 地址” 的命令,后续 7 位为某个方格的地址)。
void LCD_SetCursor(unsigned char Line, unsigned char Column) { if (Line == 1) { // 行1:设置光标到特定位置的命令格式: 0x80 | 地址值 //假设要将光标设置在第一行第一列的方格中,该方格的地址值为0x00,所以代码为(Column - 1) LCD_WriteCommand(0x80 | (Column - 1)); } else if (Line == 2) { // 行2: LCD_WriteCommand(0x80 | (Column - 1 + 0x40)); } }6.数字 / 进制转换
数字(如 123)需先拆分为单个数字(1、2、3),转换为 ASCII 码('1'=0x31等)后,再按字符显示流程发送给 LCD。十六进制 / 二进制同理,需先拆分每一位并转换为对应字符(A~F 或 0~1)。
二 . LCD1602.c
#include <REGX52.H> // 51单片机寄存器定义头文件 // 引脚配置(硬件连接定义) sbit LCD_RS = P2^6; // RS引脚接P2.6(寄存器选择:0=命令,1=数据) sbit LCD_RW = P2^5; // RW引脚接P2.5(读写选择:0=写,1=读) #define LCD_DataPort P0 // 数据口接P0(8位数据总线) sbit LCD_EN = P2^7; // EN引脚接P2.7(使能信号:高脉冲锁存数据) // 内部函数定义(用户无需直接调用) /** * @brief LCD1602延时函数 * @param 无 * @retval 无 * @note 12MHz晶振下,该函数执行约1ms,用于满足LCD时序要求 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); // 内层循环:239次递减 } while (--i); // 外层循环:2次递减(总延时≈1ms) } /** * @brief 向LCD1602写入命令 * @param Command 要写入的命令(8位二进制,参考LCD1602 datasheet) * @retval 无 * @note 命令用于配置LCD(如初始化、设置光标、清屏等) */ void LCD_WriteCommand(unsigned char Command) { LCD_RS = 0; // RS=0:表示写入命令 LCD_RW = 0; // RW=0:表示写操作 LCD_DataPort = Command; // 命令通过数据口输出 LCD_EN = 1; // EN=1:使能信号有效,LCD准备锁存数据 LCD_Delay(); // 延时等待数据稳定 LCD_EN = 0; // EN=0:下降沿锁存命令,完成写入 LCD_Delay(); // 延时等待LCD处理命令 } /** * @brief 向LCD1602写入数据 * @param Data 要写入的数据(8位ASCII码,对应显示字符) * @retval 无 * @note 数据用于在LCD上显示(如字符、数字等) */ void LCD_WriteData(unsigned char Data) { LCD_RS = 1; // RS=1:表示写入数据 LCD_RW = 0; // RW=0:表示写操作 LCD_DataPort = Data; // 数据通过数据口输出 LCD_EN = 1; // EN=1:使能信号有效,LCD准备锁存数据 LCD_Delay(); // 延时等待数据稳定 LCD_EN = 0; // EN=0:下降沿锁存数据,完成写入 LCD_Delay(); // 延时等待LCD处理数据 } /** * @brief 设置LCD1602光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 * @note LCD1602内部有地址映射,行1起始地址0x00,行2起始地址0x40 */ void LCD_SetCursor(unsigned char Line, unsigned char Column) { if (Line == 1) { // 行1:地址=0x80(命令前缀) + (列号-1)(列偏移) LCD_WriteCommand(0x80 | (Column - 1)); } else if (Line == 2) { // 行2:地址=0x80(命令前缀) + 0x40(行2偏移) + (列号-1)(列偏移) LCD_WriteCommand(0x80 | (Column - 1 + 0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 * @note 按LCD1602时序要求,依次发送初始化命令 */ void LCD_Init() { LCD_WriteCommand(0x38); // 功能设置:8位数据接口,2行显示,5×7点阵字符 LCD_WriteCommand(0x0c); // 显示控制:显示开,光标关,光标不闪烁 LCD_WriteCommand(0x06); // 输入方式设置:数据写入后光标自动右移,屏幕不滚动 LCD_WriteCommand(0x01); // 清屏命令:清除所有显示内容,光标复位到原点 } /** * @brief 在LCD1602指定位置显示单个字符(实现) * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 * @note 先设置光标位置,再写入字符数据 */ void LCD_ShowChar(unsigned char Line, unsigned char Column, char Char) { LCD_SetCursor(Line, Column); // 设置光标到目标位置 LCD_WriteData(Char); // 写入字符数据(ASCII码) } /** * @brief 在LCD1602指定位置显示字符串(实现) * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 * @note 循环写入字符串中的每个字符,直到遇到'\0'结束符 */ void LCD_ShowString(unsigned char Line, unsigned char Column, char *String) { unsigned char i; LCD_SetCursor(Line, Column); // 设置起始光标位置 for (i = 0; String[i] != '\0'; i++) // 遍历字符串 { LCD_WriteData(String[i]); // 逐个写入字符 } } /** * @brief 内部辅助函数:计算X的Y次方(用于数字拆分) * @param X 底数(此处固定为10/16/2,对应十进制/十六进制/二进制) * @param Y 指数(数字位数) * @retval 计算结果(X^Y) */ int LCD_Pow(int X, int Y) { unsigned char i; int Result = 1; for (i = 0; i < Y; i++) { Result *= X; // 循环相乘Y次 } return Result; } /** * @brief 在LCD1602指定位置显示无符号数字(实现) * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字 * @param Length 显示位数 * @retval 无 * @note 按位拆分数字(如123拆分为1、2、3),转换为ASCII码后显示 */ void LCD_ShowNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length) { unsigned char i; LCD_SetCursor(Line, Column); // 设置起始位置 for (i = Length; i > 0; i--) // 从高位到低位处理 { // 取第i位数字(如i=3时,123 / 100 %10 = 1),加'0'转换为ASCII码 LCD_WriteData(Number / LCD_Pow(10, i - 1) % 10 + '0'); } } /** * @brief 在LCD1602指定位置显示有符号数字(实现) * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字 * @param Length 显示位数(含符号位) * @retval 无 * @note 先显示符号(+/-),再按无符号数处理绝对值 */ void LCD_ShowSignedNum(unsigned char Line, unsigned char Column, int Number, unsigned char Length) { unsigned char i; unsigned int Number1; // 用于存储绝对值(转为无符号处理) LCD_SetCursor(Line, Column); if (Number >= 0) { LCD_WriteData('+'); // 正数显示'+' Number1 = Number; } else { LCD_WriteData('-'); // 负数显示'-' Number1 = -Number; // 取绝对值 } // 后续处理与无符号数相同(Length已包含符号位,实际数字位数为Length-1) for (i = Length; i > 0; i--) { LCD_WriteData(Number1 / LCD_Pow(10, i - 1) % 10 + '0'); } } /** * @brief 在LCD1602指定位置显示十六进制数字(实现) * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字 * @param Length 显示位数 * @retval 无 * @note 0~9直接显示,10~15转换为A~F显示 */ void LCD_ShowHexNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length) { unsigned char i, SingleNumber; // SingleNumber存储每一位十六进制数 LCD_SetCursor(Line, Column); for (i = Length; i > 0; i--) { SingleNumber = Number / LCD_Pow(16, i - 1) % 16; // 取第i位十六进制数 if (SingleNumber < 10) { LCD_WriteData(SingleNumber + '0'); // 0~9:直接加'0'转ASCII } else { LCD_WriteData(SingleNumber - 10 + 'A'); // 10~15:转A~F } } } /** * @brief 在LCD1602指定位置显示二进制数字(实现) * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字 * @param Length 显示位数 * @retval 无 * @note 按位拆分二进制数,每一位为0或1,直接显示 */ void LCD_ShowBinNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length) { unsigned char i; LCD_SetCursor(Line, Column); for (i = Length; i > 0; i--) { // 取第i位二进制数(如i=3时,Number / 2^(i-1) %2),加'0'转ASCII LCD_WriteData(Number / LCD_Pow(2, i - 1) % 2 + '0'); } }三 . LCD1602.h
#ifndef __LCD1602_H__ // 防止头文件重复包含 #define __LCD1602_H__ // 用户可调用的函数声明 /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 * @note 初始化完成后,LCD1602进入可显示状态 */ void LCD_Init(); /** * @brief 在LCD1602指定位置显示单个字符 * @param Line 行位置,范围:1~2(LCD1602共2行) * @param Column 列位置,范围:1~16(每行16个字符) * @param Char 要显示的字符(ASCII码字符) * @retval 无 */ void LCD_ShowChar(unsigned char Line, unsigned char Column, char Char); /** * @brief 在LCD1602指定位置开始显示字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串(以'\0'结尾) * @retval 无 * @note 字符串长度超过剩余显示位置时,超出部分不显示 */ void LCD_ShowString(unsigned char Line, unsigned char Column, char *String); /** * @brief 在LCD1602指定位置开始显示无符号数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535(16位无符号数) * @param Length 要显示的数字位数,范围:1~5(不足补前导0) * @retval 无 */ void LCD_ShowNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length); /** * @brief 在LCD1602指定位置开始显示有符号数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767(16位有符号数) * @param Length 要显示的数字位数(含符号位),范围:1~5 * @retval 无 * @note 正数前显示'+',负数前显示'-',不足补前导0 */ void LCD_ShowSignedNum(unsigned char Line, unsigned char Column, int Number, unsigned char Length); /** * @brief 在LCD1602指定位置开始显示十六进制数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF(16位无符号数) * @param Length 要显示的位数,范围:1~4(不足补前导0) * @retval 无 * @note 字母A~F大写显示 */ void LCD_ShowHexNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length); /** * @brief 在LCD1602指定位置开始显示二进制数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF(16位无符号数) * @param Length 要显示的位数,范围:1~16(不足补前导0) * @retval 无 */ void LCD_ShowBinNum(unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length); #endif