{
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->APB1ENR|=1<<28;//使能电源时钟
RCC->APB1ENR|=1<<27;//使能备份时钟
PWR->CR|=1<<8; //取消备份区写保护
//上面三步是必须的!
RTC->CRL|=1<<4; //允许配置
RTC->CNTL=seccount&0xffff;
RTC->CNTH=seccount>>16;
RTC->CRL&=~(1<<4);//配置更新
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
RTC_Get();//设置完之后更新一下数据
return 0;
}
该函数用于设置时间,把我们输入的时间,转换为以1970年1月1日0时0分0秒当做起始时间的秒钟信号,后续的计算都以这个时间为基准的,由于STM32的秒钟计数器可以保存136年的秒钟数据,这样我们可以计时到2106年。
接着,我们介绍一下RTC_Get函数,该函数用于获取时间和日期等数据,其代码如下:
//得到当前的时间,结果保存在calendar结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC->CNTH; //得到计数器中的值(秒钟数)
timecount<<=16;
timecount+=RTC->CNTL;
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;
}
函数其实就是将存储在秒钟寄存器RTC->CNTH和RTC->CNTL中的秒钟数据转换为真正的时间和日期。该代码还用到了一个calendar的结构体,calendar是我们在rtc.h里面将要定义的一个时间结构体,用来存放时钟的年月日时分秒等信息。因为STM32的RTC只有秒钟计数器,而年月日,时分秒这些需要我们自己软件计算。我们把计算好的值保存在calendar里面,方便其他程序调用。
最后,我们介绍一下秒钟中断服务函数,该函数代码如下:
//RTC时钟中断
//每秒触发一次
void RTC_IRQHandler(void)
{
if(RTC->CRL&0x0001) //秒钟中断
{
RTC_Get(); //更新时间
//printf("sec:%d\r\n",calendar.sec);
}
if(RTC->CRL&0x0002) //闹钟中断
{
RTC->CRL&=~(0x0002); //清闹钟中断
//printf("Alarm!\n");
}
RTC->CRL&=0X0FFA; //清除溢出,秒钟中断标志
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
}
此部分代码比较简单,我们通过RTC->CRL的不同位来判断发生的是何种中断,如果是秒钟中断,则执行一次时间的计算,获得最新时间。从而,我们可以在calendar里面读到时间、日期等信息。
rtc.c的其他程序,这里就不再介绍了,请大家直接看光盘的源码。保存rtc.c,然后将rtc.c加入HARDWARE组下,在rtc.h里面输入如下代码:
#ifndef __RTC_H
#define __RTC_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_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);//设置时间
#endif
从上面代码可以看到_calendar_obj结构体所包含的东西,是一个完整的公历信息,包括年、月、日、周、时、分、秒等7个元素。我们以后要知道当前时间,只需要通过RTC_Get函数,执行时钟转换,然后就可以从calendar里面读出当前的公历时间了。
在test.c里面,我们修改代码如下:
int main(void)
{
u8 t;
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init(); //初始化LCD
usmart_dev.init(72); //初始化USMART
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"RTC TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2012/9/7");
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,2,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);
};
}
这部分代码就不再需要详细解释了,在包含了rtc.h之后,通过判断calendar.sec是否改变来决定要不要更新时间显示。同时我们设置LED0每2秒钟闪烁一次,用来提示程序已经开始跑了。
为了方便设置时间,我们在usmart_config.c里面,修改usmart_nametab如下:
struct _m_usmart_nametab usmart_nametab[]=
{
#if USMART_USE_WRFUNS==1 //如果使能了读写操作
(void*)read_addr,"u32 read_addr(u32 addr)",
(void*)write_addr,"void write_addr(u32 addr,u32 val)",
#endif
(void*)delay_ms,"void delay_ms(u16 nms)",
(void*)delay_us,"void delay_us(u32 nus)",
(void*)RTC_Set,"u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)",
};
将RTC_Set加入了usmart,同时去掉了上一章的设置(减少代码量),这样通过串口就可以直接设置RTC时间了。
至此,RTC实时时钟的软件设计就完成了,接下来就让我们来检验一下,我们的程序是否正确了。
20.4 下载验证将程序下载到战舰STM32后,可以看到DS0不停的闪烁,提示程序已经在运行了。同时可以看到TFTLCD模块开始显示时间,实际显示效果如图20.4.1所示:
图20.4.1 RTC实验测试图
如果时间不正确,大家可以用上一章介绍的方法,通过串口调用RTC_Set来设置一下当前时间。