【51单片机】6. 定时器、按键切换流水灯&时钟Demo

1. 定时器

  • 定义:51单片机定时器属于单片机内部资源,电路的连接和运转均在单片机内部完成

  • 作用:

    • 用作计时系统,实现软件计时,或使程序隔固定时间完成一项操作

    • 替代长时间的Delay,提高CPU运行效率和处理速度

举例子,在写流水灯代码的时候,置状态→Delay→置状态→Delay→...,假如想要边流水灯,边使用按键控制流水灯就会很困难,因为Delay的时候会占用所有的CPU,导致无法同时进行其他操作。

  • 原理:

    • 定时器在单片机内部像一个小闹钟,根据时钟的输出信号,每隔"一秒",计数单元数值增加一

    • 当计数单元数值增加到"设定的闹钟提醒时间"时,计数单元就会向中断系统发出中断申请,产生"响铃提醒"

    • 从而程序跳转到中断服务函数中执行

2. 常用的定时模式(16位定时器/计数器)

  • Sysclk:系统时钟,即晶振周期,使用这个就是定时器

    • Sysclk有÷12÷6,这个表示的是12分频或6分频。江科大视频里的单片机是12MHz的,如果选择÷12就得到输出的频率是1MHz,一个周期就表示1μs的时间,如果选择÷6就得到2MHz,则是2μs
  • T0 Pin:由单片机外部引脚提供时,单片机的定时器就是计数器

  • 其中, C / T ‾ C/\overline{T} C/T表示选下(计数器Counter)还是选上(定时器Timer):=0表示选择上面的定时器,=1表示选择下面的计数器

  • 下面向右旋转90°的表示非门,GATE输入为0,经非门为1

  • 最靠近非门的带有轻微弯曲的,还连接了 I N T 0 ‾ \overline{INT0} INT0的表示或门

  • 靠近或门的直的、还连接了 T R 0 TR_0 TR0的表示与门

  • TL0表示Time Low,TH0表示Time High,就是低位和高位的区别,TL0和TH0连起来就是16位的计数器,每次+1+1,作为定时器使用,到达上限65536后触发中断

3. 中断系统

这是计算机组成原理里边比较基础的一个内容,这里仅粘贴一些概念:

  • 中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的

  • CPU正处理某件事时外界发生了紧急事件请求,要求CPU暂停当前工作,转而去处理紧急事件,处理完后,再回到原来被中断的地方继续工作,则称为中断

  • 微型机中断系统允许多个中断源,多个中断源同时向CPU请求中断,要求为它服务时,CPU优先响应哪一个中断源请求,通常根据中断源轻重缓急优先处理最紧急的中断请求源。即每隔中断源有一个优先级别 ,CPU总是优先响应优先级别最高的中断请求

  • CPU在处理一个中断请求时,发生了另一个优先级更高的中断请求,CPU能够暂停对原来中断源的服务,转而去处理高优先级的中断请求源,处理完后再回到原低级中断服务程序,这样的过程称为中断嵌套 ,这样的中断系统称为多级中断系统。而没有中断嵌套功能的中断系统称为单级中断系统

4. 定时器、中断相关寄存器

单片机通过配置寄存器来控制内部线路的连接。

定时器/计数器与中断相关的寄存器如下所示:

只需要给这些寄存器赋对应的值,就可以控制电路进行工作。

以寄存器电路图为例:

这其中,根据单片机相关说明书:

  • EA:CPU总中断允许控制位,EA=1,CPU开放中断,EA=0,CPU屏蔽所有中断申请。EA的作用是使中断允许形成两级控制。即各中断源首先受EA控制;其次还受各中断源自己的中断允许控制位控制。

  • EX0/EX1,ET0/1:外部中断0/1中断允许位。为1时允许中断,为0禁止中断

  • PX0/1,PT0/1则是控制中断优先级的:为0低优先级,为1高优先级

5. 初始化定时器

配置可以参考STC89C52手册里面中断和定时器/计数器相关的内容。需要配置TCON和TMOD

定时器相关位如下:

M1和M0共同决定工作在那种模式:

  • M1 M0 模式

  • 0 0 13位定时器/计数器

  • 0 1 16位定时器/计数器(常用)

  • 1 0 8位自动重装载定时器

  • 1 1 定时器0此时作为双8位定时器/计数器

C / T ‾ C/\overline{T} C/T给0就是定时器模式,给1就是计数器模式

GATE门控端给0就是TR_0单独控制

c 复制代码
TMOD = 0x01;

PS: 不可位寻址表示必须整体赋值,不能单个赋值;可位寻址就是可以整体赋值也可以单个赋值。

