基于STM32F103的RTC功能实现

您所在的位置:网站首页 stm32f103zet6晶振电路 基于STM32F103的RTC功能实现

基于STM32F103的RTC功能实现

2024-03-29 10:14| 来源: 网络整理| 查看: 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 =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, 0X5050); //向指定的后备寄存器中写入用户程序数据 } 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 } //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=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= 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