RTC实验:神舟IV

copy正点程序,在神舟IV串口输出,KEY2按下后中断,输出当前时间

先给出正点原子地址:http://openedv.com/posts/list/12119.htm

STM32的实时时钟(RTC)是一个独立的定时器。STM32RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。

 

RTC由两个主要部分组成(参见图20.1.1),第一部分(APB1接口)用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作。APB1接口由APB1总线时钟驱动,用来与APB1总线连接。

 

另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是RTC的预分频模块,它可编程产生1秒的RTC时间基准TR_CLKRTC的预分频模块包含了一个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的备份寄存器。

 

备份寄存器是4216位的寄存器(战舰开发板就是大容量的),可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。

 

复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问:

 

1)通过设置寄存器RCC_APB1ENRPWRENBKPEN位来打开电源和后备接口的时钟

 

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 结构体存放时间

_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吧,

RTC_NVIC_Config
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 }
RTC_Init
 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

RTC_Set
 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 获得年月日时分秒以及星期,这两个函数写的挺巧。

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 }
RTC_Get_Week
 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 }    

中间需要的判断是否是闰年的函数

Is_Leap_Year
 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 }    

中断函数

RTC_IRQHandler
 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

不好意思,年份写错了。

 

 

原文地址:https://www.cnblogs.com/wwjdwy/p/2969816.html