RTT学习之RTC设备
RTC:可以用单片机的片上rtc,也可以用专用的RTC芯片DS1302,PCF8563等,当然没有的也可以软件模拟。
- 片上RTC需要使能片上RTC的时钟(LSI或LSE(外接36.768K晶体)
- 时钟芯片芯片一般都是i2c接口,需要使能。对于时间要求严格,并且没有连接网络无法同步网络时间,则需要选择独立RTC.
- 在RT-THREAD的组件-驱动里面使能RTC组件驱动。
- 软件模拟需要使能软件模拟RTC设备驱动。
RTT兼容C库的时间获取函数——“time”,并重写了time函数,通过查找注册的RTC设备“rtc”来设置和获取时间并通过time.h中的接口调用。
驱动PCF8563可以直接用RTT的设备驱动框架调用组件-驱动-rtc.c进行RTC设备的注册,通过设备控制来获取或设置时间。也可以自己按RTT驱动框架下,自己编写PCF8563驱动《RT-THREAD驱动——RTC PCF8563》:RTC是I2C(底层读写寄存器)和RTC(上层应用)的复合设备。通过I2C驱动框架读写对应寄存器进行读写时间,通过RTC给出读写时间的通用接口。
1 struct pcf8563_device 2 { 3 struct rt_device rtc_parent; 4 struct rt_i2c_bus_device *i2c_device; 5 }; 6 7 rt_uint8_t pcf8563_read_reg(rt_uint8_t reg,rt_uint8_t *data,rt_uint8_t data_size) 8 { 9 struct rt_i2c_msg msg[2]; 10 11 msg[0].addr = PCF8563_ARRD; 12 msg[0].flags = RT_I2C_WR; 13 msg[0].len = 1; 14 msg[0].buf = ® 15 msg[1].addr = PCF8563_ARRD; 16 msg[1].flags = RT_I2C_RD; 17 msg[1].len = data_size; 18 msg[1].buf = data; 19 if(rt_i2c_transfer(pcf8563_dev.i2c_device, msg, 2) != 2) 20 return RT_ERROR; 21 return RT_EOK; 22 } 23 rt_uint8_t pcf8563_write_reg(rt_uint8_t reg, rt_uint8_t *data,rt_uint8_t data_size) 24 { 25 struct rt_i2c_msg msg[2]; 26 27 msg[0].addr = PCF8563_ARRD; 28 msg[0].flags = RT_I2C_WR; 29 msg[0].len = 1; 30 msg[0].buf = ® 31 msg[1].addr = PCF8563_ARRD; 32 msg[1].flags = RT_I2C_WR | RT_I2C_NO_START; 33 msg[1].len = data_size; 34 msg[1].buf = data; 35 if(rt_i2c_transfer(pcf8563_dev.i2c_device, msg, 2) != 2) 36 return RT_ERROR; 37 return RT_EOK; 38 } 39 40 static unsigned char bcd_to_hex(unsigned char data) 41 {//BCD To Byte 42 unsigned char temp; 43 44 temp = ((data>>4)*10 + (data&0x0f)); 45 return temp; 46 } 47 static unsigned char hex_to_bcd(unsigned char data) 48 {//Byte To BCD 49 unsigned char temp; 50 51 temp = (((data/10)<<4) + (data%10)); 52 return temp; 53 } 54 55 static rt_err_t rt_pcf8563_control(rt_device_t dev, rt_uint8_t cmd, void *args) 56 { 57 time_t *time; 58 struct tm time_temp; 59 rt_uint8_t buff[7]; 60 61 RT_ASSERT(dev != RT_NULL); 62 rt_memset(&time_temp, 0, sizeof(struct tm)); 63 switch (cmd) 64 { 65 case RT_DEVICE_CTRL_RTC_GET_TIME: 66 time = (time_t *)args; 67 pcf8563_read_reg(REG_PCF8563_SEC,buff,7); 68 time_temp.tm_year = bcd_to_hex(buff[6]&SHIELD_PCF8563_YEAR) + 2000 - 1900; 69 time_temp.tm_mon = bcd_to_hex(buff[5]&SHIELD_PCF8563_MON) - 1; 70 time_temp.tm_mday = bcd_to_hex(buff[3]&SHIELD_PCF8563_DAY); 71 time_temp.tm_hour = bcd_to_hex(buff[2]&SHIELD_PCF8563_HOUR); 72 time_temp.tm_min = bcd_to_hex(buff[1]&SHIELD_PCF8563_MIN); 73 time_temp.tm_sec = bcd_to_hex(buff[0]&SHIELD_PCF8563_SEC); 74 *time = mktime(&time_temp); 75 break; 76 case RT_DEVICE_CTRL_RTC_SET_TIME: 77 { 78 struct tm *time_new; 79 80 time = (time_t *)args; 81 time_new = localtime(time); 82 buff[6] = hex_to_bcd(time_new->tm_year + 1900 - 2000); 83 buff[5] = hex_to_bcd(time_new->tm_mon + 1); 84 buff[3] = hex_to_bcd(time_new->tm_mday); 85 buff[4] = hex_to_bcd(time_new->tm_wday+1); 86 buff[2] = hex_to_bcd(time_new->tm_hour); 87 buff[1] = hex_to_bcd(time_new->tm_min); 88 buff[0] = hex_to_bcd(time_new->tm_sec); 89 pcf8563_write_reg(REG_PCF8563_SEC,buff,7); 90 } 91 break; 92 default:break; 93 } 94 return RT_EOK; 95 } 96 97 int rt_hw_pcf8563_init(void) 98 { 99 struct rt_i2c_bus_device *i2c_device; 100 uint8_t data; 101 102 i2c_device = rt_i2c_bus_device_find("i2c1"); 103 if (i2c_device == RT_NULL) 104 { 105 #ifdef RT_USE_FINSH_DEBUG 106 rt_kprintf("i2c bus device %s not found!\r\n", "i2c1"); 107 #endif 108 return RT_ERROR; 109 } 110 pcf8563_dev.i2c_device = i2c_device; 111 /* register rtc device */ 112 pcf8563_dev.rtc_parent.type = RT_Device_Class_RTC; 113 pcf8563_dev.rtc_parent.init = RT_NULL; 114 pcf8563_dev.rtc_parent.open = rt_pcf8563_open; 115 pcf8563_dev.rtc_parent.close = RT_NULL; 116 pcf8563_dev.rtc_parent.read = rt_pcf8563_read; 117 pcf8563_dev.rtc_parent.write = RT_NULL; 118 pcf8563_dev.rtc_parent.control = rt_pcf8563_control; 119 pcf8563_dev.rtc_parent.user_data = RT_NULL; /* no private */ 120 rt_device_register(&pcf8563_dev.rtc_parent, "rtc", RT_DEVICE_FLAG_RDWR); 121 122 //init pcf8563 123 data = 0x7f; //close clock out 124 pcf8563_write_reg(REG_PCF8563_CLKOUT,&data,1); 125 126 return 0; 127 } 128 INIT_DEVICE_EXPORT(rt_hw_pcf8563_init);
- 目前系统内只允许存在一个 RTC 设备,且名称为
"rtc",所以不用查找设备
-
启用 Soft RTC (软件模拟 RTC),对无硬件RTC
-
启用 NTP 时间自动同步,需要NTP网络校时功能
- FISH命名:设置:date
年
月
日
时
分
秒;读时间:date
RTC的应用:
#include <time.h> /* struct tm { int tm_sec; /* seconds after the minute, 0 to 60 (0 - 60 allows for the occasional leap second) */ int tm_min; /* minutes after the hour, 0 to 59 */ int tm_hour; /* hours since midnight, 0 to 23 */ int tm_mday; /* day of the month, 1 to 31 */ int tm_mon; /* months since January, 0 to 11 */ int tm_year; /* years since 1900 */ int tm_wday; /* days since Sunday, 0 to 6 */ int tm_yday; /* days since January 1, 0 to 365 */ int tm_isdst; /* Daylight Savings Time flag */ union { /* ABI-required extra fields, in a variety of types */ struct { int __extra_1, __extra_2; }; struct { long __extra_1_long, __extra_2_long; }; struct { char *__extra_1_cptr, *__extra_2_cptr; }; struct { void *__extra_1_vptr, *__extra_2_vptr; }; }; }; */ time_t now; struct tm *timeinfo; now =time(NULL); timeinfo =local_time(&now);
文件应用:生成带日期时间戳的文件如log-date-time.txt;数据库应用:日期-时间 数据;其中重要的UTC与时间的换算,中间还涉及到闰年天数问题,千年虫问题(struct tm结构体中的“tm_year”(是从1900年开始的)和Y2038的bug问题(对于Linux世界来说这个时间的起点是1970年1月1日0时(UTC))。
Y2038的bug问题:解决办法见《单片机的UTC时间时区转换》
View Code
1 typedef struct 2 { 3 uint8_t tm_sec; 4 uint8_t tm_min; 5 uint8_t tm_hour; 6 uint8_t tm_mday; 7 uint8_t tm_mon; 8 uint8_t tm_wday; 9 uint16_t tm_year; 10 //uint16_t tm_yday; 11 }TimeType; 12 13 14 //平年累积月分天数表 15 static const uint16_t NonleapYearMonth[12] = { 16 31,//1 17 31 + 28, //2 18 31 + 28 + 31, //3 19 31 + 28 + 31 + 30, //4 20 31 + 28 + 31 + 30 + 31, //5 21 31 + 28 + 31 + 30 + 31 + 30, //6 22 31 + 28 + 31 + 30 + 31 + 30 + 31, //7 23 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, //8 24 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //9 25 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //10 26 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //11 27 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12 28 }; 29 30 //闰年累积月分天数表 31 static const uint16_t LeapYearMonth[12] = { 32 31,//1 33 31 + 29, //2 34 31 + 29 + 31, //3 35 31 + 29 + 31 + 30, //4 36 31 + 29 + 31 + 30 + 31, //5 37 31 + 29 + 31 + 30 + 31 + 30, //6 38 31 + 29 + 31 + 30 + 31 + 30 + 31, //7 39 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31, //8 40 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //9 41 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //10 42 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //11 43 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12 44 }; 45 46 uint8_t alg_IsLeapYear(uint32_t year) 47 { 48 if((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) //能被4整除,不能被百整除,能被400整除。 49 { 50 return 1; //闰年 51 } 52 else 53 { 54 return 0; //平年 55 } 56 } 57 58 TimeType alg_Utc2LocalTime(uint32_t UtcVal, int8_t TimeZone) 59 { 60 uint32_t i = 0; 61 TimeType LocalTime; 62 uint32_t Hour,Days,Year; 63 64 65 LocalTime.tm_sec = UtcVal%60; //得到秒余数 66 LocalTime.tm_min = (UtcVal/60)%60; //得到整数分钟数 67 68 Hour = (UtcVal/60)/60; //得到整数小时数 69 LocalTime.tm_hour = Hour%24+TimeZone; //得到小时余数+时区 70 if(LocalTime.tm_hour>23) 71 { 72 LocalTime.tm_hour-=24; 73 Days=Hour/24+1; 74 } 75 else 76 { 77 Days=Hour/24; 78 } 79 LocalTime.tm_wday=(Days+4)%7; //计算星期,0-表示星期天 注:1970-1-1 是星期4 80 81 //注:400年=146097天,100年=36524天,4年=1461天 82 Year = 1970; //utc时间从1970开始 83 Year += (Days/146097)*400; 84 85 Days %= 146097; //计算400年内的剩余天数 86 Year += (Days/36525)*100; 87 88 Days %= 36525; 89 Year += (Days/1461)*4; 90 91 Days %= 1461; //计算4年内剩余天数,1970平1972闰年 92 while( Days > 365) 93 { 94 if(alg_IsLeapYear(Year)) 95 { 96 Days--; 97 } 98 Days -= 365; 99 Year++; 100 } 101 if (!alg_IsLeapYear(Year) && (Days == 365) ) 102 { 103 Year++; 104 LocalTime.tm_mday =1; 105 LocalTime.tm_mon =1; 106 LocalTime.tm_year =Year; 107 return LocalTime; 108 } 109 LocalTime.tm_year =Year; 110 LocalTime.tm_mon=0; 111 LocalTime.tm_mday=0; 112 if (alg_IsLeapYear(Year)) //本年是闰年 113 { 114 for (i = 0; i < 12; i++) 115 { 116 if (Days < LeapYearMonth[i]) 117 { 118 LocalTime.tm_mon = i + 1; 119 if (i == 0) 120 { 121 LocalTime.tm_mday = Days; 122 } 123 else 124 { 125 LocalTime.tm_mday = Days - LeapYearMonth[i - 1]; 126 } 127 LocalTime.tm_mday++; 128 return LocalTime; 129 } 130 } 131 } 132 else //本年是平年 133 { 134 for (i = 0; i < 12; i++) 135 { 136 if (Days < NonleapYearMonth[i]) 137 { 138 LocalTime.tm_mon = i + 1; 139 if (i == 0) 140 { 141 LocalTime.tm_mday = Days; 142 } 143 else 144 { 145 LocalTime.tm_mday = Days - NonleapYearMonth[i - 1]; 146 } 147 LocalTime.tm_mday++; 148 return LocalTime; 149 } 150 } 151 } 152 return LocalTime; 153 } 154 155 156 uint32_t alg_LocalTime2Utc(TimeType LocalTime, int8_t TimeZone) 157 { 158 uint32_t y = LocalTime.tm_year -1970; //看一下有几个400年,几个100年,几个4年 159 uint32_t dy = (y / 400); 160 uint32_t days = dy * (400 * 365 + 97); //400年的天数 161 162 dy = (y % 400) / 100; 163 days += dy * (100 * 365 + 25); //100年的天数 164 165 dy = (y % 100) / 4; 166 days += dy * (4 * 365 + 1); //4年的天数 167 168 dy = y % 4; //注意:这里1972是闰年,与1970只差2年 169 days += dy * 365 ; 170 171 if(dy == 3) //这个4年里,有没有闰年就差1天 172 { 173 days++; //只有这个是要手动加天数的,因为1973年计算时前面的天数按365天算,1972少算了一天 174 } 175 176 if (LocalTime.tm_mon != 1) 177 { 178 if(alg_IsLeapYear(LocalTime.tm_year)) //看看今年是闰年还是平年 179 { 180 days += LeapYearMonth[(LocalTime.tm_mon - 1) - 1]; 181 } 182 else 183 { 184 days += NonleapYearMonth[(LocalTime.tm_mon - 1) - 1]; //如果给定的月份数为x则,只有x-1个整数月 185 } 186 } 187 days += LocalTime.tm_mday - 1; 188 189 return (days * 24 * 3600 + ((uint32_t)LocalTime.tm_hour - TimeZone)* 3600 + (uint32_t)LocalTime.tm_min * 60 + (uint32_t)LocalTime.tm_sec); 190 }