STM32
直播中

感谢相遇

9年用户 968经验值
擅长:嵌入式技术 制造/封装 控制/MCU
私信 关注
[问答]

如何去实现STM32的实时时钟RTC的时间计算部分

STM32的实时时钟RTC是什么?
STM32的实时时钟RTC是由哪些部分组成的?
如何去实现STM32的实时时钟RTC的时间计算部分?

回帖(1)

李博

2021-11-22 10:10:51
  STM32 的实时时钟(RTC)是一个独立的定时器。STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
  RTC 模块和时钟配置系统(RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护
  RTC 的简化框图:
  
  RTC 由两个主要部分组成(见上图),第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。另一部分(RTC 核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是 RTC 的预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。第二个模块是一个 32 位的可编程计数器RTC_CNT),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与
  136年的计算,取32位的最大值:
  ffffffff(十六进制) / 3600 (十进制,一小时的秒数) / 24 (一天24小时) / 365 (一年按照365天计算) = 136 年
  ffffffff(十六进制) = 4294967295(十进制)
  
  回归到本次文章的重点吧,代码讲解,寄存器部分,各位自己去了解吧,开始使用的时候,其实可以直接套模板用着先,有时间了自己在深入了解寄存器就行(只讲初始化部分,代码是原子的):
  下面的代码是RTC时钟的初始化部分,以及中断部分:
  我想讲解的是,如何去实现时间计算部分,在下面:
  //RTC时钟中断
  //每秒触发一次
  void RTC_IRQHandler(void)
  {
  if(RTC-》CRL&0x0001)//秒钟中断
  {
  RTC_Get();//更新时间
  //printf(“sec:%drn”,calendar.sec);
  }
  if(RTC-》CRL&0x0002)//闹钟中断
  {
  RTC-》CRL&=~(0x0002); //清闹钟中断
  //printf(“Alarm!n”);
  }
  RTC-》CRL&=0X0FFA; //清除溢出,秒钟中断标志
  while(!(RTC-》CRL&(1《《5)));//等待RTC寄存器操作完成
  }
  //实时时钟配置
  //初始化RTC时钟,同时检测时钟是否工作正常
  //BKP-》DR1用于保存是否第一次配置的设置
  //返回0:正常
  //其他:错误代码
  u8 RTC_Init(void)
  {
  //检查是不是第一次配置时钟
  u8 temp=0;
  if(BKP-》DR1!=0X5050)//第一次配置
  {
  RCC-》APB1ENR|=1《《28; //使能电源时钟
  RCC-》APB1ENR|=1《《27; //使能备份时钟
  PWR-》CR|=1《《8; //取消备份区写保护
  RCC-》BDCR|=1《《16; //备份区域软复位
  RCC-》BDCR&=~(1《《16); //备份区域软复位结束
  RCC-》BDCR|=1《《0; //开启外部低速振荡器
  while((!(RCC-》BDCR&0X02))&&temp《250)//等待外部时钟就绪
  {
  temp++;
  delay_ms(10);
  };
  if(temp》=250)return 1;//初始化时钟失败,晶振有问题
  RCC-》BDCR|=1《《8; //LSI作为RTC时钟
  RCC-》BDCR|=1《《15;//RTC时钟使能
  while(!(RTC-》CRL&(1《《5)));//等待RTC寄存器操作完成
  while(!(RTC-》CRL&(1《《3)));//等待RTC寄存器同步
  RTC-》CRH|=0X01; //允许秒中断
  while(!(RTC-》CRL&(1《《5)));//等待RTC寄存器操作完成
  RTC-》CRL|=1《《4; //允许配置
  RTC-》PRLH=0X0000;
  RTC-》PRLL=32767; //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767
  RTC_Set(2014,3,8,22,10,55); //设置时间
  RTC-》CRL&=~(1《《4); //配置更新
  while(!(RTC-》CRL&(1《《5))); //等待RTC寄存器操作完成
  BKP-》DR1=0X5050;
  printf(“FIRST TIMEn”);
  }else//系统继续计时
  {
  while(!(RTC-》CRL&(1《《3)));//等待RTC寄存器同步
  RTC-》CRH|=0X01; //允许秒中断
  while(!(RTC-》CRL&(1《《5)));//等待RTC寄存器操作完成
  printf(“OKn”);
  }
  MY_NVIC_Init(0,0,RTC_IRQn,2);//优先级设置
  RTC_Get();//更新时间
  return 0; //ok
  }
  实现时间计算部分代码:
  说一下除以和求余的区别:
  除运算得到的是整数倍 除数的整数;
  求余预算得到的是拿了整数倍 除数,,剩余的整数;
  例如 100 / 3 = 33.。..。..。..3 33是除运算得到的,1是求余的得到的
  先说一下实现计算时间的实现思路(主要抓住中断函数就行,中断是每1s中断一次):
  1、中断,跳转至我们的中断服务函数,取出计数器计算到的秒数;
  2、对秒数进行计算,求出过了多少天;
  加入现在的秒数是 8000000s,
  那现在就是 8000000 / 3600 / 24 / = 92.59 天
  这里计算出来的天数(整数 加1 )要和上一次的天数比较,如果多一天的话,就继续执行,要是后365天了,就要判断下一年是否是闰 年(接下来计算的2月要使用),计算闰年,就考验大家的算法了
  将闰年的和平年每个月的天数 保存在数组里面,将计算出的天数和数组比较机也可以得出现在是几月
  例如:今年是平年 92.59 - 31(1月)-28(2月)-31(3月)= 2.59天
  2.59天 《 30天(4月) 那就可以判断现在是4月
  2.59天 取整数 再加1,就是哪天,2.59取整就是2,再加1就是3,那今天就是4月3号
  3、时间计算:
  1)小时:计算的秒数对一天的秒数求余数得到不够一天的秒数之后,在除以一小时的秒数;
  8000000 % (3600 *24) / 3600 = 14 ,那现在的时钟就是下午的2时
  2)分钟:计算的秒数对一天的秒数求余数得到不够一天的秒数之后,
  再对一小时的秒数求余,得到不够一小时的秒数;
  最后在除以一分钟的秒数得到现在是 几分钟
  3)秒数: 计算的秒数对一天的秒数求余数得到不够一天的秒数之后,
  再对一小时的秒数求余,得到不够一小时的秒数;
  最后再一分钟的秒数求余,得到现在是 几秒
  4)星期几,这个就自己看吧,也不难,代码应该能看懂的了
  //判断是否是闰年函数
  //月份 1 2 3 4 5 6 7 8 9 10 11 12
  //闰年 31 29 31 30 31 30 31 31 30 31 30 31
  //非闰年 31 28 31 30 31 30 31 31 30 31 30 31
  //year:年份
  //返回值:该年份是不是闰年.1,是.0,不是
  u8 Is_Leap_Year(u16 year)
  {
  if(year%4==0) //必须能被4整除
  {
  if(year%100==0)
  {
  if(year%400==0)return 1;//如果以00结尾,还要能被400整除
  else return 0;
  }else return 1;
  }else return 0;
  }
  //设置时钟
  //把输入的时钟转换为秒钟
  //以1970年1月1日为基准
  //1970~2099年为合法年份
  //返回值:0,成功;其他:错误代码。
  //月份数据表
  u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
  //平年的月份日期表
  const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
  //syear,smon,sday,hour,min,sec:年月日时分秒
  //返回值:设置结果。0,成功;1,失败。
  u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
  {
  u16 t;
  u32 seccount=0;
  if(syear《1970||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《smon;t++) //把前面月份的秒钟数相加
  {
  seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
  if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
  }
  seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
  seccount+=(u32)hour*3600;//小时秒钟数
  seccount+=(u32)min*60; //分钟秒钟数
  seccount+=sec;//最后的秒钟加上去
  //设置时钟
  RCC-》APB1ENR|=1《《28;//使能电源时钟
  RCC-》APB1ENR|=1《《27;//使能备份时钟
  PWR-》CR|=1《《8; //取消备份区写保护
  //上面三步是必须的!
  RTC-》CRL|=1《《4; //允许配置
  RTC-》CNTL=seccount&0xffff;
  RTC-》CNTH=seccount》》16;
  RTC-》CRL&=~(1《《4);//配置更新
  while(!(RTC-》CRL&(1《《5)));//等待RTC寄存器操作完成
  RTC_Get();//设置完之后更新一下数据
  return 0;
  }
  //得到当前的时间,结果保存在calendar结构体里面
  //返回值:0,成功;其他:错误代码。
  u8 RTC_Get(void)
  {
  static u16 daycnt=0;
  u32 timecount=0;
  u32 temp=0;
  u16 temp1=0;
  timecount=RTC-》CNTH;//得到计数器中的值(秒钟数)
  timecount《《=16;
  timecount+=RTC-》CNTL;
  temp=timecount/86400; //得到天数(一天的秒数)
  if(daycnt!=temp)//超过一天了
  {
  daycnt=temp;
  temp1=1970; //从1970年开始
  while(temp》=365)
  {
  if(Is_Leap_Year(temp1))//是闰年
  {
  if(temp》=366)temp-=366;//闰年的秒钟数
  else break;
  }
  else temp-=365; //平年
  temp1++;
  }
  calendar.w_year=temp1;//得到年份
  temp1=0;
  while(temp》=28)//超过了一个月
  {
  if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
  {
  if(temp》=29)temp-=29;//闰年的秒钟数
  else break;
  }
  else
  {
  if(temp》=mon_table[temp1])temp-=mon_table[temp1];//平年
  else break;
  }
  temp1++;
  }
  calendar.w_month=temp1+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;
  }
  //获得现在是星期几
  //功能描述:输入公历日期得到星期(只允许1901-2099年)
  //year,month,day:公历年月日
  //返回值:星期号
  u8 RTC_Get_Week(u16 year,u8 month,u8 day)
  {
  u16 temp2;
  u8 yearH,yearL;
  yearH=year/100; yearL=year%100;
  // 如果为21世纪,年份数加100
  if (yearH》19)yearL+=100;
  // 所过闰年数只算1900年之后的
  temp2=yearL+yearL/4;
  temp2=temp2%7;
  temp2=temp2+day+table_week[month-1];
  if (yearL%4==0&&month《3)temp2--;
  return(temp2%7);
  }
举报

更多回帖

发帖
×
20
完善资料,
赚取积分