Linux下RTC驱动开发(硬件采用DS1302)
一、前言
在Linux系统上主要有两个时间基准,一个数是系统时间和,一个是RTC 时间。 其中系统时间是系统运行时由定时器(滴答定时器)维护的时间,掉电不保存数据。而RTC时间,是由RTC实时时钟芯片维护的时间,一般都接了后备电源(常见表现行为就是一颗纽扣电池供电),系统掉电后它不受影响,还是会运行保证时间准确。 每次系统开机时,系统会从RTC芯片里读取当前时间给系统时间赋值,保证系统开机之后时间也是准确的。
那么系统开机如何读取RTC时间的? 这个就需要用到1个命令:hwclock 。 这个命令是专门读写RTC驱动的。当然,通过打开设备文件,利用ioctl的命令也可以与RTC设备进行交互,使用hwclock命令更简单些。
而在linux应用层,可以通过date 和 time 命令来设置系统时间的,而刚才说到的 hwclock 命令是用来设置和读写 RTC 时间的。
下面是系统 RTC 实时时钟时间的获取与设置命令用法案例:
1. 将 RTC 时间同步到系统时间
[root@XiaoLong /]# hwclock -s
为了在启动时自动执行 RTC 时间同步到系统时间,可以把 hwclock -s 命令加入到 profile 或者 rcS 文件中。
2. 获取显示 RTC 时间
[root@XiaoLong /]# hwclock -r
Sun May 1 00:09:36 2016 0.000000 seconds
3. 将系统时间同步到 RTC,用于设置时间
[root@XiaoLong /]# hwclock -w
4. 查看 RTC 的信息
[root@XiaoLong /]# cat /proc/driver/rtc
rtc_time : 00:09:27
rtc_date : 2016-05-01
alrm_time : 23:24:07
alrm_date : 2016-05-01
alarm_IRQ : no
alrm_pending : no
update IRQ enabled : no
periodic IRQ enabled : no
periodic IRQ frequency : 1
max user IRQ frequency : 32768
24hr : yes
periodic_IRQ : no
二、设计思路
为了驱动的编写规范,Linux下规定了很多标准的驱动框架结构,而RTC驱动也是有一套标准结构,只有按照标准写的RTC驱动,才可以对接上应用层的命令。这个结构称为:RTC子系统。 RTC是实时时钟,一般可以采用芯片内置的RTC实时时钟,也可以采用外置的RTC实时时钟芯片,比如:DS1320。 这篇文章接下来编写RTC驱动案例,硬件就采用外置的DS1302来作为RTC实时时钟芯片。
在第一章里介绍了hwclock命令的用法,我们发现这个命令主要功能就是从驱动里读写时间,那么对于驱动而言就是要响应这两个操作。根据之前编写这么多驱动的经验,大家应该也就想到了,应用层命令访问到驱动层肯定会有两个接口:这两个接口就是设置时间和获取时间。 驱动层只要把这两个接口实现了,应用层的命令就可以正确的读写RTC时间了。和之间讲到的块设备驱动(实现读写扇区接口),网络设备驱动(实现网络数据收发接口)的意思差不多。
标准的RTC驱动安装注册之后,在/dev目录下会生成:rtcX这样的名字。后面的X是数字,系统里可以存在多个RTC驱动。默认情况下hwclock命令是读写的/dev/rtc0作为选择的主RTC驱动。这个可以在内核源码的配置里去修改的。
下面是调用框图:
去掉内核自带的RTC驱动:
Device Drivers ---> [*] Real Time Clock ---> <M> Samsung S3C series SoC RTC |
将内核自带的RTC驱动编译成模块,方便后面动态加载测试。
配置之后,重新内核,烧写内核。
三、DS1302
当前RTC驱动的时钟芯片采用的是DS1302,下面看看DSS1302的介绍(来至数据手册):
DS1302 是 DALLAS 公司推出的涓流充电时钟芯片 内含有一个实时时钟/日历和 31 字节静态 RAM 通过简单的串行接口与单片机进行通信 实时时钟/日历电路提供秒 分 时 日 日期 月 年的信息 每月的天数和闰年的天数可自动调整 时钟操作可通过 AM/PM 指示决定采用 24 或 12 小时格式 DS1302 与单片机之
间能简单地采用同步串行的方式进行通信 仅需用到三个口线 1 RES 复位 2 I/O 数据线 3 SCLK串行时钟 时钟/RAM 的读/写数据以一个字节或多达 31 个字节的字符组方式通信 DS1302 工作时功耗很低 保持数据和时钟信息时功率小于 1mW。
DS1302 是由 DS1202 改进而来 增加了以下的特性 双电源管脚用于主电源和备份电源供应 Vcc1 为可编程涓流充电电源 附加七个字节存储器 它广泛应用于电话 传真 便携式仪器以及电池供电的仪器仪表等产品领域。
下面将主要的性能指标
1. 实时时钟具有能计算 2100 年之前的秒 分 时 日 日期 星期 月 年的能力 还有闰年调整的能力
2. 31 8 位暂存数据存储 RAM
3. 串行 I/O 口方式使得管脚数量最少
4. 宽范围工作电压 2.0 5.5V
5. 工作电流 2.0V 时,小于 300nA
6. 读/写时钟或 RAM 数据时 有两种传送方式 单字节传送和多字节传送 字符组方式
7. 8 脚 DIP 封装或可选的 8 脚 SOIC 封装 根据表面装配
8. 简单 3 线接口
9. 与 TTL 兼容 Vcc=5V
10. 可选工业级温度范围 -40 +85
11. 与 DS1202 兼容
12. 在 DS1202 基础上增加的特性
对 Vcc1 有可选的涓流充电能力
双电源管用于主电源和备份电源供应
备份电源管脚可由电池或大容量电容输入
附加的 7 字节暂存存储器
下面是实物图:
四、实现代码
4.1 内核提供的 rtc 底层注册与注销函数
1. RTC 框架注册函数
struct rtc_device *rtc_device_register(
const char *name, //RTC 时钟名称
struct device *dev, //设备指针。该指针需要需要通过平台设备获取。
const struct rtc_class_ops *ops, //rtc 文件操作集合
struct module *owner) //驱动所有者。填: THIS_MODULE
使用示例: rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);
使用 rtc_device_register 函数注册成功之后,在/dev/下可以看到 rtcx 的设备节点(x 是 rtc 的顺序编号)。
2. RTC 框架注销函数
void rtc_device_unregister(struct rtc_device *rtc)
经过 RTC 注册函数形参分析,RTC 子系统的注册需要通过平台设备框架完成,在平台设备的驱动端的 probe
函数里进行 rtc 注册,remove 函数里进行注销,在 rtc 设备端向驱动端传递 RTC 硬件需要的一些信息。
4.2 驱动代码(平台设备层)
#include "linux/module.h"
#include "linux/init.h"
#include <linux/platform_device.h>
/*
* device 设备端
*/
//释放平台总线
static void pdev_release(struct device *dev)
{
printk("rtc_pdev:the rtc_pdev is close!!!\n");
}
/*设备端结构体*/
struct platform_device rtc_pdev= /*设备结构体,设备名字很重要!*/
{
.name = "DS1302rtc", /*设备名*/
.id = -1, /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
.dev = /*驱动卸载时调用*/
{
.release = pdev_release,/*释放资源*/
},
};
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
platform_device_register(&rtc_pdev);/*注册平台设备端*/
return 0;
}
/*平台设备端出口函数*/
static void __exit plat_dev_exit(void)
{
platform_device_unregister(&rtc_pdev);/*注销平台设备端*/
}
module_init(plat_dev_init);
module_exit(plat_dev_exit);
MODULE_LICENSE("GPL");
4.3 驱动代码(平台应用层)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
struct rtc_time time; //保存时间值
int main(int argc,char **argv)
{
if(argc!=2)
{
printf("传参格式:/dev/rtc\r\n");
return;
}
int fd=open(argv[1],O_RDWR); // 2==O_RDWR
if(fd<0)
{
printf("驱动设备文件打开失败!\r\n");
return 0;
}
time.tm_year=2017;
time.tm_mon=10;
time.tm_mday=13;
time.tm_hour=21;
time.tm_min=10;
time.tm_sec=10;
//注意:年月日必须填写正常,否则会导致底层函数无法调用成功
ioctl(fd,RTC_SET_TIME,&time);
while(1)
{
ioctl(fd,RTC_RD_TIME,&time);
printf("%d-%d-%d %d:%d:%d\r\n",time.tm_year,time.tm_mon,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec);
sleep(1);
}
}
4.4 驱动代码(平台驱动层)
#include <linux/module.h> /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h> /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h> /*中断相关头文件*/
#include <linux/irq.h> /*中断相关头文件*/
#include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h> /*内核定时器*/
#include <asm-generic/poll.h>
#include <linux/poll.h> /* poll机制*/
#include <linux/platform_device.h> /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
/*--------------------------------DS1302相关操作代码---------------------------------------------*/
static unsigned char RTC_bin2bcd(unsigned val)
{
return ((val/10)<<4)+val%10;
}
static unsigned RTC_bcd2bin(unsigned char val)
{
return (val&0x0f)+(val>>4)*10;
}
/*
函数功能:DS1302初始化
Tiny4412硬件连接:
CLK :GPB_4
DAT :GPB_5
RST :GPB_6
*/
void DS1302IO_Init(void)
{
/*1. 注册GPIO*/
gpio_request(EXYNOS4_GPB(4), "DS1302_CLK");
gpio_request(EXYNOS4_GPB(5), "DS1302_DAT");
gpio_request(EXYNOS4_GPB(6), "DS1302_RST");
/*2. 配置GPIO口模式*/
s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT); //时钟
s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT); //数据
// s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_INPUT); //输入模式
s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_OUTPUT); //复位
/*3. 上拉GPIO口*/
gpio_set_value(EXYNOS4_GPB(4), 1); //CLK
gpio_set_value(EXYNOS4_GPB(5), 1); //DAT
gpio_set_value(EXYNOS4_GPB(6), 1); //RST
gpio_set_value(EXYNOS4_GPB(6), 0); //RST脚置低
gpio_set_value(EXYNOS4_GPB(4), 0); //SCK脚置低
}
//#define RTC_CMD_READ 0x81 /* Read command */
//#define RTC_CMD_WRITE 0x80 /* Write command */
//#define RTC_ADDR_RAM0 0x20 /* Address of RAM0 */
//#define RTC_ADDR_TCR 0x08 /* Address of trickle charge register */
//#define RTC_ADDR_YEAR 0x06 /* Address of year register */
//#define RTC_ADDR_DAY 0x05 /* Address of day of week register */
//#define RTC_ADDR_MON 0x04 /* Address of month register */
//#define RTC_ADDR_DATE 0x03 /* Address of day of month register */
//#define RTC_ADDR_HOUR 0x02 /* Address of hour register */
//#define RTC_ADDR_MIN 0x01 /* Address of minute register */
//#define RTC_ADDR_SEC 0x00 /* Address of second register */
//DS1302地址定义
#define ds1302_sec_add 0x80 //秒数据地址
#define ds1302_min_add 0x82 //分数据地址
#define ds1302_hr_add 0x84 //时数据地址
#define ds1302_date_add 0x86 //日数据地址
#define ds1302_month_add 0x88 //月数据地址
#define ds1302_day_add 0x8a //星期数据地址
#define ds1302_year_add 0x8c //年数据地址
#define ds1302_control_add 0x8e //控制数据地址
#define ds1302_charger_add 0x90
#define ds1302_clkburst_add 0xbe
//初始时间定义
static unsigned char time_buf[8] = {0x20,0x10,0x06,0x01,0x23,0x59,0x55,0x02};//初始时间2010年6月1号23点59分55秒 星期二
static unsigned char readtime[14];//当前时间
static unsigned char sec_buf=0; //秒缓存
static unsigned char sec_flag=0; //秒标志位
//向DS1302写入一字节数据
static void ds1302_write_byte(unsigned char addr, unsigned char d)
{
unsigned char i;
gpio_set_value(EXYNOS4_GPB(6), 1); //启动DS1302总线
//写入目标地址:addr
addr = addr & 0xFE; //最低位置零,寄存器0位为0时写,为1时读
for(i=0;i<8;i++)
{
if(addr&0x01){gpio_set_value(EXYNOS4_GPB(5), 1);}
else{gpio_set_value(EXYNOS4_GPB(5), 0);}
gpio_set_value(EXYNOS4_GPB(4), 1); //产生时钟
gpio_set_value(EXYNOS4_GPB(4), 0);
addr=addr >> 1;
}
//写入数据:d
for(i=0;i<8;i++)
{
if(d & 0x01) {gpio_set_value(EXYNOS4_GPB(5), 1);}
else {gpio_set_value(EXYNOS4_GPB(5), 0);}
gpio_set_value(EXYNOS4_GPB(4), 1); //产生时钟
gpio_set_value(EXYNOS4_GPB(4), 0);
d = d >> 1;
}
gpio_set_value(EXYNOS4_GPB(6), 0); //停止DS1302总线
}
//从DS1302读出一字节数据
static unsigned char ds1302_read_byte(unsigned char addr)
{
unsigned char i,temp;
gpio_set_value(EXYNOS4_GPB(6), 1);//启动DS1302总线
//写入目标地址:addr
addr=addr | 0x01; //最低位置高,寄存器0位为0时写,为1时读
for(i=0; i<8; i++)
{
if(addr & 0x01){gpio_set_value(EXYNOS4_GPB(5), 1);}
else {gpio_set_value(EXYNOS4_GPB(5), 0);}
gpio_set_value(EXYNOS4_GPB(4), 1);
gpio_set_value(EXYNOS4_GPB(4), 0);
addr=addr >> 1;
}
s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_INPUT); //输入模式
//输出数据:temp
for(i=0; i<8; i++)
{
temp=temp>>1;
if(gpio_get_value(EXYNOS4_GPB(5))){temp |= 0x80;}
else{temp&=0x7F;}
gpio_set_value(EXYNOS4_GPB(4), 1);
gpio_set_value(EXYNOS4_GPB(4), 0);
}
s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT); //输出模式
gpio_set_value(EXYNOS4_GPB(6), 0); //停止DS1302总线
return temp;
}
//向DS302写入时钟数据
static void ds1302_write_time(struct rtc_time *time)
{
ds1302_write_byte(ds1302_control_add,0x00); //关闭写保护
ds1302_write_byte(ds1302_sec_add,0x80); //暂停时钟
//ds1302_write_byte(ds1302_charger_add,0xa9); //涓流充电
/*设置RTC时间*/
//因为DS1302的年份只能设置后两位,所有需要使用正常的年份减去2000,得到实际的后两位
ds1302_write_byte(ds1302_year_add,RTC_bin2bcd(time->tm_year-2000)); //年
ds1302_write_byte(ds1302_month_add,RTC_bin2bcd(time->tm_mon)); //月
ds1302_write_byte(ds1302_date_add,RTC_bin2bcd(time->tm_mday)); //日
ds1302_write_byte(ds1302_hr_add,RTC_bin2bcd(time->tm_hour)); //时
ds1302_write_byte(ds1302_min_add,RTC_bin2bcd(time->tm_min)); //分
ds1302_write_byte(ds1302_sec_add,RTC_bin2bcd(time->tm_sec)); //秒
//ds1302_write_byte(ds1302_day_add,RTC_bin2bcd(time->tm_wday)); //周 time->tm_wday一周中的某一天
ds1302_write_byte(ds1302_control_add,0x80); //打开写保护
}
static int DS1302_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
/*设置RTC时间*/
struct rtc_time time;
copy_from_user(&time,(const void __user *)arg,sizeof(struct rtc_time));
ds1302_write_time(&time);
return 0;
}
//此函数通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
rtc_tm->tm_year=RTC_bcd2bin(ds1302_read_byte(ds1302_year_add))+2000; //年
rtc_tm->tm_mon=RTC_bcd2bin(ds1302_read_byte(ds1302_month_add)); //月
rtc_tm->tm_mday=RTC_bcd2bin(ds1302_read_byte(ds1302_date_add)); //日
rtc_tm->tm_hour=RTC_bcd2bin(ds1302_read_byte(ds1302_hr_add)); //时
rtc_tm->tm_min=RTC_bcd2bin(ds1302_read_byte(ds1302_min_add)); //分
rtc_tm->tm_sec=RTC_bcd2bin((ds1302_read_byte(ds1302_sec_add))&0x7f);//秒,屏蔽秒的第7位,避免超出59
//time_buf[7]=ds1302_read_byte(ds1302_day_add); //周
return 0;
}
//此函数通过应用层的ioctl的RTC_SET_TIME命令进行调用
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
ds1302_write_time(tm);
return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops DS1302_rtcops = {
.ioctl=DS1302_rtc_ioctl,
.read_time = tiny4412_rtc_gettime,
.set_time = tiny4412_rtc_settime
};
static struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{
rtc = rtc_device_register("DS1302RTC",&pdev->dev, &DS1302_rtcops,THIS_MODULE);
if(rtc==NULL)
printk("RTC驱动注册失败1\n");
else
{
printk("RTC驱动注册成功1\n");
}
/*1. 初始化GPIO口*/
DS1302IO_Init();
msleep(10);
return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
/*释放GPIO口*/
gpio_free(EXYNOS4_GPB(4));
gpio_free(EXYNOS4_GPB(5));
gpio_free(EXYNOS4_GPB(6));
rtc_device_unregister(rtc);
printk("RTC驱动卸载成功\n");
return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver drv=
{
.probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/
.remove = drv_remove, /*创建一个remove函数,用于设备退出*/
.driver =
{
.name = "DS1302rtc", /*设备名称,用来与设备端匹配(非常重要)*/
},
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
platform_driver_register(&drv);/*注册平台驱动*/
return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init); /*驱动模块的入口*/
module_exit(plat_drv_exit); /*驱动模块的出口*/
MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/
- 点赞
- 收藏
- 关注作者
评论(0)