STM32速成笔记(9)—RTC

发布时间:2024-01-17  

一、RTC简介

RTC(Real Time Clock)实时时钟,它是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置都是在后备区域,无论单片机处于何种状态,只要保证后备区正常供电,RTC就会一直工作。

二、STM32的RTC

2.1 主要特性

  • • 可编程的预分频系数 :分频系数最高为2^20

  • • 32位的可编程计数器 ,可用于较长时间段的测量

  • • 可以选择以下三种RTC的时钟源 ─ HSE时钟除以128 ─ LSE振荡器时钟 ─ LSI振荡器时钟

  • • 3个专门的可屏蔽中断 ─ 闹钟中断,用来产生一个软件可编程的闹钟中断 ─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒) ─ 溢出中断,指示内部可编程计数器溢出并回转为0的状态

    22.2 RTC框图介绍

    图片

    RTC框图

  • • RTCCLK通常选择低功耗32.768kHz外部晶振(LSE)

  • • RTC预分频器通常设置为32768,LES时钟经过RTC预分频器,输入频率变为1Hz,也就是1秒

  • • RTC_CNT输入时钟为1Hz时,1s加1次

  • • RTC_ALR是用来做闹钟的,RTC_CNT的值会与RTC_ALR的值进行比较,二者相等时,会产生闹钟中断

    三、访问后备区域步骤

STM32系统复位之后,对后备寄存器和RTC的访问被禁止 ,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作,可以访问后备区域寄存器

  • • 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟

  • • 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问

完成上面的设置之后,就可以操作后备寄存器。第一次通过APB1总线访问RTC时,需要等待APB1和RTC同步,确保读取出来的RTC的寄存器值是正确的。如果同步之后,一直没有关闭APB1和RTC外设接口,就不需要再同步了。

如果内核需要对RTC寄存器写入数据,在内核发送指令后,RTC会在3个RTCCLK时钟之后,开始写入数据。每次写入时,必须要检查RTC关闭操作标志位RTOFF是否置1来判断是否写操作完成。

四、RTC配置步骤

  • • 使能电源时钟和后备域时钟,开启RTC后备寄存器写访问

  • • 复位备份区域,开启外部低速振荡器(LSE)

  • • 选择RTC时钟,并使能

  • • 设置RTC的分频系数,配置RTC时钟

  • • 更新配置,设置RTC中断分组

  • • 编写RTC中断服务函数


五、RTC程序配置

55.1 RTC结构体定义

// RTC结构体

typedef struct 

{

    // 时分秒

    u8 hour;

    u8 min;

    u8 sec;

    

    // 年月日周

    u16 w_year;

    u8  w_month;

    u8  w_date;

    u8  week;   

}_calendar;

5.2 RTC初始化函数

/*

 *==============================================================================

 *函数名称:RTC_Init

 *函数功能:初始化RTC

 *输入参数:无

 *返回值:0:成功;1:失败

 *备  注:无

 *==============================================================================

 */

u8 RTC_Init (void)

{

    u8 temp=0;   // 超时监控变量

    // 结构体定义

    NVIC_InitTypeDef NVIC_InitStructure;

    

    // 使能PWR和BKP外设时钟  

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); 

    PWR_BackupAccessCmd(ENABLE);   // 使能后备寄存器访问

    

    // 检测是否是第一次配置RTC

    // 配置时会想RTC寄存器写入0xA0A0,如果读出的数据不是0xA0A0,认为是第一次配置RTC

    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0)

    {    

        BKP_DeInit();   // 复位备份区域  

        RCC_LSEConfig(RCC_LSE_ON);   // 设置外部低速晶振(LSE),使用外设低速晶振

        

        // 等待低速晶振就绪

        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp< 250)

        {

            temp++;

            delay_ms(10);

        }

        // 初始化时钟失败,晶振有问题 

        if(temp >=250)

        {

            return 1;

        }   

        

        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);   // 设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    

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

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

        

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

        RTC_ITConfig(RTC_IT_SEC, ENABLE);   // 使能RTC秒中断

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

        

        RTC_EnterConfigMode();   // 允许配置 

        RTC_SetPrescaler(32767);   // 设置RTC预分频的值

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

        

        RTC_Set_Date(2023,6,26,11,15,00);   // 设置初始时间 

        RTC_ExitConfigMode();   // 退出配置模式  

        BKP_WriteBackupRegister(BKP_DR1, 0XA0A0);   // 向指定的后备寄存器中写入用户程序数据

    }

    // 系统继续计时

    else

    {

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

        RTC_ITConfig(RTC_IT_SEC, ENABLE);   // 使能RTC秒中断

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

    }

    

  // 配置RTC中断分组

    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;   // RTC全局中断

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;   // 先占优先级1位,从优先级3位

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;   // 先占优先级0位,从优先级4位

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   // 使能该通道中断

    NVIC_Init(&NVIC_InitStructure);   // 根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

    

    RTC_Get_CurDate();   // 获取当前时间 

    return 0;   // 配置成功

}

