Linux下RTC驱动开发(硬件采用DS1302)

举报
DS小龙哥 发表于 2022/10/16 14:10:02 2022/10/16
【摘要】 在Linux系统上主要有两个时间基准,一个数是系统时间和,一个是RTC 时间。 其中系统时间是系统运行时由定时器(滴答定时器)维护的时间,掉电不保存数据。而RTC时间,是由RTC实时时钟芯片维护的时间,一般都接了后备电源(常见表现行为就是一颗纽扣电池供电),系统掉电后它不受影响,还是会运行保证时间准确。 每次系统开机时,系统会从RTC芯片里读取当前时间给系统时间赋值,保证系统开机之后时间也是准确的

一、前言

在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");       /*驱动的许可证-声明*/
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。