当前位置: 首页 > 工具软件 > rtc.io > 使用案例 >

STM32F4 HAL库开发 -- RTC

倪棋
2023-12-01

一、STM32F407 RTC时钟简介

STM32F407的RTC,是一个独立的BCD定时器/计数器。RTC提供了一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A和ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。
两个32位寄存器(TR和DR)包含二进码十进制格式(BCD)的秒、分钟、小时(12或24小时制)、星期、日期、月份和年份。此外,还可提供二进制格式的亚秒值。
STM32F429 的 RTC 可以自动将月份的天数补偿为 28、 29(闰年)、 30 和 31 天。

二、HAL库配置RTC

1、使能电源时钟,并使能RTC及RTC后备寄存器写访问

电源时钟使能,通过RCC_APB1ENR寄存器来设置:RTC及RTC备份寄存器的写访问,通过PWR_CR寄存器的DBP位设置。
HAL库设置方法为:

__HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟 PWR
HAL_PWR_EnableBkUpAccess();//取消备份区域写保护

2、开启外部低速振荡器LSE,选择RTC时钟,并使能

配置开启LSE的函数为 HAL_RCC_OscConfig,使用方法为:

RCC_OscInitStruct.OscillatorType=RCC_OSCILLATORTYPE_LSE;//LSE 配置
RCC_OscInitStruct.PLL.PLLState=RCC_PLL_NONE;
RCC_OscInitStruct.LSEState=RCC_LSE_ON; //RTC 使用 LSE
HAL_RCC_OscConfig(&RCC_OscInitStruct);

选择RTC时钟源函数为HAL_RCCEx_PeriphCLKConfig,使用方法为:

PeriphClkInitStruct.PeriphClockSelection=RCC_PERIPHCLK_RTC;//外设为 RTC
PeriphClkInitStruct.RTCClockSelection=RCC_RTCCLKSOURCE_LSE;//RTC 时钟源为 LSE
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);

使能RTC时钟方法为:

__HAL_RCC_RTC_ENABLE();//RTC 时钟使能

3、初始化RTC,设置RTC的分频,以及配置RTC参数

在HAL中,初始化RTC是通过函数HAL_RTC_Init实现的,该函数声明为:

HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);

同样按照以前的方式,我们来看看RTC初始化参数结构体RTC_HandleTypeDef 定义:

typedef struct
{
RTC_TypeDef *Instance;
RTC_InitTypeDef Init;
HAL_LockTypeDef Lock;
__IO HAL_RTCStateTypeDef State;
}RTC_HandleTypeDef;

这里我们着重讲解成员变量Init含义,因为它是真正的RTC初始化变量,它是RTC_InitTypeDef 结构体类型,结构体 RTC_InitTypeDef 定义为:

typedef struct
{
uint32_t HourFormat; //小时格式
uint32_t AsynchPrediv; //异步预分频系数
uint32_t SynchPrediv; //同步预分频系数
uint32_t OutPut; //选择连接到 RTC_ALARM 输出的标志
uint32_t OutPutPolarity; //设置 RTC_ALARM 的输出极性
uint32_t OutPutType; //设置 RTC_ALARM 的输出类型为开漏输出还是推挽输出
}RTC_InitTypeDef;

该结构体有6个成员变量。
成员变量 HourFormat 用来设置小时格式,为12小时制或者24小时制,取值为RTC_HOURFORMAT_12 或者 RTC_HOURFORMAT_24。
AsynchPrediv 用来设置RTC的异步预分频系数,也就是设置RTC_PRER寄存器的PREDIV_A相关位,因为异步分频系数是7位,所以最大值位0x7F,不能超过这个值。
SynchPrediv 用来设置RTC的同步预分频系数,也就是设置 RTC_PRER寄存器的PREDIV_S相关位,因为同步预分频系数也是15位,所以最大值位0x7FFF,不能超过这个值。
OutPut 用来选择要连接到 RTC_ALARM 输出的标志,取值为: RTC_OUTPUT_DISABLE(禁止输出), RTC_OUTPUT_ALARMA(使能闹钟 A 输出), RTC_OUTPUT_ALARMB(使能闹钟 B 输出)和 RTC_OUTPUT_WAKEUP(使能唤醒输出)。
OutPutPolarity 用来设置 RTC_ALARM 的输出极性,与 Output 成员变量配合使用,取值为RTC_OUTPUT_POLARITY_HIGH(高电平)或 RTC_OUTPUT_POLARITY_LOW(低电平)。
OutPutType 用 来 设 置 RTC_ALARM 的 输 出 类 型 为 开 漏 输 出( RTC_OUTPUT_TYPE_OPENDRAIN)还是推挽输出( RTC_OUTPUT_TYPE_PUSHPULL),与成员变量 OutPut 和 OutPutPolarity 配合使用。

