4.7.5. 设计说明¶
4.7.5.1. 源码说明¶
源代码位于 bsp/artinchip/
:
bsp/artinchip/drv/rtc/drv_rtc.c,RTC Driver 层实现
bsp/artinchip/hal/rtc/hal_rtc.c,RTC HAL 层实现
bsp/artinchip/include/hal/hal_rtc.h,RTC HAL 层接口头文件
4.7.5.2. 模块架构¶
RTC 驱动 Driver 层采用 RT-Thread 的SDIO设备驱动框架,如果只使用HAL层也可以支持 baremetal 方式的应用场景。

图 4.29 RTC 驱动的软件架构图¶
RTC 控制器除了适配到通用的 RTC 时间、闹钟接口,其他非标准的特性有:
- Alarm的中断输出:
是否有输出完全由板级电路的设计决定,软件上只需要使能中断信号即可。需要在menuconfig中使能AIC_RTC_ALARM_IO_OUTPUT,详见 驱动配置
- 时钟校准:
控制器支持±975ppm的校准范围,用户需要实测32K晶振的时钟频率,然后配置给menuconfig中的参数AIC_RTC_CLK_RATE,详见 驱动配置。
- SYS_BAK寄存器:
RTC控制器提供了128bit的NVMEM寄存器,系统复位时不会丢失数据,目前主要用于记录系统复位状态。 详见 WRI模块的复位原因管理说明。
- 8bit寄存器的读写:
在驱动设计时将8bit数据的拆解、打包进行封装,可以尽量减少对代码的干扰,封装如下:
#define RTC_WRITEL(val, reg) \
do { \
RTC_WRITE_ENABLE; \
writeb((val) & 0xFF, RTC_BASE + (reg)); \
writeb(((val) >> 8) & 0xFF, RTC_BASE + (reg) + 0x4); \
writeb(((val) >> 16) & 0xFF, RTC_BASE + (reg) + 0x8); \
writeb(((val) >> 24) & 0xFF, RTC_BASE + (reg) + 0xC); \
RTC_WRITE_DISABLE; \
} while (0)
#define RTC_READL(reg) (readb(RTC_BASE + reg) \
| (readb((RTC_BASE + reg) + 0x4) << 8) \
| (readb((RTC_BASE + reg) + 0x8) << 16) \
| (readb((RTC_BASE + reg) + 0xC) << 24))
4.7.5.3. 关键流程设计¶
4.7.5.3.1. 初始化流程¶
RTC 驱动的初始化接口通过 INIT_BOARD_EXPORT(drv_rtc_init)
完成,主要是通过调用RTC子系统的接口 rt_hw_rtc_register() 注册一个RTC设备。
RTC 控制器的初始化流程放在 rtc_ops_init() 接口中实现,其中主要步骤有:
初始化模块的clk
注册中断
设置校准参数
配置Alarm IO的输出信号(如果menuconfig中有配置的话)
4.7.5.3.2. 校准算法设计¶
校准的算法原理是,将输入的 32KHz 晶振时钟校准到理想的 32KHz,公式如下:
(100 * 1024 * 1024 + 100 * calibrate) / (clock-rate / 32) = 1024
=> calibrate = (clock-rate * 32 - 100 * 1024 * 1024) / 100;
其中:
clock-rate: 是用户实测 32K晶振的频率值 * 100,需要配置在 menuconfig 中,详见 驱动配置
calibrate: 最终要填入RTC控制器的校准值
注解
校准值calibrate分正负,正 - 表示32K晶振实际偏快了,负 - 表示32K晶振偏慢了。
4.7.5.4. 数据结构设计¶
4.7.5.4.1. struct aic_rtc_dev¶
属于 HAL 层接口,记录RTC控制器的配置信息:
struct aic_rtc_dev {
rtc_callback_t callback;
u32 clk_rate;
u32 clk_drv;
u32 alarm_io;
u32 cal_fast;
s32 cal_val;
s32 inited;
};
4.7.5.5. Driver 层接口设计¶
以下接口是 RTC 设备驱动框架的标准接口。
struct rt_rtc_ops
{
rt_err_t (*init)(void);
rt_err_t (*get_secs)(time_t *sec);
rt_err_t (*set_secs)(time_t *sec);
rt_err_t (*get_alarm)(struct rt_rtc_wkalarm *alarm);
rt_err_t (*set_alarm)(struct rt_rtc_wkalarm *alarm);
rt_err_t (*get_timeval)(struct timeval *tv); // 暂未实现
rt_err_t (*set_timeval)(struct timeval *tv); // 暂未实现
};
4.7.5.5.1. rtc_ops_init¶
函数原型 |
rt_err_t rtc_ops_init(void) |
---|---|
功能说明 |
RTC 控制器的初始化 |
参数定义 |
无 |
返回值 |
0,成功 |
注意事项 |
4.7.5.5.2. rtc_ops_get_secs¶
函数原型 |
rt_err_t rtc_ops_get_secs(time_t *sec) |
---|---|
功能说明 |
获取当前的 RTC 时间 |
参数定义 |
sec - 用于保存返回的时间信息,单位:秒 |
返回值 |
0,成功 |
注意事项 |
4.7.5.5.3. rtc_ops_set_secs¶
函数原型 |
rt_err_t rtc_ops_set_secs(time_t *sec) |
---|---|
功能说明 |
设置当前的 RTC 时间 |
参数定义 |
sec - 用于保存要设置的时间信息,单位:秒 |
返回值 |
0,成功 |
注意事项 |
4.7.5.5.4. rtc_ops_get_alarm¶
函数原型 |
rt_err_t rtc_ops_get_alarm(struct rt_rtc_wkalarm *alarm) |
---|---|
功能说明 |
获取当前的 Alarm 信息 |
参数定义 |
alarm - 用于保存返回的alarm信息 |
返回值 |
0,成功 |
注意事项 |
4.7.5.5.5. rtc_ops_set_alarm¶
函数原型 |
rt_err_t rtc_ops_set_alarm(struct rt_rtc_wkalarm *alarm) |
---|---|
功能说明 |
设置 Alarm 信息 |
参数定义 |
alarm - 用于保存要设置的 Alarm 信息 |
返回值 |
0,成功 |
注意事项 |
4.7.5.6. HAL 层接口设计¶
HAL 层的函数接口声明存放在 hal_rtc.h 中,主要接口有:
s32 hal_rtc_init(void);
s32 hal_rtc_deinit(void);
void hal_rtc_read_time(u32 *sec);
void hal_rtc_set_time(u32 sec);
s32 hal_rtc_read_alarm(u32 *sec);
void hal_rtc_set_alarm(u32 sec);
void hal_rtc_alarm_io_output(void);
void hal_rtc_32k_clk_output(void);
irqreturn_t hal_rtc_irq(int irq, void *arg);
void hal_rtc_cali(s32 clk_rate);
s32 hal_rtc_register_callback(rtc_callback_t callback);
4.7.5.7. Demo¶
如果要获取 RTC 时间,调用 RT-Thread 的标准时间接口即可,如: - time() - clock_gettime() - gettimeofday()
本Demo是 test_alarm 的源码(bsp/examples/test-alarm/test_alarm.c),设置一个oneshot类型的 alarm:
static void test_alarm_callback(rt_alarm_t alarm, time_t timestamp)
{
pr_info("Test alarm callback function.\n");
}
static void cmd_test_alarm(int argc, char **argv)
{
struct rt_alarm_setup setup;
struct rt_alarm *alarm = RT_NULL;
u32 timeout = 0;
time_t now;
struct tm p_tm;
if (argc != 2) {
pr_err("Invalid parameter\n");
return;
}
sscanf((char *)argv[1], "%u", &timeout);
now = time(NULL) + timeout;
gmtime_r(&now, &p_tm);
setup.wktime = p_tm;
alarm = rt_alarm_create(test_alarm_callback, &setup);
if (alarm) {
alarm->flag = RT_ALARM_ONESHOT;
rt_alarm_start(alarm);
}
}