谢邀,一般单片机一般只有一个核心,做多线程实际上是分时复用。

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