打印
[其他]

HC32F460 timer4例程 时钟频率的计算错误 以及 中断位未清零错误

[复制链接]
995|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
nawu|  楼主 | 2023-9-20 11:23 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、定时器计时频率的计算
1.1 系统时钟频率
在单片机中,由内部/外部时钟作为系统的时钟源,你可以理解为,单片机在供电后,时钟不停地在输出电压信号,波形为方波,该信号就像人的心脏,心脏跳动一次,为人体供血,而时钟的信号跳变一次,单片机内部各模块接到一个动作信号,单片机所有模块的动作都依赖这个时钟源输出的信号,所以系统时钟的频率会决定单片机各个模块的工作频率。

1.2 定时器频率
一般的定时器使用系统时钟,在此基础上进行分频,在HC32F460的timer0中,使用系统时钟中的pclk1(在用户手册可查询),460芯片的主频(也就是系统时钟频率)为200MHz,pclk1为100MHz,所以timer0的默认频率是100MHz,在此基础上分频。以100MHz为例,即timer0不停地在收到系统发过来的时钟信号,信号的频率为100MHz,也就是一个完整的信号周期事件为1/100M s,即100us,0.1ms。
不同单片机的不同计时器使用的时钟源不一样,默认的时钟频率也不一样(这一点可能是和内部的硬件电路有关系),比如timer4的默认频率为8MHz,在此基础上分频。

1.2 计数寄存器
计数寄存器counter,该寄存器在定时器收到系统一个时钟信号时,加一,该值在定时器运行过程中,是不停地在计数的。计数频率为定时器频率,在一个计数周期中,由零向上加一直加到设定的最大值MAX(不同波形不一样,三角波有向下计数的部分),随后清零,循环计数。在计数的过程中,可根据需要,产生相应的中断或者事件。

1.3 周期基准寄存器
周期基准寄存器,存储计数周期最大值MAX,用于规定定时器一个计时周期的最大值,在不同单片机中的缩写可能有所不同。

1.4 比较匹配寄存器
比较匹配寄存器,存储值一般在0和MAX之间(如果1.3和1.4都使用16位寄存器,如MAX设为0xABCD,而比较匹配寄存器设为0xFFFF,或大于MAX的任何值,则不会发生比较匹配),计数过程,当计数寄存器和比较匹配寄存器的设定值相等,可以产生比较匹配中断或事件。在主程序中对比较匹配寄存器进行连续的修改,可以对输出波形的占空比进行调制,常用于调制PWM占空比。

1.5 举例说明
华大timer4_oco_single_high_ch官方例程如下(选取核心代码,按代码顺序)

1.5.1 周期基准寄存器
#include "hc32_ddl.h"

/* Timer4 CNT */
#define TIMER4_UNIT                     (M4_TMR41)
#define TIMER4_CNT_CYCLE_VAL            (50000u)        /* Timer4 counter cycle value */

/* Timer4 OCO */
#define TIMER4_OCO_HIGH_CH              (Timer4OcoOuh)  /* only Timer4OcoOuh  Timer4OcoOvh  Timer4OcoOwh */ // even High odd low

/* Timer4 OCO interrupt number */
#define TIMER4_OCO_HIGH_CH_INT_NUM      (INT_TMR41_GCMUH)
其中TIMER4_CNT_CYCLE_VAL值为50000u,即为周期基准寄存器的设定值,u表示整数

1.5.2 定时器频率设置
/* Timer4 CNT : Initialize CNT configuration structure */
stcCntInit.enBufferCmd = Disable;
stcCntInit.enClk = Timer4CntPclk;
stcCntInit.enClkDiv = Timer4CntPclkDiv16;   /* CNT clock divide */
stcCntInit.enCntMode = Timer4CntSawtoothWave;
stcCntInit.enZeroIntMsk = Timer4CntIntMask0;
stcCntInit.enPeakIntMsk = Timer4CntIntMask0;
TIMER4_CNT_Init(TIMER4_UNIT, &stcCntInit);                      /* Initialize CNT */
TIMER4_CNT_SetCycleVal(TIMER4_UNIT, TIMER4_CNT_CYCLE_VAL);      /* Set CNT Cycle value */
其中使用了16分频,定时器的波形为锯齿波

1.5.3 比较匹配寄存器
uint16_t OcoHighChOccrVal = TIMER4_CNT_CYCLE_VAL / 2u;

/* Timer4 OCO : Initialize OCO channel configuration structure */
stcOcoInit.enOcoIntCmd = Enable;
stcOcoInit.enPortLevel = OcPortLevelLow; //输出无效时,端口状态
stcOcoInit.enOcmrBufMode = OcmrBufDisable;
stcOcoInit.enOccrBufMode = OccrBufDisable;
TIMER4_OCO_Init(TIMER4_UNIT, TIMER4_OCO_HIGH_CH, &stcOcoInit);  /* Initialize OCO high channel */

/* Set OCO compare value */
TIMER4_OCO_WriteOccr(TIMER4_UNIT, TIMER4_OCO_HIGH_CH, OcoHighChOccrVal);

/* Enable OCO */
TIMER4_OCO_OutputCompareCmd(TIMER4_UNIT, TIMER4_OCO_HIGH_CH, Enable);
其中OcoHighChOccrVal为比较匹配寄存器,设定值为周期基准寄存器的1/2

