赞
踩
1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
上一章我们学习了ATK-DLRK3568内置RTC外设,了解了Linux系统下RTC驱动框架。一般的应用场合使用SOC内置的RTC就可以了,而且成本也低,但是在一些对于时间精度要求比较高的场合,SOC内置的RTC就不适用了。我们需要根据自己的应用要求选择合适的外置RTC芯片,正点原子ATK-DLRK3568开发板上板载了一个RTC芯片:AT8563T,这是一个IIC接口的外置RTC芯片,本章我们就来学习一下如何驱动外置RTC芯片。
31.1 AT8563芯片
31.1.1 AT8563简介
AT8563是一个CMOS实时时钟/日历芯片,针对低功耗进行了优化。计时计数器由世纪、年、月、日、日、时、分、秒位组成。系统可以设置或读取AT8563中存放的时间,从而对数据进行相应的处理。可以通过I2C总线接口与系统之前串行传输数据,比起采用并行总线的方案,可以减少电路板上的线路布线数目,非常适合于复杂的系统。
该芯片具有以下特点:
◎提供年、月、日、星期,时、分、秒计时,使用外置32.768Khz晶振。
◎低后备电流:0.25uA ,VDD=3.0V,温度25℃。
◎IIC接口,速度最高400KHz。
◎可编程时钟输出,可以供其他设备使用,可输出的时钟频率有32.768kHz、1.024kHz、32Hz和1Hz。
◎支持闹钟和定时功能。
◎IIC读IC读地址为0XA3,写地址为0XA2
◎内部集成震荡电容,片内电源复位功能
◎封装形式:DIP8、SO8、SSO8、MSOP8。
由下图31.1.1.1得知ATK-DLRK3568开发板板载的封装为SO8,对应就是AT8563T芯片。
图31.1.1.1 封装表格
AT8563框架如图31.1.1.2所示
图31.1.1.2 AT8563框图
简单分析一下图31.1.1.2中的框图:
图标2这是AT8563的32.768kHz晶振引脚,AT8563必选要外接32.768kHz晶振。
图标5、6这是AT8563的IIC引脚,AT8563通过IIC接口与主控进行通信,因此AT8563本质是个IIC器件。
图标3这是中断引脚。
31.1.2 AT8563寄存器详解
AT8563有16个内部寄存器,这些寄存器都是8位的。前两个寄存器(0X00和0X01)为控制/状态寄存器。0X020X08为时间和日期寄存器,这些寄存器保存着秒、分、时、日、星期、月和年信息。0X090X0C为闹钟寄存器,保存闹钟信息。0X0D为时钟输出频率寄存器,0X0E和0X0F这两个寄存器时钟控制寄存器。注意、时分秒、年月日、闹钟等时间信息为BCD格式。
接下来我们看一下这些寄存器如何使用:
1、控制/状态寄存器(0X00和0X01)
寄存器结构体如图31.1.2.1所示:
图31.1.2.1 控制状态寄存器
首先看寄存器0X00,相应的位含义如下:
TEST1(bit7):0代表正常模式,1代表测试模式。
N(bit6,bit4,bit2-0):未使用。
STOP(bit5):0代表RTC时钟运行,1代表RTC时钟停止。
TESTC(bit3):0代表正常模式,关闭上电复位覆写;1代表使能上电复位覆写。
接下来看寄存器0X01,相应的位含义如下:
N(bit7-5):未使用。
TI_TP(bit4):0代表INT引脚取决于TF位,1代表INT引脚输出指定频率的脉冲。
AF(bit3):闹钟标志位,1代表闹钟发生,写0清除,写1保持状态不变。
TF(bit2):定时器标志位,1代表定时发生,写0清除,写1保持状态不变。
AIE(bit1):闹钟中断使能位,0代表关闭闹钟中断;1代表使能闹钟中断。
TIE(bit0):定时器中断使能位,0代表关闭定时器中断;1代表使能定时器中断。
2、时间和日期寄存器(0X02-0X08)
接下来看一下时间和日期相关寄存器,一共7个寄存器,结构如图31.1.2.2所示:
图31.1.2.2 时间和日期寄存器
我们依次来看一下这些寄存器:
0X02:此寄存器为秒钟寄存器,AT8563是有低电压检测的,当VDD电压低于最小允许电压的时候VL(bit)位就会置1,表示时钟异常,如果电压正常的话就为0。 SECONDS(bit60)这7位表示具体的秒数,范围059,为BCD格式。
0X03:此寄存器为分钟寄存器,MINUTES(bit60)这7位有效,表示具体的分钟数,范围059,为BCD格式。
0X04:此寄存器为小时寄存器,HOURS(bit50)这6位有效,表示具体的小时数,范围023,为BCD格式。
0X05:此寄存器为日期寄存器,DAYS(bit50)这6位有效,表示具体的小时数,范围131,为BCD格式。
0X06:此寄存器为星期寄存器,WEEKDAYS(bit20)这3位有效,表示具体的星期,范围06,为BCD格式。
0X06:此寄存器为星期寄存器,WEEKDAYS(bit20)这3位有效,表示具体的星期,范围06,为BCD格式。0为星期日,1为星期一,以此类推,6就是星期六。
0X07:此寄存器为月份寄存器,其中C(bit7)为世纪标志位,如果为1的话表示20xx年,为0的话表示19xx年。MONTHS(bit40)这5位有效,表示具体的月份,范围112,分别为1~12月,为BCD格式。
0X08:此寄存器为年寄存器,YEARS(bit70)这8位有效,表示具体年份,范围099。
3、闹钟寄存器(0X09-0X0C)
接下来看一下闹钟寄存器,一共4个寄存器,如下图31.1.2.3所示:
图31.1.2.3 闹钟寄存器
我们依次来看一下这些寄存器:
0X09:此寄存器为闹钟分钟寄存器,AE_M(bit7)为分钟闹钟使能位,为0的话使能分钟闹钟,为1的话关闭。 MINUTE_ALARM(bit60)这7位表示具体的闹钟分钟,范围059,为BCD格式。
0X0A:此寄存器为闹钟小时寄存器,含义和0X09寄存器类似。
0X0B:此寄存器为闹钟日期寄存器,含义和0X09寄存器类似。
0X0C:此寄存器为闹钟日期寄存器,含义和0X09寄存器类似。
另外还有时钟输出寄存(0X0D)以及定时器寄存器(0X0E和0X0F),这里我们不用AT8563的时钟输出和定时器功能,这里就不讲解了,感兴趣的可以参考AT8563数据手册。
总体来说,AT8563还是很简单的,这是一个IIC接口的RTC芯片,因此在Linux系统下就涉及到两类驱动:
◎IIC驱动,需要IIC驱动框架来读写AT8563芯片。
◎RTC驱动,因为这是一个RTC芯片,因此要用到RTC驱动框架。
如果要用到中断功能的话,还需要用到Linux系统中的中断子系统,这些我们前面都有相应的实验讲解。所以AT8563的Linux驱动并不复杂,而且重点是Linux系统默认就已经集成了AT8563驱动,我们使用起来非常简单,直接修改设备树,添加AT8563节点信息,然后使能内核的AT8563驱动即可。
31.2 硬件原理图分析
AT8563原理图如图31.2.1所示:
图31.2.1 AT8563原理图
从图31.2.1可以看出,AT8563连接到了ATK-DLRK3568的I2C5接口上,引脚为GPIO3_B3和GPIO3_B4。另外,AT8563的INT引脚连接到了GPIO0_D3引脚上。
31.3 实验驱动编写
31.3.1 修改设备树
1、添加或者查找AT8563所使用的IO的pinmux配置
AT8563的IIC接口连接到了I2C5上,对应的引脚GPIO3_B3和GPIO3_B4。在设备树中添加这2个引脚对应的配置信息。打开rk3568-pinctrl.dtsi文件,查找一下有没有I2C5的引脚配置信息,默认是有的,内容如下:
示例代码31.3.1.1 i2c5引脚节点
1 i2c5 { 2 /omit-if-no-ref/ 3 i2c5m0_xfer: i2c5m0-xfer { 4 rockchip,pins = 5 /* i2c5_sclm0 */ 6 <3 RK_PB3 4 &pcfg_pull_none_smt>, 7 /* i2c5_sdam0 */ 8 <3 RK_PB4 4 &pcfg_pull_none_smt>; 9 }; 10 11 /omit-if-no-ref/ 12 i2c5m1_xfer: i2c5m1-xfer { 13 rockchip,pins = 14 /* i2c5_sclm1 */ 15 <4 RK_PC7 2 &pcfg_pull_none_smt>, 16 /* i2c5_sdam1 */ 17 <4 RK_PD0 2 &pcfg_pull_none_smt>; 18 }; 19 };
从第6、8行可以看出,I2C5默认就是GPIO3_B3和GPIO_B4,所以I2C5的引脚是不需要我们去修改的,直接使用i2c5m0_xfer即可。如果一个引脚作为GPIO功能的话可以不用添加pinctrl信息,因此,重点引脚GPIO0_D3就不操作了。
2、在I2C5节点追加AT8563子节点
接着我们在rk3568-atk-evb1-ddr4-v10.dtsi文件,通过节点内容追加的方式,向I2C5节点中添加“rtc@51”子节点,节点如下所示:
示例代码31.3.1.2 追加at8563子节点
1 &i2c5 {
2 rtc@51 {
3 compatible = "nxp,pcf8563";
4 reg = <0x51>;
5 interrupt-parent = <&gpio0>;
6 interrupts = <RK_PD3 IRQ_TYPE_LEVEL_LOW>;
7 #clock-cells = <0>;
8 };
9 }
第2行,rtc子节点,@后面的“51”是AT8563的器件地址。
第3行,设置compatible值为“nxp,pcf8563”。
第4行,reg属性也是设置AT8563器件地址的,因此reg设置为0x51。
第5行,interrupt-parent属性设置中断控制器,因为GPIO0_D3数据在GPIO0组,所以这里设置中断控制器为GPIO0。
第6行,interrupts设置中断信息,RK_PD3表示GPIO0_D3,IRQ_TYPE_LEVEL_LOW表示下降沿触发。
31.3.2 AT8563驱动使能
1、使用Linux内核自带的PCF8563驱动
这里为什么要使用PCF8563驱动呢?原因很简单,AT8563与PCF8563的驱动兼容。输入如下命令打开Linux配置菜单:
make ARCH=arm64 menuconfig
配置路径如下:
-> Device Drivers
-> Real Time Clock
-> <*> Philips PCF8563/Epson RTC8564 //选中PCF8563
如图31.3.2.1所示:
图31.3.2.1 使能PCF8563驱动
选择并保存后,重新编译内核源码使用新的boot.img文件启动开发板。
31.4 运行测试
当配置好AT8563驱动的时候,启动过程会提示如图31.4.1.1所示信息:
图31.4.1 AT8563启动过程
也可以在系统查看,如图31.4.2所示信息:
图31.4.2 查看AT8563设备
这里介绍一下,rtc0就是核心板上的RK809内部硬件RTC时钟,rtc1则是AT8563。系统已经识别出了AT8563,说明驱动没有问题。
将当前时间写入RTC1,注意读RTC之前需要写一次RTC才能读。
date +%T //查看系统时间并输出24小时制
hwclock -w -f /dev/rtc1 //将当前时间写入RTC1
hwclock -f /dev/rtc1 –show //读取RTC1的时间
图31.4.3 操作结果
从图31.4.3可以看出,正确的读出了时间信息,整个开发板掉电以后AT8563也会继续计时,因为有一个纽扣电池供电。
31.5 AT8563驱动分析
上一节我们已经测试了AT8563,本小节我们来简单看一下AT8563驱动源码,根据示例代码31.3.1.2中的第3行的compatible属性值可以找到相对应到驱动文件,在Linux源码中搜索字符串“nxp,pcf8563”即可找到驱动文件,路径为drivers/rtc/rtc-pcf8563.c。
AT8563是个I2C器件,因此基础驱动框架是I2C,在rtc-pcf8563.c文件中找到如下所示内容:
示例代码31.5.1 pcf8563 I2C驱动框架
1 static const struct i2c_device_id pcf8563_id[] = { 2 { "pcf8563", 0 }, 3 { "rtc8564", 0 }, 4 { } 5 }; 6 MODULE_DEVICE_TABLE(i2c, pcf8563_id); 7 8 #ifdef CONFIG_OF 9 static const struct of_device_id pcf8563_of_match[] = { 10 { .compatible = "nxp,pcf8563" }, 11 { .compatible = "epson,rtc8564" }, 12 { .compatible = "microcrystal,rv8564" }, 13 {} 14 }; 15 MODULE_DEVICE_TABLE(of, pcf8563_of_match); 16 #endif 17 18 static struct i2c_driver pcf8563_driver = { 19 .driver = { 20 .name = "rtc-pcf8563", 21 .of_match_table = of_match_ptr(pcf8563_of_match), 22 }, 23 .probe = pcf8563_probe, 24 .id_table = pcf8563_id, 25 }; 26 27 module_i2c_driver(pcf8563_driver);
上述示例代码就是个标准的I2C驱动框架,第9~14行的pcf8563_of_match结构体数组就是设备树匹配数组,第10行的compatible属性为“nxp,pcf8563”,和我们的设备树向匹配。匹配以后第23行的pcf8563_probe函数就会执行。
接下来看一下pcf8563_probe函数,函数源码如下(有缩略):
示例代码31.5.2 pcf8563_probe 函数
1 static int pcf8563_probe(struct i2c_client *client, 2 const struct i2c_device_id *id) 3 { 4 struct pcf8563 *pcf8563; 5 int err; 6 unsigned char buf; ...... 13 pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563), 14 GFP_KERNEL); 15 if (!pcf8563) 16 return -ENOMEM; 17 18 i2c_set_clientdata(client, pcf8563); 19 pcf8563->client = client; 20 device_set_wakeup_capable(&client->dev, 1); 21 22 /* Set timer to lowest frequency to save power */ 23 buf = PCF8563_TMRC_1_60; 24 err = pcf8563_write_block_data(client, PCF8563_REG_TMRC, 1, &buf); 25 if (err < 0) { 26 dev_err(&client->dev, "%s: write error\n", __func__); 27 return err; 28 } 29 30 /* Clear flags and disable interrupts */ 31 buf = 0; 32 err = pcf8563_write_block_data(client, PCF8563_REG_ST2, 1, &buf); 33 if (err < 0) { 34 dev_err(&client->dev, "%s: write error\n", __func__); 35 return err; 36 } 37 38 pcf8563->rtc = devm_rtc_device_register(&client->dev, 39 pcf8563_driver.driver.name, 40 &pcf8563_rtc_ops, THIS_MODULE); 41 42 if (IS_ERR(pcf8563->rtc)) 43 if (IS_ERR(pcf8563->rtc)) 44 if (client->irq > 0) { 45 err = devm_request_threaded_irq(&client->dev, client->irq, 46 NULL, pcf8563_irq, 47 IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW, 48 pcf8563_driver.driver.name, client); 49 if (err) { 50 dev_err(&client->dev, "unable to request IRQ %d\n", 51 client->irq); 52 return err; 53 } 54 55 } 56 57#if defined(CONFIG_COMMON_CLK) && !defined(CONFIG_ROCKCHIP_THUNDER_BOOT) 58 /* register clk in common clk framework */ 59 pcf8563_clkout_register_clk(pcf8563); 60#endif 61 62 /* the pcf8563 alarm only supports a minute accuracy */ 63 pcf8563->rtc->uie_unsupported = 1; 64 65 return 0; 66}
第13行,申请内存内存,rtc-pcf8563.c定义了一个pcf8563结构体来描述AT8563芯片,所以这里就是申请一个AT8563实例。
第23~36行,初始化AT8563。
第38~40行,pcf8563结构体里面有个rtc成员变量,此成员变量是个rtc_device结构体指针。看到这里,大家应该很熟悉了,这个就是上一章讲解的RTC驱动框架最核心的rtc_device。这里需要对这个rtc指针分配内存。设置rtc_device的ops成员变量为pcf8563_rtc_ops,pcf8563_rtc_ops包含了AT8563的具体操作,包括设置时间、读取时间、设置闹钟等。
第44~55行,中断初始化,AT8563有个中断引脚INT,因此可以使用中断功能。这里使用devm_request_threaded_irq函数完成中断申请已经初始化,中断函数为pcf8563_irq。
总结一下,pcf8563_probe函数的核心就是初始化AT8563,然后使用上一章讲的RTC驱动框架来设置AT8563,然后向内核注册。
接下来我们看一下AT8563的核心:pcf8563_rtc_ops,内容如下:
示例代码31.5.3 pcf8563_rtc_ops
1 static const struct rtc_class_ops pcf8563_rtc_ops = {
2 .ioctl = pcf8563_rtc_ioctl,
3 .read_time = pcf8563_rtc_read_time,
4 .set_time = pcf8563_rtc_set_time,
5 .read_alarm = pcf8563_rtc_read_alarm,
6 .set_alarm = pcf8563_rtc_set_alarm,
7 .alarm_irq_enable = pcf8563_irq_enable,
8 };
pcf8563_rtc_ops提供了AT8563的时间以及闹钟读写操作函数,应用程序对AT8563的所有操作最终都是通过这些函数来完成的。我们以读时间为例,当应用程序读取AT8563当前时间的时候,.read_time就会执行,在这里就是pcf8563_rtc_read_time函数,此函数又封装了一层函数函数pcf8563_get_datetime源码如下(有省略):
示例代码31.5.4 pcf8563_get_datetime函数
1 static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *tm) 2 { 3 4 struct pcf8563 *pcf8563 = i2c_get_clientdata(client); 5 unsigned char buf[9]; 6 int err; 7 8 err = pcf8563_read_block_data(client, PCF8563_REG_ST1, 9, buf); 9 if (err) 10 return err; 11 12 if (buf[PCF8563_REG_SC] & PCF8563_SC_LV) { 13 pcf8563->voltage_low = 1; 14 dev_err(&client->dev, 15 "low voltage detected, date/time is not reliable.\n"); 16 return -EINVAL; 17 } ...... 28 tm->tm_sec = bcd2bin(buf[PCF8563_REG_SC] & 0x7F); 29 tm->tm_min = bcd2bin(buf[PCF8563_REG_MN] & 0x7F); 30 tm->tm_hour = bcd2bin(buf[PCF8563_REG_HR] & 0x3F); 31 tm->tm_mday = bcd2bin(buf[PCF8563_REG_DM] & 0x3F); 32 tm->tm_wday = buf[PCF8563_REG_DW] & 0x07; 33 tm->tm_mon = bcd2bin(buf[PCF8563_REG_MO] & 0x1F) - 1; 、 34 tm->tm_year = bcd2bin(buf[PCF8563_REG_YR]) + 100; 35 /* detect the polarity heuristically. see note above. */ 36 pcf8563->c_polarity = (buf[PCF8563_REG_MO] & PCF8563_MO_C) ? 37 (tm->tm_year >= 100) : (tm->tm_year < 100); ...... 45 return 0; 46 }
第8行,使用pcf8563_read_block_data函数从PCF8563_REG_ST1寄存器(地址为0X00)开始,连续读取9个寄存器的数据。这样就可以得到AT8563的控制与状态寄存器1和2,以及事件与日期寄存器的值。
第12行,判断AT8563的0X02寄存器VL位是否为1,也就是检查AT8563是否处于低电压模式,事件和日期是否有效。
第28~34行,依次获取AT8563中的时间和日期值,这里使用bcd2bin函数将原始的BCD值转换为时间值。将获取到的时间和日期打包到参数tm中,tm是个rtc_time结构体指针变量。
第36行,判断0X07寄存器的C位(bit7)的值,此位为1的话表示20xx年,为0的话就是19xx年。
可以看出pcf8563_rtc_read_time函数很简单,就是读取AT8563内部的时间和日期值,然后将其打包进rtc_time里面。其他的函数大同小异,大家可以自行分析一下,这里就不讲解了。
至此,AT8563驱动就简单分析完成了,其他IIC接口的RTC芯片驱动基本都是类似的,大家可以在实际项目开发中选择合适的RTC芯片。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。