完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
RTC时钟简介 实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器。修改计数器的值可以重新设置系统当前的时间和日期。 RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。 系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问: ● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟 ● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。 主要特性 ● 可编程的预分频系数:分频系数高为220。 ● 32位的可编程计数器,可用于较长时间段的测量。 ● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟 频率的四分之一以上)。 ● 可以选择以下三种RTC的时钟源: ─ HSE时钟除以128; ─ LSE振荡器时钟; ─ LSI振荡器时钟 ● 2个独立的复位类型: ─ APB1接口由系统复位; ─ RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位 ● 3个专门的可屏蔽中断: ─ 闹钟中断,用来产生一个软件可编程的闹钟中断。 ─ 秒中断,用来产生一个可编程的周期性中断信号(长可达1秒)。 ─ 溢出中断,指示内部可编程计数器溢出并回转为0的状态。 RTC描述和框图 RTC由两个主要部分组成(参见下图)。 第一部分(APB1接口)用来和APB1总线相连。此单元还包 含一组16位寄存器,可通过APB1总线对其进行读写操作。APB1接口由APB1总线 时钟驱动,用来与APB1总线接口。 另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是RTC的预分频模块,它可编程产生长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个TR_CLK周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始 化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时 间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断 在看配置步骤之前我自己是偏向于看寄存器版本的,更能理解实际的过程,但是我们常常使用库函数方式,因为进行了封装比较方便。RTC正常工作的一般配置步骤 1.使能电源时钟和备份区域时钟 这也是很多配置过程的第一步,可以通过RCC_APB1ENR寄存器来设置。在中文参考手册中是设置寄存器RCC_APB1ENR的PWREN和BKPEN位 寄存器方式:RCC_APB1ENR=1<<28; RCC_APB1ENR=1<<27; 库函数方式:RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |RCC_APB1Periph_BKP, ENABLE); 2.取消备份区写保护 要向备份区写入数据先要取消备份区写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。 设置寄存器PWR_CR(电源控制寄存器)的DBP位,使能对后备寄存器和RTC的访问。 寄存器方式:PWR->CR|=1<<8; 库函数方式:PWR_BackupAccessCmd(ENABLE); 3.复位备份区域,开启外部低速振荡器 在取消备份区域写保护之后,可以先对这个区域复位,可以清除前面的设置,然后可以使能外部低速振荡器,这里一般要先判断RCC_BDCR(备份域控制寄存器)的LSERDY位来确定低速振荡器已经就绪。 寄存器方式: RCC->BDCR|=1<<16; //备份区域软件复位 RCC->BDCR&=~(1<<16); //备份区域软件复位结束 RCC->BDCR|=1<<0;//开启外部低速振荡器 RCC->BDCR|=0X02; //外部低速LSE就绪 库函数方式: BKP_DeInit(); //复位备份区域 RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振 RCC_GetFlagStatus(RCC_FLAG_LSERDY) = RESET; //外部低速LSE就绪 说明:在最后一步外部低速LSE就绪,一般是在if中用于判断,用==号。 4.选择RTC时钟,并使能 这里我们将通过 RCC_BDCR 的 RTCSEL 来选择选择外部 LSE(32.768K 的外部晶振)作为 RTC 的时钟。然后通过 RTCEN 位使能 RTC 时钟,为什么选这个时钟??是通过时钟树决定的,RTC时钟可以有三个来源 寄存器方式: RCC->BDCR|=1<<8; RCC->BDCR|=1<<15; 库函数方式: RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); 5.设置RTC的分频,以及配置RTC时钟 在开启了 RTC 时钟之后,我们要做的就是设置 RTC 时钟的分频数,通过 RTC_PRLH 和 RTC_PRLL (RTC预分频装载寄存器)来设置,但是在设置RTC时钟分频数时,要先检查RTC_CR寄存器的RTOFF位。 预分频装载寄存器用来保存RTC预分频器的周期计数值。它们受RTC_CR寄存器的RTOFF位保护,仅当RTOFF值为’1’时允许进行写操作。然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置 RTC 的允许配置位,设置时间或者设置闹钟。 寄存器方式: while(RTC->CRL&=(1<<5));//检查RTC寄存器的RTOFF位 while(!(RTC->CRL&(1<<3))); //等待 RTC 寄存器同步 RTC->CRH|=0X01; //允许秒中断 RTC->CRH|=0X02; //允许闹钟中断 while(!(RTC->CRL&(1<<5)));//等待 RTC 寄存器操作完成 RTC->CRL|=1<<4; //允许配置 RTC->PRLH=0X0000; RTC->PRLL=32767; //时钟周期设置 理论值:32767 库函数方式: RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成 RTC_WaitForSynchro(); //等待 RTC 寄存器同步 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断 RTC_WaitForLastTask(); //等待 RTC 寄存器操作完成 RTC_EnterConfigMode(); // 允许配置 RTC_SetPrescaler(32767); //设置 RTC 预分频的值 6.更新配置,设置 RTC 中断 在设置完时钟之后,我们将配置更新,这里还是通过 RTC_CRH 的 CNF 来实现。在这之后 我们在备份区域 BKP_DR1 中写入 0X5050 代表我们已经初始化过时钟了,下次开机(或复位) 的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5050 来决定是不是要配置,避免重复配置。接着我们配置 RTC 的秒钟中断,并进行分组。 寄存器方式: RTC->CRL&=~(1<<4); //配置更新 while(!(RTC->CRL&(1<<5))); //等待 RTC 寄存器操作完成 BKP->DR1=0X5050; //标记已经配置过 库函数方式: RTC_WaitForLastTask(); //等待 RTC 寄存器操作完成 RTC_ExitConfigMode(); //退出配置模式 BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据 0x5050 在退出配置模式之前可以进行时间设置 7.编写中断服务函数 设置时间函数RTC_Set() 该函数用于设置时间,把我们输入的时间,转换为以 1970 年 1 月 1 日 0 时 0 分 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}; 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 if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数 else seccount+=31536000; //平年的秒钟数 } smon-=1; for(t=0;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_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外设时钟 PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问 RTC_SetCounter(seccount); //设置 RTC 计数器的值 RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成 return 0; } 用于获取时间和日期等数据函数RTC_Get() 函数其实就是将存储在秒钟寄存器 RTC->CNTH 和 RTC->CNTL 中的秒钟数据转换为真正的时间和日期。该代码还用到了一个 calendar 的结构体。 因为 STM32 的 RTC 只有秒钟计数器,而年月日,时分秒这些需要我们自己软件计算。把计算好的值保存在 calendar 里面,方便其他程序调用。 typedef struct { vu8 hour; vu8 min; vu8 sec; vu16 w_year; vu8 w_month; vu8 w_date; vu8 week; }_calendar_obj; extern _calendar_obj calendar; //得到当前的时间,结果保存在 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; } 秒钟中断服务函数 //RTC 时钟中断 //每秒触发一次 void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //秒钟中断 { RTC_Get(); //更新时间 } if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) //闹钟中断 { RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断 RTC_Get(); //更新时间 printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month, calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间 } RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断 RTC_WaitForLastTask(); } 最后是想要用按键调整时间,后面再改成TFTLCD试试吧,main函数如下,按键写在外部中断里面: int main(void) { u8 t=0; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //LED端口初始化 LCD_Init(); EXTIX_Init(); usmart_dev.init(SystemCoreClock/1000000); //初始化USMART RTC_Init(); //RTC初始化 //显示时间 POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(60,130,200,16,16," - - "); LCD_ShowString(60,170,200,16,16," : : "); while(1) { if(t!=calendar.sec) { t=calendar.sec; LCD_ShowNum(60,130,calendar.w_year,4,16); LCD_ShowNum(100,130,calendar.w_month,2,16); LCD_ShowNum(124,130,calendar.w_date,2,16); switch(calendar.week) { case 0: LCD_ShowString(60,148,200,16,16,"Sunday "); break; case 1: LCD_ShowString(60,148,200,16,16,"Monday "); break; case 2: LCD_ShowString(60,148,200,16,16,"Tuesday "); break; case 3: LCD_ShowString(60,148,200,16,16,"Wednesday"); break; case 4: LCD_ShowString(60,148,200,16,16,"Thursday "); break; case 5: LCD_ShowString(60,148,200,16,16,"Friday "); break; case 6: LCD_ShowString(60,148,200,16,16,"Saturday "); break; } LCD_ShowNum(60,170,calendar.hour,2,16); LCD_ShowNum(84,170,calendar.min,2,16); LCD_ShowNum(108,170,calendar.sec,2,16); LED0=!LED0; } delay_ms(10); } } 1 u8 cnt=0; //外部中断0服务程序 void EXTI0_IRQHandler(void) { delay_ms(10);//消抖 if(WK_UP==1) //WK_UP按键 { if(cnt<4) { cnt++; } else { cnt=0; } } EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位 } //外部中断3服务程序 void EXTI3_IRQHandler(void) { delay_ms(10);//消抖 if(KEY1==0&&cnt==0) //年 { RTC_Set(calendar.w_year+1,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec); } else if(KEY1==0&&cnt==1) //月 { RTC_Set(calendar.w_year,calendar.w_month+1,calendar.w_date,calendar.hour,calendar.min,calendar.sec); } else if(KEY1==0&&cnt==2) //日 { RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date+1,calendar.hour,calendar.min,calendar.sec); } else if(KEY1==0&&cnt==3) //时 { RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour+1,calendar.min,calendar.sec); } else if(KEY1==0&&cnt==4) //分 { RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min+1,calendar.sec); } EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位 } void EXTI4_IRQHandler(void) { delay_ms(10);//消抖 if(KEY0==0&&cnt==0) //年 { RTC_Set(calendar.w_year-1,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec); } else if(KEY0==0&&cnt==1) //月 { if(calendar.w_month>0) { RTC_Set(calendar.w_year,calendar.w_month-1,calendar.w_date,calendar.hour,calendar.min,calendar.sec); } } else if(KEY0==0&&cnt==2) //日 { if(calendar.w_date>0) { RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date-1,calendar.hour,calendar.min,calendar.sec); } } else if(KEY0==0&&cnt==3) //时 { if(calendar.hour>0) { RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour-1,calendar.min,calendar.sec); } } else if(KEY0==0&&cnt==4) //分 { if(calendar.min>0) { RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min-1,calendar.sec); } } EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位 } 因为在减时间的时候会出现bug,目前还没看出什么毛病,所以就不允许时间跨度递减,比如从2020-1-1把月份减一变成2019-12-31可能就会出现bug,导致时钟紊乱,其他日,时,分也是。 在这里运用KEY0对时间减,KEY1对时间加,KEY_UP换位,这里用到了cnt这个标志位,初始是cnt=0,表示调整年,cnt=1,表示调整月,依此类推,但是不对秒进行调整,原因是当按键触发中断的时候要先运行中断函数对时间进行调整,导致秒中断被打断,所以秒会不准。 实验结果 这是毕业设计里面的一小部分,后面再把毕业设计里面的东西再总结一遍吧,其实想用TFTLCD屏幕直接数字修改时间,后面再改改。 有错误的话欢迎指出来呀 |
|
|
|
只有小组成员才能发言,加入小组>>
623 浏览 0 评论
6140 浏览 1 评论
3774 浏览 7 评论
TIM3定时器输出PWM波控制LED灯的亮暗程度,PWM输出的高电平还有不同的值吗?
2358 浏览 11 评论
1697 浏览 5 评论
804浏览 9评论
1184浏览 8评论
4075浏览 6评论
592浏览 6评论
628浏览 6评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 粤ICP备14022951号 )
GMT+8, 2023-8-18 17:47 , Processed in 1.127779 second(s), Total 68, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 深圳华秋电子有限公司
电子发烧友 (电路图) 粤公网安备 44030402000349 号 电信与信息服务业务经营许可证:粤 B2-20160233 工商网监 湘ICP备2023018690号