打印
[应用相关]

STM32 RTC 串口打印

[复制链接]
2907|37
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码

u8 RTC_Init(void)
{
        //检查是不是第一次配置时钟
        u8 temp=0;
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);        //使能PWR和BKP外设时钟   
        PWR_BackupAccessCmd(ENABLE);        //使能后备寄存器访问  
        if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)                //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
                {                                

                BKP_DeInit();        //复位备份区域        
                RCC_LSEConfig(RCC_LSE_ON);        //设置外部低速晶振(LSE),使用外设低速晶振
                while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)        //检查指定的RCC标志位设置与否,等待低速晶振就绪
                        {
                        temp++;
                        delay_ms(10);
                        }
                if(temp>=250)return 1;//初始化时钟失败,晶振有问题            
                RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);                //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟   
                RCC_RTCCLKCmd(ENABLE);        //使能RTC时钟  
                RTC_WaitForLastTask();        //等待最近一次对RTC寄存器的写操作完成
                RTC_WaitForSynchro();                //等待RTC寄存器同步  
                RTC_ITConfig(RTC_IT_SEC, ENABLE);                //使能RTC秒中断
                RTC_WaitForLastTask();        //等待最近一次对RTC寄存器的写操作完成
                RTC_EnterConfigMode();/// 允许配置       
                RTC_SetPrescaler(32767); //设置RTC预分频的值
                RTC_WaitForLastTask();        //等待最近一次对RTC寄存器的写操作完成
                RTC_Set(2018,8,31,17,07,55);  //设置时间       
                RTC_ExitConfigMode(); //退出配置模式  
                BKP_WriteBackupRegister(BKP_DR1, 0X5050);        //向指定的后备寄存器中写入用户程序数据
                }
        else//系统继续计时
                {

                RTC_WaitForSynchro();        //等待最近一次对RTC寄存器的写操作完成
                RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中断
                RTC_WaitForLastTask();        //等待最近一次对RTC寄存器的写操作完成
                }
        RTC_NVIC_Config();//RCT中断分组设置                                                         
        RTC_Get();//更新时间       
        return 0; //ok

}               

沙发
gaoke231|  楼主 | 2018-8-31 22:13 | 只看该作者
//RTC时钟中断
//每秒触发一次  
//extern u16 tcnt;
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_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);                //清闹钟中断
        RTC_WaitForLastTask();                                                                                           
}

使用特权

评论回复
板凳
gaoke231|  楼主 | 2018-8-31 22:15 | 只看该作者
//判断是否是闰年函数
//月份   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
//输入:年份
//输出:该年份是不是闰年.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;       
}                

使用特权

评论回复
地板
gaoke231|  楼主 | 2018-8-31 22:15 | 只看该作者
//设置时钟
//把输入的时钟转换为秒钟
//以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<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_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);        //使能PWR和BKP外设时钟  
        PWR_BackupAccessCmd(ENABLE);        //使能RTC和后备寄存器访问
        RTC_SetCounter(seccount);        //设置RTC计数器的值

        RTC_WaitForLastTask();        //等待最近一次对RTC寄存器的写操作完成         
        RTC_Get();
        return 0;            
}

使用特权

评论回复
5
gaoke231|  楼主 | 2018-8-31 22:17 | 只看该作者
//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
        static u16 daycnt=0;
        u32 timecount=0;
        u32 temp=0;
        u16 temp1=0;          
    timecount=RTC_GetCounter();         
        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 {temp1++;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;
}

使用特权

评论回复
6
gaoke231|  楼主 | 2018-8-31 22:18 | 只看该作者
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号                                                                                                                                                                                 
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);
}       

使用特权

评论回复
7
gaoke231|  楼主 | 2018-8-31 22:18 | 只看该作者
h文件

//时间结构体
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;        //日历结构体

extern u8 const mon_table[12];        //月份日期数据表
void Disp_Time(u8 x,u8 y,u8 size);//在制定位置开始显示时间
void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
u8 RTC_Init(void);        //初始化RTC,返回0,失败;1,成功;
u8 Is_Leap_Year(u16 year);//平年,闰年判断
u8 RTC_Get(void);         //更新时间   
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间       

使用特权

评论回复
8
gaoke231|  楼主 | 2018-8-31 22:20 | 只看该作者
//加入以下代码,支持printf函数,而不需要选择use MicroLIB          
#if 1
#pragma import(__use_no_semihosting)            
//标准库需要的支持函数                 
struct __FILE
{
        int handle;

};

FILE __stdout;      
//定义_sys_exit()以避免使用半主机模式   
_sys_exit(int x)
{
        x = x;
}

使用特权

