目录
[13.1 AT24C02 基础知识](#13.1 AT24C02 基础知识)
[13.2 AT24C02 引脚定义](#13.2 AT24C02 引脚定义)
[13.3 I2C总线](#13.3 I2C总线)
[13.3.1 I2C简介](#13.3.1 I2C简介)
[13.3.2 I2C时序结构](#13.3.2 I2C时序结构)
[13.3.3 I2C数据帧](#13.3.3 I2C数据帧)
[⭐ I2C发送一帧数据](#⭐ I2C发送一帧数据)
[⭐ I2C接收一帧数据](#⭐ I2C接收一帧数据)
[⭐ I2C先发送再接收一帧数据](#⭐ I2C先发送再接收一帧数据)
[13.4 AT24C02数据帧](#13.4 AT24C02数据帧)
[13.5 项目示例1:AT24C02数据存储](#13.5 项目示例1:AT24C02数据存储)
[13.6 项目示例2:秒表(定时器扫描数码管)](#13.6 项目示例2:秒表(定时器扫描数码管))
13.1 AT24C02 基础知识

13.2 AT24C02 引脚定义

AT24C02 为标准 8 引脚芯片,不管是直插还是贴片,引脚顺序和功能 完全固定 , 核心有用引脚仅 4 个,其余为辅助引脚,超级简单!
- 引脚1(A0)、引脚2(A1)、引脚3(A2) :器件地址引脚 → 全部接 GND 即可!(固定接法,不用改)
- 引脚4(GND) :电源地 → 接单片机GND
- 引脚5(SDA) :I2C串行数据线 → 接单片机任意IO口(软件模拟,如P2^0)
- 引脚6(SCL) :I2C串行时钟线 → 接单片机任意IO口(软件模拟,如P2^1)
- 引脚7(WP) :写保护引脚 → 【重中之重】接GND=允许读写;接VCC=只读模式(禁止写入)
- 引脚8(VCC) :电源正极 → 接单片机5V电源
✔ 引脚核心重点总结
- A0/A1/A2 必须接 GND,这是 51 单片机驱动 AT24C02 的固定接法,唯一选择;
- SDA、SCL 是灵魂引脚,I2C 总线的两根线,所有数据收发都靠这两个引脚;
- WP 引脚 一定要接 GND!如果接 VCC,你只能读数据,永远写不进去,新手 90% 的写失败都是这个原因;
13.3 I2C总线
13.3.1 I2C简介

13.3.2 I2C时序结构
⭐起始条件

- 【标准定义】
SCL 保持高电平期间,SDA 引脚从高电平向低电平 跳变 → 产生起始信号,告知所有挂载在 I2C 总线上的从设备: 主机要开始发起通信了,请准备接收数据。
起始条件:SCL高 → SDA 高 ↓ 低
- 【时序结构步骤】(严格按顺序执行,一步都不能错)
① 置 SDA = 1 (释放数据线,拉高)
② 置 SCL = 1 (释放时钟线,拉高,进入总线空闲状态)
③ 短暂延时 (Delay(1)) → 稳定电平,避免毛刺
④ 置 SDA = 0 (核心跳变:SCL高电平时,SDA由高变低)
⑤ 短暂延时 (Delay(1)) → 让从机识别到起始信号
⑥ 置 SCL = 0 (拉低时钟线,锁定起始信号,准备发送后续数据)
- 【对应你的 51 驱动代码】(一字不差匹配)
cpp
void I2C_Start(void)
{
I2C_SDA = 1; //① SDA拉高
I2C_SCL = 1; //② SCL拉高
Delay(1); //③ 延时稳定
I2C2_SDA = 0; //④ SCL高,SDA高→低 起始信号
Delay(1); //⑤ 延时识别
I2C_SCL = 0; //⑥ SCL拉低,准备发数据
}
⭐终止条件
- 【标准定义】
SCL 保持高电平期间,SDA 引脚从低电平 向 高电平 跳变 → 产生终止信号,告知所有从设备: 主机本次通信结束,总线恢复空闲状态。
终止条件:SCL高 → SDA 低 ↑ 高
- 【时序结构步骤】(严格按顺序执行,一步都不能错)
① 置 SDA = 0 (拉低数据线,为跳变做准备)
② 置 SCL = 1 (拉高时钟线,满足SCL高电平的前提)
③ 短暂延时 (Delay(1)) → 稳定电平
④ 置 SDA = 1 (核心跳变:SCL高电平时,SDA由低变高)
⑤ 短暂延时 (Delay(1)) → 让从机识别到终止信号
- 【对应你的 51 驱动代码】(一字不差匹配)
cpp
void I2C_Stop(void)
{
I2C_SDA = 0; //① SDA拉低
I2C_SCL = 1; //② SCL拉高
Delay(1); //③ 延时稳定
I2C_SDA = 1; //④ SCL高,SDA低→高 终止信号
Delay(1); //⑤ 延时识别
}
⭐发送一个字节

- 【标准定义】
主机在发起起始信号后,通过 SDA 引脚逐位发送 8 位二进制数据 ,配合 SCL 时钟的高低电平完成数据传输, 固定先发最高位 (bit7),最后发最低位 (bit0),无任何例外。 - 【时序结构步骤】(严格按顺序执行,一步都不能错)
① 置 SCL = 0 (拉低时钟,允许SDA电平变化,准备发送当前位)
② 将待发送字节的【当前最高位】放到SDA引脚上
③ 短暂延时 (Delay(1)) → 稳定当前位数据
④ 置 SCL = 1 (拉高时钟,从机在此时采样SDA电平,读取当前位)
⑤ 短暂延时 (Delay(1)) → 让从机完成采样
⑥ 置 SCL = 0 (拉低时钟,允许SDA准备下一位)
⑦ 将待发送字节【左移一位】,把次高位变为新的最高位
⑧ 重复①~⑦,直到发送完8位数据
- 【对应你的 51 驱动代码】(一字不差匹配)
cpp
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++) //循环8次,发送8位
{
I2C_SDA = Byte&0x80; //② 取Byte的最高位,赋值给SDA
Delay(1); //③ 稳定电平
I2C_SCL = 1; //④ 拉高时钟,从机采样
Delay(1); //⑤ 采样完成
I2C_SCL = 0; //⑥ 拉低时钟,准备下一位
Byte<<=1; //⑦ 字节左移一位,更新最高位
}
}
⭐接收一个字节

- 【标准定义】
主机在发起起始信号 + 发送从机地址后,切换为接收模式,通过 SDA 引脚逐位读取8 位二进制数据,配合 SCL 时钟的高低电平完成数据接收,固定先收最高位 (bit7),最后收最低位 (bit0),无任何例外。
✔ 接收与发送的区别:接收时, 主机的 SDA 引脚为输入状态,需要先释放 SDA(置 1),由从机控制 SDA 的电平高低。
- 【时序结构步骤】(循环 8 次,接收 8 个 bit)
① 置 SDA = 1 (释放数据线,主机改为输入模式,由从机控制SDA电平)
② 置 SCL = 1 (拉高时钟,主机在此时采样SDA电平,读取当前位)
③ 短暂延时 (Delay(1)) → 稳定电平,完成采样
④ 将读取到的SDA电平值,存入接收字节的【最低位】
⑤ 置 SCL = 0 (拉低时钟,允许从机切换SDA电平,准备下一位)
⑥ 短暂延时 (Delay(1)) → 稳定电平
⑦ 将接收字节【左移一位】,为接收下一位腾出最低位
⑧ 重复①~⑦,直到接收完8位数据
- 【对应你的 51 驱动代码】(一字不差匹配)
cpp
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0;
I2C_SDA = 1; //① 释放SDA,主机转为输入
for(i=0;i<8;i++) //循环8次,接收8位
{
I2C_SCL = 1; //② 拉高时钟,主机采样SDA
Delay(1); //③ 稳定电平
Byte<<=1; //⑦ 字节左移一位,腾出最低位
Byte|=I2C_SDA; //④ 读取SDA电平,存入最低位
Delay(1);
I2C_SCL = 0; //⑤ 拉低时钟,准备下一位
}
return Byte;
}
⭐发送应答

- 【标准定义】
主机从从机读取数据(如:从 AT24C02 读取存储的数据)后, 主机需要向从机发送应答信号,告知从机「是否继续发送下一个字节」,是读数据的必备时序 - 【时序结构步骤】(循环 8 次,接收 8 个 bit)
① 置 SCL = 0 (拉低时钟,允许SDA电平变化,准备发送应答位)
② 将应答位电平(0=ACK,1=NACK)赋值给SDA引脚
③ 短暂延时 (Delay(1)) → 稳定电平
④ 置 SCL = 1 (拉高时钟,从机采样SDA电平,读取应答位)
⑤ 短暂延时 (Delay(1)) → 采样完成
⑥ 置 SCL = 0 (拉低时钟,结束应答位发送)
- 【对应你的 51 驱动代码】(一字不差匹配)
cpp
void I2C_SendAck(unsigned char AckBit)
{
I2C_SCL = 0; //① 拉低时钟
I2C_SDA = AckBit; //② 赋值应答位:0=ACK,1=NACK
Delay(1); //③ 稳定电平
I2C_SCL = 1; //④ 拉高时钟,从机采样
Delay(1); //⑤ 采样完成
I2C_SCL = 0; //⑥ 拉低时钟,结束应答
}
⭐接收应答
- 【标准定义】
主机向从机发送数据(如:给 AT24C02 发从机地址、发存储地址、发写入数据)后, 主机需要读取从机的应答信号 ,判断从机是否成功接收数据,是你代码中 最常用的应答时序。 - 【时序结构步骤】(循环 8 次,接收 8 个 bit)
① 置 SDA = 1 (释放数据线,主机转为输入,由从机控制SDA电平)
② 置 SCL = 1 (拉高时钟,进入第9个时钟周期,主机采样应答位)
③ 短暂延时 (Delay(1)) → 稳定电平,读取SDA的电平值(应答位)
④ 记录应答位的电平值(0=ACK成功,1=NACK失败)
⑤ 置 SCL = 0 (拉低时钟,结束应答位采样)
⑥ 返回应答位的电平值
- 【对应你的 51 驱动代码】(一字不差匹配)
cpp
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA = 1; //① 释放SDA,主机输入
Delay(1);
I2C_SCL = 1; //② 拉高时钟,采样应答位
Delay(1);
AckBit = AT24C02_SDA; //④ 读取应答位电平
Delay(1);
I2C_SCL = 0; //⑤ 拉低时钟,结束应答
return AckBit; //⑥ 返回应答状态:0=成功,1=失败
}
✅ I2C.c
cpp
#include <REGX52.H>
//I2C总线引脚定义:SCL时钟线接P2^1,SDA数据线接P2^0
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
/**
* @brief I2C开始信号
* @param 无
* @retval 无
* @note 标准I2C起始条件:SCL高电平期间,SDA由高电平拉低为低电平
*/
void I2C_Start(void)
{
I2C_SDA=1; //释放数据线,置高电平
I2C_SCL=1; //释放时钟线,置高电平
I2C_SDA=0; //SCL高电平时,SDA高变低,产生起始信号
I2C_SCL=0; //拉低时钟线,锁定总线,准备后续数据传输
}
/**
* @brief I2C停止信号
* @param 无
* @retval 无
* @note 标准I2C终止条件:SCL高电平期间,SDA由低电平拉高为高电平
*/
void I2C_Stop(void)
{
I2C_SDA=0; //拉低数据线,为电平跳变做准备
I2C_SCL=1; //释放时钟线,置高电平
I2C_SDA=1; //SCL高电平时,SDA低变高,产生停止信号,总线恢复空闲
}
/**
* @brief I2C发送一个字节数据
* @param Byte 要发送的单字节数据(0x00~0xFF)
* @retval 无
* @note 高位先行,依次发送8位数据;SCL高电平采样,低电平准备数据
*/
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++) //循环8次,发送完整的8位数据
{
//0x80为二进制10000000,右移i位依次匹配bit7~bit0,按位与取出当前发送位
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1; //拉高时钟线,从机在此刻采样SDA电平,读取当前位数据
I2C_SCL=0; //拉低时钟线,允许SDA电平变化,准备下一位数据
}
}
/**
* @brief I2C接收一个字节数据
* @param 无
* @retval 接收到的单字节数据(0x00~0xFF)
* @note 高位先行,依次接收8位数据;主机需先释放SDA,由从机控制SDA电平
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00; //初始化接收字节为0,存储最终接收的数据
I2C_SDA=1; //释放数据线,主机转为输入模式,由从机控制SDA电平
for(i=0;i<8;i++) //循环8次,接收完整的8位数据
{
I2C_SCL=1; //拉高时钟线,主机在此刻采样SDA电平,读取当前位数据
if(I2C_SDA) //如果采样到SDA为高电平
{
Byte|=(0x80>>i); //将对应位赋值为1,低电平则保持0不变
}
I2C_SCL=0; //拉低时钟线,允许从机切换SDA电平,准备下一位数据
}
return Byte; //返回接收到的完整字节数据
}
/**
* @brief I2C发送应答位
* @param AckBit 应答位数据,0为应答(ACK),1为非应答(NACK)
* @retval 无
* @note 应答位为第9个时钟周期,主机接收数据后,向从机反馈的应答信号
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit; //赋值应答位电平,0=继续传输,1=终止传输
I2C_SCL=1; //拉高时钟线,从机采样应答位
I2C_SCL=0; //拉低时钟线,应答完成
}
/**
* @brief I2C接收应答位
* @param 无
* @retval 接收到的应答位,0为应答(ACK),1为非应答(NACK)
* @note 应答位为第9个时钟周期,从机接收数据后,向主机反馈的应答信号
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit; //存储应答位状态
I2C_SDA=1; //释放数据线,主机转为输入模式,由从机控制应答位电平
I2C_SCL=1; //拉高时钟线,主机采样应答位电平
AckBit=I2C_SDA; //读取并保存应答位状态
I2C_SCL=0; //拉低时钟线,应答完成
return AckBit; //返回应答状态,0=成功应答,1=无应答
}
✅I2C.h
cpp
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);
#endif
13.3.3 I2C数据帧
⭐ I2C发送一帧数据

⭐ I2C接收一帧数据

⭐ I2C先发送再接收一帧数据

13.4 AT24C02数据帧

✅AT24C02.c
cpp
#include <REGX52.H>
#include "I2C.h" // 包含软件模拟I2C的底层驱动函数头文件
#define AT24C02_ADDRESS 0xA0 // AT24C02的I2C设备固定写地址,不可修改
/**
* @brief AT24C02写入一个字节数据
* @param WordAddress 要写入字节的存储地址,范围0x00~0xFF
* @param Data 要写入的单字节数据,范围0x00~0xFF
* @retval 无
* @note 遵循I2C写时序,写完自动产生停止信号,AT24C02为字节写入模式
*/
void AT24C02_WriteByte(unsigned char WordAddress,unsigned char Data)
{
I2C_Start(); // 1. 发送I2C起始信号,开启通信
I2C_SendByte(AT24C02_ADDRESS); // 2. 发送AT24C02的I2C写地址 0xA0
I2C_ReceiveAck(); // 3. 等待接收从机应答,确认设备在线
I2C_SendByte(WordAddress); // 4. 发送要写入数据的存储单元地址
I2C_ReceiveAck(); // 5. 等待接收从机应答,确认地址接收成功
I2C_SendByte(Data); // 6. 发送需要写入的单字节数据
I2C_ReceiveAck(); // 7. 等待接收从机应答,确认数据接收成功
I2C_Stop(); // 8. 发送I2C停止信号,结束本次写操作
}
/**
* @brief AT24C02读取一个字节数据
* @param WordAddress 要读出字节的存储地址,范围0x00~0xFF
* @retval 成功读出的单字节数据
* @note 遵循I2C随机地址读时序,先写地址再读数据,读单字节发非应答
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data; // 定义变量,存储读取到的数据
I2C_Start(); // 1. 发送I2C起始信号,开启通信
I2C_SendByte(AT24C02_ADDRESS); // 2. 发送AT24C02的I2C写地址 0xA0
I2C_ReceiveAck(); // 3. 等待接收从机应答,确认设备在线
I2C_SendByte(WordAddress); // 4. 发送要读取数据的存储单元地址
I2C_ReceiveAck(); // 5. 等待接收从机应答,确认地址接收成功
I2C_Start(); // 6. 发送I2C重复起始信号,切换读写模式
I2C_SendByte(AT24C02_ADDRESS|0x01); //7.发送AT24C02的I2C读地址 0xA1
I2C_ReceiveAck(); // 8. 等待接收从机应答,确认进入读模式
Data=I2C_ReceiveByte(); // 9. 接收从机返回的一个字节数据
I2C_SendAck(1); //10.发送【非应答位】,告知从机只读1个字节,结束读取
I2C_Stop(); //11.发送I2C停止信号,结束本次读操作
return Data; //返回读取到的数据
}
✅AT24C02.h
cpp
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
13.5 项目示例1:AT24C02数据存储
✅项目功能:

✅项目架构:

✅ main.c主函数
cpp
#include <REGX52.H>
#include "LCD1602.h" // 包含LCD1602液晶显示驱动头文件
#include "Key.h" // 包含独立按键扫描驱动头文件
#include "AT24C02.h" // 包含AT24C02存储芯片驱动头文件
#include "Delay.h" // 包含延时函数驱动头文件
unsigned char KeyNum; // 定义按键编号变量,存储扫描到的按键值
unsigned int Num; // 定义无符号整型变量(2字节/16位),存储核心数值 范围0~65535
void main()
{
LCD_Init(); // 初始化LCD1602液晶显示模块
LCD_ShowNum(1,1,Num,5); // 初始化显示:在LCD第1行第1列,显示Num的值,固定显示5位数字
while(1) // 死循环,持续扫描按键+执行对应功能
{
KeyNum=Key(); // 调用按键扫描函数,获取当前按下的按键编号
if(KeyNum==1) // 如果按下K1按键,执行数值自增功能
{
Num++; // 数值+1
LCD_ShowNum(1,1,Num,5); // 更新LCD显示最新数值
}
if(KeyNum==2) // 如果按下K2按键,执行数值自减功能
{
Num--; // 数值-1
LCD_ShowNum(1,1,Num,5); // 更新LCD显示最新数值
}
if(KeyNum==3) // 如果按下K3按键,执行【数值写入AT24C02】功能
{
// AT24C02每个地址只能存储1字节(8位)数据,Num是2字节(16位)整型,需拆分为高低位分别存储
AT24C02_WriteByte(0,Num%256); // 把Num的【低8位数据】写入AT24C02的0号存储地址
Delay(5); // 必须延时5ms,等待AT24C02完成写入操作
AT24C02_WriteByte(1,Num/256); // 把Num的【高8位数据】写入AT24C02的1号存储地址
Delay(5); // 必须延时5ms,等待AT24C02完成写入操作
LCD_ShowString(2,1,"Write OK"); // LCD第2行第1列显示"写入成功"提示
Delay(1000); // 提示语显示1秒钟
LCD_ShowString(2,1," "); // 清掉提示语,显示8个空格占位
}
if(KeyNum==4) // 如果按下K4按键,执行【从AT24C02读取数值】功能
{
// 从AT24C02的对应地址读取高低位数据,再拼接还原成完整的2字节整型数据
Num=AT24C02_ReadByte(0); // 先读取0号地址的【低8位数据】赋值给Num
Num|=AT24C02_ReadByte(1)<<8; // 读取1号地址的【高8位数据】左移8位后,拼接至Num高位
LCD_ShowNum(1,1,Num,5); // 更新LCD显示读取到的数值
LCD_ShowString(2,1,"Read OK "); // LCD第2行第1列显示"读取成功"提示
Delay(1000); // 提示语显示1秒钟
LCD_ShowString(2,1," "); // 清掉提示语,显示8个空格占位
}
}
}
13.6 项目示例2:秒表(定时器扫描数码管)
✅项目功能:

✅项目架构:

✅main.c主函数
cpp
#include <REGX52.H>
#include "Timer0.h" // 包含定时器0初始化驱动头文件
#include "Key.h" // 包含按键扫描驱动头文件
#include "Nixie.h" // 包含8位数码管显示驱动头文件
#include "Delay.h" // 包含延时函数驱动头文件
#include "AT24C02.h" // 包含AT24C02存储芯片驱动头文件
unsigned char KeyNum; // 存储扫描到的按键编号 1-4
unsigned char Min,Sec,MiniSec;// 秒表计时变量:Min=分钟,Sec=秒钟,MiniSec=10ms单位的毫秒位
unsigned char RunFlag; // 秒表启停标志位 0=停止计时 1=开始计时
void main()
{
Timer0_Init(); // 初始化定时器0,开启定时器中断,作为整个程序的时基核心
while(1) // 死循环,持续执行:按键扫描+功能执行+数码管显示缓存刷新
{
KeyNum=Key(); // 调用按键扫描函数,获取当前按下的按键值
if(KeyNum==1) // K1按键按下
{
RunFlag=!RunFlag; // 启停标志位取反翻转 停止→启动 启动→停止
}
if(KeyNum==2) // K2按键按下
{
Min=0; // 分钟清0
Sec=0; // 秒钟清0
MiniSec=0; // 毫秒位清0 实现秒表清零复位
}
if(KeyNum==3) // K3按键按下
{
// 将当前秒表的【分钟、秒钟、毫秒位】分别写入AT24C02对应地址,实现掉电保存
AT24C02_WriteByte(0,Min); // 分钟数据写入AT24C02的0号地址
Delay(5); // 延时5ms,等待AT24C02完成写入(硬件必要时间)
AT24C02_WriteByte(1,Sec); // 秒钟数据写入AT24C02的1号地址
Delay(5);
AT24C02_WriteByte(2,MiniSec); // 毫秒位数据写入AT24C02的2号地址
Delay(5);
}
if(KeyNum==4) // K4按键按下
{
// 从AT24C02对应地址读取存储的计时数据,还原到秒表变量,实现掉电恢复计时
Min=AT24C02_ReadByte(0); // 读取0号地址 → 分钟
Sec=AT24C02_ReadByte(1); // 读取1号地址 → 秒钟
MiniSec=AT24C02_ReadByte(2); // 读取2号地址 → 毫秒位
}
// 设置数码管显示缓存,分配8位数码管的显示内容,驱动数码管显示当前计时数据
Nixie_SetBuf(1,Min/10); // 第1位数码管 → 分钟的十位
Nixie_SetBuf(2,Min%10); // 第2位数码管 → 分钟的个位
Nixie_SetBuf(3,11); // 第3位数码管 → 显示分隔符【-】(11为数码管驱动中定义的分隔符码值)
Nixie_SetBuf(4,Sec/10); // 第4位数码管 → 秒钟的十位
Nixie_SetBuf(5,Sec%10); // 第5位数码管 → 秒钟的个位
Nixie_SetBuf(6,11); // 第6位数码管 → 显示分隔符【-】
Nixie_SetBuf(7,MiniSec/10);// 第7位数码管 → 毫秒位的十位
Nixie_SetBuf(8,MiniSec%10);// 第8位数码管 → 毫秒位的个位
}
}
/**
* @brief 秒表计时驱动函数,定时器10ms调用一次该函数
* @param 无
* @retval 无
* @note 只有RunFlag=1时才会计时,计时规则:100个10ms=1秒,60秒=1分钟,60分钟清零
*/
void Sec_Loop(void)
{
if(RunFlag) // 判断秒表启停标志位,为1则开始计时,为0则停止计时
{
MiniSec++; // 10ms单位的毫秒位自增1 → 每调用一次+10ms
if(MiniSec>=100) // 毫秒位累计到100 → 等价于 10ms*100 = 1000ms = 1秒钟
{
MiniSec=0; // 毫秒位清零
Sec++; // 秒钟自增1
if(Sec>=60) // 秒钟累计到60 → 1分钟
{
Sec=0; // 秒钟清零
Min++; // 分钟自增1
if(Min>=60)// 分钟累计到60 → 秒表最大计时60分钟,满60清零循环
{
Min=0; // 分钟清零
}
}
}
}
}
/**
* @brief 定时器0中断服务函数,系统核心时基,定时500us进入一次中断
* @param 无
* @retval 无
* @note 中断内实现:按键消抖扫描、数码管动态刷新、秒表计时 分时复用,互不干扰
*/
void Timer0_Routine() interrupt 1
{
// 定义静态计数变量,静态变量中断中值不丢失,用于累计中断次数,实现不同时长的定时调用
static unsigned int T0Count1,T0Count2,T0Count3;
TL0 = 0x18; // 重装定时器0低8位初值
TH0 = 0xFC; // 重装定时器0高8位初值 定时初值FC18 → 定时500微秒(0.5ms)
T0Count1++; // 累计0.5ms中断次数
if(T0Count1>=20)// 累计20次 → 0.5ms*20 = 20ms
{
T0Count1=0; // 计数清零
Key_Loop(); // 20ms调用一次按键扫描函数,实现按键消抖和扫描
}
T0Count2++; // 累计0.5ms中断次数
if(T0Count2>=2) // 累计2次 → 0.5ms*2 = 2ms
{
T0Count2=0; // 计数清零
Nixie_Loop();//2ms调用一次数码管驱动函数,实现8位数码管动态显示,无闪烁
}
T0Count3++; // 累计0.5ms中断次数
if(T0Count3>=10)// 累计10次 → 0.5ms*10 = 10ms
{
T0Count3=0; // 计数清零
Sec_Loop(); // 10ms调用一次秒表计时函数,驱动秒表走时
}
}