51单片机使用定时器实现LCD1602的时间显示(STC89C52RC)

本文前半部分直接给出实现(注意进位问题是秒->分->小时,用 if 嵌套即可实现),后半部分讲解定时器和中断系统。

效果展示:

LCD1602电路图:

项目结构:

代码实现:

main.c

cpp 复制代码
#include <REGX52.H>    //包含51单片机寄存器定义头文件
#include "LCD1602.h"   // 包含LCD1602液晶驱动头文件
#include "Timer0.h"    // 包含定时器T0驱动头文件

// 定义时间变量并初始化(23:59:57)
unsigned int Hour = 23;  
unsigned int Min = 59;   
unsigned int Sec = 57;  

void main() {
    LCD_Init();      // 初始化LCD1602液晶显示屏
    Timer0Init();    // 初始化定时器T0
    
    // 在LCD上显示静态文本
    LCD_ShowString(1, 1, "Clock:");  // 第1行第1列显示"Clock:"
    LCD_ShowString(2, 3, ":");       // 第2行第3列显示冒号(分隔小时和分钟)
    LCD_ShowString(2, 6, ":");       // 第2行第6列显示冒号(分隔分钟和秒钟)

    while(1) {
        // 持续更新时间显示(动态刷新)
        LCD_ShowNum(2, 1, Hour, 2);  // 第2行第1列显示2位小时数
        LCD_ShowNum(2, 4, Min, 2);   // 第2行第4列显示2位分钟数
        LCD_ShowNum(2, 7, Sec, 2);   // 第2行第7列显示2位秒钟数
    }
}

// 定时器T0中断服务函数(中断号1)
void Timer0_Routine() interrupt 1 {
    static unsigned int T0Count;  // 静态变量,用于计数中断次数
    
    // 重新装载定时器初值(配置为1ms中断一次,实际需根据晶振频率计算)
    TL0 = 0x66;  // 定时器低位初值
    TH0 = 0xFC;  // 定时器高位初值
    
    T0Count++;  // 中断次数计数器自增
    
    // 当计数达到1000次(约1秒)时更新时间
    if(T0Count >= 1000) {
        T0Count = 0;  // 重置计数器
        Sec++;         // 秒钟加1
        
        // 时间进位处理
        if(Sec >= 60) {     // 超过59秒
            Sec = 0;        // 秒钟归零
            Min++;          // 分钟加1
            if(Min >= 60) { // 超过59分钟
                Min = 0;    // 分钟归零
                Hour++;     // 小时加1
                if(Hour >= 24) { // 超过23小时
                    Hour = 0;    // 小时归零
                }
            }
        }
    }
}

LCD1602.h

cpp 复制代码
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

LCD1602.c

cpp 复制代码
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
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次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
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--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
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;
	}
	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 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
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--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

Timer0.h

cpp 复制代码
#ifndef __TIMER0_H__
#ifndef __TIMER0_H__

void Timer0Init(void);
#endif

Timer0.c

cpp 复制代码
#include <REGX52.H>

/**
  * @brief  定时器0初始化,1毫秒@11.0592MHZ
  * @param 	无
  * @retval 无
  */
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//清零低4位(对应定时器0的配置),保留高4位(定时器1的配置)不变
	TMOD |= 0x01;		//设置定时器0为模式1(二进制0000 0001中的低4位),即16位定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除定时器0溢出标志位,防止初始化后立即触发中断
	TR0 = 1;		//启动定时器0,开始计数
	ET0=1;			//开启定时器0中断(ET0)和全局中断(EA),允许中断触发			
	EA=1;
	PT0=0;			//设置定时器0中断优先级为低优先级
}

//定时器通用模板
//void Timer0_Routine() interrupt 1{
//	static unsigned int T0Count;
//	TL0 = 0x66;		//设置定时初值
//	TH0 = 0xFC;		//设置定时初值
//	T0Count++;
//	if(T0Count>=1000)
//	{
//		T0Count=0;
//	}
//}

定时器和中断系统:

1.定时器

**定时器介绍:**51单片机定时器属于单片机的内部资源,其电连接和运转均在单片机内部完成。