评论回复
9
gaoke231|  楼主 | 2018-8-31 22:20 | 只看该作者
//重定义fputc函数
int fputc(int ch, FILE *f)
{      
        while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
        return ch;
}

使用特权

评论回复
10
gaoke231|  楼主 | 2018-8-31 22:20 | 只看该作者
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
         
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);        //使能USART1,GPIOA时钟
  
        //USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX          GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;                //子优先级3
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

        USART_InitStructure.USART_BaudRate = bound;//串口波特率
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
        USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
        USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;        //收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1

}

使用特权

评论回复
11
gaoke231|  楼主 | 2018-8-31 22:21 | 只看该作者
void USART1_IRQHandler(void)                        //串口1中断服务程序
        {
        u8 Res;
#if SYSTEM_SUPPORT_OS                 //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
        OSIntEnter();   
#endif
        if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
                {
                Res =USART_ReceiveData(USART1);        //读取接收到的数据
               
                if((USART_RX_STA&0x8000)==0)//接收未完成
                        {
                        if(USART_RX_STA&0x4000)//接收到了0x0d
                                {
                                if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
                                else USART_RX_STA|=0x8000;        //接收完成了
                                }
                        else //还没收到0X0D
                                {       
                                if(Res==0x0d)USART_RX_STA|=0x4000;
                                else
                                        {
                                        USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
                                        USART_RX_STA++;
                                        if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收          
                                        }                 
                                }
                        }                    
     }
#if SYSTEM_SUPPORT_OS         //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
        OSIntExit();                                                                                           
#endif
}
#endif       

使用特权

评论回复
12
gaoke231|  楼主 | 2018-8-31 22:22 | 只看该作者
int main(void)
{
        u8 t;       
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
        delay_init();                     //延时函数初始化          
        uart_init(9600);                 //串口初始化为9600
        LED_Init();                                //初始化与LED连接的硬件接口                        
  printf("RTC 测试实验\r\n");
        while(RTC_Init())                //RTC初始化        ,一定要初始化成功
        {
    printf("RTC 初始化失败 \r\n");               
                delay_ms(800);
        }                                                                   
        //显示时间                             
        while(1)
        {                                                                    
                if(t!=calendar.sec)
                {
                        t=calendar.sec;
                        printf("%d 年 %d 月 %d 日 \r\n",calendar.w_year,calendar.w_month,calendar.w_date);       
      printf("%d 时 %d 分 %d 秒 \r\n",calendar.hour,calendar.min,calendar.sec);       
                        LED0=!LED0;
                }       
                delay_ms(10);                                                                  
        };                                                                                              
}       

使用特权

评论回复
13
gaoke231|  楼主 | 2018-8-31 22:24 | 只看该作者
RTC实时时钟简介:
STM32的RTC外设,实质是一个掉电后还继续运行的定时器,从定时器的角度来看,相对于通用定时器TIM外设,它的功能十分简单,只有计时功能(也可以触发中断).但是从掉电还能继续运行来看,它是STM32中唯一一个具有这个功能功能的外设.(RTC外设的复杂之处不在于它的定时,而在于它掉电还可以继续运行的特性)
所谓掉电,是指电源Vpp断开的情况下,为了RTC外设掉电可以继续运行,必须给STM32芯片通过VBAT引脚街上锂电池.当主电源VDD有效时,由VDD给RTC外设供电.当VDD掉电后,由VBAT给RTC外设供电.无论由什么电源供电,RTC中的数据始终都保存在属于RTC的备份域中,如果主电源和VBA都掉电,那么备份域中保存的所有数据都将丢失.(备份域除了RTC模块的寄存器,还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数序,系统复位或电源复位时,这些数据也不会被复位).

使用特权

评论回复
14
gaoke231|  楼主 | 2018-8-31 22:25 | 只看该作者
从RTC的定时器特性来说,它是一个32位的计数器,只能向上计数.他使用的时钟源有三种,分别为:
1,高速外部时钟的128分频:HSE/128;
2,低速内部时钟LSI;
3,低速外部时钟LSE;
使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC正常工作.所以RTC一般都时钟低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,因为32768 = 2^15,分频容易实现,所以被广泛应用到RTC模块.(在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式).

使用特权

评论回复
15
gaoke231|  楼主 | 2018-8-31 22:26 | 只看该作者
RTC工作过程:

使用特权

