基于STM32F103的RTC功能实现

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

实现功能:

  1. 串口显示日期和时间
  2. 串口设置日期和时间
  3. 串口设置闹钟

所需元器件:

  1. stm32f103c8t6核心板
  2. 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 <stdlib.h>
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) //检查指定的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, 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(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寄存器的写操作完成  	
	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(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_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<3)temp2--;
	return(temp2%7);
}			  

void get_time()
{
		 //Dec  3 2021
		if( pd[4] == ' ' )
		{
			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 <= 12; i++)
		{
			if(strcmp((const char *)pd, (char *)month[i]) == 0)
			{
				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 <string.h>
#include <stdlib.h>

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
}

最后:

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

  • 19
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
### 回答1: 基于STM32F103RBT6的RTC功能实现需要以下步骤: 1. 初始化RTC时钟:使用RCC寄存器配置RTC外设的时钟源,可以选择LSE(低速外部晶体)或LSI(低速内部振荡器)。 2. 配置RTC的预分频器:根据需求设置RTCCK(RTC时钟)的频率分频比,可以通过RTC_PRLH和RTC_PRLL寄存器设置。 3. 配置RTC计数器:使用RTC_CRL和RTC_CRH寄存器配置RTC计数器值,可以设置小时、分钟和秒钟的初始值。 4. 配置RTC的闹钟:如果需要使用RTC的闹钟功能,可以通过RTC_ALRH和RTC_ALRL寄存器设置闹钟的小时、分钟和秒钟。 5. 配置RTC的中断:可以通过RTC_CRL和RTC_CRH寄存器配置RTC的中断,如秒钟中断、闹钟中断等。 6. 启动RTC:配置完RTC后,通过设置RTC_CRL寄存器的CNF位为1进入配置模式,然后设置RTC_CRL寄存器的RSF位为0,并设置RTC_CRL寄存器的RTOFF位为0,即可启动RTC。 7. 读取RTC的日期和时间:可以通过RTC_CNTH和RTC_CNTL寄存器读取当前的日期和时间值。 8. 处理RTC的中断:根据配置的中断类型,可以在中断服务函数中编写相应的处理逻辑。 需要注意的是,上述步骤只是基于STM32F103RBT6的RTC功能的基本实现方式,具体的配置和使用细节可能会根据具体的应用需求有所差异。可以参考STM32F103RBT6的参考手册或官方文档,以获取更详细的信息和实例代码。 ### 回答2: STM32F103RBT6是一款常用的STM32系列单片机,它内置了实时时钟(RTC功能,可以实现实时时间的计时和存储。 实现基于STM32F103RBT6的RTC功能,需要按照以下步骤进行: 1. 初始化RTC模块:首先需要设置RTC时钟源,并开启RTC外设时钟。可以选择使用外部低速晶振(如32.768kHz的晶振)或者使用内部低速RC振荡器作为RTC的时钟源。 2. 配置RTC时钟预分频:通过设置预分频寄存器(PRLH和PRLL)来设置RTC时钟的预分频系数,以得到正确的时间精度和计数周期。 3. 设置RTC初始时间:可以通过设置RTC寄存器(如TR、DR和CRH)来设置RTC的初始时间,包括秒、分、时、日期、星期等。可以通过外部设备(如按键或串口)输入初始时间,也可以使用默认时间。 4. 配置RTC闹钟:RTC可以配置闹钟功能,通过设置RTC寄存器(如ALRH和ALRL)来设置闹钟的时间。当RTC的实时时间与闹钟时间相同时,可以触发中断或其他相关操作。 5. 开启RTC中断:可以选择开启RTC的中断功能,通过设置RTC中断使能寄存器(IER)来开启RTC中断,如秒中断、闹钟中断等。可以根据需要选择开启不同类型的中断。 6. 读取和存储实时时间:通过读取RTC寄存器(如CNT、TR、DR等)来获取RTC的实时时间。可以使用相关方法将实时时间存储到EEPROM或其他存储设备中,以保证下次上电时能够恢复到正确的时间。 7. 相关操作和功能:除了基本的时间计时和存储功能外,STM32F103RBT6的RTC还支持其他一些功能,如备份寄存器、时钟校准、温度测量等。可以根据具体需求选择相应的功能进行配置和使用。 通过以上步骤,可以在STM32F103RBT6上成功实现RTC功能,可以用于各种需要实时时间计时和存储的应用,如时钟、日历、定时器、报警器等。 ### 回答3: 基于STM32F103RBT6的RTC功能实现是通过使用芯片自带的RTC单元实现的。以下是实现RTC功能的步骤: 1. 配置RTC时钟源:首先,需要选择合适的RTC时钟源,可以使用外部低频晶振或外部低频振荡器。 2. 配置RTC预分频器和计数器:RTC单元有一个预分频器和一个计数器,通过配置预分频器和计数器的值可以实现所需的时钟精度。 3. 配置RTC时钟和唤醒:RTC可以通过设置时钟和唤醒功能来唤醒系统,以便在低功耗模式下实现时间跟踪。 4. 配置RTC闹钟:RTC还可以配置闹钟功能,使得在指定时间触发中断或者唤醒。 5. 配置RTC中断:在需要使用RTC功能的地方,可以配置RTC中断以便获取相关的事件和状态。 6. 设置RTC时间和日期:使用RTC的预分频器和计数器,可以设置RTC的时间和日期。 7. 读取RTC时间和日期:同样使用RTC的预分频器和计数器,可以读取RTC的时间和日期。 以上是基于STM32F103RBT6的RTC功能实现的主要步骤。当配置完这些参数后,就可以在需要的地方使用RTC功能RTC功能可以用于实现实时时钟,定时器,闹钟等功能,非常实用。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

⁽⁽ଘ晴空万里ଓ⁾⁾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值