寄存器
在单片机中,具有很多用来储存数据的单元,当我们的代码下载进单片机时,就是将代码转化成二进制的机械码并储存在这种单元中,在这些单元中,有一部分特殊的单元,对里面储存不同的值会导致单片机工作的方式不同,这部分具有特殊功能的单元我们称为寄存器(由多个寄存器组成的一个功能整体我们称之为外设)。(例如 P0 , P0 这个寄存器的作用就是改变 IO 的输出状态。)
不同的芯片内部具有的寄存器是不完全相同的,所以当我们使用不熟悉的芯片时,需要根据芯片数据手册来编写代码。
接下来我们介绍关于 STC12C5A60S2 这个系列的芯片上常用的寄存器。
定时器
在大多数通信或者实际项目中,对于信息处理的时间是有严格要求的,但是由于不同的芯片在功能等方面的差异,我们不能精准的知道运行一行代码的时间长短,所以大多数芯片都会具有定时器这个外设,来满足我们项目需求中的时间需求。
时钟
已经知道芯片中有定时器了,但是定时器是由什么作为参考来计时呢?
在大多数电路中,芯片附近可以找到晶振存在,晶振的作用就是提供一个固定的时钟信号。
像上图这样的我们就称之为时钟信号,晶振所发出的时钟信号的频率时固定且精准的,我们就可以通过读取这个时钟信号的周期次数来计时。
定时器寄存器
我们打开芯片手册
目录中可以直接找到定时器,计数器。
里面对于定时器的介绍有这一段话,通过记录时钟周期的次数来决定计时脉冲的时间,每一次的时钟周期的时间一定,我们改变一次计时脉冲所需要的时钟周期次数,就可以改变一次计时脉冲的时间,我们使用的芯片的定时器支持一个时钟周期就等同于一个计数脉冲,也支持十二个时钟周期等同于一个计数脉冲,默认情况下,是十二个时钟周期等同于一个计数脉冲。
接下来我们可以看到定时器外设所含有的寄存器,我们先讲解 TH 和 TL 寄存器(先不管 0 和 1 的区别,后面会讲到)
记录计数脉冲的次数就是使用的这两个寄存器,两个寄存器都是只有 8 位宽,没过一次计数脉冲, TL 的值就会增加 1 ,随着时间增加, TL 加到 255 时再次加 1 就会溢出,此时 TH 的值会加 1 , TL 的值会清零,随后 TH 的值加到 255 后再次加 1 也会溢出,此时 TH 也会清零。所以定时器不能无限记录时间的长短,是有范围的,在使用定时器时我们就要去考虑定时器的计时范围。
在 STC12C5A60S2 中,一共有两个定时器,分别是 T0 和 T1,所以我们可以看到 TH 和 TL 也被分为了 0 和 1 。
我们再来介绍 TMOD 寄存器的作用,我们可以看到这个寄存器也是八位,被分成了两个四位分别给 T0 和 T1 两个定时器使用。、
每单独的四位里前两位我们不用去深究,有兴趣可以看一下,我们只需要去配置后两位 M0, M1 ,作用时配置定时器的工作方式。
我们可以看到,后两位可以组成四种不同的工作方式。
这儿介绍常用的工作方式。
01
这个工作方式下,TH 和 TL 两个八位的寄存器全部使用,组成一个十六位的寄存器,计数溢出后计数清零。
10
在这个工作方式下,计数器只用了 TL 的八位,计数 256 次后溢出,但是溢出后的值会把 TH 的值存入 TL ,TL 就不被清零。
我们可以看到,在 TCON 这个寄存器中,每一位都被给予了新的名字,其中 TR 的作用时允许定时器开始计时,我们可以把它当作一个开关的功能,当每次计数溢出后, TF 会被置 1,当产生中断(后面会讲)时,这个位才会被清零。
我们需要配置的寄存器也就这些了,其他的默认就行。
代码例程
//所以,我们要开启定时器计时,就对这些寄存器配置就行
void Init(void) {
TCOM = 0x01; //把定时器1配置成01的模式
TR0 = 1; //允许定时器1计数
//定时器计数的初始化就完成了,我们可以通过读取TH和TL的值来判断代码运行时间
}
定时器中断
中断
当我们正在做一件事情的时候,突然被另一件重要的事情打断,所以我们选择优先完成重要的事情,再回来接着完成之前没做完的事情,这个过程我们就称之为发生了中断,重要的事我们称之为中断事件。
定时器中断
由定时器的计数寄存器溢出,从而产生的中断,我们又称之为定时器中断。
上面我们讲到 TF 寄存器,当定时器溢出,这个寄存器会被置 1 ,此时就会发生中断,并且将此寄存器自动清 0 。
从 IE 寄存器中可以看到,定时器中断的允许位为 ET ,总中断的允许位为 EA ,我们将两个同时打开就可以打开我们的定时器中断。
代码例程
void Init(void) {
TCOM = 0x01;
TR0 = 1;
ET0 = 1; //允许定时器中断
EA = 1; //打开总中断
}
//打开了中断,那我们怎么跳转到中断事件去呢?可以通过中断函数
void Int0(void) interrupt 1 { //中断函数是特殊的函数,不需要在main函数之中调用,并且需要通过interrupt来标注这是一个中断函数,后面的数字代表了不同的中断函数,后面会讲。
//这儿就可以写我们需要做的中断事件。
}
我们可以看到,默认情况下,定时器 0 所对应的就是 interrupt 1 ,这些我们都可以根据芯片手册看到。
现在我们以及有了计时,有了中断,那我们怎么才能使每次进入中断的时间相等呢?
我们可以通过对 TH 和 TL 赋值的方法来控制进入中断的时间。
代码例程
//当使用模式 1 0 时
//我们只赋值一次就可以,每次溢出后TL的值会变成我们设置好的TH的值
void Init(void) {
TCOM = 0x02;
TH0 = 10; //每次TL溢出后讲TH的值装进去
TL0 = 10; //TL一开始就从10开始计数人为的控制进入中断的间隔时间
TR0 = 1;
ET0 = 1;
EA = 1;
}
void Int0(void) interrupt 1 {
}
//当使用模式 0 1 时
//我们只赋值一次就可以,每次溢出后TL的值会变成我们设置好的TH的值
void Init(void) {
TCOM = 0x01;
TH0 = 10; //TH一开始就从10开始计数人为的控制进入中断的间隔时间
TL0 = 10; //TL一开始就从10开始计数人为的控制进入中断的间隔时间
TR0 = 1;
ET0 = 1;
EA = 1;
}
void Int0(void) interrupt 1 {
TH0 = 10; //由于不会自动的赋值,所以我们需要在每次进入中断函数(也就是刚刚好溢出时)重新赋值
TL0 = 10;
}
时间计算
我们怎么去控制进入中断的时间呢?
我是用的晶振是 11.0592M 的晶振,所以可以知道 1s 有 11059200 个时钟周期,我们对其除 12 就可以得到一个计数周期在 1s 内的次数,此时我们再除定时器的溢出次数就可以得到 1s 内的中断次数,用 1 除这个次数就可以得到时间。在这儿我们是可以把 TH 和 TL 赋值为负数,正数取反加一这个过程反过来就可以将负数转化成正数,最后发现,当我们赋值成正数时,计数到 256 溢出,当赋值为负数时,计数到 0 溢出。
定时器流水灯实现
#include <STC12C5A60S2.h>
unsigned int nTimer = 0; //定义一个变量作为后面计时使用
void main(void) {
unsigned char i = 0; //定义一个变量作为后面流水使用
TMOD = 0x01; //定时器模式设置成定时器1 01模式
TH0 = -9; //通过计算我们可以得到 115200/12/256/9 = 400,1/400 = 0.0025s定时器溢出,进入一次中断
ET0 = 1; //打开定时器中断
TR0 = 1; //允许中断计数
EA = 1; //打开总中断
while (1) {
if(nTimer >= 200) { //当定时器溢出200次时流水一次,流水灯以200*0.0025 = 0.5s速度流水
P0 = 0x01 << i++; //流水灯流水
i &= 7; //当i>=8时,将i清零
nTimer = 0; //次数清零
}
}
}
void Int0(void) interrupt 1 { //中断函数
TH0 = -9; //重新赋初值
nTimer++; //每次进入中断函数计数加1
}
定时器按键实现
#include <STC12C5A60S2.h>
unsigned int nTimer = 0;
unsigned char cKey, cKeyCode;
unsigned int nDelayKey, nLED_Time = 200;
bit bLoose, bStop;
void DisposeKey(void); //按键函数
void main(void) {
unsigned char i = 0;
TMOD = 0x01;
TH0 = -9;
ET0 = 1;
TR0 = 1;
EA = 1;
while (1) {
if (nTimer >= nLED_Time) {
if (bStop == 0) P0 = 0x01 << i++;
i &= 7;
nTimer = 0;
}
if (cKeyCode != 0) DisposeKey(); //判断按键按下后执行按键函数
}
}
void Int0(void) interrupt 1 {
TH0 = -9;
nTimer++;
if (nDelayKey == 0) { //当没有按键按下时,此时恒为0
cKey = P2 & 0x07 //将按键的值保存在cKey中
if (cKey != 0x07) nDelayKey = 4; //当按键按下后,值不等于0x70,将nDelayKey赋4用来消抖
else bLoose = 0; //松开按键后清零松手标志
} else {
if (--nDelayKey == 0) { //定时器进入四次,相当于10ms按键消抖
cKeyCode = P2 & 0x07; //再次存入按键的值
if (cKeyCode != cKey) cKeyCode = 0; //如果前后两次按键值不相等,就证明按键没有按下,将按键值清零
}
}
}
void DisposeKey(void) {
if(bLoose == 0) { //判断是否松手,没有松手就只会执行一次按键的作用
if (cKeyCode == 6) { //按键具体值
if (nLED_Time <= 400) nLED_Time += 40; //流水速度加快
} else if (cKeyCode == 5) {
if (nLED_Time >= 40) nLED_Time -= 40; //流水速度减慢
} else if (cKeyCode == 3) bStop = !bStop; //流水停止
bLoose = 1; //表示此次按键按下按键作用已经执行了一次了
}
cKeyCode = 0; //对按键进行清零
}
版权声明:本文为CSDN博主「YVinci•」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_53624282/article/details/121332402
暂无评论