评论回复
16
gaoke231|  楼主 | 2018-8-31 22:27 | 只看该作者
RTC架构:
图中浅灰色的部分都是属于备份域的,在VDD掉电时可在VBAT的驱动下继续运行.这部分仅包括RTC的分频器,计数器,和闹钟控制器.若VDD电源有效,RTC可以触发RTC_Second(秒中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断).从结构图可以看到到,其中的定时器溢出事件无法被配置为中断.如果STM32原本处于待机状态,可由闹钟事件或WKUP事件(外部唤醒事件,属于EXTI模块,不属于RTC)使它退出待机模式.闹钟事件是在计数器RTC_CNT的值等于闹钟寄存器RTC_ALR的值时触发的.
因为RTC的寄存器是属于备份域,所以它的所有寄存器都是16位的.它的计数RTC_CNT的32位由RTC_CNTL和RTC_CNTH两个寄存器组成,分别保存计数值的低16位和高16位.在配置RTC模块的时钟时,把输入的32768Hz的RTCCLK进行32768分频得到实际驱动计数器的时钟TR_CLK = RTCCLK/37768 = 1Hz,计时周期为1秒,计时器在TR_CLK的驱动下计数,即每秒计数器RTC_CNT的值加1(常用)

使用特权

评论回复
17
gaoke231|  楼主 | 2018-8-31 22:27 | 只看该作者
由于备份域的存在,使得RTC核具有了完全独立于APB1接口的特性,也因此对RTC寄存器的访问要遵守一定的规则.
系统复位后,禁止访问后备寄存器和RCT,防止对后卫区域(BKP)的意外写操作.(执行以下操作使能对后备寄存器好RTC的访问):
1,设置RCC_APB1ENR寄存器的PWREN和BKPEN位来使能电源和后备接口时钟.
2,设置PWR_CR寄存器的DBP位使能对后备寄存器和RTC的访问.
设置为可访问后,在第一次通过APB1接口访问RTC时,必须等待APB1与RTC外设同步,确保被读取出来的RTC寄存器值是正确的,如果在同步之后,一直没有关闭APB1的RTC外设接口,就不需要再次同步了.
如果内核要对RTC寄存器进行任何的写操作,在内核发出写指令后,RTC模块在3个RTCCLK时钟之后,才开始正式的写RTC寄存器操作.我们知道RTCCLK的频率比内核主频低得多,所以必须要检查RTC关闭操作标志位RTOFF当这个标志被置1时,写操作才正式完成.
(以上操作在STM32库里面都有库函数,不需要具体的查阅寄存器~~~~)

使用特权

评论回复
18
gaoke231|  楼主 | 2018-8-31 22:28 | 只看该作者
UNIX时间戳:
假如从现在起,把计数器RTC_CNT的计数值置0,然后每秒加1,RTC_CNT什么时候会溢出? RTC_CNT是一个32位寄存器,可存储的最大值为(2^32-1),这样的话就是在2^32秒之后溢出,大概换算为:
Time = 2^32/365/24/60/60大约等于136年
假如某个时刻读取到计数器的数值为X = 60*60*24*2(2天),又知道计数器是在2016年1月1日的0时0分0秒置0的,那么根据计数器的这个相对时间数值,可以计算得到这个时刻是2016年1月3日的0时0分0秒了,而计数器会在(2016+136)年左右溢出.(如果我们穿越回到2016年1月1日,如果还在使用这个计数器提供事件的话就会出问题啦.).
定时器被置0的这个事件被称为计时元年,相对计时元年经过的秒数称为时间戳.

使用特权

评论回复
19
gaoke231|  楼主 | 2018-8-31 22:30 | 只看该作者
PS:
大多数操作系统都是利用时间戳和计时元年来计算当前时间的,而这个时间戳和计时元年大家都取了同一个标准——UNIX时间戳和UNIX计时元年.UNIX 计时元年被设置为格林威治时间1970年1月1日0时0分0秒,大概是为了纪念UNIX的诞生吧.而UNIX时间戳即为当前时间相对于UNIX计时元年经过的秒数.在这个计时系统中,使用的是有符号的32位整型变量来保存UNIX时间戳的,即实际可用计数位数比我们上面例子中的少了一位,少了这一位,UNIX 计时元年也相对提前了,这个计时方法在2038年1月19日03时14分07秒将会发生溢出.这个时间离我们并不远,UNIX时间戳被广泛应用到各种系统中,溢出可能会导致系统发生严重错误,差不多到这个时候,记得注意这个问题呀.

使用特权

评论回复
20
gaoke231|  楼主 | 2018-8-31 22:30 | 只看该作者
实例分析:
利用RTC提供北京时间:
RTC外设这个连续计数的计数器,在相应软件配置下,可提供时钟日历的功能,修改计数器的值则可以重新设置系统当前的时间和日期.而 由于它的时钟配置系统(RCC_BDCR 寄存器)是在备份域,在系统复位或从待机模式唤醒后RTC的设置和时间维持不变,利用它,可以实现实时时钟的功能.

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

54

主题

1310

帖子

5

粉丝