RTC初始化的一般格式:

  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24; //RTC 设置为 24 小时格式
  hrtc.Init.AsynchPrediv = 127; //RTC 异步分频系数(1~0X7F)
  hrtc.Init.SynchPrediv = 255; //RTC 同步分频系数(0~7FFF)
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

同样, HAL 库也提供了 RTC 初始化 MSP 函数。函数声明为:

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{

  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspInit 0 */

  /* USER CODE END RTC_MspInit 0 */
    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();
  /* USER CODE BEGIN RTC_MspInit 1 */

  /* USER CODE END RTC_MspInit 1 */
  }
}

该函数内部一般存放时钟使能,时钟源选择等操作程序。

4、设置RTC的时间

HAL库中,设置RTC时间的函数为:

HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,
RTC_TimeTypeDef *sTime, uint32_t Format);

HAL_RTC_SetTime 函数的第三个参数 Format,用来设置输入的时间格式为BIN格式还是BCD格式,可选值为 RTC_FORMAT_BIN(使用十进制 例如 18年你得到到是 18年)和 RTC_FORMAT_BCD(使用16进制 例如 18年你得到的是 0x18年
)。
结构体 RTC_TimeTypeDef 的定义:

typedef struct
{
uint8_t Hours; //时
uint8_t Minutes; //分
uint8_t Seconds; //秒
uint8_t TimeFormat; //AM/PM 符号
uint32_t SubSeconds; //读取保存亚秒
uint32_t SecondFraction; //用来读取保存同步预分频系数
uint32_t DayLightSaving; //用来设置日历时间增加 1 小时,减少 1 小时,还是不变
uint32_t StoreOperation; //用户可对此变量设置以记录是否已对夏令时进行更改
}RTC_TimeTypeDef;

实例:

  RTC_TimeTypeDef sTime = {0};
  sTime.Hours = 0x0;
  sTime.Minutes = 0x0;
  sTime.Seconds = 0x0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }

5、设置RTC的日期

设置RTC的日期函数为:

HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc,
RTC_DateTypeDef *sDate, uint32_t Format);

我们着重讲解第二个入口参数sData,它是结构体
RTC_DateTypeDef 指针类型变量,结构体 RTC_DateTypeDef 定义如下:

typedef struct
{
uint8_t WeekDay; //星期几
uint8_t Month; //月份
uint8_t Date; //日期
uint8_t Year; //年份
}RTC_DateTypeDef;

6、获取RTC当前日期和时间

获取当前 RTC 时间的函数为:

HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc,
RTC_TimeTypeDef *sTime, uint32_t Format);

获取当前 RTC 日期的函数为:

HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc,
RTC_DateTypeDef *sDate, uint32_t Format);

三、计时器

SYS_DATETIME rtctime = { 0 };
uint32_t Inctime = 0;
uint32_t Networktime = 0;

