打印
[STM32F1]

STM32F103的RTC模块用作32位计数器

[复制链接]
164|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
t60yz|  楼主 | 2023-12-22 16:14 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
需求背景
在使用FreeRTOS时,如果需要统计每个任务的运行时长及百分比,则需要提供一个计时基准。

分别实现以下2个函数:

void vConfigureTimerForRunTimeStats( void );    /* Prototype of function that initialises the run time counter. */
unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */


前一个函数需用初始化配置计时器;后一个函数用于在任务切换时获取当前计时值。

要求计时的频率比tick的频率要高一个数量级。目前tick为1KHz,则计时频率为10K比较合适。

使用特权

评论回复
沙发
t60yz|  楼主 | 2023-12-22 16:14 | 只看该作者
方案分析
方案一:使用硬件定时器TIMx。

void vConfigureTimerForRunTimeStats(void)
{
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

        // 时钟使能
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

        //定时器TIMx初始化
        TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock / 1000000 - 1; //预分频后为1MHz
        TIM_TimeBaseStructure.TIM_Period = configTICK_RATE_HZ * 10 - 1;
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit(TIMER_FOR_RUN_TIME_STAT, &TIM_TimeBaseStructure);

        TIM_Cmd(TIM3, ENABLE);
}

unsigned long ulGetRunTimeCounterValue(void)
{
        return TIM_GetCounter(TIM3);

使用特权

评论回复
板凳
t60yz|  楼主 | 2023-12-22 16:14 | 只看该作者
问题:STM32F103的硬件定时器是16位的。最大计数值为64K,当计数频率为40K时,只需要1秒多一点就溢出了。

使用特权

评论回复
地板
t60yz|  楼主 | 2023-12-22 16:14 | 只看该作者
方案二:使用2个硬件定时器级联,组成一个32位计数器

使用特权

评论回复
5
t60yz|  楼主 | 2023-12-22 16:14 | 只看该作者
方案三:使用os的tick
代码如下。

unsigned long ulGetRunTimeCounterValue(void)
{
        return xTaskGetTickCount();


问题:精度较低。

使用特权

评论回复
6
t60yz|  楼主 | 2023-12-22 16:15 | 只看该作者
使用RTC作为计数器
在STM32F103中,RTC的本质并不是一个实时时钟模块,只是一个计数器而已。可以配置为每秒加一,然后软件将秒数转换为日历时间(类似于unix中,以1970-1-1为起点的秒计数)。

现在不需要时钟了,就把它用于一个10KHz的计数器。最大可计时长为4G/10K = 400K秒,约为100多个小时。

使用特权

评论回复
7
t60yz|  楼主 | 2023-12-22 16:15 | 只看该作者
RTC的时钟源:使用内部低速时钟LSI。然后再4分频,即可得到10KHz。

使用特权

评论回复
8
t60yz|  楼主 | 2023-12-22 16:15 | 只看该作者
RTC工作时需要配置以下几个寄存器:
使能PWR和BKP模块时钟



使用特权

评论回复
9
t60yz|  楼主 | 2023-12-22 16:16 | 只看该作者
Disable Backup Domain write:

使用特权

评论回复
10
t60yz|  楼主 | 2023-12-22 16:16 | 只看该作者

使用特权

评论回复
11
t60yz|  楼主 | 2023-12-22 16:16 | 只看该作者
开启LSI:

使用特权

评论回复
12
t60yz|  楼主 | 2023-12-22 16:17 | 只看该作者
复位备份域:

先将BDRST设置为1,再设置为0。否则,不能更改RTCSEL等参数。

使用特权

评论回复
13
t60yz|  楼主 | 2023-12-22 16:17 | 只看该作者
参见手册:6.3.9 备份域控制寄存器 (RCC_BDCR)

注意: 备份域控制寄存器中(RCC_BDCR)的LSEON、LSEBYP、RTCSEL和RTCEN位处于备份域。因此,这些位在复位后处于写保护状态,只有在电源控制寄存器(PWR_CR)中的DBP位置’1’后才能对这些位进行改动。进一步信息请参考5.1节。这些位只能由备份域复位清除(见6.1.3节)。任何内部或外部复位都不会影响这些位。

使用特权

评论回复
14
t60yz|  楼主 | 2023-12-22 16:17 | 只看该作者
RTC时钟选择LSI:

使用特权

评论回复
15
t60yz|  楼主 | 2023-12-22 16:17 | 只看该作者
RTC时钟使能:

使用特权

评论回复
16
t60yz|  楼主 | 2023-12-22 16:18 | 只看该作者
设置分频系数:

使用特权

评论回复
17
t60yz|  楼主 | 2023-12-22 16:18 | 只看该作者
代码如下:
void vConfigureTimerForRunTimeStats(void)
{
        // 使能模块时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP | RCC_APB1Periph_PWR, ENABLE);

        // 使能备份区域访问。将PWR.CR.DBP置1.
        PWR_BackupAccessCmd(ENABLE);

        // 复位备份域(Backup Domain)
        BKP_DeInit();

        //启动内部低速晶振
        RCC_LSICmd(ENABLE);

        //等待外部低速晶振重启
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);

        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

        RCC_RTCCLKCmd(ENABLE); //使能RTC时钟

        RTC_WaitForSynchro(); //等待RTC寄存器同步完成

        RTC_WaitForLastTask(); //等待最后一次对RTC的寄存器写操作完成

        RTC_SetPrescaler(4);        // 4分频后为10KHz

        RTC_WaitForLastTask();

}

/*-----------------------------------------------------------*/

unsigned long ulGetRunTimeCounterValue(void)
{
        return RTC_GetCounter();
}

使用特权

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

本版积分规则

121

主题

947

帖子

0

粉丝