cortex_m3_stm32嵌入式学习笔记(十四):RTC实时时钟(秒中断)

STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(BKP)来标记是否配置过时钟

简单介绍BKP:备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里, 当 VDD 电源被切断,他们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。此外,
BKP 控制寄存器用来管理侵入检测和 RTC 校准功能。

简单说一下我对时钟工作原理的理解:一个32位的计数器,从0向上计数的话,假设每加一就是1秒,那么一个32位的计数器跑到溢出需要100多年。。已经很长了,这里时钟自带一个秒中断,当每加一的时候就会触发一次秒中断,我们通过往秒中断里写更新时间的函数来达到时间同步的效果

由于rtc.c里函数很多。。我就贴一下说几个比较重要的吧。。

#include "rtc.h"
_calendar_obj calendar;//时钟结构体
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
const u8  table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
u8 Is_LeapYear(u16 year)
{
	return (year%4==0&&year%100!=0)||year%400==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);
}
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寄存器
}
//得到当前的时间,结果保存在calendar结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=RTC_GetCounter();
	u32 daynum=timecount/86400;
	u16 tem=0;
	if(daycnt!=daynum)//大于1天
	{
			daycnt=daynum;
			tem=1970;
			while(daynum>=365)
			{
				if(Is_LeapYear(tem))
				{
					if(daynum>=366)daynum-=366;
					else break;
				}
				else daynum-=365;
				tem++;
			 }
			calendar.w_year=tem;//年
			tem=0;
			while(daynum>=28)
			{
				if(Is_LeapYear(calendar.w_year)&&tem==1)
				{
					if(daynum>=29)daynum-=29;
					else break;
				}
				else
				{
					if(daynum>=mon_table[tem])daynum-=mon_table[tem];
					else break;
				}
				tem++;
			}
			calendar.w_month=tem+1;//月
			calendar.w_date=daynum+1;//日
	}
	  daynum=timecount%86400;
		calendar.hour=daynum/3600;//时
		calendar.min=(daynum%3600)/60;//分
		calendar.sec=(daynum%3600)%60;//秒
		calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);
    return 0;
}

//设置时钟
//把输入的时钟转换为秒钟
//以 1970 年 1 月 1 日为基准
//1970~2099 年为合法年份
//返回值:0,成功;其他:错误代码.
//平年的月份日期表
//year,mon,day,hour,min,sec:年月日时分秒
//返回值:设置结果。 0,成功; 1,失败。
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
	u16 i;u32 seccnt=0;
	if(year<1970||year>2099)return 1;
	for(i=1970;i<year;i++)
	{
		if(Is_LeapYear(i))seccnt+=31622400;
		else seccnt+=31536000;
	}
	mon-=1;
	for(i=0;i<mon;i++)
	{
		seccnt+=(u32)mon_table[i]*86400;
		if(Is_LeapYear(year)&&i==1)
			seccnt+=86400;
	}
	seccnt+=(u32)(day-1)*86400;
	seccnt+=(u32)hour*3600;
	seccnt+=(u32)min*60;
	seccnt+=(u32)sec;
	//使能 PWR 和 BKP 外设时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);
  PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
  RTC_SetCounter(seccnt);  //设置 RTC 计数器的值
  RTC_WaitForLastTask();  //等待最近一次对 RTC 寄存器的写操作完成
  return 0;
}
//初始化RTC
u8 RTC_Init(void)
{
	u8 tem=0;
	if(BKP_ReadBackupRegister(BKP_DR1)!=0x5050)//如果没配置过
	{
		//使能 PWR 和 BKP外设时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);
		PWR_BackupAccessCmd(ENABLE);//使能后备寄存器访问
		BKP_DeInit();//③复位备份区域
		RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振(LSE)
		//检查指定的RCC标志位设置与否,等待低速晶振就绪
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)
		{
			tem++;
			delay_ms(10);
		}
		if(tem>=250)return 1;//初始化时钟失败,晶振有问题
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//设置RTC时钟
		RCC_RTCCLKCmd(ENABLE);//使能 RTC 时钟
		RTC_WaitForLastTask();//等待最近一次对 RTC 寄存器的写操作完成
		RTC_WaitForSynchro();//等待 RTC 寄存器同步
		RTC_ITConfig(RTC_IT_SEC,ENABLE);//使能 RTC 秒中断
		RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_SetPrescaler(32767);  //设置 RTC 预分频的值
    RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
    RTC_Set(2009,12,2,10,0,55); //设置时间
    RTC_ExitConfigMode();  //退出配置模式
		//向指定的后备寄存器中写入用户程序数据 0x5050
    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
}
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();
}