1.5.4 计算
timer4
分频后的计数频率    16/8M    (counter每隔这么长时间+1)
周期基准寄存器      50000u  
计时时间           (16/8M) * 50000 = 0.1s    即10Hz
经过计算后,得到的频率是10Hz,即LED灯0.1s发生一次状态切换
而在华大timer4_oco_single_high_ch官方例程的readme.txt对例程的表述如下: 对频率的判断为5hz,也就是0.2s,LED_GREEN切换一次状态。

================================================================================
使用步骤
================================================================================
1)打开工程并重新编译;
2)启动IDE的下载和调试功能,全速运行;
3)LED_GREEN不断闪烁,频率为5Hz,测试结果符合预期。

================================================================================
至此,笔者产生疑惑,仔细检查后,不知道是哪里出错,也没有示波器,外接LED闪烁太快,肉眼识别不出,于是决定修改timer4的比较匹配中断函数代码,外接LED灯进行检测。然而又出现了新的错误。


二、中断位未清零错误
2.1 简介
例程中,使用了比较匹配中断,由于使用的是锯齿波,所以比较匹配的周期与定时器计数周期相同,中断回调函数如下:

//原代码
static void OcoIrqCallback(void)
{
        BSP_LED_Toggle(LED_GREEN);               
    TIMER4_OCO_ClearIrqFlag(TIMER4_UNIT, TIMER4_OCO_HIGH_CH);       
}

//修改后,代码A
uint16_t tick = 0 ;
static void OcoIrqCallback(void)
{
        tick++;
        if(tick>=10)
        {
                BSP_LED_Toggle(LED_GREEN);               
        TIMER4_OCO_ClearIrqFlag(TIMER4_UNIT, TIMER4_OCO_HIGH_CH);       
        tick=0;
        }               
}

笔者本意是,10次比较匹配中断后,切换一次LED状态,按理LED闪烁频率应该比原代码慢10倍,可是在实际测试过程中,闪烁频率并没有变化,和预期不符,为此困扰了很长时间,却不知道为何。今天在看另一个例程的延时tick写法时,茅塞顿开,对代码A进行了修改,修改如下:

//修改后,代码B
uint16_t tick = 0 ;
static void OcoIrqCallback(void)
{
        tick++;
        if(tick>=10)
        {
                BSP_LED_Toggle(LED_GREEN);        
        tick=0;
        }
    TIMER4_OCO_ClearIrqFlag(TIMER4_UNIT, TIMER4_OCO_HIGH_CH);       
}
你们能看出代码A和代码B的区别吗?

2.2 错误原因
定时器计数匹配产生中断,执行中断回调函数OcolrqCallback(),未及时清零中断标志位。

代码A,在前9次进入回调函数中,都没有清除中断标志位,所以在跳出中断回调函数后,立马就又进入了回调函数,回调函数的运行时间很短,远小于定时器的中断时间,tick++,很快就达到10次,随后切换了LED状态,并且清除了中断标志。所以在实验过程中,观察到的结果是,LED切换的时间并没有增加10倍,而是和时钟中断频率相近。
代码B,在每一次回调函数中,都清除了中断标志,所以在10次中断后,切换LED状态,LED切换的频率是时钟中断频率的10倍。

综上所述,代码A是在每一次中断+系统时钟频率运行10次后切换LED状态,而代码B是在每10次中断后切换LED状态。

2.3 实验结果
对代码B进行实验,发现LED每1s闪烁一次,因此定时器timer4的频率和笔者计算预期一致,为0.1s,笔者认为官方例程在readme.txt中的描述错误。如果华大的开发者看见该贴,可以进行实验,验证一下笔者是否错误。

三、总结
3.1 定时器计时频率的计算公式
计数周期    (分频系数/定时器时钟频率)*周期基准计数器值
3.2 中断标志位重置
编程中,切记手动清零中断标志位。

在大多数情况下,微处理器在处理完一个中断后会自动清除中断标志,然后恢复到处理主程序,并且会在下一次定时器中断到来时再次响应中断。即使在处理中断期间,中断标志没有被手动清除,它在处理完中断过后也会被自动清除。并且,一般来说,新的中断不会在当前的中断处理程序运行期间被触发,也就是说,处理程序不会被嵌套调用。 然而,如果中断标志不是自动清除的(这在一些定时器配置中是可能的),在定时器中断处理期间并没有清除中断标志,这可能会导致处理器在中断处理程序运行期间再次触发中断,而这可能导致处理程序被嵌套调用,并且可能会导致处理器陷入死循环。
————————————————
版权声明:本文为CSDN博主「henry_xiong030」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/henry_xiong030/article/details/132730245

使用特权

评论回复
沙发
tpgf| | 2023-10-13 10:12 | 只看该作者
定时器的中断标志位是自动清零还是手动清零的呢

使用特权

评论回复
板凳
guanjiaer| | 2023-10-13 13:13 | 只看该作者
tpgf 发表于 2023-10-13 10:12
定时器的中断标志位是自动清零还是手动清零的呢

这个跟中断种类有关系吧  得看是什么原因进入的定时器中断

使用特权

评论回复
地板
keaibukelian| | 2023-10-13 13:48 | 只看该作者
不能自动清除的话就需要在执行完之后手动清除一下

使用特权

评论回复
5
heimaojingzhang| | 2023-10-13 14:25 | 只看该作者
正常情况下应该在什么时候清除中断标志位呢

使用特权

评论回复
6
paotangsan| | 2023-10-14 08:55 | 只看该作者
怎么样做才可以比较快速的发现频率计算错误呢

使用特权

评论回复
7
renzheshengui| | 2023-10-14 10:02 | 只看该作者
定时器技术匹配为什么会产生中断呢?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

72

主题

3307

帖子

3

粉丝