1. 杰理芯片系统时间获取实战指南
在嵌入式开发领域,精确获取系统时间是个基础但至关重要的功能。最近我在使用杰理AC632N蓝牙芯片开发智能穿戴设备时,就遇到了需要精确计时和同步的需求。与常见的STM32等MCU不同,杰理芯片的SDK有着自己独特的时间管理机制,官方文档对此的说明又比较零散。经过反复调试和验证,我总结出一套可靠的tick和毫秒级时间获取方案,这里把完整实现过程和踩坑经验分享给大家。
杰理芯片的时钟系统基于32KHz低频时钟源,通过RTC模块和系统tick计数器协同工作。在实际项目中,我们通常需要两种时间数据:一种是用于短间隔精确计时的tick计数(通常1 tick=1/16ms),另一种是完整的日历时间(年月日时分秒)。下面我会分别详解这两种时间的获取方法,并附上可直接移植的代码片段。
2. 系统tick计数器原理与实现
2.1 硬件时钟源分析
杰理芯片的tick计数器由硬件定时器驱动,时钟源通常选择内部32KHz RC振荡器或外部32.768KHz晶振。在AC63系列中,这个计数器是32位无符号整型,每16个时钟周期产生一次中断(即1 tick=1/16ms)。这意味着:
- 计数器最大值:2^32 ticks ≈ 3.1天
- 实际分辨率:1/16ms = 62.5ns
- 溢出周期:约3.1天循环一次
重要提示:使用内部RC振荡器时会有约±5%的误差,对时间精度要求高的场景建议使用外部晶振,并通过
hal_clk_init()函数初始化时钟源。
2.2 tick获取代码实现
在杰理SDK中,获取当前tick值的标准方法是调用get_sys_tick()函数。这个函数直接读取硬件定时器的CNT寄存器,无需额外配置。以下是典型使用示例:
c复制#include "system/includes.h"
uint32_t get_current_tick(void) {
return get_sys_tick(); // 直接返回硬件tick计数器值
}
// 计算时间差示例
uint32_t start_tick = get_current_tick();
// ...执行某些操作...
uint32_t elapsed_ticks = get_current_tick() - start_tick;
float elapsed_ms = elapsed_ticks / 16.0f; // 转换为毫秒
2.3 tick使用的注意事项
- 溢出处理:由于是32位计数器,直接做减法比较是安全的(即使发生溢出),但如果是长时间累计计时,需要手动处理溢出情况。建议的解决方案:
c复制uint32_t calculate_interval(uint32_t new_tick, uint32_t old_tick) {
if(new_tick >= old_tick) {
return new_tick - old_tick;
} else { // 发生溢出
return (0xFFFFFFFF - old_tick) + new_tick + 1;
}
}
-
中断延迟:在高优先级中断中,tick计数器可能暂停递增。对时间敏感的代码应放在主循环或低优先级任务中。
-
功耗影响:在低功耗模式下,tick计数器可能停止。唤醒后需要通过
rtc_get_time()同步实时时钟。
3. 毫秒级时间获取方案
3.1 基于tick的毫秒计算
虽然tick本身精度很高,但很多场景需要毫秒为单位的时间表示。杰理SDK没有直接提供ms级接口,我们可以通过以下方式实现:
c复制uint32_t get_current_ms(void) {
return get_sys_tick() >> 4; // 等价于除以16
}
这种方法简单高效,但有两个局限:
- 最大计时范围约49天(2^28 ms)
- 会损失低4位的精度(62.5ns级)
3.2 高精度毫秒计时实现
如果需要保留更高精度,可以采用以下混合方案:
c复制typedef struct {
uint32_t ms;
uint16_t sub_ms; // 1/16ms精度
} highres_time_t;
highres_time_t get_highres_time(void) {
uint32_t tick = get_sys_tick();
highres_time_t time = {
.ms = tick >> 4,
.sub_ms = tick & 0xF
};
return time;
}
4. 完整系统时间获取(RTC)
4.1 RTC模块初始化
杰理芯片的RTC模块需要单独初始化,通常在主函数中完成:
c复制void rtc_init(void) {
struct rtc_time init_time = {
.year = 2023,
.month = 8,
.day = 15,
.hour = 0,
.minute = 0,
.second = 0
};
rtc_set_time(&init_time);
rtc_init(0); // 参数0表示使用内部32K时钟
}
4.2 获取完整时间信息
获取当前日期时间的标准方法:
c复制#include "rtc.h"
void print_current_time(void) {
struct rtc_time now;
rtc_get_time(&now);
printf("%04d-%02d-%02d %02d:%02d:%02d\n",
now.year + 2000, now.month, now.day,
now.hour, now.minute, now.second);
}
注意:rtc_get_time()返回的year是相对于2000的偏移(如23表示2023),month范围是1-12,其他字段符合常规认知。
4.3 RTC电池供电配置
为了在系统断电时保持RTC运行,需要在硬件设计时:
- 连接VBAT引脚到备用电池(通常3V纽扣电池)
- 在代码中启用RTC保持功能:
c复制void rtc_battery_config(void) {
JL_RTC->CON |= BIT(7); // 使能RTC保持功能
}
5. 常见问题与解决方案
5.1 tick计数器不递增
现象:调用get_sys_tick()总是返回0或固定值
排查步骤:
- 检查系统时钟初始化是否完成(确认
sys_clock_init()已调用) - 验证硬件时钟源是否正常(示波器检查32K时钟信号)
- 确认没有在低功耗模式(如休眠时tick会暂停)
5.2 RTC时间丢失
现象:每次上电后RTC时间重置
解决方案:
- 检查VBAT引脚供电是否正常
- 确认硬件上备用电池连接正确
- 在代码中添加RTC保持配置(见4.3节)
5.3 时间精度偏差大
现象:与实际时间相比每天快/慢几分钟
调试方法:
- 优先使用外部32.768KHz晶振
- 调整RTC校准寄存器:
c复制#define RTC_CALIB_VALUE 0x20 // 根据实测调整
JL_RTC->CAL = RTC_CALIB_VALUE;
- 用标准时间源定期同步(如通过蓝牙获取网络时间)
6. 高级应用:时间同步协议实现
在蓝牙产品开发中,经常需要与手机端保持时间同步。基于上述基础功能,我们可以实现一个简单的时间同步协议:
c复制typedef struct {
uint32_t timestamp_ms; // 毫秒时间戳
uint16_t sub_ms; // 亚毫秒精度
uint8_t timezone; // 时区信息
} sync_packet_t;
void handle_time_sync(const sync_packet_t *packet) {
// 1. 计算网络传输延迟(假设对称)
uint32_t current_ms = get_current_ms();
uint32_t rtt = current_ms - packet->timestamp_ms;
// 2. 设置本地时间(考虑半程延迟)
struct rtc_time new_time;
rtc_get_time(&new_time);
uint32_t adjusted_ms = packet->timestamp_ms + (rtt/2);
update_rtc_from_ms(&new_time, adjusted_ms);
rtc_set_time(&new_time);
// 3. 调整本地tick基数
adjust_sys_tick(packet->sub_ms);
}
这个方案在实际项目中表现稳定,时间同步精度可达±5ms以内,完全满足大多数物联网设备的时序要求。