初始化函数使用时,可以用while等待初始化成功,但是需要增加一个超时检测,这里简单给出一个写法,如果1s内,RTC没有初始化成功,直接跳过


u32 tempVar = 0;   // 初始化RTC时的超时计数变量

    

    while (RTC_Init() && tempVar < 100)   // RTC初始化

    {

        delay_ms (10);

        // 10ms自加1

        tempVar = tempVar + 1;

    }

5.3 设置年月日,时分秒

/*

 *==============================================================================

 *函数名称:RTC_Set_Date

 *函数功能:设置RTC的年月日,时分秒

 *输入参数:无

 *返回值:0:成功;1:失败

 *备  注:时间范围为1970年到2099年,可修改

 *==============================================================================

 */

u8 RTC_Set_Date (u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)

{

    u16 t;

    u32 seccount=0;

    

    // 判断是否为合法年份

    if(syear < 1970 || syear > 2099)

    {

        return 1;

    }

    

    for(t = 1970;t < syear;t ++)   // 把所有年份的秒钟相加

    {

        // 闰年的秒钟数

        if(Is_Leap_Year(t))

        {

            seccount += 31622400;

        }

        // 平年的秒钟数

        else

        {

            seccount += 31536000;

        }

    }

    

    smon -= 1;

    

    for(t = 0;t < smon;t ++)   // 把前面月份的秒钟数相加

    {

        seccount += (u32)mon_table[t] * 86400;   // 月份秒钟数相加

        // 闰年2月份增加一天的秒钟数

        if(Is_Leap_Year(syear) && t == 1)

        {

            seccount += 86400;

        }   

    }

    seccount += (u32)(sday-1) * 86400;   // 把前面日期的秒钟数相加 

    

    seccount += (u32)hour * 3600;   // 小时秒钟数

    

    seccount += (u32)min*60;   // 分钟秒钟数

    

    seccount += sec;   // 最后的秒钟加上去


    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);   // 使能PWR和BKP外设时钟  

    PWR_BackupAccessCmd(ENABLE);   // 使能RTC和后备寄存器访问

    

    RTC_SetCounter(seccount);   // 设置RTC计数器的值


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

    return 0;     

}

5.4 判断闰年函数

/*

 *==============================================================================

 *函数名称:Is_Leap_Year

 *函数功能:判断输入年份是否为闰年

 *输入参数:无

 *返回值:0:不是闰年;1:是闰年

 *备  注:四年一闰;百年不闰,四百年再闰

 *==============================================================================

 */

u8 Is_Leap_Year (u16 year)

{

    // 是否能被4整除

    if(year % 4 == 0)

    {

        // 是否能被100整除

        if(year % 100 == 0) 

        {

            // 如果以00结尾,还要能被400整除 

            if(year % 400 == 0)

            {

                return 1;

            }

            // 是100的倍数,但是不是400的倍数

            else

            {

                return 0;

            }    

        }

        // 是4的倍数,不是100的倍数

        else

        {

            return 1; 

        }   

    }

    // 不是4的倍数

    else

    {

        return 0; 

    }

}

5.5 获取当前年月日,时分秒

/*

 *==============================================================================

 *函数名称:RTC_Get_CurDate

 *函数功能:获取当前年月日,时分秒

 *输入参数:无

 *返回值:0:成功;1:失败

 *备  注:无

 *==============================================================================

 */

u8 RTC_Get_CurDate (void)

{

    // 存储上一次的总天数值,用来监测时间变化是否超过一天

    static u16 daycnt = 0;

    u32 timecount = 0; 

    // 临时计算变量

    u32 temp = 0;

    u16 temp1 = 0;

    

    timecount = RTC_GetCounter();   // 获取当前总秒数

    

     temp = timecount / 86400;   // 得到总天数

    

    // 超过一天了

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>