谢邀,一般单片机一般只有一个核心,做多线程实际上是分时复用。
谢邀,一般单片机一般只有一个核心,做多线程实际上是分时复用。自己做的话可以写个循环,循环里面多个进程轮流执行。更方便一点是用嵌入式操作系统,如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》