蓝桥杯国赛——第三站RTC秒中断
端午假期,周六上了一天实验课,晚上用来打游戏了。今天,周日,再继续搞,。
RTC中断相关:
1.目前看来,断电运行是不可能的,因为板子不带电源。不过说起来,想要实现断电运行,关键就是RTC备份域【BKP】能够在断电时保持数据,所以只要使能寄存器,正确读写就没问题。
从RTC 的定时器特性来说,它是一个32 位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的128 分频(HSE/128)、低速内部时钟LSI 以及低速外部时钟LSE;使HSE分频时钟或LSI 的话,在主电源VDD 掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC 正常工作。因此RTC 一般使用低速外部时钟LSE,在设计中,频率通常为实时时钟模块中常用的32.768KHz,这是因为32768 = 215,分频容易实现,所以它被广泛应用到RTC 模块。在主电源VDD 有效的情况下(待机),RTC 还可以配置闹钟事件使STM32 退出待机模式。
实际上,在阅读手册的时候,可以发现st对RTC读写时间和访问备份域设置了很多reg级别的约束条件,但是在直接使用Hal库编程时,很多东西被库函数屏蔽了(搞得我抓不清学习的重点),所以,库这个东西,有利有弊吧。
2.RTC秒中断和定时闹钟
hal库的delay函数会使得程序重心都放到delay上面,其他动作不会执行,而使用uwTick来比较的方式,就不会这样。但是如果在中断中sys定时时间到,理论上会先执行中断,之后再重新运行。那么这种时间上的冲突应该怎么避免呢?也就是,单片机执行多个程序,如果没有理解错的话,它作为单核单线程,应该不能同时调用cpu资源运行,所以它采取的应该是分时复用,或者索性直接在while循环里面顺序执行,用高速的80MHz乃至更高的频率来实现基本同时运行。故而应该不存在能够让冲突的程序严格按照时序运行的解决方案。
现在疑问的就是有没有相关文章介绍过这些?
谢邀,一般单片机一般只有一个核心,做多线程实际上是分时复用。
谢邀,一般单片机一般只有一个核心,做多线程实际上是分时复用。自己做的话可以写个循环,循环里面多个进程轮流执行。
更方便一点是用嵌入式操作系统,如ucos freertos vxworks 等操作系统做,里面自带任务调度及任务间通信等功能。
里面的任务调度可以按照时间片轮训,优先级抢占等方式,比你自己搞省事多了。 觉得不错请按赞XD! 作者:东东bh 链接:https://www.zhihu.com/question/323241954/answer/674988619 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
仿佛从中看到了操作系统以及嵌入式更有趣的前沿,可能单片机还是太入门了吧。haha
那么,我目前就先采用这种解决方案吧,尽可能使得分时复用之间的时间冲突变小。如果你在疑问我为什么在RTC这里思考这些,往下面看吧//。
实现RTC的秒中断【分钟中断等同理】
有关秒中断的原理,简单概述,大家感兴趣可以康康其他文章。有相关基础的想迫切知道其中有没有什么坑的可以继续看下去。
42.4.1 等待时钟同步和操作完成 RTC 区域的时钟比APB 时钟慢,访问前需要进行时钟同步,只要调用库函数RTC_WaitForSynchro即可,而如果修改了RTC 的寄存器,又需要调用RTC_WaitForLastTask 函数确保数据已写入。 42.4.2 使能备份域说及RTC 配置 默认情况下,RTC 所属的备份域禁止访问,可使用库函数PWR_BackupAccessCmd 使能访问,该函数通过PWR_CR 寄存器的DBP 位使能访问,使能后才可以访问RTC 相关的寄存器,然而若 希望修改RTC 的寄存器,还需要进一步使能RTC 控制寄存器的CNF 位使能寄存器配置。 42.4.3 设置RTC 时钟分频 42.4.4 设置、获取RTC 计数器及闹钟 tip:配置标志位用于上电继续运行 检测备份域寄存器RTC_BKP_DRX 内的值是否等于RTC_BKP_DATA 而分成两个分支。 //*摘自火哥的《零死角玩转stm32》 在我刚接触单片机时,火哥的固件库的参考书目有莫大作用。它们编写的真的不错。
【目的:实现秒中断,每次中断通过串口发送当前时间】
CubeMX配置
说明:
cubemx基本把调用RTC初始化和闹钟(中断)的配置都完成了。
tip:wakeup(唤醒)中断暂时不想探究
等待时钟同步不需要额外配置,库函数基本能自己完成【推测,我没有详细读相关的代码哈】
其中active Clock Source 和 Active Calendar基本对应于使能备份域;
Data Format建议就采用binary,虽然bcd也能用,但是似乎bcd编码格式在之后获取时间然后再人为递增时候有点麻烦。下面贴一个bcd格式在库里面的写法【库还提供了一个函数转换rtc的binary和bcd】
下面,想要实现秒中断就要mask一些不关注的东西:
屏蔽day、hour、minute,这样就只关注second,在second达到条件就中断,实现不管是什么分钟、小时,都能有秒中断。
finished。
代码
/**************Cube自动生成的RTC代码*****************/ /* Includes ------------------------------------------------------------------*/ #include "rtc.h" /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ RTC_HandleTypeDef hrtc; /* RTC init function */ void MX_RTC_Init(void) { /* USER CODE BEGIN RTC_Init 0 */ /* USER CODE END RTC_Init 0 */ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; RTC_AlarmTypeDef sAlarm = {0}; /* USER CODE BEGIN RTC_Init 1 */ /* USER CODE END RTC_Init 1 */ /** Initialize RTC Only */ hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 124; hrtc.Init.SynchPrediv = 5999; hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE; hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; hrtc.Init.OutPutPullUp = RTC_OUTPUT_PULLUP_NONE; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN Check_RTC_BKUP */ /* USER CODE END Check_RTC_BKUP */ /** Initialize RTC and set the Time and Date */ sTime.Hours = 1; sTime.Minutes = 1; sTime.Seconds = 50; sTime.SubSeconds = 0; sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; sTime.StoreOperation = RTC_STOREOPERATION_RESET; if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } sDate.WeekDay = RTC_WEEKDAY_SUNDAY; sDate.Month = RTC_MONTH_JUNE; sDate.Date = 5; sDate.Year = 22; if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } /** Enable the Alarm A */ sAlarm.AlarmTime.Hours = 1; sAlarm.AlarmTime.Minutes = 1; sAlarm.AlarmTime.Seconds = 55; sAlarm.AlarmTime.SubSeconds = 0; sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY|RTC_ALARMMASK_HOURS |RTC_ALARMMASK_MINUTES; sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL; sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE; sAlarm.AlarmDateWeekDay = 1; sAlarm.Alarm = RTC_ALARM_A; if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN RTC_Init 2 */ /* USER CODE END RTC_Init 2 */ } void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle) { RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; if(rtcHandle->Instance==RTC) { /* USER CODE BEGIN RTC_MspInit 0 */ /* USER CODE END RTC_MspInit 0 */ /** Initializes the peripherals clocks */ PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC; PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_HSE_DIV32; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } /* RTC clock enable */ __HAL_RCC_RTC_ENABLE(); __HAL_RCC_RTCAPB_CLK_ENABLE(); /* RTC interrupt Init */ HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0); HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn); /* USER CODE BEGIN RTC_MspInit 1 */ /* USER CODE END RTC_MspInit 1 */ } } void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle) { if(rtcHandle->Instance==RTC) { /* USER CODE BEGIN RTC_MspDeInit 0 */ /* USER CODE END RTC_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_RTC_DISABLE(); __HAL_RCC_RTCAPB_CLK_DISABLE(); /* RTC interrupt Deinit */ HAL_NVIC_DisableIRQ(RTC_Alarm_IRQn); /* USER CODE BEGIN RTC_MspDeInit 1 */ /* USER CODE END RTC_MspDeInit 1 */ } } /* USER CODE BEGIN 1 */ /* USER CODE END 1 */
手敲大部分的main代码:
/************* 手敲大部分的main代码:***********/ /***************不重要的就删了***************/ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "rtc.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ RTC_TimeTypeDef Time_RTC; RTC_DateTypeDef Date_RTC; RTC_AlarmTypeDef Alarm_RTC; void Uart_Proc(void); void SystemClock_Config(void); /** * @brief The application entry point. * @retval int */ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_RTC_Init(); MX_USART1_UART_Init(); HAL_UART_Receive_IT(&huart1,&rx_data,1); while (1) { Uart_Proc(); } } void Uart_Proc(void) { sprintf((char *)tx_buff,"Hello\n");//*似乎是这个sprintf特别占用系统时间,使得秒中断没有立刻进入 //*不对,sprintf屏蔽之后还是两秒进入一次中断 //*因此,我担心的是程序执行其他时候,会不会也打断中断,无法实现秒中断。因此有必要在完整的模板程序中试验 //*又或者,只是因为一个时刻占用了两词transmit导致的? //*又或者,是sys中断优先级高于秒中断优先级导致的?但是如果秒中断优先级高于sys,那么系统其他的程序赖以依靠的sys时钟就不准确了 //改变rtc优先级更高的时候,能够做到即时走进秒中断,但是明显对uart两秒钟传输一次的精度有影响; //我能想到最优的方案就是中断服务函数中尽量少的程序,然后把执行程序提出来让他在sys的协调下运行 HAL_UART_Transmit(&huart1,tx_buff,strlen(tx_buff),50); } void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
//*获取当前的闹钟时间设置情况 HAL_RTC_GetAlarm(hrtc, &Alarm_RTC, RTC_ALARM_A, RTC_FORMAT_BIN); //*设置下一次闹钟的时间 if(Alarm_RTC.AlarmTime.Seconds != 59) Alarm_RTC.AlarmTime.Seconds= Alarm_RTC.AlarmTime.Seconds+1; else Alarm_RTC.AlarmTime.Seconds = 0; //*发送当前的时间给串口 HAL_RTC_GetTime(hrtc,&Time_RTC,RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc,&Date_RTC,RTC_FORMAT_BIN); sprintf(i_disp,"RTC:%02d-%02d-%02d\n",Time_RTC.Hours,Time_RTC.Minutes,Time_RTC.Seconds); HAL_UART_Transmit(&huart1,(uint8_t *)i_disp,strlen(i_disp),50); //*重新使能闹钟中断 HAL_RTC_SetAlarm_IT(hrtc, &Alarm_RTC, RTC_FORMAT_BIN); }
问题:
在不当的设置条件下,明明我设置的是1s一次中断,但是发给串口却是两秒一次;
测试之后感觉有很多可能的原因,最后发现应该是秒中断的优先级不够,导致只有程序跳到执行语句(也就是不在systick的中断中时,才能响应rtc_alarm的中断)。
此时,问题很明显,怎么保证其他程序时序能正常运行呢?
一、sys中断不要那么高,尤其是在我的程序编写习惯下,sys的中断用来调节所有程序的进程,完全可以等一个程序运行ok了再考虑进入另一个外设的进程;
二、外设中断的服务函数不要执行太多任务,这样长期处于中断中,程序停摆了相当于。可以把任务放到外面,服务函数只留标志位。
回看火哥的书,才发现也是这样做的:RTC 的秒中断服务函数只是简单地对全局变量TimeDisplay 置1,在main 函数的while 循环中会检测这个标志,当标志为1 时,就调用Time_Display 函数显示一次时间,达到每秒钟更新当前时间的效果。——摘自P1012
最终结果:
参考:
(6条消息) stm32 RTC时钟配置_HardessGod的博客-CSDN博客_rtc时钟设置
(6条消息) STM32F030R8Tx HAL库实现RTC 1秒中断_仙剑情缘的博客-CSDN博客_hal库rtc秒中断
《野火——零死角玩转STM32》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南