C8051F020 定时器中断
概述
C8051DF020内部有5个计数器/定时器,具体功能如下图,其中定时器0、1可以用来作为通用定时器使用,来产生周期性的中断请求。
时钟滴答周期
位4: T1M:定时器1 时钟选择。该位控制提供给定时器1 的系统时钟的分频数。
0:定时器1 使用系统时钟的12 分频
1:定时器1 使用系统时钟
位3: T0M:定时器0 时钟选择。该位控制提供给定时器0 的系统时钟的分频数。
0:计数器/定时器使用系统时钟的12 分频
1:计数器/定时器使用系统时钟
如果设置此为控制器的系统时钟为24MHz,则默认12分频后,时钟频率为2MHz
此时若对定时器1设置:
TL0=(256-200); // when 24MHz & SYSCLK/12, 10kHz timeout
TH0=(256-200); // reload
则定时器溢出的频率为:
即周期为$100μs$
如果设置T0M(T1M)位为1,此时使用的是系统时钟,即如果控制器的系统时钟为24MHz,则定时器时钟频率也为24MHz。
定时器0和定时器1
因为这两个定时器是可以产生通用定时器中断的,所以重点介绍一下这两个定时器的一些功能。
相关寄存器
定时器0和定时器1都是一个16位的寄存器,在被访问时以两个字节出现:低字节(TL0或TL1)和一个高字节(TH0或TH1)。定时器控制寄存器(TCON)用于允许定时器0和1以及指示它们的状态。。这两个定时器都有四种工作方式,通过设置定时器方式寄存器(TMOD)中的方式选择位M1-M0 来选择工作方式。相关寄存器说明如下
THX和TLX
X=0 or 1
简单理解:定时器0和定时器1都是一个16位的寄存器,THX和TLX是装寄存器的数的“盒子”;H是”High”的意思,代表着THX装的是16位中的高8位数;L是”Low”的意思,代表着TLX装的是16位中的低8位数。
TCON:定时器控制寄存器
此寄存器中位$0\sim 3$控制的是外部中断,暂且忽略。控制定时器0、1的是$4\sim 7$。
以定时器0为例,TCON中位4、5控制的是定时器0,位4是控制定时器0是否运行控制,默认0禁止运行,当设置为1时为允许定时器0控制。
TF0是定时器0的溢出标志位,在使用定时器0中断时该为可自动被清零,不需要手动控制,所以在此不必深究。
位6、7对应定时器1控制位,功能同定时器0。
所以关于此寄存器的定时器中断相关编程只有控制TR0或TR1来控制允许定时器0或1:
TR0=1; //允许定时器0运行控制:即打开定时器0,开始工作
TR1=1; //允许定时器1运行控制
TMOD: 定时器方式寄存器
TMOD寄存器低四位是控制定时器0的方式,高四位控制定时器1的方式,下面以定时器0为例进行介绍。
位0-1控制的是定时器0的工作方式选择,其四种工作方式会在下面详细介绍。在这四种方式里在使用定时器中断时用的最多的是方式2,即T0M0=0,T0M1=1。
位2为选择功能为计数器还是定时器,在使用定时器中断毫无疑问设置为0。
位3为定时器0门控位,此位的功能是可以将定时器的运作和外部中断联系起来,多用于脉冲宽度的测量,在本项目中应该用不到,故设置为0即可。
定时器1同定时器0。
TMOD=0X02; //设置定时器的工作方式为自动重装载的8位定时器,其是否允许控制与外部中断无关
定时器0、1工作方式
方式2:8位自动重装载的定时器
此方式在定时器中断中使用的最多。
在此方式下,TL0保持计数值,TH0保持重载值。当TL0 中的计数值发生溢出(从全‘1’到0x00)时,定时器溢出标志TF0(TCON.5)被置位,TH0 中的重载值被重新装入到TL0。如果中断被允许,在TF0 被置位时将产生一个中断。TH0 中的重载值保持不变。
TMOD=0X02; //设置定时器的工作方式为自动重装载的8位定时器
TL0=(256-200); // when 24MHz & SYSCLK/12, 10kHz timeout
TH0=(256-200); // reload
TR0=1; // 为保证第一次计数正确,必须在允许定时器之前将TL0初始化为希望的数值
方式0,1,3
方式0:13位计数器/定时器。简单来说,本来应该是THX、TLX两个字节,但是现在只用了13位,所以这里TLX的高三位没用使用,予以忽略。
方式1:16位计数器/定时器。很明显,和方式0的区别在于两个字节所有位都使用上了。
方式3:两个8位计数器/定时器(仅定时器0)。定时器0的高、低8位作为两个独立的定时器/计数器来运作,它们的功能不同。使用此方式时,定时器1停止运行,因为TH0用的占用了定时器1的运行控制位TR1,此时定时器1只可以用于产生串口的波特率时钟。
关于定时器的中断
相关寄存器
IE:中断允许寄存器
对于定时器中断来说,我们只需要关注位1、位3、位7
位1:ET0:定时器0中断允许位。此为设置为1,即可允许TF0溢出标志位的中断请求。注意和TR0的区别:ET0是定时器0的中断允许位,TR0是允许定时器0工作,所以在使用定时器0中断时,这两位都应该置为1。
位3:ET1:定时器1中断允许位。原理同定时器0。
位7:EA:允许所有中断。相当于一个中断的“总开关”,在使用任何中断时此位都必须置1。
EA=1; //打开总中断
ET0=1; //允许定时器0中断
ET1=1; //允许定时器1中断
IP:中断优先级寄存器
此寄存器是为了设置中断的优先级,默认中断优先级为
中断源 | 优先级 |
---|---|
复位 | 最高 |
外部中断0(INT0) | 0 |
定时器0溢出 | 1 |
外部中断1(INT1) | 2 |
定时器1溢出 | 3 |
UART0 | 4 |
定时器2溢出 | 5 |
如果需要调整中断源的优先级,即可通过控制IP寄存器来实现,比如设置定时器0中断为高优先级:
IP=0X02; 设置定时器0中断为高优先级
这个如果不是特别复杂的项目,一般用的也不多
软件实现
实现定时器中断的软件程序一般较为固定,假设使用定时器0工作方式2(8位自动重载),溢出周期为100μs,程序实现如下:
// 初始化设置
TL0=(256-200); // 24MHz/(12×200)=10KHz,100μs
TH0=(256-200); // 重载值
TMOD=0x02; // 设置定时器0工作方式2
EA=1; // 开总中断
ET0=1; // 允许定时器0中断
TR0=1; // 启动定时器0
// 定时器0中断服务程序
void t0_isr(void) __interrupt (1)
{
t0_isr_callback();
}
// 定时器0中断服务
void t0_isr_callback(void)
{
//用户设置相关程序,即进入了定时器中断之后要干什么事
}
中断服务程序中“__interrupt (x)”是编译器规定的固定语法,x 代表这个函数是作为第几号中断的服务程序。
其实在定时器0中断服务程序中直接写相关程序也可,这里老师是为了程序结构清晰且利于维护,加了一个函数t0_isr_callback(void)。
老师原话:“这个中断服务程序里什么都没做,只是调用了函数t0_isr_callback()。为什么这样做?这可是我的精心设计啊!有了这个函数,你不必关心事情何时发生,你只需要关心事情发生了要怎么处理,即由你来写回调函数里的内容。因为是人家调用你,所以叫“回调(callback)”。在很多已经做好框架(Framework)的软件项目里,回调经常被使用,它为框架搭建者和内容填充者提供了清晰的分工。”