一、rtc简介rtc(real time clock)实时时钟,它是一个独立的定时器。rtc模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
rtc模块和时钟配置都是在后备区域,无论单片机处于何种状态,只要保证后备区正常供电,rtc就会一直工作。
二、stm32的rtc2.1 主要特性• 可编程的预分频系数 :分频系数最高为2^20
• 32位的可编程计数器 ,可用于较长时间段的测量
• 可以选择以下三种rtc的时钟源 ─ hse时钟除以128 ─ lse振荡器时钟 ─ lsi振荡器时钟
• 3个专门的可屏蔽中断 ─ 闹钟中断,用来产生一个软件可编程的闹钟中断 ─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒) ─ 溢出中断,指示内部可编程计数器溢出并回转为0的状态
22.2 rtc框图介绍
rtc框图
• rtcclk通常选择低功耗32.768khz外部晶振(lse)
• rtc预分频器通常设置为32768,les时钟经过rtc预分频器,输入频率变为1hz,也就是1秒
• rtc_cnt输入时钟为1hz时,1s加1次
• rtc_alr是用来做闹钟的,rtc_cnt的值会与rtc_alr的值进行比较,二者相等时,会产生闹钟中断
三、访问后备区域步骤stm32系统复位之后,对后备寄存器和rtc的访问被禁止 ,这是为了防止对后备区域(bkp)的意外写操作。执行以下操作,可以访问后备区域寄存器
• 设置寄存器rcc_apb1enr的pwren和bkpen位,使能电源和后备接口时钟• 设置寄存器pwr_cr的dbp位,使能对后备寄存器和rtc的访问完成上面的设置之后,就可以操作后备寄存器。第一次通过apb1总线访问rtc时,需要等待apb1和rtc同步,确保读取出来的rtc的寄存器值是正确的。如果同步之后,一直没有关闭apb1和rtc外设接口,就不需要再同步了。
如果内核需要对rtc寄存器写入数据,在内核发送指令后,rtc会在3个rtcclk时钟之后,开始写入数据。每次写入时,必须要检查rtc关闭操作标志位rtoff是否置1来判断是否写操作完成。
四、rtc配置步骤• 使能电源时钟和后备域时钟,开启rtc后备寄存器写访问
• 复位备份区域,开启外部低速振荡器(lse)
• 选择rtc时钟,并使能
• 设置rtc的分频系数,配置rtc时钟
• 更新配置,设置rtc中断分组
• 编写rtc中断服务函数
五、rtc程序配置55.1 rtc结构体定义// rtc结构体typedef struct { // 时分秒 u8 hour; u8 min; u8 sec; // 年月日周 u16 w_year; u8 w_month; u8 w_date; u8 week; }_calendar;5.2 rtc初始化函数/* *============================================================================== *函数名称:rtc_init *函数功能:初始化rtc *输入参数:无 *返回值:0:成功;1:失败 *备 注:无 *============================================================================== */u8 rtc_init (void){ u8 temp=0; // 超时监控变量 // 结构体定义 nvic_inittypedef nvic_initstructure; // 使能pwr和bkp外设时钟 rcc_apb1periphclockcmd(rcc_apb1periph_pwr | rcc_apb1periph_bkp, enable); pwr_backupaccesscmd(enable); // 使能后备寄存器访问 // 检测是否是第一次配置rtc // 配置时会想rtc寄存器写入0xa0a0,如果读出的数据不是0xa0a0,认为是第一次配置rtc if (bkp_readbackupregister(bkp_dr1) != 0xa0a0) { bkp_deinit(); // 复位备份区域 rcc_lseconfig(rcc_lse_on); // 设置外部低速晶振(lse),使用外设低速晶振 // 等待低速晶振就绪 while (rcc_getflagstatus(rcc_flag_lserdy) == reset&&temp=250) { return 1; } rcc_rtcclkconfig(rcc_rtcclksource_lse); // 设置rtc时钟(rtcclk),选择lse作为rtc时钟 rcc_rtcclkcmd(enable); // 使能rtc时钟 rtc_waitforlasttask(); // 等待最近一次对rtc寄存器的写操作完成 rtc_waitforsynchro(); // 等待rtc寄存器同步 rtc_itconfig(rtc_it_sec, enable); // 使能rtc秒中断 rtc_waitforlasttask(); // 等待最近一次对rtc寄存器的写操作完成 rtc_enterconfigmode(); // 允许配置 rtc_setprescaler(32767); // 设置rtc预分频的值 rtc_waitforlasttask(); // 等待最近一次对rtc寄存器的写操作完成 rtc_set_date(2023,6,26,11,15,00); // 设置初始时间 rtc_exitconfigmode(); // 退出配置模式 bkp_writebackupregister(bkp_dr1, 0xa0a0); // 向指定的后备寄存器中写入用户程序数据 } // 系统继续计时 else { rtc_waitforsynchro(); // 等待最近一次对rtc寄存器的写操作完成 rtc_itconfig(rtc_it_sec, enable); // 使能rtc秒中断 rtc_waitforlasttask(); // 等待最近一次对rtc寄存器的写操作完成 } // 配置rtc中断分组 nvic_initstructure.nvic_irqchannel = rtc_irqn; // rtc全局中断 nvic_initstructure.nvic_irqchannelpreemptionpriority = 0; // 先占优先级1位,从优先级3位 nvic_initstructure.nvic_irqchannelsubpriority = 0; // 先占优先级0位,从优先级4位 nvic_initstructure.nvic_irqchannelcmd = enable; // 使能该通道中断 nvic_init(&nvic_initstructure); // 根据nvic_initstruct中指定的参数初始化外设nvic寄存器 rtc_get_curdate(); // 获取当前时间 return 0; // 配置成功}初始化函数使用时,可以用while等待初始化成功,但是需要增加一个超时检测,这里简单给出一个写法,如果1s内,rtc没有初始化成功,直接跳过
u32 tempvar = 0; // 初始化rtc时的超时计数变量 while (rtc_init() && tempvar < 100) // rtc初始化 { delay_ms (10); // 10ms自加1 tempvar = tempvar + 1; }5.3 设置年月日,时分秒/* *============================================================================== *函数名称:rtc_set_date *函数功能:设置rtc的年月日,时分秒 *输入参数:无 *返回值:0:成功;1:失败 *备 注:时间范围为1970年到2099年,可修改 *============================================================================== */u8 rtc_set_date (u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec){ u16 t; u32 seccount=0; // 判断是否为合法年份 if(syear 2099) { return 1; } for(t = 1970;t < syear;t ++) // 把所有年份的秒钟相加 { // 闰年的秒钟数 if(is_leap_year(t)) { seccount += 31622400; } // 平年的秒钟数 else { seccount += 31536000; } } smon -= 1; for(t = 0;t = 365) { // 是闰年 if(is_leap_year(temp1)) { // 已经过完了366天 if(temp >= 366) { temp -= 366; // 闰年的天数 } // 刚过完365天,当前是第366天 else { temp1 ++; // 年份加1 break; } } // 是平年 else { temp -= 365; // 平年的天数 } temp1 ++; // 年份加1 } calendar.w_year = temp1; // 得到年份 temp1=0; // 清零临时计算变量 // 此时temp为小于一年的天数,开始计算当前月份 while(temp >= 28) // 超过了一个月 { // 当年是闰年的2月份 if(is_leap_year(calendar.w_year) && temp1 == 1) { // 是闰年的二月份且天数大于等于29天 if(temp >= 29) { temp -= 29; // 闰年的2月份天数 } // 是闰年的2月份,天数小于闰年2月份天数 else { break; } } // 是平年 else { // 查询月份天数表 if(temp >= mon_table[temp1]) { // 超过当月天数,减去 temp -= mon_table[temp1]; } else { break; } } temp1 ++; // 月份加1 } // 加1是因为月份表索引是0~11 calendar.w_month = temp1 + 1; // 得到月份 // 当前日期为已经过去的天数加1 calendar.w_date = temp + 1; // 得到日期 } temp = timecount % 86400; // 得到秒钟数 calendar.hour = temp / 3600; // 小时 calendar.min = (temp % 3600) / 60; // 分钟 calendar.sec = (temp % 3600) % 60; // 秒钟 calendar.week = rtc_get_week(calendar.w_year,calendar.w_month,calendar.w_date); // 获取星期 return 0;}5.6 获取星期几该函数设计是根据蔡勒公式设计,程序如下
/* *============================================================================== *函数名称:rtc_get_week *函数功能:获取当前是星期几 *输入参数:year:当前年;month:当前月;day:当前日 *返回值:星期几 *备 注:无 *============================================================================== */u8 rtc_get_week (u16 year,u8 month,u8 day){ u16 temp; u8 yearh,yearl; yearh = year / 100; yearl = year % 100; // 如果为21世纪,年份数加100 if (yearh > 19) { yearl += 100; } // 所过闰年数只算1900年之后的 temp = yearl + yearl / 4; temp = temp % 7; temp = temp + day + table_week[month - 1]; if (yearl % 4 == 0 && month < 3) { temp --; } return(temp % 7);}5.7 中断服务函数/* *============================================================================== *函数名称:rtc_irqhandler *函数功能:rtc中断服务函数 *输入参数:无 *返回值:无 *备 注:更新时间 *============================================================================== */void rtc_irqhandler(void){ // 秒中断 if (rtc_getitstatus(rtc_it_sec) != reset) { rtc_get_curdate(); // 获取当前时间 // 串口打印当前时间 printf(rtc time:%d-%d-%d %d:%d:%d week:%dn,calendar.w_year,calendar.w_month,calendar.w_date, calendar.hour,calendar.min,calendar.sec,calendar.week); } rtc_clearitpendingbit(rtc_it_sec | rtc_it_ow); //清除秒中断标志位 rtc_waitforlasttask(); // 等待最近一次对rtc寄存器的写操作完成 }六、拓展在实际使用时,通常会通过网络授时,也就是利用wifi模块连接网络,请求api获得初始时间。但是可能会存在些许差异。比如请求api后,获得的时间为2023.06.26.14:48:00。实际单片机解析出时间时已经过去了几秒或者十几秒,或者其他问题导致了实际解析出时间后已经与实际值有差距。此时就需要对时间进行矫正。博主在实际应用时差了14s,这里贴一下当时的矫正程序。可能大家用不到,这里只是觉得思考的过程有意思,所以贴出来分享一下。
void rtc_time_correct(void) // 开机时间校正{ // 加14秒不满1分钟 if (gtimesec = 46) { gtimesec = gtimesec + 14 - 60; // 分钟数需要加1 // 加1分钟不满1小时 if (gtimemin < 59) { gtimemin = gtimemin + 1; } // 分钟数加1满1小时 else if (gtimemin == 59) { gtimemin = 0; // 小时数需要加1 // 加1小时不满1天 if (gtimehour < 23) { gtimehour = gtimehour + 1; } // 加1小时满1天 else if (gtimehour == 23) { gtimehour = 0; // 天数需要加1 // 天数小于28直接加1 if (gtimeday < 28) { gtimeday = gtimeday + 1; } // 天数等于28 else if (gtimeday == 28) { // 当前为二月 if (gtimemon == 2) { // 闰年 if (is_leap_year(gtimeyear)) { gtimeday = gtimeday + 1; } // 当前为2月且不是闰年 else { gtimeday = 0; // 天数置零 gtimemon = gtimemon + 1; // 月份加1 } } } // 天数等于30 else if (gtimeday == 30) { // 当前月份只有30天 if (gtimemon == 2 || gtimemon == 4 || gtimemon == 6 || gtimemon == 9 || gtimemon == 11) { gtimeday = 0; gtimemon = gtimemon + 1; } // 当前月份有31天 else { gtimeday = gtimeday + 1; } } // 天数等于31 else if (gtimeday == 31) { gtimeday = 0; // 加1月不满1年 if (gtimemon != 12) { gtimemon = gtimemon + 1; } else { gtimemon = 1; gtimeyear = gtimeyear + 1; } } } } }}
MEMS压力传感器应用
三星3nm技术指标曝光 竟然还不如Intel
电子元器件如何做到高性能不掉链子
音圈电机在各振动柔性供料场景中的运用
集成无源和有源器件提高移动电话性能
STM32速成笔记(9)—RTC
高压隔离探头具体怎么使用?
人机交互的内容有哪些_人机交互主要研究什么
NEC推新款手机芯片 支持1080p视频拍摄
智能照明控制系统的结构组成、功能和应用
Nexperia针对汽车以太网推具有开创性且符合OPEN Alliance 标准的硅基ESD器件
全球半导体设备五强酝酿大变局
电源芯片制造商Intersil拟裁员18%,波及数百人
单向可控硅调压电路
Lombok同时使用@Data和@Builder的一个必须要避开的巨坑
利用工业互联网引领传统工厂走向智能化
半导体行业在经济发展中扮演的角色
受蜘蛛网启发的量子纳米机械谐振器
狐狸狡猾更显猎人真功,广东省总队查获一起无人机高空飞线走私案
4K双路虚拟演播系统应用