//设置RTC时间
void RTC_Sync_SYS_Time(SYS_DATETIME *pDTime)
{
	RTC_DateTypeDef RTC_DateStruct;
	RTC_TimeTypeDef RTC_TimeStruct;
	
	RTC_DateStruct.Year = pDTime->year - DEFAULT_YEAR;
	RTC_DateStruct.Month = pDTime->month;
	RTC_DateStruct.Date = pDTime->day;
	if (HAL_RTC_SetDate(&hrtc, &RTC_DateStruct, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
	RTC_TimeStruct.Hours = pDTime->hour;
	RTC_TimeStruct.Minutes = pDTime->minute;
	RTC_TimeStruct.Seconds = pDTime->second;
	if (HAL_RTC_SetTime(&hrtc, &RTC_TimeStruct, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
}

//得到RTC时间
void SYS_Time_Sync_RTC(SYS_DATETIME *pDTime)
{
	RTC_DateTypeDef RTC_DateStruct;
	RTC_TimeTypeDef RTC_TimeStruct;

	HAL_RTC_GetTime(&hrtc,&RTC_TimeStruct,RTC_FORMAT_BIN);
	pDTime->hour = RTC_TimeStruct.Hours;
	pDTime->minute = RTC_TimeStruct.Minutes;
	pDTime->second = RTC_TimeStruct.Seconds;
	
	HAL_RTC_GetDate(&hrtc,&RTC_DateStruct,RTC_FORMAT_BIN);
	pDTime->year = (RTC_DateStruct.Year + DEFAULT_YEAR);
	pDTime->month = RTC_DateStruct.Month;
	pDTime->day = RTC_DateStruct.Date;
}

//判断闰年
uint8_t Is_Leap_Year(uint16_t year)
{
  if(((year % 4) == 0) && ((year % 100) != 0) || ((year % 400) == 0))
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

//判断2月
uint8_t Last_Day_Of_Mon(uint8_t month, uint16_t year)
{
	const uint8_t Sys_Mon[MONTH_PER_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	
  if ((month == 0) || (month > 12))
	{
    return Sys_Mon[1] + Is_Leap_Year(year);
  }
  if (month != 2)
	{
    return Sys_Mon[month - 1];
  }
	else
	{
   return Sys_Mon[1] + Is_Leap_Year(year);
  }
}

//UNIX时间转RTC时间
void Unix2Rtc(uint32_t temp, SYS_DATETIME *rtctime, uint8_t time_zone)
{
	uint32_t sec;
	uint16_t y;
	uint32_t day;
	uint8_t m;
	uint16_t d;

	/* time zone */
	temp += (time_zone * SEC_PER_HOUR);

	/* hour */
	sec = temp % SEC_PER_DAY;
	rtctime->hour = sec / SEC_PER_HOUR;

	/* min */
	sec %= SEC_PER_HOUR;
	rtctime->minute = sec / SEC_PER_MIN;

	/* sec */
	rtctime->second = sec % SEC_PER_MIN;
	/* year */
	day = temp / SEC_PER_DAY;
	for (y = UTC_BASE_YEAR; day > 0; y++)
	{
		d = (DAY_PER_YEAR + Is_Leap_Year(y));
		if (day >= d)
		{
			day -= d;
		}
		else
		{
			break;
		}
	}

	rtctime->year = y;

	/* month */
	for (m = 1; m < MONTH_PER_YEAR; m++)
	{
		d = Last_Day_Of_Mon(m, y);
		if (day >= d)
		{
			day -= d;
		}
		else
		{
			break;
		}
	}

	rtctime->month = m;

	/*day*/
	rtctime->day = day + 1;
}

//RTC时间转UNIX时间
uint32_t RTC2Unix(SYS_DATETIME *systime)
{
	uint16_t i;
	uint32_t no_of_days = 0;
	uint32_t utc_time;

  if (systime->year < UTC_BASE_YEAR)
	{
    return 0;
  }

    /* year */
  for (i = UTC_BASE_YEAR; i < systime->year; i++)
	{
    no_of_days += (DAY_PER_YEAR + Is_Leap_Year(i));
  }

  /* month */
  for (i = 1; i < systime->month; i++)
	{
    no_of_days += Last_Day_Of_Mon((unsigned char) i, systime->year);
  }

	/* day */
	no_of_days += (systime->day - 1);

	/* sec */
	utc_time = no_of_days * SEC_PER_DAY + (systime->hour * SEC_PER_HOUR + systime->minute * SEC_PER_MIN + systime->second);

	return utc_time;
}

//设置RTC时间
void THC_Set_RTC_Time(uint32_t time)
{
	memset(&rtctime, 0, sizeof(rtctime));
	Unix2Rtc(time, &rtctime, 8);
	RTC_Sync_SYS_Time(&rtctime);
	Networktime = time;
	Inctime = 0;
}

//得到RTC时间
void THC_Get_RTC_Time(void)
{
	memset(&rtctime, 0, sizeof(rtctime));
	SYS_Time_Sync_RTC(&rtctime);
	Networktime =  RTC2Unix(&rtctime);
	Inctime = 0;
}

//得到RTC时间
SYS_DATETIME THC_Get_SysTime(void)
{
	SYS_DATETIME sRTCtime;
	uint32_t time = 0;
	time = Networktime + (Inctime/10);
	Unix2Rtc(time, &sRTCtime, 8);
	return sRTCtime;
}

//TIM中断函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	//100ms定时中断
	if(htim==(&htim7))  
	{
		Inctime++; //RTC计时
	}
}
 类似资料: