RT-Thread驱动——RTC PCF8563
1 介绍
1.1 RTC选择
RTC选择不外乎就两种,独立外挂和CPU集成,精度要求不高或者联网的情况下使用集成RTC即可,可节约成本。独立RTC的选择则比较多,从低端到高精度的,各大厂商都有可选择,常用的如DS1302、PCF8563、DS3231等。对于时间要求严格,并且没有连接网络无法同步网络时间,则需要选择独立RTC,对于RT-Thread来说,本人针对PCF8563和DS3231都进行测试过,并且在产品上已经使用。所以选择一款经典的RTC芯片,以PCF8563为例,阐述RTT下的RTC驱动实现,举一反三,根据此更换其他RTC芯片则是依葫芦画瓢。
1.2 PCF8563
PCF8563是一款非常经典的实时时钟(RTC)芯片,是飞利浦(PHILIPS)司推出的一款工业级内含I2C 总线接口功能的具有极低功耗的多功能时钟/日历芯片。PCF8563的驱动源码和说明文档也是非常多,特别是可以参考Linux内核的PCF8563源码。
2 RT-Thread驱动
2.1 RTT驱动模型
RTT驱动模型和Linux比较类似,严格分为几层,而且层次分明,层与层之间都有标准的访问接口,最上层封则装成统一的接口,即是open、read、write、close
。
RTT 驱动类型为“struct rt_device”结构体,其原型如下:
/*** Device structure*/
struct rt_device
{struct rt_object parent; /**< inherit from rt_object */enum rt_device_class_type type; /**< device type */rt_uint16_t flag; /**< device flag */rt_uint16_t open_flag; /**< device open flag */rt_uint8_t ref_count; /**< reference count */rt_uint8_t device_id; /**< 0 - 255 *//* device call back */rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);/* common device interface */rt_err_t (*init) (rt_device_t dev);rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);rt_err_t (*close) (rt_device_t dev);rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);rt_err_t (*control)(rt_device_t dev, rt_uint8_t cmd, void *args);void *user_data; /**< device private data */
};
其中的函数指针部分就是需要我们在底层重点实现的,其他参数则为描述驱动类型或者调用驱动的方式设置。
2.2 RTT的RTC框架模型
RTT兼容C库的时间获取函数—“time”
,并重写了time
函数,其实现源码如下。
#if defined (__IAR_SYSTEMS_ICC__) && (__VER__) >= 6020000
#pragma module_name = "?time"
time_t (__time32)(time_t *t) /* Only supports 32-bit timestamp */
#else
time_t time(time_t *t)
#endif
{static rt_device_t device = RT_NULL;time_t time_now = 0;/* optimization: find rtc device only first. */if (device == RT_NULL){device = rt_device_find("rtc");}/* read timestamp from RTC device. */if (device != RT_NULL){if (rt_device_open(device, 0) == RT_EOK){rt_device_control(device, RT_DEVICE_CTRL_RTC_GET_TIME, &time_now);rt_device_close(device);}}/* if t is not NULL, write timestamp to *t */if (t != RT_NULL){*t = time_now;}return time_now;
}
设置日期和时间的函数分别为“set_date”
和“set_time”
,这个几个函数需要我们关注的地方是RTC驱动注册名称为“rtc”
,及“control”
函数。了解这几个函数,主要是熟悉访问RTC驱动的方式,以下面实现PCF8563驱动过程。
2.3 RTT驱动调用过程
3 RTT PCF8563驱动
在使用RTT驱动框架控制一个外设时,原则上最基本的需要实现的函数实体分别有“init”、“open”、“close”、“read”、“write”
以及“control”
函数,这样应用层就可也驱动框架接口(API)访问底层外设。
而通过上文分析RTT下的RTC设备模型可看出,访问RTC设备是循序地按照“open->read/write->close”
的来处理,对于行时间的获取和设置,都是通“control”
函数接口进行。因此我们在这里需要关键实现的即是“control”
函数接口对应的实体函数,该函数的功能包括从PCF8563读出时间和向PCF8563写入设定的时间。而对于“open”、“close”
函数,则暂保留为空,如果后期RTT框架有改动,则增加open
和close
的内容。
3.1 PCF8563设备接口实现——control函数实现
static rt_err_t rt_pcf8563_control(rt_device_t dev, int cmd, void *args)
{rt_err_t ret = RT_EOK;time_t *time;struct tm time_temp; rt_uint8_t buff[7];RT_ASSERT(dev != RT_NULL);rt_memset(&time_temp, 0, sizeof(struct tm));switch (cmd){case RT_DEVICE_CTRL_RTC_GET_TIME:time = (time_t *)args;ret = pcf8563_read_reg(REG_PCF8563_SEC,buff,7);if(ret == RT_EOK){time_temp.tm_year = bcd_to_hex(buff[6]&SHIELD_PCF8563_YEAR) + 2000 - 1900;time_temp.tm_mon = bcd_to_hex(buff[5]&SHIELD_PCF8563_MON) - 1;time_temp.tm_mday = bcd_to_hex(buff[3]&SHIELD_PCF8563_DAY);time_temp.tm_hour = bcd_to_hex(buff[2]&SHIELD_PCF8563_HOUR);time_temp.tm_min = bcd_to_hex(buff[1]&SHIELD_PCF8563_MIN);time_temp.tm_sec = bcd_to_hex(buff[0]&SHIELD_PCF8563_SEC);*time = mktime(&time_temp);}break;case RT_DEVICE_CTRL_RTC_SET_TIME:{struct tm *time_new;time = (time_t *)args;time_new = localtime(time);buff[6] = hex_to_bcd(time_new->tm_year + 1900 - 2000);buff[5] = hex_to_bcd(time_new->tm_mon + 1);buff[3] = hex_to_bcd(time_new->tm_mday);buff[4] = hex_to_bcd(time_new->tm_wday+1);buff[2] = hex_to_bcd(time_new->tm_hour);buff[1] = hex_to_bcd(time_new->tm_min);buff[0] = hex_to_bcd(time_new->tm_sec);ret = pcf8563_write_reg(REG_PCF8563_SEC,buff,7);}break;default:break;}return RT_EOK;
}
“RT_DEVICE_CTRL_RTC_GET_TIME”
和“RT_DEVICE_CTRL_RTC_SET_TIME”
分别是RTT定义的获取时间命令字和设置时间命令字。RTT的RTC模型与Linux系统类似,将具体年月日换算成时间戳。
关于年和月的处理:
struct tm
为标准C库定义的结构体,结构体中的“tm_year”
(年份)是从1900年开始的,“tm_mon”
(月份)范围是0—11,0表示1月;而PCF8563的年份值范围是0—99,月份值范围是1—12,因此要做相关处理。“pcf8563_read_reg”
和“pcf8563_write_reg”
是PCF8563读写寄存器函数,通过此两函数从寄存器获取时间或者设置(写入)时间。从PCF8563中获取的时间值为BCD码,故需编写
“bcd_to_hex”
转换函数将BCD码转换为十进制数;同理在设置时间前,需要将十进制数转换为BCD码,即“hex_to_bcd”
函数,然后再写寄存器。两者函数源码如下。
/* bcd to hex */
static unsigned char bcd_to_hex(unsigned char data)
{unsigned char temp;temp = ((data>>4)*10 + (data&0x0f));return temp;
}/* hex_to_bcd */
static unsigned char hex_to_bcd(unsigned char data)
{unsigned char temp;temp = (((data/10)<<4) + (data%10));return temp;
}
3.2 PCF8563底层驱动
PCF8563底层驱动即是通过i2c总线读写其寄存器,而i2c总线RTT已经统一好框架,通过RTT的标准框架调用i2c总线进行访问PCF8563。这里i2c总线框架与底层分离,i2c总线底层的实现则与具体cpu型号相关。
第一步,定义一个注册设备用的PCF8563结构体
struct pcf8563_device
{struct rt_device rtc_parent;struct rt_i2c_bus_device *i2c_device;
};
rtc_parent
是标准RTT驱动设备框架参数。i2c_device
是i2c总线指针,通过该指针调用i2c总线,使用前必须初始化。
第二步,编写PCF8563读写函数
/* pcf8563 read register */
rt_uint8_t pcf8563_read_reg(rt_uint8_t reg,rt_uint8_t *data,rt_uint8_t data_size)
{struct rt_i2c_msg msg[2];msg[0].addr = PCF8563_ARRD;msg[0].flags = RT_I2C_WR;msg[0].len = 1;msg[0].buf = ®msg[1].addr = PCF8563_ARRD;msg[1].flags = RT_I2C_RD;msg[1].len = data_size;msg[1].buf = data;if(rt_i2c_transfer(pcf8563_dev.i2c_device, msg, 2) != 2)return RT_ERROR; return RT_EOK;
}
/* pcf8563 write register */
rt_err_t pcf8563_write_reg(rt_uint8_t reg, rt_uint8_t *data,rt_uint8_t data_size)
{struct rt_i2c_msg msg[2];msg[0].addr = PCF8563_ARRD;msg[0].flags = RT_I2C_WR;msg[0].len = 1;msg[0].buf = ®msg[1].addr = PCF8563_ARRD;msg[1].flags = RT_I2C_WR | RT_I2C_NO_START;msg[1].len = data_size;msg[1].buf = data;if (rt_i2c_transfer(pcf8563_dev.i2c_device, msg, 2) == 2){return RT_EOK;}else{rt_kprintf("i2c bus write failed!\r\n");return -RT_ERROR;}
}
- 使用到RTT的i2c接口函数为
“rt_i2c_transfer”
,入口参数分别为:i2c设备指针、信息帧、帧数。 “struct rt_i2c_msg”
是RTT定义的i2c信息帧,原型如下。
struct rt_i2c_msg
{rt_uint16_t addr;rt_uint16_t flags;rt_uint16_t len;rt_uint8_t *buf;
};
addr
,i2c器件地址,不包括读写位,如PCF8563的地址为0x51。flags
,标识位,有几种类型,包括读、写、10bit i2c地址选择、应答、非应答。
#define RT_I2C_WR 0x0000
#define RT_I2C_RD (1u << 0)
#define RT_I2C_ADDR_10BIT (1u << 2) /* this is a ten bit chip address */
#define RT_I2C_NO_START (1u << 4)
#define RT_I2C_IGNORE_NACK (1u << 5)
#define RT_I2C_NO_READ_ACK (1u << 6) /* when I2C reading, we do not ACK */
len
,数据长度。buf
,数据缓存指针(发送或者接收)。- 对于读函数,需要两帧信息,第一帧发送读寄存器地址数据,第二帧开始接收返回数据;对于写函数,同样需要两帧,第一帧发送写寄存器地址数据,第二帧发送待写入的数据。
3.3 初始化
初始化包括两部分,一是指定i2c总线,PCF8563读写函数通过调用i2c总线进行访问;而是RTC设备初始化,上层应用从而可通过标准接口访问PCF8563。
/* pcf8563 device int */
int rt_hw_pcf8563_init(void)
{ struct rt_i2c_bus_device *i2c_device;uint8_t data;i2c_device = rt_i2c_bus_device_find("i2c1");if (i2c_device == RT_NULL){#ifdef RT_USE_FINSH_DEBUGrt_kprintf("i2c bus device %s not found!\r\n", "i2c1");#endifreturn 1;} pcf8563_dev.i2c_device = i2c_device;/* register rtc device */pcf8563_dev.rtc_parent.type = RT_Device_Class_RTC;pcf8563_dev.rtc_parent.init = RT_NULL;pcf8563_dev.rtc_parent.open = rt_pcf8563_open;pcf8563_dev.rtc_parent.close = RT_NULL;pcf8563_dev.rtc_parent.read = rt_pcf8563_read;pcf8563_dev.rtc_parent.write = RT_NULL;pcf8563_dev.rtc_parent.control = rt_pcf8563_control;pcf8563_dev.rtc_parent.user_data = RT_NULL; /* no private */rt_device_register(&pcf8563_dev.rtc_parent, "rtc", RT_DEVICE_FLAG_RDWR);/* init pcf8563 */data = 0x7f; /* close clock out */if (pcf8563_write_reg(REG_PCF8563_CLKOUT, &data, 1) != RT_EOK){return -RT_ERROR;}return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_pcf8563_init);
- 初始化最开始定义的
“pcf8563_dev”
,其中“rt_pcf8563_open”、“rt_pcf8563_read”
只是空函数,没有实现内容;然后注册为“rtc”
设备,RTT默认调用RTC的名称为“rtc”
,采用统一命名,更换RTC芯片时保证该命名一致。 - 调用RTT宏
“INIT_DEVICE_EXPORT”
完成初始化。
4 完整源码
#ifndef _DRV_PCF8563_H_
#define _DRV_PCF8563_H_#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>/* slave address */
#define PCF8563_ARRD 0x51/* register */
#define REG_PCF8563_STATE1 0x00
#define REG_PCF8563_STATE2 0x01
#define REG_PCF8563_SEC 0x02
#define REG_PCF8563_MIN 0x03
#define REG_PCF8563_HOUR 0x04
#define REG_PCF8563_DAY 0x05
#define REG_PCF8563_WEEK 0x06
#define REG_PCF8563_MON 0x07
#define REG_PCF8563_YEAR 0x08
#define REG_PCF8563_CLKOUT 0x0d/* offset */
#define SHIELD_PCF8563_STATE1 (unsigned char)0xa8
#define SHIELD_PCF8563_STATE2 (unsigned char)0x1f
#define SHIELD_PCF8563_SEC (unsigned char)0x7f
#define SHIELD_PCF8563_MIN (unsigned char)0x7f
#define SHIELD_PCF8563_HOUR (unsigned char)0x3f
#define SHIELD_PCF8563_DAY (unsigned char)0x3f
#define SHIELD_PCF8563_WEEK (unsigned char)0x07
#define SHIELD_PCF8563_MON (unsigned char)0x1f
#define SHIELD_PCF8563_YEAR (unsigned char)0xffextern int rt_hw_pcf8563_init(void);#endif
/** File : drv_pcf8563.c* This file is part of RT-Thread RTOS* COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License along* with this program; if not, write to the Free Software Foundation, Inc.,* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.** Change Logs:* Date Author Notes* 2018-04-20 Acuity the first version*/#include <board.h>
#include <rtdevice.h>
#include <time.h>
#include "drv_pcf8563.h"#define RT_USING_PCF8563#ifdef RT_USING_PCF8563 struct pcf8563_device
{struct rt_device rtc_parent;struct rt_i2c_bus_device *i2c_device;
};
static struct pcf8563_device pcf8563_dev;/* bcd to hex */
static unsigned char bcd_to_hex(unsigned char data)
{unsigned char temp;temp = ((data>>4)*10 + (data&0x0f));return temp;
}/* hex_to_bcd */
static unsigned char hex_to_bcd(unsigned char data)
{unsigned char temp;temp = (((data/10)<<4) + (data%10));return temp;
}/* pcf8563 read register */
rt_err_t pcf8563_read_reg(rt_uint8_t reg,rt_uint8_t *data,rt_uint8_t data_size)
{struct rt_i2c_msg msg[2];msg[0].addr = PCF8563_ARRD;msg[0].flags = RT_I2C_WR;msg[0].len = 1;msg[0].buf = ®msg[1].addr = PCF8563_ARRD;msg[1].flags = RT_I2C_RD;msg[1].len = data_size;msg[1].buf = data;if (rt_i2c_transfer(pcf8563_dev.i2c_device, msg, 2) == 2){return RT_EOK;}else{rt_kprintf("i2c bus write failed!\r\n");return -RT_ERROR;}
}/* pcf8563 write register */
rt_err_t pcf8563_write_reg(rt_uint8_t reg, rt_uint8_t *data,rt_uint8_t data_size)
{struct rt_i2c_msg msg[2];msg[0].addr = PCF8563_ARRD;msg[0].flags = RT_I2C_WR;msg[0].len = 1;msg[0].buf = ®msg[1].addr = PCF8563_ARRD;msg[1].flags = RT_I2C_WR | RT_I2C_NO_START;msg[1].len = data_size;msg[1].buf = data;if (rt_i2c_transfer(pcf8563_dev.i2c_device, msg, 2) == 2){return RT_EOK;}else{rt_kprintf("i2c bus write failed!\r\n");return -RT_ERROR;}
}static rt_err_t rt_pcf8563_open(rt_device_t dev, rt_uint16_t flag)
{if (dev->rx_indicate != RT_NULL){/* open interrupt */}return RT_EOK;
}static rt_size_t rt_pcf8563_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{return RT_EOK;
}static rt_err_t rt_pcf8563_control(rt_device_t dev, int cmd, void *args)
{rt_err_t ret = RT_EOK;time_t *time;struct tm time_temp; rt_uint8_t buff[7];RT_ASSERT(dev != RT_NULL);rt_memset(&time_temp, 0, sizeof(struct tm));switch (cmd){case RT_DEVICE_CTRL_RTC_GET_TIME:time = (time_t *)args;ret = pcf8563_read_reg(REG_PCF8563_SEC,buff,7);if(ret == RT_EOK){time_temp.tm_year = bcd_to_hex(buff[6]&SHIELD_PCF8563_YEAR) + 2000 - 1900;time_temp.tm_mon = bcd_to_hex(buff[5]&SHIELD_PCF8563_MON) - 1;time_temp.tm_mday = bcd_to_hex(buff[3]&SHIELD_PCF8563_DAY);time_temp.tm_hour = bcd_to_hex(buff[2]&SHIELD_PCF8563_HOUR);time_temp.tm_min = bcd_to_hex(buff[1]&SHIELD_PCF8563_MIN);time_temp.tm_sec = bcd_to_hex(buff[0]&SHIELD_PCF8563_SEC);*time = mktime(&time_temp);}break;case RT_DEVICE_CTRL_RTC_SET_TIME:{struct tm *time_new;time = (time_t *)args;time_new = localtime(time);buff[6] = hex_to_bcd(time_new->tm_year + 1900 - 2000);buff[5] = hex_to_bcd(time_new->tm_mon + 1);buff[3] = hex_to_bcd(time_new->tm_mday);buff[4] = hex_to_bcd(time_new->tm_wday+1);buff[2] = hex_to_bcd(time_new->tm_hour);buff[1] = hex_to_bcd(time_new->tm_min);buff[0] = hex_to_bcd(time_new->tm_sec);ret = pcf8563_write_reg(REG_PCF8563_SEC,buff,7);}break;default:break;}return RT_EOK;
}/* pcf8563 device int */
int rt_hw_pcf8563_init(void)
{ struct rt_i2c_bus_device *i2c_device;uint8_t data;i2c_device = rt_i2c_bus_device_find("i2c1");if (i2c_device == RT_NULL){#ifdef RT_USE_FINSH_DEBUGrt_kprintf("i2c bus device %s not found!\r\n", "i2c1");#endifreturn 1;} pcf8563_dev.i2c_device = i2c_device;/* register rtc device */pcf8563_dev.rtc_parent.type = RT_Device_Class_RTC;pcf8563_dev.rtc_parent.init = RT_NULL;pcf8563_dev.rtc_parent.open = rt_pcf8563_open;pcf8563_dev.rtc_parent.close = RT_NULL;pcf8563_dev.rtc_parent.read = rt_pcf8563_read;pcf8563_dev.rtc_parent.write = RT_NULL;pcf8563_dev.rtc_parent.control = rt_pcf8563_control;pcf8563_dev.rtc_parent.user_data = RT_NULL; /* no private */rt_device_register(&pcf8563_dev.rtc_parent, "rtc", RT_DEVICE_FLAG_RDWR);/* init pcf8563 */data = 0x7f; /* close clock out */if (pcf8563_write_reg(REG_PCF8563_CLKOUT, &data, 1) != RT_EOK){return -RT_ERROR;}return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_pcf8563_init);
#endif
RT-Thread驱动——RTC PCF8563相关推荐
- xpt 2046的触摸屏 rt thread设备驱动框架
1 基于rtt 开发触摸屏驱动 准备使用rtt 框架 , 驱动xpt 2046的触摸屏, 翻阅大量资料发现, 大部分文章强调的是时序图, 而且很多代码要么直接操作寄存器, 要么是io 口模拟, 只能用 ...
- rt thread studio使用QBOOT和片外flash实现OTA升级
我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...
- RT Thread根据开发板制作BSP方法
之前一直不懂怎么使用RT Thread的软件包,感谢网上的大神,看了你们的博客后大概了解一些,在此做下记录.用RT Thread软件包需要RT Thread的系统,但是RT Thread和RT Thr ...
- 基于rt thread smart构建EtherCAT主站
我把源码开源到到了gitee,https://gitee.com/rathon/rt-thread-smart-soem 有兴趣的去可以下载下来跑一下 软件工程推荐用vscode 打开.rt thre ...
- rt thread系统下添加wiznet软件包后,不插网线CPU利用率100%问题
rt thread系统下添加wiznet软件包后如果不插网线的话其他任务运行很卡,使用ps命令发现优先级低的任务很多都超时了 rt thread线程错误码 添加了一个可以查看CPU利用率的软件包CPU ...
- stm32f407单片机rt thread 片外spi flash OTA升级配置示例
参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...
- 关于RT thread系统节拍时钟的配置
关于RT thread系统节拍时钟的配置 -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...
- rt thread 使用FAL遇到fal_init() undefined reference
rt thread FAL 0.5版,之前有没有不知道,遇到一个坑. 在main.cpp里面已经 #include <fal.h> fal_init() 编译报错,说 fal_init() ...
- RT Thread Free Modbus移植问题整理
RT Thread Free Modbus移植问题整理 问题描述: 在读写寄存器中,写数据正常,只能读1个寄存器的值,多个值会异常. 在移植过程中发现串口(或RS485)数据接收长度异常. 一.环境描 ...
最新文章
- 深入TCP/IP协议1---图解OSI参考模型
- 26张图带你彻底搞懂volatile关键字
- python对象编程例子-Python3.5面向对象编程图文与实例详解
- Linux学习之系统编程篇:互斥锁(pthread_mutex_init / lock / trylock / unlock / destroy)
- Luogu2439 [SDOI2005]阶梯教室设备利用 (动态规划)
- java代码如何删除文件_Java如何删除文件和目录代码? 爱问知识人
- 使用app-inspector时报错connect ECONNREFUSED 127.0.0.1:8001的解决方案
- 软件工程 第二章 可行性研究
- 【车牌识别】基于matlab GUI模板匹配车牌库识别【含Matlab源码 416期】
- 怎么升级计算机的操作系统,电脑如何升级系统版本_Windows10/7电脑升级系统版本的操作步骤...
- 开机引导界面grub找不到
- Cisco(思科)无线路由器
- 超分算法在 WebRTC 高清视频传输弱网优化中的应用
- EasyBoot制作启动光盘教程
- 设计模式 -- 访问者模式(Visitor)
- 交接读代码得过程和整理文档
- Squid反向代理加速WEB
- python做日历牌,怎么使用python tkinter制作日历?
- 2018三七互娱前端笔试
- Jetson 相机编码