定时器的作用:

  • 基于时钟信号计数,提供精准的时间基准(如微秒/毫秒级延时)
  • 替代长时间的Delay,提高CPU的运行效率和处理速度
  • 当计数值达到预设阈值时,触发中断,执行特定任务(如周期任务调度)
  • 通过调节占空比和频率,输出脉宽调制信号(PWM)

STC89C52定时器资源:

  • 定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源
  • 注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的

定时器工作原理框图:

  • 定时器在单片机的内部就像一个小闹钟一样,根据时钟的输出信号,每隔"一秒",计数单元的数值就增加一,当计数单元数值增加到"设定的闹钟提醒时间"时,计数单元就会向中断系统发出中断申请,产生"闹钟提醒",使程序跳到中断服务函数中执行。

定时器工作模式:

  • STC89C52的T0和T1均有四种工作模式:

模式0:13位定时器/计数器

模式1:16位定时器/计数器(常用)

模式2:8位自动重装模式

模式3:两个8位计数器

定时器/计数器除模式3外,其他工作模式与定时器/计数器0相同,T1在模式3时无效,停止计数。

  • 下面以模式1(16位定时器/计数器)为例:

一、时钟

  • SYSclk:系统时钟(System Clock),即晶振周期,单片机核心工作频率,决定指令执行速度。本开发板上晶振为11.0592MHz。
  • ÷12/÷6:分频器

12T模式:每12个时钟周期构成1个机器周期(速度较慢,兼容传统设计)。

6T模式:每6个时钟周期构成1个机器周期(速度更快,适合高性能场景)。

  • C/:模式选择位(Counter/Timer Select Bit)

C/=0:定时器模式,对分频后的系统时钟脉冲计数(用于计时)。

C/=1:计数器模式,对T0 Pin(外部引脚)的脉冲信号计数(用于统计外部事件)。

二、计数单元

  • TR0:定时器0运行控制位(Timer 0 Run Control)。

TR0=1:启动定时器/计数器;TR0=0:停止。

  • GATE:门控位。

GATE=1:定时器启动需要满足TR0=1 且=1(外部中断0引脚高电平)。

GATE=0:仅需TR0=1即可启动。

  • :外部中断0引脚,用于门控模式下协同控制。
  • 16位寄存器:

TL0(8 Bits):低8位计数器,存储计数值低字节。

TH0(8 Bits):高8位计数器,存储计数值高字节。

三、中断

  • TF0:定时器0溢出标志(Timer 0 Overflow Flag)

当计数器从65535加1变为0时,TF0自动置1,触发中断请求。

  • Interrupt:中断信号,通知CPU处理定时器溢出事件(例如更新显示、执行任务)。

2.中断系统

当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前

的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工

作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为
断源

举个煮面例子说明:

关键对应关系:

|----------|------------|---------------------|
| 厨房场景 | 中断系统 | 电路图对应(TF0中断) |
| 煮面流程 | 主程序执行 | 定时器正常计数 (TL0/TH0累加) |
| 电话响铃 | 中断源触发 | 定时器溢出导致TF0=1,触发中断 |
| 关火并记住进度 | 保存CPU现场 | 硬件自动保存程序计数器(PC) |
| 接电话 | 执行中断服务程序 | 跳转到中断向量表执行定时器中断代码 |
| 重新开火继续煮面 | 恢复现场并继续主程序 | RETI指令返回,恢复PC继续主流程 |

下图以STC89C51RC/RD+系列讲解中断系统结构示意图:

一、中断源与触发机制

图中左侧标注了 8个中断源,按触发类型分为三类:

  • 外部中断(4个)

INT0(外部中断0):

触发方式:由 TCON.0/IT0 位设置(0=低电平触发,1=下降沿触发)。

标志位:IE0(TCON.1),触发后自动置1。

INT1/INT2/INT3(外部中断1/2/3):类似INT0,分别由 TCON.2/IT1XICON.0/IT2XICON.2/IT3 控制触发方式,标志位为 IE1/IE2/IE3

  • 定时器中断(3个)

Timer0/TF0:定时器0溢出时 TF0(TCON.5)置1,允许位 ET0(IE.1)。

Timer1/TF1:定时器1溢出时 TF1(TCON.7)置1,允许位 ET1(IE.3)。

Timer2/TF2/EXF2:溢出触发 TF2(T2CON.7),外部触发 EXF2(T2CON.6),允许位 ET2(IE.5)。

  • 串口中断(UART)

