基于STM32F103的RTC功能实现

您所在的位置:网站首页 stm32f103rbt6最小系统板 基于STM32F103的RTC功能实现

基于STM32F103的RTC功能实现

2023-08-07 05:31| 来源: 网络整理| 查看: 265

前言: 最近心血来潮,想用stm32f103c8t6这块小板子实现定时的功能,但是发现网上没有太多的资料,所以自己弄了一个。 有几点需要大家注意的是: 由于这个核心板没有外部晶振,所以在RTC初始化时用的是LSI(内部低速晶振),频率约为40KHZ。 由于没有纽扣电池,故断电后无法继续计时。

实现功能:

串口显示日期和时间串口设置日期和时间串口设置闹钟

所需元器件:

stm32f103c8t6核心板USB转串口模块

实物展示: 在这里插入图片描述

结果展示: 在这里插入图片描述

代码实现: main,c文件

#include "sys.h" int main(void) { delay_init(); //延时函数初始化 LED_GPIO_Config(); //LED引脚配置 My_USART1(); //串口初始化 printf("串口初始化完成\r\n"); RTC_Init(); //RTC初始化 //RTC_Alarm_Set(2021,12,30,17,35,20); //闹钟设置 while(1) { GPIO_ResetBits(GPIOC, GPIO_Pin_13); delay_ms(500); GPIO_SetBits( GPIOC, GPIO_Pin_13); delay_ms(500); } }

RTC.C文件

#include "rtc.h" #include "sys.h" #include "string.h" #include "led.h" #include const char *pt = __TIME__; //20:15:05 const char *pd = __DATE__; //Dec 30 2021 u8 month[13][5] = {"NUL","Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; struct SET_ALARM alarm; _calendar calendar;//时钟结构体 static void RTC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 } /******************************************************************************* * 函 数 名 : RTC_Init_LSI * 函数功能 : RTC初始化 * 输 入 : 无 * 输 出 : 0,初始化成功 1,LSI开启失败 解决复位之后RTC_WaitForSynchro();卡死问题:此句在if外面开启时钟,RCC_LSICmd(ENABLE); 内部晶振低速时钟40KHZ 注意:使用内部低速时钟断电后无法继续走时,即使有备用电池也不行 LSI需由主电源VDD供电,而VBAT只能使LSE起振。 *******************************************************************************/ u8 RTC_Init_LSI(void) { //检查是不是第一次配置时钟 u8 temp=0; RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问 RCC_LSICmd(ENABLE); //设置内部低速晶振(LSI) if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎 { BKP_DeInit(); //复位备份区域 //RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振 while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET&& temp RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成 RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒中断、闹钟中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 } RTC_NVIC_Config();//RCT中断分组设置 RTC_Get();//更新时间 return 0; //ok } //RTC时钟中断 //每秒触发一次 void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断 { RTC_Get();//更新时间 printf("RTC Time:%d-%d-%d %d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间 } if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断 { RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断 RTC_Get(); //更新时间 printf("Alarm Time:%d-%d-%d %d:%d:%d\r\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(); } //判断是否是闰年函数 //月份 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; } //月份数据表 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}; /******************************************************************************* * 函 数 名 : RTC_Set * 函数功能 : RTC设置日期时间函数(以1970年1月1日为基准,把输入的时钟转换为秒钟) 1970~2099年为合法年份 * 输 入 : syear:年 smon:月 sday:日 hour:时 min:分 sec:秒 * 输 出 : 0,成功 1,失败 *******************************************************************************/ u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) { u16 t; u32 seccount=0; if(syear2099)return 1; for(t=1970;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; } //初始化闹钟 //以1970年1月1日为基准 //1970~2099年为合法年份 //syear,smon,sday,hour,min,sec:闹钟的年月日时分秒 //返回值:0,成功;其他:错误代码. u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) { u16 t; u32 seccount=0; if(syear2099)return 1; for(t=1970;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_SetAlarm(seccount); RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 return 0; } //得到当前的时间 //返回值: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; } //获得现在是星期几 //功能描述:输入公历日期得到星期(只允许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 calendar.w_date = pd[5]-'0';//得到日 } else //Dec 30 2021 { calendar.w_date= (pd[4]-'0')*10 + (pd[5]-'0');//得到日 } calendar.w_year = (pd[9]-'0')*10 + (pd[10]-'0');//得到年 //printf("年:%d\r\n",calendar.w_year ); u8 i; for(i = 1; i break;//找到月份了 } } calendar.w_month = i;//得到月 calendar.hour = (pt[0]-'0')*10 + (pt[1]-'0');//得到小时 calendar.min = (pt[3]-'0')*10 + (pt[4]-'0');//得到分钟 calendar.sec = (pt[6]-'0')*10 + (pt[7]-'0');//得到秒 }

my_usart1.c文件

#include "sys.h" #include "my_usart1.h" #include #include void My_USART1(void) { //定义结构体变量,注意:只能放在{后面 GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART1_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; /*1串口时钟、GPIO时钟初始化*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); /*2GPIOA端口模式设置*/ //配置引脚TX GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用输出 GPIO_InitStruct.GPIO_Pin = USART1_GPIO_PIN_TX; //PA9 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); //配置引脚RX GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入 GPIO_InitStruct.GPIO_Pin = USART1_GPIO_PIN_RX; //PA10 //GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); /*3串口参数初始化*/ USART1_InitStruct.USART_BaudRate = 9600; //波特率 USART1_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //流控 USART1_InitStruct.USART_Mode = USART_Mode_Tx |USART_Mode_Rx ; //串口模式 USART1_InitStruct.USART_Parity = USART_Parity_No; //校验位 USART1_InitStruct.USART_StopBits = USART_StopBits_1 ; //停止位 USART1_InitStruct.USART_WordLength = USART_WordLength_8b; //数据位 USART_Init(USART1,&USART1_InitStruct); /*4开启中断并且初始化NVIC*/ USART_ITConfig( USART1, USART_IT_RXNE, ENABLE);//开启接收中断 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //选择中断源 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; //响应优先级 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //中断使能 NVIC_Init(&NVIC_InitStruct); /*5使能串口*/ USART_Cmd(USART1,ENABLE); /*6编写中断处理函数*/ } /*实现发送字符串的功能描述: 依次发送字符串中的字符,每发送一个检查下TXE标志位,修改库函数中的USART_SendData()函数即可 发送完全部字符以后,最后检查TC标志位 ,*/ void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data) { /* Check the parameters */ assert_param(IS_USART_ALL_PERIPH(USARTx)); assert_param(IS_USART_DATA(Data)); /* Transmit Data */ USARTx->DR = (Data & (uint16_t)0x01FF); while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET); } void USART_SendString(USART_TypeDef* USARTx, char *str) { while(*str!='\0') { USART_SendByte(USARTx,*str++); } while( USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET); } uint8_t USART_ReceiveByte(USART_TypeDef* USARTx) { while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE)==RESET); return (uint8_t) USART_ReceiveData(USART1); } static volatile uint8_t g_usart1_buf[128]={0}; static volatile uint32_t g_usart1_cnt=0; void USART1_IRQHandler(void) //串口1中断服务程序 { uint8_t d=0; int i = 0; u16 alarm_buf[64]={0}; u16 rtc_buf[64]={0}; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { //接收串口数据 d = USART_ReceiveData(USART1); g_usart1_buf[g_usart1_cnt] = d; g_usart1_cnt++; //设置闹钟 if(d == 'A'|| g_usart1_cnt>= sizeof(g_usart1_buf)) { char *s = strtok((char *)g_usart1_buf,"- :"); //分割符是- : while(s!=NULL) { alarm_buf[i] = atoi(s); //2022-1-10 23:50:5A i++; s = strtok(NULL,"- :"); g_usart1_cnt = 0; } RTC_Alarm_Set(alarm_buf[0],alarm_buf[1],alarm_buf[2],alarm_buf[3], alarm_buf[4],alarm_buf[5]); //闹钟设置 printf("%d-%d-%d %d:%d:%d 设置闹钟成功!\r\n",alarm_buf[0],alarm_buf[1],alarm_buf[2],alarm_buf[3],alarm_buf[4],alarm_buf[5]); } //设置时间 else if(d == 'R'|| g_usart1_cnt>= sizeof(g_usart1_buf)) { char *s = strtok((char *)g_usart1_buf,"- :"); //分割符是- : while(s!=NULL) { rtc_buf[i] = atoi(s); //2022-1-10 23:50:5R i++; s = strtok(NULL,"- :"); g_usart1_cnt = 0; } RTC_Set(rtc_buf[0],rtc_buf[1],rtc_buf[2],rtc_buf[3], rtc_buf[4],rtc_buf[5]); //设置时间 printf("%d-%d-%d %d:%d:%d 设置时间成功!\r\n",rtc_buf[0],rtc_buf[1],rtc_buf[2],rtc_buf[3],rtc_buf[4],rtc_buf[5]); } //清空串口接收中断标志位 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } /*不勾选微库则需要这个*/ #pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; void _sys_exit(int x) { x = x; } ///重定向c库函数printf到串口,重定向后可使用printf函数 int fputc(int ch, FILE *f) { /* 发送一个字节数据到串口 */ USART_SendData(USART1, (uint8_t) ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return (ch); } ///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 int fgetc(FILE *f) { /* 等待串口输入数据 */ while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); return (int)USART_ReceiveData(USART1); }

追加内容: 如果有3V纽扣电池和32.768KHz的石英晶振,就可以实现断电后RTC继续走时的功能。 根据数据手册可知,外部低速时钟电路可以这样搭建: 其中 CL1 和 CL2 为 5pF~15pF 之间的瓷介电容器,OSC32_IN为引脚PC14,OSC32_OUT为引脚PC15。 使用32.768kH晶体的典型应用 纽扣电池电路可以这样搭建: 当接电池和有v3.3电源时,就会选择v3.3供电。当接电池和没有v3.3电源时,就会选择电池供电,即3v3掉电后RTC也能照常工作,备用的纽扣电池。当不接电池和有v3.3电源时也会选择v3.3供电。 在这里插入图片描述 详细内容请看STM32 VBAT外围电路接法详解

代码实现如下: 除RTC的初始化不一样外,其他都一样。

/******************************************************************************* * 函 数 名 : RTC_Init_LSE * 函数功能 : RTC初始化 * 输 入 : 无 * 输 出 : 0,初始化成功 1,LSE开启失败 外部低速时钟 注意:使用外部低速时钟断电后如果有备用电池可以继续走时 *******************************************************************************/ u8 RTC_Init_LSE(void) { //检查是不是第一次配置时钟 u8 temp=0; RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问 //RCC_LSICmd(ENABLE); //设置内部低速晶振(LSI) if (BKP_ReadBackupRegister(BKP_DR1) != 0x6060) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎 { BKP_DeInit(); //复位备份区域 RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&& temp < 250) //检查指定的RCC标志位设置与否,等待低速晶振就绪 { temp++; delay_ms(10); } if(temp>=250)return 1;//初始化时钟失败,晶振有问题 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟 //RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); //设置RTC时钟(RTCCLK),选择LSI作为RTC时钟 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_WaitForSynchro(); //等待RTC寄存器同步 RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒中断、闹钟中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_EnterConfigMode();/// 允许配置 RTC_SetPrescaler(32767); //设置RTC预分频的值 //RTC_SetPrescaler(40000); //设置RTC预分频的值 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 //RTC_Set(2017,3,6,0,0,0); //设置时间 get_time(); RTC_Set(calendar.w_year+2000-1 ,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec); //设置时间 RTC_ExitConfigMode(); //退出配置模式 BKP_WriteBackupRegister(BKP_DR1, 0X6060); //向指定的后备寄存器中写入用户程序数据 } else//系统继续计时 { RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成 RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒中断、闹钟中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 } RTC_NVIC_Config();//RCT中断分组设置 RTC_Get();//更新时间 return 0; //ok } 最后:

需要代码的可以自行下载。代码下载链接 下载操作: 在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3