但上面这种配置会有一个问题,可能原本有其他程序在使用高4位的内容,这样一配置就会覆盖了高4位,所以换成如下方式:

c 复制代码
TMOD &= 0xF0; // TMOD低四位清零,高四位不变
TMOD |= 0x01; // TMOD最低位置1,高位不变

TF0是定时器/计数器T0溢出中断标志。置1表示向CPU请求中断。

c 复制代码
TF0 = 0;

TR0是定时器运行控制位,TR0=1时允许T0开始计数,TR0=0时禁止T0计数。

c 复制代码
TR0 = 1;

IE0:IE0=1表示外部中断0向CPU请求中断,CPU响应外部中断时由硬件将IE0置为0。

IT0:外部中断0。

PS:IE0和IT0用于管GATE那一部分的,但是GATE已经置0了,此时不是从外面输入的无需理会。

6. 定时器进一步初始化

频率f为12MHz,定时器时钟为12T,根据公式 T = 1 f T=\frac{1}{f} T=f1,有 12 T = 1 12 × 10 6 H z 12T=\frac{1}{12×10^6Hz} 12T=12×106Hz1,即1μs计时一次

定时器每次+1表示1μs,作为16位定时器,总共能计65536μs,也就是65ms。

想要能够计到1s,唯一的办法是每计1ms就触发中断来重置定时器,重复1000次后就是1s。

为了实现 "每计1ms就触发中断来重置定时器",我们需要给TL0和TH0置不同的数:

也就是从64536开始计数,我们要给TH0和TL0一共赋上64536的值。通过除法和求余可以取出高8位值和低8位值:

c 复制代码
TH0 = 64536 / 256;
TL0 = 64536 % 256;

以上定时器的位置,也可以通过stc-isp这个软件来快速初始化:

上图选的有点问题,应该选16位 而不是16位自动重载

7. 中断部分初始化

实际上就是将4中的中断通路给打通,等待中断到来时,发起中断,观察4中的图,实际上需要处理的寄存器包括:

  • ET0

  • EA

  • PT0(IP,中断优先级)

c 复制代码
ET0=1;
EA=1;
PT0=0;	

8. 用定时器,让LED灯1s亮1次

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

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	// 定时器初始化
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	// 中断初始化
	ET0=1;
	EA=1;
	PT0=0; // 优先级设置
}


unsigned int timeCount = 0;
void Timer0_Rountine() interrupt 1 //定时器0中断函数,定时器计了1000次1μs为1ms
{
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	timeCount++;   // 统计计数次数
	if (timeCount == 1000)  // 产生1000次中断,即timeCount计了1000次为1s
	{
		timeCount = 0;  // 重置计数
		P2_0 = ~P2_0;  // 亮灯取反
	}
}

void main()
{
	Timer0_Init();
	while (1)
	{
	}
}

实际上我的板子是11.0592MHz,这样速度可能会比1s快,推理如下:

bash 复制代码
选择12MHz & 12T → 1μs计时一次,1ms = 1000μs,计1000次得1ms,while循环反复1000得1s
选择11.0592MHz & 12T → 0.922μs计时一次,1ms = 1000μs,计1000次得0.922ms,while循环反复1000得0.92s

整体的思路就是:

  1. 上电时初始化定时器及中断的寄存器(Timer0_Init()函数)

  2. 写中断函数,处理自己想要的流程

9. 利用定时器实现按钮控制LED流水灯向左/向右闪

主函数:

c 复制代码
#include <REGX52.H>
#include <INTRINS.H>
#include "Timer0.h"
#include "Key.h"

unsigned char keyNum,LEDMode = 0; // 表示获取的keyNum,以及当前流水灯所处状态

void main()
{
	P2 = 0xFE; // 流水灯初始状态,第1个灯亮
	Timer0_Init(); // 上电,定时器初始化
	while (1)
	{
		keyNum = key(); // 获取keyNum
		if (keyNum) // 如果keyNum不等于0,判断是否需要转变模式
		{
			if (keyNum == 1) // 如果按下按键
			{
				LEDMode++; // LED模式改变
				if (LEDMode == 2) LEDMode = 0; // 达到2时变回1
			}
		}
	}
}

unsigned int timeCount = 0;
void Timer0_Rountine() interrupt 1 //定时器0中断函数,定时器计了1000次1μs为1ms
{
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	timeCount++;   // 统计计数次数
	if (timeCount == 500)  // 产生1000次中断,即timeCount计了1000次为1s
	{
		timeCount = 0;  // 重置计数
		if (LEDMode == 0) P2 = _crol_(P2,1); // INTRINS.H封装的好用函数,左移1位,溢出会自动判断并回去
		else P2 = _cror_(P2,1); // INTRINS.H封装的好用函数,右移1位,溢出会自动判断并回去
	}
}