我们是以1970年为基准,往后计时的,即当1970年一月一日00点00分00秒时,计数器从0开始计时。。

说一个比较粗心的地方(导致调试了一晚上都没调好),在写RTC_Get()函数(更新时间)的时候不小心将一个16位的变量当成32的用了。。结果是一晚上都忙的热火朝天还没找到是哪错了,早晨来到一直到中午才弄好。。sad

rtc.h

#ifndef _RTC_H
#define _RTC_H
#include "sys.h"
#include "delay.h"
#include "usart.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;//日历结构体
//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_LeapYear(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);//设置时间
#endif

主函数

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "usmart.h"
#include "rtc.h"

void init(void)
{
	NVIC_Configuration();
	delay_init();
	uart_init(9600);
	LED_Init();
	LCD_Init();
	usmart_dev.init(72);//初始化SMART组件
}
int main(void)
{
	u8 t;
	init();
  POINT_COLOR=RED;
	while(RTC_Init())		//RTC初始化	,一定要初始化成功
	{
		LCD_ShowString(60,130,200,16,16,"RTC ERROR!   ");
		delay_ms(800);
		LCD_ShowString(60,130,200,16,16,"RTC Trying...");
	}
	//显示时间
	POINT_COLOR=BLUE;//设置字体为蓝色
	LCD_ShowString(60,130,200,16,16,"    -  -     ");
	LCD_ShowString(60,162,200,16,16,"  :  :  ");
	while(1)
	{
		if(t!=calendar.sec)
		{
			t=calendar.sec;
			LCD_ShowNum(60,130,calendar.w_year,4,16);
			LCD_ShowNum(100,130,calendar.w_month,1,16);
			LCD_ShowNum(124,130,calendar.w_date,2,16);
			switch(calendar.week)
			{
				case 0:
					LCD_ShowString(60,148,200,16,16,"Sunday   ");
					break;
				case 1:
					LCD_ShowString(60,148,200,16,16,"Monday   ");
					break;
				case 2:
					LCD_ShowString(60,148,200,16,16,"Tuesday  ");
					break;
				case 3:
					LCD_ShowString(60,148,200,16,16,"Wednesday");
					break;
				case 4:
					LCD_ShowString(60,148,200,16,16,"Thursday ");
					break;
				case 5:
					LCD_ShowString(60,148,200,16,16,"Friday   ");
					break;
				case 6:
					LCD_ShowString(60,148,200,16,16,"Saturday ");
					break;
			}
			LCD_ShowNum(60,162,calendar.hour,2,16);
			LCD_ShowNum(84,162,calendar.min,2,16);
			LCD_ShowNum(108,162,calendar.sec,2,16);
			LED0=!LED0;
		}
		delay_ms(10);
	}
}

通过USMART就可以设置任意时间啦。。

时间: 2024-10-02 04:34:58

cortex_m3_stm32嵌入式学习笔记(十四):RTC实时时钟(秒中断)的相关文章

cortex_m3_stm32嵌入式学习笔记(四):外部中断实验

本章学习将STM32的IO口作为外部中断输入(实现和按键扫描一样的功能) STM32 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32 的强大之处. STM32F103 的中断控制器支持 19 个外部中断/事件请求.每个中断设有状位,每个中断/事件都有独立的触发和屏蔽设置. STM32F103 的19 个外部中断为: 线 0~15:对应外部 IO 口的输入中断.(本章只学习这一种) 线 16:连接到 PVD 输出. 线 17:连接到 RTC 闹钟事件. 线 18:连接到 USB

