为嵌入式设备提供精准的时间基准是许多应用的基本需求。在RT-Thread实时操作系统中,可以方便地调用RTC(实时时钟)设备驱动来管理时间。本文将详细介绍在STM32F103平台上,基于RT-Thread完成RTC驱动的配置、硬件初始化与应用层调用的完整流程。
1. 启用RTC设备驱动框架
在RT-Thread中启用RTC主要分为两个步骤:首先在驱动框架中打开RTC支持,然后在板级支持包中完成具体配置。
使用RT-Thread的menuconfig图形化配置工具是启用驱动的标准方式。在配置界面中,你需要依次导航至以下路径并进行配置:
- 首先进入
Hardware Drivers Config -> On-chip Peripheral Drivers 菜单。
- 在此菜单中,找到并选中
Enable RTC 选项。
- 随后,进入其子菜单
Select clock source,为RTC选择时钟源。对于STM32F103,为确保走时精度和低功耗,通常选择使用外部低速晶振 (RTC USING LSE)。
完成以上配置后,按快捷键保存并退出。
第二步:确保HAL库配置正确
STM32的HAL库提供了硬件抽象层驱动。你需要打开工程中的 stm32f1xx_hal_conf.h 文件,确认 HAL_RTC_MODULE_ENABLED 宏已被定义,以确保RTC的HAL驱动模块被启用。
第三步:在设备驱动菜单中启用RTC设备
为了使RTC作为标准设备被RT-Thread内核识别和管理,还需在设备驱动层进行配置。在 menuconfig 中,导航至 RT-Thread Configuration -> Device Drivers。在长长的设备驱动列表中,找到名为 Using RTC device drivers 的选项(通常会被一个红色箭头高亮指示),并将其选中启用。
完成以上所有配置后,RT-Thread的驱动框架便为RTC设备做好了准备。
2. 关键的硬件初始化与备份域管理
RTC模块属于STM32的备份域,其运行独立于主电源域,因此需要特殊的电源和时钟管理。如果这部分配置不当,RTC将无法正常工作或无法保持时间。
- 备份域访问使能:在尝试读写任何RTC或备份寄存器之前,必须依次使能电源接口时钟、备份域时钟,并解除备份域的写保护。这通常在驱动的初始化函数中完成。
- 时钟源配置:如前所述,推荐使用外部32.768kHz低速晶振(LSE)作为RTC时钟源。这需要在系统时钟初始化函数中正确配置。以下是一个典型的
SystemClock_Config函数示例,其中关键步骤是启用LSE并将其选为RTC时钟源。
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
// 配置振荡器:开启HSE、HSI、LSE、LSI
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE
|RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSEState = RCC_LSE_ON; // 确保LSE开启
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
// 配置系统时钟(SYSCLK)、AHB、APB1和APB2时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
// 关键步骤:配置外设时钟,将RTC时钟源设置为LSE
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_ADC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; // 选择LSE作为RTC时钟
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
- MSP初始化函数:针对RTC外设的底层引脚和时钟初始化通常位于
HAL_RTC_MspInit() 函数中。在RT-Thread的BSP(板级支持包)中,相关的初始化逻辑可能被封装在 drv_rtc.c 文件的 rt_rtc_config 函数里。因此,你可以选择屏蔽掉自动生成的 HAL_RTC_MspInit(),或确保其内容与驱动要求一致。一个典型的 HAL_RTC_MspInit 实现如下:
void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc)
{
if(hrtc->Instance==RTC)
{
__HAL_RCC_PWR_CLK_ENABLE(); // 使能电源时钟
HAL_PWR_EnableBkUpAccess(); // 允许访问备份域
__HAL_RCC_BKP_CLK_ENABLE(); // 使能备份域时钟
__HAL_RCC_RTC_ENABLE(); // 使能RTC外设时钟
}
}
注意:关于SystemDesign,备份域的设计体现了嵌入式系统中电源管理和数据持久化的一种常见模式,对于设计需要掉电记忆功能的系统至关重要。
- 初始化标志存储:为避免每次芯片复位都重新初始化RTC(这会导致时间重置),驱动通常利用备份寄存器(如
BKP_DR1)存储一个“魔术数字”作为初始化标志。上电后,先检查该标志,若已设置则跳过RTC分频器等配置,直接读取时间计数器。这是RTC能够持续计时、系统设计的关键。
3. 应用层调用与验证
当驱动正确配置并初始化后,RTC设备(默认设备名为 "rtc")便注册到了RT-Thread的I/O设备管理层,可以通过标准API进行访问。
3.1 使用FinSH/MSH命令快速验证
通过串口终端连接设备,使用RT-Thread内置的shell命令是最快捷的验证方式:
- 查看当前时间:输入命令
date,系统会打印出当前的年、月、日、时、分、秒。
- 设置时间:使用命令
date [year] [month] [day] [hour] [minute] [second]。例如,date 2026 02 24 18 30 00 可将系统时间设置为2026年2月24日18点30分00秒。
3.2 在应用程序中调用RTC
以下是一个标准的RTC设备使用样例程序,它演示了如何查找设备、初始化、设置时间以及读取时间。
#include <rtthread.h>
#include <rtdevice.h>
#define RTC_NAME "rtc"
static int rtc_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
time_t now;
rt_device_t device = RT_NULL;
/* 1. 通过设备名查找RTC设备 */
device = rt_device_find(RTC_NAME);
if (!device)
{
rt_kprintf("find %s failed!\n", RTC_NAME);
return RT_ERROR;
}
/* 2. 以只读方式打开设备 */
if(rt_device_open(device, 0) != RT_EOK)
{
rt_kprintf("open %s failed!\n", RTC_NAME);
return RT_ERROR;
}
/* 3. 设置日期 */
ret = set_date(2018, 12, 3);
if (ret != RT_EOK)
{
rt_kprintf("set RTC date failed\n");
return ret;
}
/* 4. 设置时间 */
ret = set_time(11, 15, 50);
if (ret != RT_EOK)
{
rt_kprintf("set RTC time failed\n");
return ret;
}
/* 5. 延时3秒 */
rt_thread_mdelay(3000);
/* 6. 获取并打印当前时间 */
now = time(RT_NULL);
rt_kprintf("%s\n", ctime(&now));
return ret;
}
/* 将示例命令导出到MSH */
MSH_CMD_EXPORT(rtc_sample, rtc sample);
提示:在编写此类底层设备操作代码时,对C/C++中指针、结构体等概念的理解非常重要,尤其是处理类似RTC_HandleTypeDef这样的硬件抽象层结构体时。
4. 关键注意事项与掉电保存
- VBAT引脚供电:若要实现主电源(VDD)断开后RTC依然持续运行,必须为STM32的
VBAT引脚连接备用电源,通常是一颗3V的纽扣电池(如CR2032)或一个大容量电容。否则,主电源掉电后,RTC将停止工作,时间信息会丢失。
- 手动调用初始化:特别注意,RT-Thread系统在自动注册RTC设备时,不会自动调用硬件初始化函数。你需要在应用启动初期,或在设备打开之前,手动调用一次
rt_rtc_config() 函数(该函数通常定义在BSP的 drv_rtc.c 中)来完成对RTC时钟源、分频器等的底层配置。这是许多初学者容易遗漏导致RTC无法工作的关键一步。
- 查阅官方手册:遇到配置问题时,务必参考STM32F103的参考手册(Reference Manual)和RT-Thread的官方文档,其中对备份域、RTC寄存器的描述最为权威。
参考资料
[1] 单片机 STM32F103 RTC(实时时钟)的配置和使用, 微信公众号:mp.weixin.qq.com/s/QU8x62QHUMCG8LF8TDlPcw
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。