基本定时器一启动就直接进中断的两种解决方式

关于基本定时器的影子寄存器的解释

  • 预分频器和自动重装载寄存器都是有影子寄存器的(预加载寄存器和影子寄存器)。
    • 重装载的寄存器中的预加载寄存器是:只要确定了预加载寄存器,那么后续的值会先到预加载寄存器中,等待counter溢出了之后,才会加载到影子寄存器中直接给counter。如果没有勾选预加载寄存器的话,后续的值会直接到影子寄存器中,影子寄存器会实时同步到counter中去。有没有预加载寄存器就是有没有缓存,有的话,需要等待到溢出的时候,其中的值才会实时同步到。没有话,只要改变了重装载中的值,那么就会实时同步到counter。——我们默认是没有的,如果寄存器ARPE是0的话(默认为0),那么就是没有缓存,同步更新;如果这个寄存器是1的话,那么就是有缓存,就不能同步更新。
    • 而预分频器中也是有影子寄存器: 预分频器值寄存器TIMx_PSC存在影子寄存器,所以在定时器启动后更改TIMx_PSC的值并不会立即影响当前定时器的时钟频率。要等到下一个更新事件(UEV)发生时才会生效。 总之需要记住,预分频器自动有缓存的功能,只有经过一次更新之后,其频率才能进入到影子寄存器中,然后输出出去,其和上述的重装载寄存器类似,也有一个预分频的寄存器当做是缓存,而这个缓存是一定存在的,是没有控制寄存器的位数来控制的。

关于基本定时器中两个控制寄存器位的用法以及基本定时器一启动就直接进中断的两种解决方式

  • 在认识这两种寄存器之前呢,我们不得不有一个背景知识的了解:当我们的counter溢出的时候,会产生一次更新,这个更新是万能的,不仅可以触发中断,触发DMA输出(没学到),还可以对寄存器进行一次更新,因为这个更新的本质是溢出,溢出代表一个新的轮回的开始。不管开了中断与否,中断标志位会被置位为1,触发中断,自动重装载寄存器中的预装载寄存器中的值会自动更新到影子寄存器,同步更新到counter中(前提是开启了预装载缓存),预分频器中的缓存也会发送到影子寄存器中进行一个输出(这里的缓存是不可以更更改的。)
  • 接下来介绍的这两个寄存器和上述的更新是有关系的
    • 事件产生寄存器(EGR)中的控制位UG——一般由软件产生的叫做事件。事件是什么呢?事件可以理解为是一种另类的更新,这个更新和上述将的溢出的更新是一样的,这个是一种人为的更新。那这个有什么用呢?我们知道在自动重装载寄存器中,如果不开缓存的话,其写入寄存器中的数据会直接同步给counter的,但是预分频器可没有这样的功能,其中的缓存是固定的,不可以开启和取消,也就是说当你配置预分频器的时候,你配置的值会自动进入到缓存之中,而不会输出出去,这就导致了什么呢?这就导致输出给counter的频率还是原来的频率即72Mhz,而不是设置的10khz,这样的话counter里面的数值会以比以前7200倍的速度运行,那么其中的延时就不再准确了,counter里面的数据就是很快溢出,进而很快标志中断位,进而很快执行中断服务函数,这就是为什么基本定时器一启动就给人一种直接进入中断的感觉。而其中的UG就可以解决这个问题,这个问题根源在于分频的频率没有顺利输出,而存在于缓存当中,在上述解释(上述的解释均来自于手册)中我们可以知道,设置了UG之后,会自动产生一个更新,有了这次更新之后,预分频器中的缓存就能同步进影子寄存器然后直接输出了。这样的话,预分频器和重装载器中的数值就是我们所需要的了。不过等到执行的时候,又发现,一启动就直接进入中断了,这是为什么呢?原来啊,你软件完成了一次更新之后,你的中断标志位变成1了,这样的话,你一开启中断又马上进入中断了,根本来不及counter溢出了,就执行了一次中断,这就是原因。怎么解决呢?在执行外UG之后,在手动将中断标志位置为0就好了,就关闭中断了。
    • CR1控制寄存器中控制位URS(更新请求源),这个有什么用呢?在上述的解决办法中,设置UG(update generation)控制的位是必须的,但随之而来的副作用是其中的中断标志位会自动的置为1,这个是手动更新导致的。而这个URS刚好可以改变更新触发的其他的变化,在手册中,当URS为1的时候,只有溢出才能触发中断,也就是说当你UG为1的时候,就不会触发中断了。
    • 额外的感悟:原来将UG控制位激活之后,产生了一次更新,而这个更新之所以能够控制中断并不是由其本身自己这个寄存器位来决定的,而是由其他寄存器决定的。有些寄存器的位数在没有启用的时候,其默认值一般都是0,但即便是默认值也是有相应的功能。比如上述两个寄存器中控制位的关系。
    • 其中的UG的功能是产生一个更新,而这个更新只能是更新寄存器,而不能产生中断。产生中断的功能从哪里来呢?就从URS这个控制位上来,如果我们不管这个控制位的话,那么其默认就是0,那么其中就已经说明了UG是可以产生中断的,如果将这个URS变成1的话,那么就不可以产生中断了。

两种解决办法的相关代码实现

void Dri_TIM6_Init()
{
    /*打开定时器的时钟*/
    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    /*配置定时器的重装载*/
    TIM6->ARR = AUTO - 1;

    /*配置定时器的预分频*/
    TIM6->PSC = 7200 - 1;

    printf("begin!\r\n");

    /*手动产生一次更新*/
    TIM6->EGR |= TIM_EGR_UG;

    /*关闭由于更新产生的中断位*/
    TIM6->SR &= ~TIM_SR_UIF;

    /*配置计数器的使能*/
    TIM6->CR1 |= TIM_CR1_CEN;

    /*NVIC的部分*/
    /*配置中断优先级组*/
    NVIC_SetPriorityGrouping(3);

    /*配置的中断优先级*/
    NVIC_SetPriority(TIM6_IRQn, 1);

    /*配置所有的中断*/
    NVIC_EnableIRQ(TIM6_IRQn);

    /*配置中断反馈使能*/
    TIM6->DIER |= TIM_DIER_UIE;
}

void TIM6_IRQHandler(void)
{
    TIM6->SR &= ~TIM_SR_UIF;
    Int_LED_Toggle(LED2);
    printf("1s\r\n");
}
void Dri_TIM6_Init()
{
    /*打开定时器的时钟*/
    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    /*配置定时器的重装载*/
    TIM6->ARR = AUTO - 1;

    /*配置定时器的预分频*/
    TIM6->PSC = 7200 - 1;

    printf("begin!\r\n");

    /*首先关闭UG会更新中断的功能*/
    TIM6->CR1 |= TIM_CR1_URS;

    /*手动产生一次更新*/
    TIM6->EGR |= TIM_EGR_UG;

    /*配置计数器的使能*/
    TIM6->CR1 |= TIM_CR1_CEN;

    /*NVIC的部分*/
    /*配置中断优先级组*/
    NVIC_SetPriorityGrouping(3);

    /*配置的中断优先级*/
    NVIC_SetPriority(TIM6_IRQn, 1);

    /*配置所有的中断*/
    NVIC_EnableIRQ(TIM6_IRQn);

    /*配置中断反馈使能*/
    TIM6->DIER |= TIM_DIER_UIE;
}

void TIM6_IRQHandler(void)
{
    TIM6->SR &= ~TIM_SR_UIF;
    Int_LED_Toggle(LED2);
    printf("1s\r\n");
}

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您可以参考以下代码实现: ```c #include <reg52.h> // 8051单片机的头文件 #define uchar unsigned char // 定义uchar类型为unsigned char sbit LSA = P2^2; // 数码管LSA引脚连接P2.2口 sbit LSB = P2^3; // 数码管LSB引脚连接P2.3口 sbit LSC = P2^4; // 数码管LSC引脚连接P2.4口 uchar code smgduan[] = { // 显示0~9的编码 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f }; uchar num = 0; // 计数器 bit flag = 1; // 切换标志位,初始值为1 void delayms(uint); // 延时函数声明 void display(uchar); // 数码管显示函数声明 void main() { TMOD = 0x01; // 设置定时器0为模式1 TH0 = 0xfc; // 定时器0的初值,100us定时 TL0 = 0x67; TR0 = 1; // 启动定时器0 ET0 = 1; // 开启定时器0的中断 TMOD = 0x10; // 设置定时器1为模式0 TH1 = 0x3c; // 定时器1的初值,50ms定时 TL1 = 0xb0; TR1 = 1; // 启动定时器1 ET1 = 1; // 开启定时器1的中断 EA = 1; // 开启总中断 while (1) { if (flag) { // 如果标志位为1,使用方式1 display(num % 10); // 显示个位数 } else { // 否则使用方式2 display(num / 10); // 显示十位数 } } } void timer0() interrupt 1 { // 定时器0中断服务程序 TH0 = 0xfc; TL0 = 0x67; num++; // 计数器加1 if (num >= 100) { // 计数器到达100时清零 num = 0; } } void timer1() interrupt 3 { // 定时器1中断服务程序 TH1 = 0x3c; TL1 = 0xb0; flag = ~flag; // 切换标志位,实现显示方式的切换 } void display(uchar num) { // 数码管显示函数 P0 = smgduan[num]; // 显示数码管编码 LSA = 0; LSB = 0; LSC = 0; // 选择第一位数码管 delayms(1); // 延时1ms LSA = 1; LSB = 0; LSC = 0; // 选择第二位数码管 delayms(1); P0 = 0x00; // 清空P0口 } void delayms(uint x) { // 延时函数,x表示延时的毫秒数 uint i, j; for (i = x; i > 0; i--) { for (j = 110; j > 0; j--); } } ``` 以上代码实现了通过按键和两个定时器实现数码管的两种显示方式切换。具体实现方法为:使用定时器0实现计数器功能,在定时器1中断服务程序中切换标志位,根据标志位选择显示方式,实现数码管的两种显示方式切换。其中,按键的处理可以通过外部中断实现,这里为了简化代码,未行处理。需注意,此代码仅供参考,具体实现方式需根据具体硬件行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值