一 前言

        由于去年我并没有写关于定时器的内容,今天将去年遗留的内容补上,接下来的这20天时间,我会完成定时器、串口、超声波的原理讲解以及相应的功能实现。争取这一个月给去年的自己一个不留遗憾的答卷。同时,进入四月份,我会进行写几篇博客,会关于真题的讲解,我会以此来督促自己进行蓝桥杯的学习。

        自己今年也选择了27考研,在蓝桥杯比赛结束之前,我的学习规划普遍是白天学考研晚上学蓝桥杯,所以自己会尽己所能的去完成博客攥写。

        接下来的这段时间,也许会很忙,但一定会很充实。愿自己学有所成,学有所获,也祝屏幕前的你,所行皆有所得

二 定时器的原理

  比赛的时候,大家会在电脑上收到一个文件,点开之后是这个界面:

我们点开STC15_DS这个PDF,然后找到493页最上面有个原理图,我们需要先把这个原理图看明白才可以继续之后的学习。

(1)第一部分

  我们先看画红圈的地方,我们从左往右看,一般情况下,我们在比赛的时候,默认GATE=0和TR0=1,那么GATE经过一个反向器,0变1,1又走到一个或门,我们都知道:或门是有1就输出1,那么GATE过来的信号是1,那么不管INT0这里走过来的是什么,这个或门都是输出的1,TR0=1,和或门这里来的1,1和1相与,最后到达control这里就是1,control这里是1就代表闭合了这个开关control,这才真正的把定时器这个开关开开。

大家这里可能就有疑问了,你怎么知道GATE=1 TR0=1呢?

答:因为比赛的时候会给大家提供STC-isp的下载软件,我们会在软件中直接复制粘贴即可,如果大家还是留有疑问,大家到了代码部分,问题就会迎刃而解

(2)第二部分

  现在,我们看绿色画框的地方,这里解释两个东西:

1 SYSclk:这个是系统内部提供的时钟,也就是蓝桥杯官方板子中IAP15F2K60S2芯片自己的时钟源,默认时钟频率是12MHz

    T0Pin:定时器0的外部输入引脚,工作在计数模式时,计数脉冲来自这个引脚的外部脉冲信号,我们只有在用到计数模式的时候,才会选T0Pin,大家不用担心这个

2 C:是count的缩写,表示计数器模式

   T:是time的缩写,表示定时器模式。也是我们常用的模式

那么,C/T=1或0(T上面有一个小横线):这个什么意思呢?

等于1,表示选中了Count计数器的模式

等于0,表示选中了Time定时器的模式

这个C/T等于1还是0,决定了他们要走哪条路?也就是上面图片标成橙色的箭头了。

  其次,画黄色圈的地方,这里的.7:表示它的第7位;AUXR.7/0x12=0,的意思是:AUXR寄存器的第7位 = 0,如果这个值等于0,就说明选SYSclk12分频;如果这个值等于1,就说明选SYSclk不分频;我们在后续写代码的时候也会默认选择12分频,12分频算时间更方便,也更稳。12分频什么意思呢?就是12个脉冲给后面输出一个脉冲,那么,SYSclk中12MHz➗12=1MHz,而1MHz分之一,也就是1us

(3)第三部分

  大家先记住,TH0是高八位,TL0是低八位,TH0和TL0结合是16位,这可以理解成stm32中的16位自动重装载寄存器,16位的二进制,他最多就可以计算2^16=65536个数,TL0 和TH0 用来存放当前正在计数的值。

  蓝色圈出的部分是定时器0模式0中的核心计数区域。上方的 TL0 和 TH0 组成当前工作的16位计数器,下方的 RL_TL0 和 RL_TH0 是自动重装载寄存器,用来提前保存下一轮计数的初值。当 TL0 和 TH0 计数溢出后(也就是读到了第65536个脉冲时),硬件会一方面置位 TF0 产生溢出标志,另一方面自动将 RL_TL0 和 RL_TH0 中保存的初值重新装入 TL0 和 TH0,使定时器无需软件手动赋值就能继续下一轮计数,这就是“16位自动重装载”的本质。

三 实操部分(框架搭建)

  接下来,我带大家实操一下:大家慢慢来,跟着做就好了

  我们首先要整成这个系统框架,如果大家这里不理解怎么做的,大家可以看我前面的文章,会教大家如何添加文件。

第一步

打开STC-isp软件,按照如下图,进行配置:

随后,大家点生成c代码、复制代码。

第二步

  在我们已经创建好的Timer.c文件中粘贴即可,大家要注意:ET1这个地方,不要写错了,因为此时我们用的时定时器1,定时器1用的是ET1,定时器0用的是ET0,我这次展示的主要是用定时器1。

第三步

  接下来,我们继续点开STC-ISP这个软件,找到范例程序,再点这个单片机系列

通过滑动滚轮,找到这个定时器1模式0(16位自动重装)里面的C。

第四步

  随后,我们在这个里面找到这个红色框的代码,把这几行代码复制(如上图);

  之后把我们复制的内容再放到main.c函数中的void main()的最下面,单独把这个定时器1的中断函数写出来,同时我们要把“P10 = ! P10;”这行代码删掉即可(如下图)

