1、液晶显示电路
2、电路原理简介
1. 电路组件与功能
-
PIC单片机(PIC16F887):
- 主控制器,负责处理输入输出。
-
LCD显示屏(LM061):
- 驱动数码管显示器,以显示时间和日期信息。
支持多个段显示,简化了与单片机的连接。
- 驱动数码管显示器,以显示时间和日期信息。
-
数码管(MAX7219):
- 驱动多个数码管,简化与数码管的连接和控制。通过串行接口与PIC单片机通信,显示日期和时间信息。
DS1302(实时时钟芯片):
-
用于记录当前的日期和时间。
通过 SPI 接口与单片机连接,传输时间数据。
-
电阻排(RP1):
- 作为限流电阻,确保LCD和数码管的正常工作,保护电路不受过流影响。
-
电源(B1):
- 为DS1302后备电源,通常为3V至5V(如 CR2032)以备份时间,即使在断电情况下也能保持计时。。
-
晶体振荡器(X2):
- 连接在 DS1302 上,提供稳定的时钟信号(通常为 32.768 kHz)以确保时间准确计时。
-
数码管(LEDMPX):
-
用于显示时钟信息,例如小时、分钟、秒和日期。
通过 MAX7219 进行控制和数据显示。2. 连接说明
-
PIC单片机与LCD的连接:
- RS、RW和E引脚连接至PIC的相应引脚。
- 数据引脚D0至D7连接,进行数据传输。
-
PIC单片机与MAX7219的连接:
- 控制引脚(如DIN、LOAD和CLK)连接至单片机的相应端口,以实现对数码管的控制。
-
单片机与DS1302的连接:
- SCL、SDA引脚连接至单片机的对应引脚,实现I2C通信。
-
RP1与显示模块的连接:
- 限流电阻连接在LCD和数码管的适当位置,以保护组件。
3. 工作原理
-
时间显示:
- DS1302通过晶体振荡器提供的时钟信号来保持当前时间,并定期更新数码管的显示。
-
日期显示:
- DS1302维护当前日期,由PIC单片机通过LCD和数码管显示。
-
数据传输:
- PIC与MAX7219通信,通过串行数据将数字信息发送至数码管进行显示,从而简化多位数码管的控制。
-
控制逻辑:
- 程序在PIC中运行,周期性更新LCD和数码管显示,确保时间和日期的准确性。
3、软件代码
3.1 lcd1602.c
/*********************************************************************
* 模块名称: Lcd1602.hc
* 模块名:LCD1602 控制模块
* 功能描述: 该模块定义了与1602 LCD屏幕交互所需的宏和函数原型
* 创 建 人:bianjingyuan
* 日期:2024-10-30
* 编译环境:Windows XP + MPLAB IDE 8.83 + Picc 9.60
* 版本号:1.0
* 修改记录:
*********************************************************************/
#include "Lcd1602.h"
#include "PIC16F887_Delay.h"
// LCD控制命令定义
#define Clear_Display 0x01 // 清除显示命令
#define Display_On 0x0C // 打开显示命令
#define Bit8_Line2_Display 0x38 // 设置8位数据线和2行显示模式
#define LCD_Entry_Mode_Set 0x06 // 设置输入模式命令
#define Shift_Function 0x14 // 显示或光标位移命令
#define Set_CGRAM00 0x00 // 第一行起始地址
#define Set_CGRAM40 0x40 // 第二行起始地址
#define uchar unsigned char // 无符号字符类型定义
// 第一行和第二行要显示的数据
const uchar Data_1[16] = "0123456789012345";
const uchar Data_2[16] = "abcdefghABCDEFGH";
const uchar code_Data[] = "Hello World !";
const uchar code_Data2[] = "Good Good Study, Day Day Up !";
const uchar Y_Data_0[] = {0x0f, 0x12, 0x0f, 0x0a, 0x1f, 0x02, 0x02, 0x02}; // 汉字 年 的字模
const uchar M_Data_1[] = {0x0f, 0x09, 0x0f, 0x09, 0x0f, 0x09, 0x0b, 0x11}; // 汉字 月 的字模
const uchar D_Data_2[] = {0x1f, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x1f, 0x00}; // 汉字 日 的字模
/*********************************************************************
* 函 数 名: Lcd1602_Check_Busy
* 功能描述: 检查 LCD1602 是否忙碌
* 调用函数: 无
* 全局变量:
* Lcd1602_Data: 报告 LCD 状态
* Lcd1602_RS: 选择指令或数据
* Lcd1602_RW: 读写选择
* Lcd1602_E: 使能信号
* 输 入: 无
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 函数说明: 此函数用于检查 LCD 是否处于忙碌状态。
* 通过设置相应的控制信号,读取 LCD 的状态。
* 注意事项: 确保在调用此函数之前已正确配置相关的控制引脚。
*********************************************************************/
void Lcd1602_Check_Busy()
{
unsigned char temp; // 使用完整类型名
Lcd1602_Data = 0xff; // 设置数据引脚为全高,准备读取状态
Lcd1602_RS = 0; // 设置为指令模式
Lcd1602_RW = 1; // 设置为读取模式
do {
Lcd1602_E = 1; // 拉高使能信号
temp = Lcd1602_Data; // 将 LCD 状态保存在 temp 中,用于判断忙碌状态
Lcd1602_E = 0; // 使能信号下拉
} while(temp & 0x80); // 如果高位为 1,表示 LCD 忙碌,继续循环;为 0 时不忙可以继续操作
}
/*********************************************************************
* 函 数 名: Lcd1602_Init
* 功能描述: 初始化 LCD1602 显示屏
* 调用函数: Lcd1602_Write_Cmd()
* 全局变量: 无
* 输 入: 无
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 注意事项: 在调用此函数之前,确保 LCD 硬件连接正常。
*********************************************************************/
void Lcd1602_Init()
{
Lcd1602_Write_Cmd(Clear_Display); // 指令1 ?? 清屏
Lcd1602_Write_Cmd(LCD_Entry_Mode_Set); // 指令2 ?? 设置输入模式,AC加1,画面不动
Lcd1602_Write_Cmd(Display_On); // 指令3 ?? 打开显示,光标关闭,闪烁关闭
Lcd1602_Write_Cmd(Bit8_Line2_Display); // 指令4 ?? 8位数据接口,两行显示,5x7点阵
Lcd1602_Write_Cmd(0x80); // 指令5 ?? 设置光标位置,从第一行第一列开始显示
}
/*********************************************************************
* 函 数 名: Lcd1602_Write_Cmd
* 功能描述: 向 LCD1602 写入指令
* 调用函数: Lcd1602_Check_Busy(), delayms()
* 全局变量:
* Lcd1602_RS: 选择指令或数据模式
* Lcd1602_RW: 读写选择
* Lcd1602_E: 使能信号
* Lcd1602_Data: 用于发送指令的数据信号
* 输 入: uchar Cmd - 要写入的指令
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 注意事项: 确保在调用此函数之前已正确配置控制引脚,并且 LCD 硬件连接正常。
*********************************************************************/
void Lcd1602_Write_Cmd(uchar Cmd)
{
Lcd1602_Check_Busy(); // 判忙,确保 LCD 不忙碌
Lcd1602_RS = 0; // 选择指令模式
Lcd1602_RW = 0; // 选择写模式
Lcd1602_E = 0; // 使能信号低
Lcd1602_Data = Cmd; // 发送指令
delayms(2); // 等待,确保指令处理完毕
Lcd1602_E = 1; // 拉高使能信号
delayms(5); // 等待以确保指令被执行
Lcd1602_E = 0; // 将使能信号设为低
}
/*********************************************************************
* 函 数 名: Lcd1602_Write_Data
* 功能描述: 向 LCD1602 写入数据
* 调用函数: Lcd1602_Check_Busy(), delayms()
* 全局变量:
* Lcd1602_RS: 选择指令或数据模式
* Lcd1602_RW: 读写选择
* Lcd1602_E: 使能信号
* Lcd1602_Data: 用于发送数据的数据信号
* 输 入: uchar Data - 要写入的数据
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 注意事项: 确保在调用此函数之前已正确配置控制引脚,并且 LCD 硬件连接正常。
*********************************************************************/
void Lcd1602_Write_Data(uchar Data)
{
Lcd1602_Check_Busy(); // 判忙,确保 LCD 不忙碌
Lcd1602_RS = 1; // 选择数据模式
Lcd1602_RW = 0; // 选择写模式
Lcd1602_E = 0; // 使能信号低
Lcd1602_Data = Data; // 发送数据
delayms(2); // 等待,确保数据处理完毕
Lcd1602_E = 1; // 拉高使能信号
delayms(10); // 等待以确保数据被执行
Lcd1602_E = 0; // 将使能信号设为低
}
/*********************************************************************
* 函 数 名: Lcd1602_SetCursor
* 功能描述: 设置 LCD1602 的光标位置
* 调用函数: Lcd1602_Write_Cmd()
* 全局变量: 无
* 输 入: uchar row - 行号 (0 或 1)
* uchar col - 列号 (0 至 15)
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 注意事项: 请确保 row 参数在有效范围内(0 和 1),col 参数应在 0 至 15 之间。
*********************************************************************/
void Lcd1602_SetCursor(uchar row, uchar col)
{
uchar address; // 光标地址变量
if (row == 0)
{
address = 0x00 + 0x80 + col; // 计算第一行的地址
}
else if (row == 1)
{
address = 0x40 + 0x80 + col; // 计算第二行的地址
}
Lcd1602_Write_Cmd(address); // 发送命令以设置光标位置
}
/*********************************************************************
* 函 数 名: Show_String
* 功能描述: 在 LCD1602 上显示字符串
* 调用函数: Lcd1602_Write_Data()
* 全局变量: 无
* 输 入: const uchar* line - 指向要显示的字符串的指针
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 函数说明: 此函数循环遍历字符串中的每个字符,调用写数据函数将其显示在 LCD1602 上。
* 注意事项: 确保输入字符串以 '\0' 结尾,以正确识别字符串结束。
*********************************************************************/
void Show_String(const uchar* line)
{
uchar i = 0; // 字符索引
while (line[i] != '\0')
{
Lcd1602_Write_Data(line[i++]); // 显示当前字符并移动到下一个字符
}
}
/*********************************************************************
* 函 数 名: Show_String_From_Row
* 功能描述: 从指定行开始在 LCD1602 上显示字符串
* 调用函数: Lcd1602_SetCursor(), Show_String()
* 全局变量: 无
* 输 入: const uchar* line - 指向要显示的字符串的指针
* uchar row - 行号(0 或 1)
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 函数说明: 此函数将光标设置到指定的行,然后显示传入的字符串。
* 注意事项: 确保 row 参数在有效范围内(0 和 1),并且传入的字符串以 '\0' 结束。
*********************************************************************/
void Show_String_From_Row(const uchar* line, uchar row)
{
// 设置光标位置
Lcd1602_SetCursor(row, 0); // 光标设置到指定行的起始列
// 显示字符串
Show_String(line); // 调用已定义的显示字符串函数
}
/*********************************************************************
* 函 数 名: Lcd1602_Write_Number_At
* 功能描述: 在指定位置的 LCD1602 上显示数字
* 调用函数: Lcd1602_SetCursor(), Lcd1602_Write_Data()
* 全局变量: 无
* 输 入: uchar row - 行号(0 或 1)
* uchar col - 列号(0 至 15)
* uchar number - 要显示的数字(范围在 0 到 9 )
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 函数说明: 此函数将光标移动到指定的行和列,并将给定的数字显示在 LCD1602 上。
* 注意事项: 确保 number 参数在有效范围内(0 到 9),以避免显示错误。
*********************************************************************/
void Lcd1602_Write_Number_At(uchar row, uchar col, uchar number)
{
uchar character; // 存储转换后的字符
// 设置光标到指定的行和列
Lcd1602_SetCursor(row, col);
// 将数字转换为字符 (假设数字范围在0到9)
character = number + '0'; // 将数字转换为对应的字符
// 写入数据到 LCD
Lcd1602_Write_Data(character);
}
/*********************************************************************
* 函 数 名: Lcd1602_Write_String_At
* 功能描述: 在指定位置的 LCD1602 上显示字符串
* 调用函数: Lcd1602_SetCursor(), Lcd1602_Write_Data()
* 全局变量: 无
* 输 入: uchar row - 行号(0 或 1)
* uchar col - 列号(0 至 15)
* const char* text - 指向要显示的字符串的指针
* 返 回: 无
* 设 计 者:bianjingyuan 日期:2024-10-30
* 版 本: 1.0
* 函数说明: 此函数将光标移动到指定的行和列,并将给定的字符串显示在 LCD1602 上。
* 注意事项: 确保传入的字符串以 '\0' 结尾,以保证正确识别字符串结束。
*********************************************************************/
void Lcd1602_Write_String_At(uchar row, uchar col, const char* text)
{
// 设置光标到指定的行和列
Lcd1602_SetCursor(row, col);
// 遍历字符串并逐个写入每个字符到 LCD
while (*text) // 当当前字符不为 '\0' 时
{
// 写入数据到 LCD
Lcd1602_Write_Data(*text);
text++; // 移动到下一个字符
}
}
3.2 main.c
//********************************************************************
//* 文件名: B002_887_1602.c
//* 文件描述: PIC16F887A,用于子程序实现实时时钟DS1302的驱动及1602显示
//* 编译环境:Windows XP + MPLAB IDE 8.83 + Picc 9.60
//* 编程者:bianjingyuan
//* 编程日期:2024年10月30日
//* 版本号:1.0
//* 修改记录:
//*******************************************************************/
//* 硬件资源分配表:
//* din RD5
//* clk_1 RD7=1
//* clk_0 RD7=0
//* load_1 RD6=1
//* load_0 RD6=0
//* ds1302_clk RE0 // DS1302时钟线引脚
//* ds1302_io RE1 // DS1302数据线引脚
//* ds1302_rst RE2 // DS1302复位线引脚
// 包含单片机内部资源预定义头文件
#include "pic.h"
#include "DS1302.h"
#include "max7219.h"
#include "PIC16F887_Delay.h"
#include "Lcd1602.h"
// 定义数据类型
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
// 定义单片机指令
#define nop asm("NOP") // 执行空操作指令
#define sleep asm("sleep") // 进入睡眠模式
// 定义DS1302引脚连接PIC16F887A
#define ds1302_clk RD1 // 实时时钟时钟线引脚
#define ds1302_io RD0 // 实时时钟数据线引脚
#define ds1302_rst RD2 // 实时时钟复位线引脚
// 定义数码管引脚连接 max7219
#define din RD5 // 数据输入引脚
#define clk_1 RD7=1 // 时钟高电平
#define clk_0 RD7=0 // 时钟低电平
#define load_1 RD6=1 // 锁存高电平
#define load_0 RD6=0 // 锁存低电平
// 定义芯片工作时的配置位和ID
__CONFIG (0x3F32); // 设置配置字
// __IDLOC (1974); // 设置ID位
// 定义变量
uchar m, k, i, l, add, dat, c0; // 临时变量
uchar add8, add7, add6, add5, add4, add3, add2, add1; // 显示位数据
uchar timerbcd[7]; // 存放从DS1302读回的时间数据(BCD码)
uchar timerint[7]; // 存放将BCD码转换成十进制后的时间数据
/*********************************************************************
*
* 函 数 名: initial
* 功能描述: 系统初始化,包括配置寄存器和端口。
* 输入参数: 无
* 输出参数: 无
* 设 计 者: bianjingyuan 日期: 2024年10月30日
*
*********************************************************************/
void initial(void)
{
// 配置 OPTION 寄存器
OPTION = 0b11000010; // 设置看门狗定时器、定时器0预分频器等
// 配置 I/O 口
TRISA = 0b101111; // RA4 配置为输出,其他位为输入
TRISB = 0b000000; // RB0 和 RB1 配置为输入
TRISC = 0b000000; // RC3 和 RC5 配置为输入
TRISD = 0b000000; // RD0 和 RD1 配置为输入
TRISE = 0b00001000; // RE0、RE1 和 RE2 配置为输出
// 关闭所有中断
INTCON = 0b00000000; // 关闭所有中断
// 初始化端口
PORTA = 0b00000000; // PORTA 初始化为 0
PORTB = 0b00000001; // PORTB 初始化为 1
PORTC = 0b00000000; // PORTC 初始化为 0
PORTD = 0b00000000; // PORTD 初始化为 0
PORTE = 0b00000000; // PORTE 初始化为 0
// 配置 ADC 相关寄存器
ADCON0 = 0b10000000; // 设置 A/D 转换通道为 AN0,并使能 A/D 转换
ADCON1 = 0b00001001; // 设置 A/D 转换参考电压为 VDD 和 VSS
ANSEL = 0b00001111; // 设置 RA0 为模拟输入,RA2 和 RA3 为模拟模块的 VREF- 和 VREF+
ANSELH = 0b00000000; // 设置所有 PORTB 和 PORTC 引脚为数字 I/O
ADRESH = 0b00000000; // 清空 A/D 转换结果高8位
ADRESL = 0b00000000; // 清空 A/D 转换结果低8位
// 初始化变量
add1 = add2 = add3 = 0b00000000; // 将变量 add1、add2 和 add3 初始化为 0
// 配置中断
GIE = 0b00000000; // 关闭总中断
PEIE = 0b00000000; // 关闭外围设备中断
T0IE = 0b00000001; // 开启定时器 0 中断
T0IF = 0b00000000; // 清除定时器 0 中断标志
INTF = 0b00000000; // 清除外部中断标志
// 初始化定时器 BCD 值
timerbcd[0] = 0b00000000; // 将定时器 BCD 数组的第一个元素初始化为 0
}
/*********************************************************************
*
* 函 数 名: main
* 功能描述: 主程序,负责DS1302时钟模块读取和显示。
* 输入参数: 无
* 输出参数: 无
* 设 计 者: bianjingyuan 日期: 2024年10月30日
*
*********************************************************************/
void main()
{
initial(); // 系统初始化
// 初始化DS1302引脚
ds1302_clk = 0; // 设置时钟线低电平
ds1302_io = 0; // 设置数据线低电平
ds1302_rst = 0; // 设置复位线低电平
max7219_int(); // 测试数码管芯片
ds1302_Open(); // 打开DS1302
Lcd1602_Init(); // LCD1602 初始化
redo:
nop; // 空操作
ds1302_read_time(); // 读取DS1302时间
ds1302_write(0x8e, 0x80); // 禁止写入DS1302
// 将BCD码转换为十进制
for (i = 7; i > 0; i--)
{
timerint[7 - i] = timerbcd[7 - i] & 0x0f; // 取个位数据
k = timerbcd[7 - i] >> 4; // 取十位数据
timerint[7 - i] = (10 * (k & 7)) + (timerint[7 - i]); // 转换为十进制
}
// 将时间数据分别存入显示数据数组
c0 = timerint[0]; // 秒
add7 = c0 / 10; // 秒的十位
add8 = c0 % 10; // 秒的个位
c0 = timerint[1]; // 分
add5 = c0 / 10; // 分的十位
add6 = c0 % 10; // 分的个位
c0 = timerint[2]; // 时
add3 = c0 / 10; // 时的十位
add4 = c0 % 10; // 时的个位
c0 = timerint[3]; // 日
add1 = c0 / 10; // 日的十位
add2 = c0 % 10; // 日的个位
Show_String_From_Row("date: 2024-11-03 ", 0);
Show_String_From_Row("time: ", 1);
Lcd1602_Write_Number_At(1, 6, add3);
Lcd1602_Write_Number_At(1, 7, add4);
Lcd1602_Write_String_At(1, 8, ": ");
Lcd1602_Write_Number_At(1, 9, add5);
Lcd1602_Write_Number_At(1, 10, add6);
Lcd1602_Write_String_At(1, 11, ": ");
Lcd1602_Write_Number_At(1, 12, add7);
Lcd1602_Write_Number_At(1, 13, add8);
// 发送显示数据到数码管芯片
send(0x01, add1); delayms(5); // 显示日期
send(0x02, add2); delayms(5);
send(0x03, add3); delayms(5); // 显示小时
send(0x04, add4); delayms(5);
send(0x05, add5); delayms(5); // 显示分钟
send(0x06, add6); delayms(5);
send(0x07, add7); delayms(5); // 显示秒
send(0x08, add8); delayms(5);
delayms(10); // 确保显示完成
goto redo; // 循环显示
}
4、代码解析
4.1 lcd1602
该程序的主要功能是控制LCD1602显示模块进行字符和数字的显示。具体功能包括:
- 初始化LCD1602显示屏,设置显示模式、清屏和光标位置。
- 向LCD1602写入指令和数据,实现对屏幕的控制和显示。
- 设置光标位置,以便在特定的位置进行显示。
- 显示字符串、数字在指定的行列位置。
- 提供对LCD1602忙碌状态的检测功能,以确保显示操作的顺利进行。
4.2 main
该程序实现了使用PIC16F887A单片机驱动实时时钟模块DS1302,并通过LCD1602和数码管显示时间和日期。主要功能如下:
- 配置PIC16F887A的端口和寄存器,初始化硬件设备。
- 从DS1302读取实时时间数据,以BCD码格式存储。
- 将BCD码转换为十进制,并将时间(小时、分钟、秒)及日期(天)在LCD1602和数码管显示。
- 使用LCD1602显示时间和日期,实时更新。
- 通过Max7219控制器驱动数码管显示实时时间。
5、硬件资源分配表
序号 | 引脚 | 类别 | 方向 | 功能定义 | 功能内容 |
1 | RD1 | 数字IO | 输出 | ds1302_clk | 实时时钟时钟线 |
2 | RD0 | 数字IO | 双向 | ds1302_io | 实时时钟数据线 |
3 | RD2 | 数字IO | 输出 | ds1302_rst | 实时时钟复位线 |
4 | RD5 | 数字IO | 输出 | MAX7219_din | 数据输入引脚 |
5 | RD6 | 数字IO | 输出 | MAX7219_load | 锁存控制引脚 |
6 | RD7 | 数字IO | 输出 | MAX7219_clk | 时钟控制引脚 |
7 | RE0 | 数字IO | 输出 | Lcd1602_RW | 指令/数据选择 |
8 | RE1 | 数字IO | 输出 | Lcd1602_RS | 读写选择 |
9 | RE2 | 数字IO | 输出 | Lcd1602_E | 数据锁存 |
10 | PORTC | 数据总线 | 输出 | Lcd1602_Data | 数据指令传递 |
基于上述硬件资源分配表,该程序实现了以下主要功能:
- 通过I/O引脚与DS1302进行通信,实现实时时钟时间的读取和显示。
- 将BCD码转换为十进制,再通过数码管显示。
- 通过LCD1602显示实时时钟的时间和日期。
- 提供完整的初始化和配置,以支持实时显示任务的执行。