Timer0.c实际上就是封装了初始化定时器0的函数:

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

/**
  * @brief 定时器0初始化,1毫秒@11.0592MHz
  * @param 无
  * @retval 无
  */
void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	// 定时器初始化
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	// 中断初始化
	ET0=1;
	EA=1;
	PT0=0; // 优先级设置
}

/* 定时器中断函数模板
void Timer0_Rountine() interrupt 1 //定时器0中断函数,定时器计了1000次1μs为1ms
{
	static unsigned int timeCount = 0;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	timeCount++;   // 统计计数次数
	if (timeCount == 1000)  // 产生1000次中断,即timeCount计了1000次为1s
	{
		timeCount = 0;  // 重置计数
	}
}
*/

Timer0.h:

c 复制代码
#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

Key.c就是检测按下的按键键值:

c 复制代码
#include <REGX52.H>
#include "Delay.h"

/**
  * @brief 获取独立按键键码
  * @param 无
  * @retval 
  */
unsigned char key()
{
	unsigned char currKey = 0;
	if (P3_1 == 0) {Delay(20); while(P3_1 == 0); Delay(20); currKey = 1;}
	if (P3_0 == 0) {Delay(20); while(P3_0 == 0); Delay(20); currKey = 2;}
	if (P3_2 == 0) {Delay(20); while(P3_2 == 0); Delay(20); currKey = 3;}
	if (P3_3 == 0) {Delay(20); while(P3_3 == 0); Delay(20); currKey = 4;}
	
	return currKey;
}

Key.h:

c 复制代码
#ifndef __KEY_H__
#define __KEY_H__

unsigned char key();

#endif 

Delay.c是很久之前写的延时:

c 复制代码
#include <INTRINS.H>

void Delay(unsigned int ms)		//@11.0592MHz
{
	
	unsigned char i, j;
	while (ms)
	{

		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
		
		ms--;
	}
}

Delay.h:

c 复制代码
#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int ms);

#endif

当然也可以自己试着写一下左右移1位的函数:

c 复制代码
#include <REGX52.H>
#include <INTRINS.H>
#include "Timer0.h"
#include "Key.h"

unsigned char keyNum,LEDMode = 0; // 表示获取的keyNum,以及当前流水灯所处状态
unsigned char bitMoveL(unsigned char P);
unsigned char bitMoveR(unsigned char P);

void main()
{
	P2 = 0xFE; // 流水灯初始状态,第1个灯亮
	Timer0_Init(); // 上电,定时器初始化
	while (1)
	{
		keyNum = key(); // 获取keyNum
		if (keyNum) // 如果keyNum不等于0,判断是否需要转变模式
		{
			if (keyNum == 1) // 如果按下按键
			{
				LEDMode++; // LED模式改变
				if (LEDMode == 2) LEDMode = 0; // 达到2时变回1
			}
		}
	}
}

unsigned int timeCount = 0;
void Timer0_Rountine() interrupt 1 //定时器0中断函数,定时器计了1000次1μs为1ms
{
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	timeCount++;   // 统计计数次数
	if (timeCount == 500)  // 产生1000次中断,即timeCount计了1000次为1s
	{
		timeCount = 0;  // 重置计数
		if (LEDMode == 0) P2 = bitMoveL(P2); // INTRINS.H封装的好用函数,左移1位,溢出会自动判断并回去
		else P2 = bitMoveR(P2); // INTRINS.H封装的好用函数,右移1位,溢出会自动判断并回去
	}
}

unsigned char bitMoveL(unsigned char P)
{
	return P == 0x7F? 0xFE : ~((~P) << 1);
}

unsigned char bitMoveR(unsigned char P)
{
	return P == 0xFE? 0x7F : ~((~P) >> 1);
}

注意:如果这里给的类型都是unsigned int的话,右移就会出现问题,因为unsigned int是16位的,右移可能会导致高8位部分移动过来出现一些不合理的情况

另附结果,注意:由于LED灯位置的设计,我们做左移操作时,板子上显示的是右移;我们做右移操作时,板子上显示的结果是左移。

按键切换流水灯模式

左移:


省去中间一部分...跳到D7

右移:




10. 定时器显示时钟

main.c

c 复制代码
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"
#include "LCD1602.h"

unsigned char hours = 0;
unsigned char minutes = 0;
unsigned char seconds = 0;