接收中断:RI(SCON.0)置1时触发。

发送中断:TI(SCON.1)置1时触发。

统一允许位 ES(IE.4)。

二、中断允许控制寄存器

  • IE寄存器(Interrupt Enable)

全局控制:EA=1(IE.7)时总中断允许生效。

分项控制:

EX0=1(IE.0):允许外部中断0

ET0=1(IE.1):允许定时器0中断

EX1=1(IE.2):允许外部中断1

ET1=1(IE.3):允许定时器1中断

ES=1(IE.4):允许串口中断

ET2=1(IE.5):允许定时器2中断

  • XICON寄存器(扩展中断控制)

扩展允许位:EX2=1(XICON.4)、EX3=1(XICON.5)分别允许外部中断2/3。

触发方式:IT2(XICON.0)、IT3(XICON.2)设置边沿/电平触发。

三、中断优先级控制

  • IP/IPH寄存器(Interrupt Priority)

标准优先级(IP):每个中断源对应1位(0=低优先级,1=高优先级)。

例如:PX0=1(IP.0)设置外部中断0为高优先级。

扩展优先级(IPH):与IP组合实现 4级优先级(STC特有功能):

PX0H:PX0(IPH.0:IP.0)= 00(0级)→11(3级),数值越大优先级越高。

  • 中断查询次序,图中右侧竖列标注了中断响应顺序(优先级相同时的默认查询次序):

INT0->Timer0->INT1->Timer1->UART->Timer2(优先级 高->低)

3.配置定时器和中断示例

cpp 复制代码
#include <REGX52.H>

/**
  * @brief  定时器0初始化,1毫秒@11.0592MHZ
  * @param 	无
  * @retval 无
  */
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//清零低4位(对应定时器0的配置),保留高4位(定时器1的配置)不变
	TMOD |= 0x01;		//设置定时器0为模式1(二进制0000 0001中的低4位),即16位定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除定时器0溢出标志位,防止初始化后立即触发中断
	TR0 = 1;		//启动定时器0,开始计数
	ET0=1;			//开启定时器0中断(ET0)和全局中断(EA),允许中断触发			
	EA=1;
	PT0=0;			//设置定时器0中断优先级为低优先级
}

//定时器通用模板
//void Timer0_Routine() interrupt 1{
//	static unsigned int T0Count;
//	TL0 = 0x66;		//设置定时初值
//	TH0 = 0xFC;		//设置定时初值
//	T0Count++;
//	if(T0Count>=1000)
//	{
//		T0Count=0;
//	}
//}
相关推荐
坏柠4 小时前
深入浅出SPI通信协议与STM32实战应用(W25Q128驱动)(实战部分)
stm32·单片机·嵌入式硬件
硬件进化论6 小时前
硬件工程师面试问题(四):车载MCU面试问题与详解
单片机·嵌入式硬件·数码相机·电视盒子·智能音箱·智能手表
nuannuan2311a9 小时前
CR03AM-12-ASEMI智能家居专用CR03AM-12
单片机
蓝桥_吹雪10 小时前
【备赛】蓝桥杯实现多个LED联合控制
笔记·stm32·单片机·蓝桥杯
sewinger11 小时前
STM32外部中断EXTI:原理、结构与应用
stm32·单片机·嵌入式硬件·iot
触角0101000111 小时前
STM32看门狗原理与应用详解:独立看门狗 vs 窗口看门狗(上) | 零基础入门STM32第九十四步
驱动开发·stm32·单片机·嵌入式硬件·物联网
LaoZhangGong12311 小时前
char表示有符号,还是无符号
经验分享·stm32·单片机·嵌入式硬件
多多*11 小时前
2024第十五届蓝桥杯大赛软件赛省赛Java大学B组 报数游戏 类斐波那契循环数 分布式队列 食堂 最优分组 星际旅行 LITS游戏 拼十字
java·linux·stm32·单片机·嵌入式硬件·spring·eclipse
菜鸟江多多12 小时前
32x32热成像高斯滤波图像处理
图像处理·单片机·算法
sewinger13 小时前
STM32:深入理解定时器与使用定时中断实现精准延时
单片机·嵌入式硬件