zoukankan      html  css  js  c++  java
  • 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

    不好意思,年份写错了。

     

     

  • 相关阅读:
    Finite Difference Method with Mathematica
    评论
    The Woman in Red Is Seen as a Threat by Other Wom
    Why Does Everyone Else Appear to Be Succeeding?
    The Sorrows of Young Werther
    【洛谷P5607】无力回天 NOI2017
    【YbtOJ#532】往事之树
    【YbtOJ#582】大收藏家
    【牛客Wannafly挑战赛23 F】计数
    【YbtOJ#573】后缀表达
  • 原文地址:https://www.cnblogs.com/wwjdwy/p/2969816.html
Copyright © 2011-2022 走看看