void main()
{
	//上电初始化
	LCD_Init();
	Timer0_Init();
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowNum(2,1,hours,2);
	LCD_ShowString(2,3,":");
	LCD_ShowNum(2,4,minutes,2);
	LCD_ShowString(2,6,":");
	LCD_ShowNum(2,7,seconds,2);
	while(1)
	{
	}
}

void Timer0_Rountine() interrupt 1 //定时器0中断函数,定时器计了1000次1μs为1ms
{
	static unsigned int timeCount = 0;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	timeCount++;   // 统计计数次数
	if (timeCount == 1000)  // 产生1000次中断,即timeCount计了1000次为1s
	{
		timeCount = 0;  // 重置计数
		// 更新计时
		seconds++; // 每1s,秒针+1
		if (seconds == 60) // 当到60s时
		{
			minutes++; // 分针+1
			seconds = 0; // 重置秒针
			if (minutes == 60) // 当到60min时
			{
				hours++; // 时针+1
				minutes = 0; // 重置分针
				if (hours == 24) // 当到24h时
				{
					hours = 0; // 重置时针
				}
				LCD_ShowNum(2,1,hours,2); // 显示时针
			}
			LCD_ShowNum(2,4,minutes,2); // 显示分针
		}
		LCD_ShowNum(2,7,seconds,2); // 显示秒数
	}
}

Timer0.cTimer0.hDelay.cDelay.h的代码和9中的一样

LCD1602.c:

c 复制代码
#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');
	}
}

LCD1602.h:

c 复制代码
#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

效果如下:

定时器实现时钟





江科大的代码是将LCD_ShowNum的所有内容放在While循环里不断刷新:

c 复制代码
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"
#include "LCD1602.h"

unsigned char hours = 23;
unsigned char minutes = 59;
unsigned char seconds = 55;

void main()
{
	//上电初始化
	LCD_Init();
	Timer0_Init();
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowNum(2,1,hours,2);
	LCD_ShowString(2,3,":");
	LCD_ShowNum(2,4,minutes,2);
	LCD_ShowString(2,6,":");
	LCD_ShowNum(2,7,seconds,2);
	while(1)
	{
		LCD_ShowNum(2,1,hours,2); // 显示时针
		LCD_ShowNum(2,4,minutes,2); // 显示分针
		LCD_ShowNum(2,7,seconds,2); // 显示秒数
	}
}

void Timer0_Rountine() interrupt 1 //定时器0中断函数,定时器计了1000次1μs为1ms
{
	static unsigned int timeCount = 0;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	timeCount++;   // 统计计数次数
	if (timeCount == 1000)  // 产生1000次中断,即timeCount计了1000次为1s
	{
		timeCount = 0;  // 重置计数
		// 更新计时
		seconds++; // 每1s,秒针+1
		if (seconds == 60) // 当到60s时
		{
			minutes++; // 分针+1
			seconds = 0; // 重置秒针
			if (minutes == 60) // 当到60min时
			{
				hours++; // 时针+1
				minutes = 0; // 重置分针
				if (hours == 24) // 当到24h时
				{
					hours = 0; // 重置时针
				}
			}
		}
	}
}

我是把赋值语句全写在中断里面了, 我个人感觉应该是差不多的。但是,问题是:不知道赋值语句会不会耗时长从而导致时钟计数不准

  • 不会的话,我觉得我的方法更好一些,必要时候才赋值,不用一直刷新

  • 会的话,江科大的方法更好

相关推荐
竹照煜_ysn3 小时前
蓝桥杯51单片机设计
单片机·嵌入式硬件·51单片机
Electron-er4 小时前
汽车LIN总线通讯:从物理层到协议栈的深度解析
单片机·汽车电子·lin总线·lin总线通讯
Do vis8245 小时前
STM32第十六天蓝牙模块
stm32·单片机·嵌入式硬件
学不动CV了5 小时前
ARM单片机启动流程(二)(详细解析)
c语言·arm开发·stm32·单片机·51单片机
猫猫的小茶馆7 小时前
【STM32】通用定时器基本原理
c语言·stm32·单片机·嵌入式硬件·mcu·51单片机
jingshaoqi_ccc7 小时前
stm32的USART使用DMA配置成循环模式时发送和接收有着本质区别
stm32·单片机·嵌入式硬件
玉树临风江流儿11 小时前
炸鸡派-定时器基础例程
单片机·嵌入式硬件
小宋同学在不断学习12 小时前
stm32-掌握SPI原理(一)
stm32·单片机·spi
学不动CV了12 小时前
数据结构---链表结构体、指针深入理解(三)
c语言·arm开发·数据结构·stm32·单片机·链表
工业互联网专业17 小时前
汇编与接口技术:8259中断实验
汇编·单片机·嵌入式硬件·8259中断实验