Swift学习笔记十四:构造(Initialization)

类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值.存储型属性的值不能处于一个未知的状态. 你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值.以下章节将详细介绍这两种方法. 注意: 当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers). 一.基本语法 class Human{ var name :String init(){ name = "human" } init(n

laravel3学习笔记(十四)

原作者博客:ieqi.net ==================================================================================================== 运行时配置 在 Laravel3 中很多地方我们都可以看到“约定大于配置”的影子,我本人也很喜欢这种工程哲学尤其是在框架领域,当然这并不能代替所有的配置.我们知道 Laravel3 中,主要配置都写在 application/config 文件夹下,在应用逻辑中,往往

cortex_m3_stm32嵌入式学习笔记(二十一):SPI实验(通信总线)

SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口.是 Motorola首先在其 MC68HCXX 系列处理器上定义的. SPI 接口主要应用在 EEPROM, FLASH,实时时钟, AD 转换器,还有数字信号处理器和数字信号解码器之间. SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信

cortex_m3_stm32嵌入式学习笔记(二十四):内存管理实验(动态内存)

有用过C语言编程的童鞋对动态管理内存肯定有点了解..好处就不多说了 今天实现STM32的动态内存管理 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术.其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源. 内存管理的实现方法有很多种,他们其实最终都是要实现两个函数:malloc 和 free(好熟悉): malloc 函数用于内存申请, free 函数用于内存释放. 实现方式:分块式内存管理 从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成.内存池被等

cortex_m3_stm32嵌入式学习笔记(十九):DMA实验(高速传输)

DMA,全称为: Direct Memory Access,即直接存储器访问. DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高. 即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU. 一个完整的DMA传输过程必须经过下面的4个步骤. 1.DMA请求 CPU对DMA控制器初始化,并向I/O接

cortex_m3_stm32嵌入式学习笔记(十五):待机唤醒实验(WK_UP外部中断)

很多单片机都有低功耗模式, STM32 也不例外.在系统或电源复位以后,微控制器处于运行状态.运行状态下的 HCLK 为 CPU 提供时钟,内核执行程序代码.当 CPU 不需继续运行时,可以利用多个低功耗模式来节省功耗,例如等待某个外部事件时.用户需要根据最低电源消耗,最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式. STM32 的低功耗模式有 3 种: 1)睡眠模式( CM3 内核停止,外设仍然运行) 2)停止模式(所有时钟都停止) 3)待机模式( 1.8V 内核电源关闭) 在这

cortex_m3_stm32嵌入式学习笔记(十六):ADC实验(模数转换)

之前没学过数模电,对A/D D/A转换一窍不通,也百度了很多资料大都深奥难懂..算了,先自以为是一下吧,等以后学了专业课再说..(寒假回家一定要学..恩 就这么决定了)看了那么多资料,感觉 A/D转换就是将电压(或者是其他模拟量:如 压力,图像等)转换为数字,D/A就是反过来,而ADC就是A/D转换器,他可以采集外部电压转化为数字.本节实验通过ADC采集外部电压转换为数字显示在屏幕上. STM32 拥有 1~3 个 ADC( STM32F101/102 系列只有 1 个 ADC),这些 ADC

cortex_m3_stm32嵌入式学习笔记(十):输入捕捉实验(定时器的输入捕捉)

输入捕获模式可以用来测量脉冲宽度或者测量频率. STM32 的定时器,除了 TIM6 和 TIM7,其他定时器都有输入捕获功能. STM32 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值( TIMx_CNT)存放到对应的通道的捕获/比较寄存器( TIMx_CCRx)里面,完成一次捕获.同时还可以配置捕获时是否触发中断/DMA 等. 本章我们用到 TIM2_CH1 来捕获高电平脉宽,也就是要先设置输入捕获为上升沿