copy正点程序,在神舟IV串口输出,KEY2按下后中断,输出当前时间
先给出正点原子地址:http://openedv.com/posts/list/12119.htm
STM32的实时时钟(RTC)是一个独立的定时器。STM32的RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。
RTC由两个主要部分组成(参见图20.1.1),第一部分(APB1接口)用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作。APB1接口由APB1总线时钟驱动,用来与APB1总线连接。
另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是RTC的预分频模块,它可编程产生1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个TR_CLK周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间,一个32位的时钟计数器,按秒钟计算,可以记录4294967296秒,约合136年左右,作为一般应用,这已经是足够了的。
RTC还有一个闹钟寄存器RTC_ALR,用于产生闹钟。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。
RTC内核完全独立于RTC APB1接口,而软件是通过APB1接口访问RTC的预分频值、计数器值和闹钟值的。但是相关可读寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新,RTC标志也是如此。这就意味着,如果APB1接口刚刚被开启之后,在第一次的内部寄存器更新之前,从APB1上都处的RTC寄存器值可能被破坏了(通常读到0)。因此,若在读取RTC寄存器曾经被禁止的RTC APB1接口,软件首先必须等待RTC_CRL寄存器的RSF位(寄存器同步标志位,bit3)被硬件置1。
因为我们使用到备份寄存器来存储RTC的相关信息(我们这里主要用来标记时钟是否已经经过了配置),我们这里顺便介绍一下STM32的备份寄存器。
备份寄存器是42个16位的寄存器(战舰开发板就是大容量的),可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问:
1)通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
2)电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。
我们下面来看看要经过哪几个步骤的配置才能使RTC正常工作。RTC正常工作的一般配置步骤如下:
1)使能电源时钟和备份区域时钟。
前面已经介绍了,我们要访问RTC和备份区域就必须先使能电源时钟和备份区域时钟。这个通过RCC_APB1ENR寄存器来设置。
2)取消备份区写保护。
要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。
3)复位备份区域,开启外部低速振荡器。
在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设置,当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。然后我们使能外部低速振荡器,注意这里一般要先判断RCC_BDCR的LSERDY位来确定低速振荡器已经就绪了才开始下面的操作。
4)选择RTC时钟,并使能。
这里我们将通过RCC_BDCR的RTCSEL来选择选择外部LSI作为RTC的时钟。然后通过RTCEN位使能RTC时钟。
5)设置RTC的分频,以及配置RTC时钟。
在开启了RTC时钟之后,我们要做的就是设置RTC时钟的分频数,通过RTC_PRLH和RTC_PRLL来设置,然后等待RTC寄存器操作完成,并同步之后,设置秒钟中断。然后设置RTC的允许配置位(RTC_CRH的CNF位),设置时间(其实就是设置RTC_CNTH和RTC_CNTL两个寄存器)。
6)更新配置,设置RTC中断。
在设置完时钟之后,我们将配置更新,这里还是通过RTC_CRH的CNF来实现。在这之后我们在备份区域BKP_DR1中写入0X5050代表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取BKP_DR1的值,然后判断是否是0X5050来决定是不是要配置。接着我们配置RTC的秒钟中断,并进行分组。
7)编写中断服务函数。
最后,我们要编写中断服务函数,在秒钟中断产生的时候,读取当前的时间值,并显示到TFTLCD模块上。
通过以上几个步骤,我们就完成了对RTC的配置,并通过秒钟中断来更新时间。
先进行初始化,步骤很多,有一个_calendar_obj 结构体存放时间
1 //时间结构体 2 typedef struct 3 { 4 vu8 hour; 5 vu8 min; 6 vu8 sec; 7 //公历日月年周 8 vu16 w_year; 9 vu8 w_month; 10 vu8 w_date; 11 vu8 week; 12 }_calendar_obj;
_calendar_obj calendar;//时钟结构体
中断函数要放在Init前面写,估计因为前面有static吧,
1 static void RTC_NVIC_Config(void) 2 { 3 NVIC_InitTypeDef NVIC_InitStructure; 4 NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断 5 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位 6 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位 7 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断 8 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 9 }
1 //实时时钟配置 2 //初始化RTC时钟,同时检测时钟是否工作正常 3 //BKP->DR1用于保存是否第一次配置的设置 4 //返回0:正常 5 //其他:错误代码 6 7 u8 RTC_Init(void) 8 { 9 //检查是不是第一次配置时钟 10 u8 temp=0; 11 12 if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎 13 { 14 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 15 PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问 16 BKP_DeInit(); //复位备份区域 17 RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振 18 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) //检查指定的RCC标志位设置与否,等待低速晶振就绪 19 { 20 temp++; 21 delay_ms(10); 22 if(temp>=250)return 1;//初始化时钟失败,晶振有问题 23 } 24 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟 ,外部晶振32.768kHz 25 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 26 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 27 RTC_WaitForSynchro(); //等待RTC寄存器同步 28 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断 29 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 30 RTC_EnterConfigMode();/// 允许配置 31 RTC_SetPrescaler(32767); //设置RTC预分频的值 32 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 33 RTC_Set(2009,12,2,10,0,55); //设置时间 详见函数,年、月、日、时、分、秒 34 RTC_ExitConfigMode(); //退出配置模式 35 BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据 36 } 37 else//系统继续计时 38 { 39 RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成 //等待RTC寄存器同步 40 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断 41 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 42 } 43 RTC_NVIC_Config();//RCT中断分组设置 44 RTC_Get();//更新时间 45 return 0; //ok 46 47 }
初始化函数中有个设置时间的函数RTC_Set,他将年月日时分秒转成 second counter存放在寄存器,起始时间是1970.1.1
1 //设置时钟 2 //把输入的时钟转换为秒钟,存入RTC Counter 3 //以1970年1月1日为基准 4 //1970~2099年为合法年份 5 //返回值:0,成功;其他:错误代码. 6 //月份数据表 7 u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表 8 //平年的月份日期表 9 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31}; 10 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) 11 { 12 u16 t; 13 u32 seccount=0; 14 if(syear<1970||syear>2099)return 1; 15 for(t=1970;t<syear;t++) //把所有年份的秒钟相加 16 { 17 if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数 18 else seccount+=31536000; //平年的秒钟数 19 } 20 smon-=1; 21 for(t=0;t<smon;t++) //把前面月份的秒钟数相加 ,数组从0开始所以1月是mon_table[0] 22 { 23 seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加 一天86400秒 24 if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数 25 } 26 seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 27 seccount+=(u32)hour*3600;//小时秒钟数 28 seccount+=(u32)min*60; //分钟秒钟数 29 seccount+=sec;//最后的秒钟加上去 30 31 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 32 PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 33 RTC_SetCounter(seccount); //设置RTC计数器的值 34 35 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 36 return 0; 37 }
如果我们要获取当前时间,则使用RTC_Get 获得年月日时分秒以及星期,这两个函数写的挺巧。
1 //得到当前的时间 2 //返回值:0,成功;其他:错误代码. 3 u8 RTC_Get(void) 4 { 5 static u16 daycnt=0; 6 u32 timecount=0; 7 u32 temp=0; 8 u16 temp1=0; 9 timecount=RTC->CNTH;//得到计数器中的值(秒钟数) 10 timecount<<=16; 11 timecount+=RTC->CNTL; 12 13 temp=timecount/86400; //得到天数(秒钟数对应的) 14 if(daycnt!=temp)//超过一天了 15 { 16 daycnt=temp; 17 temp1=1970; //从1970年开始 18 while(temp>=365) 19 { 20 if(Is_Leap_Year(temp1))//是闰年 21 { 22 if(temp>=366)temp-=366;//闰年的秒钟数 23 else {temp1++;break;} 24 } 25 else temp-=365; //平年 26 temp1++; 27 } 28 calendar.w_year=temp1;//得到年份 29 temp1=0; 30 while(temp>=28)//超过了一个月 31 { 32 if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份 33 { 34 if(temp>=29)temp-=29;//闰年的秒钟数 35 else break; 36 } 37 else 38 { 39 if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年 40 else break; 41 } 42 temp1++; 43 } 44 calendar.w_month=temp1+1; //得到月份 45 calendar.w_date=temp+1; //得到日期 46 } 47 temp=timecount%86400; //得到秒钟数 48 calendar.hour=temp/3600; //小时 49 calendar.min=(temp%3600)/60; //分钟 50 calendar.sec=(temp%3600)%60; //秒钟 51 calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期 52 return 0; 53 }
1 //获得现在是星期几 2 //功能描述:输入公历日期得到星期(只允许1901-2099年) 3 //输入参数:公历年月日 4 //返回值:星期号 5 u8 RTC_Get_Week(u16 year,u8 month,u8 day) 6 { 7 u16 temp2; 8 u8 yearH,yearL; 9 10 yearH=year/100; yearL=year%100; 11 // 如果为21世纪,年份数加100 12 if (yearH>19)yearL+=100; 13 // 所过闰年数只算1900年之后的 14 temp2=yearL+yearL/4; 15 temp2=temp2%7; 16 temp2=temp2+day+table_week[month-1]; 17 if (yearL%4==0&&month<3)temp2--; 18 return(temp2%7); 19 }
中间需要的判断是否是闰年的函数
1 //判断是否是闰年函数 2 //月份 1 2 3 4 5 6 7 8 9 10 11 12 3 //闰年 31 29 31 30 31 30 31 31 30 31 30 31 4 //非闰年 31 28 31 30 31 30 31 31 30 31 30 31 5 //输入:年份 6 //输出:该年份是不是闰年.1,是.0,不是 7 u8 Is_Leap_Year(u16 year) 8 { 9 if(year%4==0) //必须能被4整除 10 { 11 if(year%100==0) 12 { 13 if(year%400==0)return 1;//如果以00结尾,还要能被400整除 14 else return 0; 15 }else return 1; 16 }else return 0; 17 }
中断函数
1 void RTC_IRQHandler(void) 2 { 3 //RTC时钟中断 4 //每秒触发一次 5 6 if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断 7 { 8 RTC_Get();//更新时间 9 } 10 if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断 11 { 12 RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断 13 } 14 RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断 15 RTC_WaitForLastTask(); 16 17 }
main函数中初始化就可以了,然后按键2按下之后显示时间
if(RTC_Init()>0)
printf("\r\n RTC Failed \n\r");
else
printf("\r\n RTC Successed \n\r");
按键中断里的显示函数:
printf("\n\r Now is %d年 %d月 %d日 %d点 %d分 %d秒 ,星期%d\n\r",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec,calendar.week);
结果:
Now is 2012年 3月 19日 22点 15分 54秒 ,星期1
不好意思,年份写错了。