此时,我们就把定时器的中断框架搭完了。

四 main.c文件中的代码

#include "STC15F2K60S2.H"
#include "bsp_led.h"
#include "bsp_init.h"
#include "timer.h"

//进入定时器中断标志位
bit timer1_interrupt_into_flag = 0;

//全局变量,因为它们定义在所有函数外部,整个当前 main.c 文件中的函数都可以访问它们。
unsigned int trigger1_1ms_count = 0;

unsigned char ucled1 =0x00;

void main()
{
	Cls_Peripherial();
	Timer1Init();
	
	while(1)
	{

		//定时器1控制第2个灯翻转
		if(timer1_interrupt_into_flag == 1)
		{
			/*复位中断标志*/
			timer1_interrupt_into_flag = 0;
			trigger1_1ms_count++;
		}
		
		if((trigger1_1ms_count % 1000 == 0) && (trigger1_1ms_count > 0))
		{
			trigger1_1ms_count = 0;
			ucled1 ^= 0x40;
		}
		
		Led_Disp(ucled1);

		
	}
	
}


/* Timer1 interrupt routine */
void tm1_isr() interrupt 3
{
  timer1_interrupt_into_flag = 1;
	
}

大家看如下的思路总结的时候,建议大家把这些我写的话,大家放到别的地方,对着代码慢慢看,慢慢理解即可~

(1):整体功能

  这段程序的作用很明确,就是利用定时器1产生周期中断,再借助计数变量实现 LED 周期翻转。整个思路可以概括成一句话:定时器1每隔1ms发出一次“时间到了”的信号,主循环把这个信号累计起来,当累计到1000次的时候,就让 LED 翻转一次 因为1000个1ms刚好等于1秒,所以最终表现出来的效果就是某一个 LED 每隔1秒亮灭切换一次。

(2):关键变量的作用

程序中有三个关键变量。分别为:

timer1_interrupt_into_flag 是定时器1的中断标志位,它的作用相当于一个“通知信号”,只要定时器1进入了一次中断,这个标志位就会被置为1。

trigger1_1ms_count 是1ms计数变量,用来记录定时器1已经触发了多少次中断。由于这里默认每次中断对应1ms,所以它本质上记录的就是“程序已经累计了多少个1ms”

ucled1 则是LED状态变量,用来保存当前LED的显示状态,其中 0x40 对应的是第三个LED亮灭,后面通过对这一位进行异或来实现灯的翻转。

(3):程序启动后的执行过程

  程序进入 main() 之后,先调用 Cls_Peripherial() 完成外设初始化,再调用 Timer1Init() 对定时器1进行初始化。初始化完成后,定时器1就会按照设定的周期工作。比如说:这里已经把定时器1配置成了每1ms进入一次中断,那么程序运行起来以后,定时器1就会像一个固定节拍的时钟一样,每过1ms触发一次中断服务函数 tm1_isr()

(4):中断函数做了什么

  当定时器1发生中断时,程序会自动进入 tm1_isr() 函数。在这个函数里,并没有直接去控制LED,也没有做复杂计算,而只是简单地把 timer1_interrupt_into_flag 置为1。这样做的好处是,中断函数本身非常简短,只负责“发通知”,就是那个中断标志位,真正的处理逻辑仍然放在主循环中完成。

  也就是说,这一步可以理解成:定时器1每隔1ms告诉主循环一次,时间到了,你可以开始记这一毫秒了。

(5):主循环如何完成1秒计时

while(1) 死循环中,程序会不断检查 timer1_interrupt_into_flag 是否为1。如果为1,就说明刚刚发生过一次定时器1中断。这时主循环先把这个标志位清零,避免重复处理,然后让 trigger1_1ms_count 自加1。这样一来,每来一次1ms中断,计数变量就增加1。随着程序不断运行,trigger1_1ms_count 会从0逐步增加到1000,这就表示时间已经累计了1000ms,也就是1秒。

(6):LED为什么会翻转

trigger1_1ms_count 增加到1000时,条件判断成立,程序会先把这个计数变量清零,准备开始下一轮1秒计时。接着执行 ucled1 ^= 0x40;,这句代码的作用是把 ucled10x40 对应的那一位进行按位异或翻转。如果原来这一位是0,就变成1;如果原来是1,就变成0。于是 LED 的显示状态就发生了一次切换。最后再通过 Led_Disp(ucled1) 把当前状态输出到LED模块上,因此最终看到的现象就是:该LED每隔1秒翻转一次,实现周期闪烁。

整体原理总结

   (这段话我用chatGPT总结的,我感觉他总结的会比我的更精辟)定时器1每隔1ms进入一次中断,中断函数把标志位置1,主循环检测到这个标志后就把1ms计数变量加1。当计数变量累计到1000时,说明1秒已经到达,于是程序翻转LED状态,并重新开始下一轮计时。整个设计中,中断负责提供时间基准、主循环负责处理业务逻辑、计数变量负责累计时间、状态变量负责控制LED显示,这就是这段代码能够实现“LED每秒闪烁一次”的根本原因。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