完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
RTC简介 PART 01MM32的 RTC(实时时钟)是一个独立的定时器,通过配置,可以让它准确地每秒/每毫秒中断一次。RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。 RTC 模块和时钟配置系统(RCC_BDCR 寄存器)处于后备区域,即在系统复位或从待机模式、停机模式唤醒后,RTC 的设置和时间维持不变。 系统复位后,禁止访问后备寄存器和 RTC,防止对后备区域(BKP)的意外写操作。执行以下操作使能对后备寄存器和 RTC 的访问: ▪ 设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位来使能电源和后备接口 时钟。 ▪ 设置寄存器 PWR_CR 的 DBP 位使能对后备寄存器和 RTC 的访问。 当我们需要在掉电之后,又需要 RTC 时钟正常运行的话,单片机的 VBAT脚需外接 3.3V 的纽扣电池。当系统掉电之后 VBAT 给 RTC 工作提供供电,RTC 中的数据都会保持在后备寄存器当中,因此能够时刻保存数据。 RTC 主要特征 PART 02 RTC的可编程计数器是32位,该计数器会根据一定的频率进行计数。这个频率就是RTC的时钟输入频率,在MM32的用户手册上的时钟树可以得知RTC的时钟源可以是以下三种: ▪ HSE时钟除以128 ▪ LSE振荡器时钟 ▪ LSI振荡器时钟 RTC有3个专门的屏蔽中断: ▪ 闹钟中断,用来产生一个软件可编程的闹钟中断 ▪ 秒/毫秒中断,用来产生一个可编程的周期性中断信号 ▪ 溢出中断,指示内部可编程计数器溢出并返回0的状态 RTC 功能描述 PART 03 RTC既然是定时器,那么它一定有一个计数器,这个计数器必须有时钟来源。我们知道家用时钟一般最小单位是秒,为了得到精确的时钟,需要选择一个高精度的LSE,还要匹配最佳的电容,这样才能保证时钟的精确。常用的LSE是32.768KHz,经过32768分频后得到1秒的时钟信号。RTC的计数器就可以每隔1秒向上计数。 如果使能了RTC秒中断,那么计数器每记一次数就会产生中断,这样软件就可以将RTC计数器的值读出来,经过计算得到相应的时间显示到屏幕上。 RTC 还有一个闹钟中断功能,顾名思义,就是到了设定的时间后产生中断。此功能常被用到低功耗模式的唤醒场合,MCU进入低功耗模式,需要每隔一段时间需要唤醒,那么可以采用RTC闹钟的唤醒功能。 RT-Thread 移植 PART 04 RT-Thread的移植讲解在前面的微课堂(第121讲)已经详细介绍,有不懂的小伙伴可以翻看前面的讲解,在此就不再赘述。 主要是创建两个线程,分别为LED_Task和RTC_Task。创建一个信号量sem,RTC秒中断释放信号量,RTC_Task线程获取信号量后打印时间。 在LED_Task线程,对LED每隔500ms进行翻转一次; 在RTC_Task线程,通过获取信号量的方式来确定时间已经改变并通过串口打印出来,在RTC秒中断里面释放信号量。 实验 描述 PART 05
代码 描述 PART 06 01 main 函数分析 #include "led.h" #include "delay.h" #include "sys.h" #include "rtc.h" #include "uart.h" #include struct rt_semaphore sem; //定义信号量 #define THREAD_STACK_SIZE 1024 #define THREAD_PRIORITY 5 /* 线程1控制块及线程栈 */ static struct rt_thread LED_thread; ALIGN(4) static rt_uint8_t LED_stack[THREAD_STACK_SIZE]; /* 线程2控制块及线程栈 */ static struct rt_thread RTC_thread; ALIGN(4) static rt_uint8_t RTC_stack[THREAD_STACK_SIZE]; int main(void) { rt_err_t result; LED_Init(); //初始化与LED连接的硬件接口 uart_initwBaudRate(115200); //初始化串口 printf("MM32 Dev Boardrn"); printf("RTC TESTrn"); printf("@MindMotionrn"); /* 初始化静态信号量,初始值是0 */ rt_sem_init(&sem, "sem", 0, RT_IPC_FLAG_FIFO);//初始化信号量 /* 初始化线程1 */ result = rt_thread_init(&LED_thread, "t1", /* 线程名:t1 */ LED_Task, RT_NULL, /* 线程的入口是LED_Task */ &LED_stack[0], sizeof(LED_stack), /* 线程栈是LED_stack */ THREAD_PRIORITY, 10); if (result == RT_EOK) /* 如果返回正确,启动线程1 */ rt_thread_startup(&LED_thread); /* 初始化线程2 */ result = rt_thread_init(&RTC_thread, "t2", /* 线程名:t2 */ RTC_Task, RT_NULL, /* 线程的入口是RTC_Task */ &RTC_stack[0], sizeof(RTC_stack), /* 线程栈是RTC_stack */ THREAD_PRIORITY - 1, 10); if (result == RT_EOK) /* 如果返回正确,启动线程2 */ rt_thread_startup(&RTC_thread); return 0; } 因为我们在实验中需要用到串口,所以调用 uart_initwBaudRate ()函数将串口配置好。RTC线程要与RTC_IRQHandler秒中断做同步,需要一个信号量sem,并在main函数初始化一下信号量sem。下面就是新建2个线程,一个是用来翻转LED,另一个用来打印RTC时间信息。 02 RTC和LED线程分析 /* 线程1入口 */ static void LED_Task(void* parameter) { while (1) { GPIOA->ODR ^= GPIO_Pin_8; //LED翻转 rt_thread_delay(500); } /* 死循环 */ } /* 线程2入口 */ static void RTC_Task(void* parameter) { char str[20]; while(RTC_Init()) //RTC初始化 ,一定要初始化成功 { printf("RTC ERROR! rn"); rt_thread_delay(800); printf("RTC Trying...rn"); } while (1) { rt_sem_take(&sem, RT_WAITING_FOREVER);//永久等待信号量 sprintf(str,"%02d",calendar.w_year); printf("%s-",str); //打印年份 sprintf(str,"%02d",calendar.w_month); printf("%s-",str); //打印月份 sprintf(str,"%02d",calendar.w_date); printf("%s ",str); //打印日期 sprintf(str,"%02d",calendar.hour); printf("%s:",str); //打印小时 sprintf(str,"%02d",calendar.min); printf("%s:",str); //打印分钟 sprintf(str,"%02d",calendar.sec); printf("%s ",str); //打印秒钟 switch(calendar.week) //打印星期 { case 0: printf("Sunday rn"); break; case 1: printf("Monday rn"); break; case 2: printf("Tuesday rn"); break; case 3: printf("Wednesdayrn"); break; case 4: printf("Thursday rn"); break; case 5: printf("Friday rn"); break; case 6: printf("Saturday rn"); break; } } } //RTC时钟中断 //每秒触发一次 //extern u16 tcnt; void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断 { RTC_Get();//更新时间 rt_sem_release(&sem); //释放信号量 } if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断 { RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断 } RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断 RTC_WaitForLastTask(); } LED_Task线程就做了一件事,那就是对PA8脚进行翻转,从了达到LED闪烁的效果,LED亮灭各500ms的时间。 RTC_Task线程,进入该线程的第一件事就是初始化RTC,在后面详细讲解RTC的初始化。初始化通过后,就开始永久等待信号量被RTC秒中断释放,信号量一旦释放,表示秒增加了,此时就开始更新并打印出时间,rt_sem_take(&sem, RT_WAITING_FOREVER);这个是等待信号量被释放,不然线程不会往下执行。在RTC秒中断里面有rt_sem_release(&sem);对信号量进行释放。 03 RTC_Init()代码分析 //实时时钟配置 //初始化RTC时钟,同时检测时钟是否工作正常 //BKP->DR1用于保存是否第一次配置的设置 //返回0:正常 //其他:错误代码 u8 RTC_Init(void) { //检查是不是第一次配置时钟 u8 temp=0; //使能PWR和BKP外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能后备寄存器访问 PWR_BackupAccessCmd(ENABLE); //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎 if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) { BKP_DeInit(); //复位备份区域 RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振 //检查指定的RCC标志位设置与否,等待低速晶振就绪 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) { temp++; rt_thread_delay(10); if(temp>=250) return 1;//初始化时钟失败,晶振有问题 } //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); 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_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成 RTC_Set(2020,10,29,10,42,55); //设置时间 RTC_ExitConfigMode(); //退出配置模式 BKP_WriteBackupRegister(BKP_DR1, 0X5050);//向指定的后备寄存器中写入用户程序数据 } else//系统继续计时 { RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 } RTC_NVIC_Config();//RCT中断分组设置 RTC_Get();//更新时间 return 0; //ok } 在RTC_Init()函数里面 if 部分首先读取备份寄存器里面的值,看看备份寄存器里面的值是否正确(如果 RTC 曾经被设置过的话,备份寄存器里面的值为 0x5050)或判断这是不是第一次对 RTC 编程。如果这两种情况有任何一种发生的话,则初始化 RTC,并往串口助手打印出相应的调试信息。初始化好 RTC 之后,调用函数 RTC_Set ();(在 rtc.c 中实现)初始化时间值。 初始化完后,RTC 时钟就运行起来了。设置好时间后,把 0x5050这个值写入 RTC 的备份寄存器,这样当我们下一次上电时就不用重新输入 RTC 里面的时间值了。 如果 RTC 值曾经被设置过,则进入 else部分。else部分不需要重新设置 RTC 里面的时间值。 一切就绪之后,在main函数里面将我们的时间显示在电脑的串口助手上。然后在串口助手上显示出来,效果图如下: |
|
相关推荐
|
|
只有小组成员才能发言,加入小组>>
2248个成员聚集在这个小组
加入小组灵动微电子MM32全系列MCU产品应用手册,库函数和例程和选型表
11676 浏览 3 评论
【MM32 eMiniBoard试用连载】+基于OLED12864的GUI---U8G2
5922 浏览 1 评论
【MM32 eMiniBoard试用连载】移植RT-Thread至MM32L373PS
10953 浏览 0 评论
【MM32 eMiniBoard测评报告】+ 开箱 + 初探
4571 浏览 1 评论
灵动微课堂(第106讲) | MM32 USB功能学习笔记 —— WinUSB设备
4297 浏览 1 评论
[MM32软件] MM32F002使用内部flash存储数据怎么操作?
964浏览 1评论
792浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-24 04:58 , Processed in 0.684460 second(s), Total 69, Slave 51 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号