300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 火牛单片机rtc时钟配置_RTC 实时时钟驱动 - Linux内核之我的天下 - CSDN博客

火牛单片机rtc时钟配置_RTC 实时时钟驱动 - Linux内核之我的天下 - CSDN博客

时间:2022-12-31 04:07:34

相关推荐

火牛单片机rtc时钟配置_RTC 实时时钟驱动 - Linux内核之我的天下 - CSDN博客

RTC实时时钟驱动

-------I2C软件模拟通信

内核版本: linux-2.4.21

文档设计:侯辉华

版本: 1.01

时间: /06/10

内容简介:介绍接在I2C总线上RTC实时时钟设备的驱动,使用软件模拟的方法完成I2C的通信;介绍了Linux下的时钟系统,以及I2C的层次结构.

目录索引:

一.Linux下的时钟系统简介.

二.Linux对时间的表示.

三.Linux时钟中断的初始化及处理.

四.RTC设备驱动程序.

五.I2C总线读写.

六.Linux下的I2C驱动层次结构..一.Linux下的时钟系统简介

实际上,linux系统有两个时钟:一个是由主板电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。当操作系统关机的时候,用这个来记录时间,但是对于运行的系统是不用这个时间的。另一个时间是“System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进行计数的,内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时间来进行时间同步.

linux的内核时间实际上是记录从1970年1月1日距离现在的秒数,并且以GMT(格林尼治时间)(或者叫UTC- Coordinated Universal Time)为标准, UTC是不随着DST(夏令时)变换,需要有变化的是由应用程序自身来完成时间的转换。

二.Linux对时间的表示

通常,操作系统可以使用三种方法来表示系统的当前时间与日期:①最简单的一种方法就是直接用一个64位的计数器来对时钟滴答进行计数。②第二种方法就是用一个32位计数器来对秒进行计数,同时还用一个32位的辅助计数器对时钟滴答计数,之子累积到一秒为止。因为2的32次方超过136年,因此这种方法直至22世纪都可以让系统工作得很好。③第三种方法也是按时钟滴答进行计数,但是是相对于系统启动以来的滴答次数,而不是相对于相对于某个确定的外部时刻;当读外部后备时钟(如RTC)或用户输入实际时间时,根据当前的滴答次数计算系统当前时间。

Linux通常都采用第三种方法来维护系统的时间与日期,通过时钟点滴进行计时的基础原理,可以参看下面介绍的参考文档,主要原理是通过硬件的中断累积来计时,但必要要设置硬件中断一次所须的时间,一般具体的不同的芯片都不同, EP9302系统具体设置如下:

1.EP93xx系列芯片有四个Timer计时器,使用的是Timer1,与具体芯片相关的内容在如下两个文件:

linux-2.4.21\arch\arm\mach-ep93xx\time.c,

linux-2.4.21\arch\arm\mach-ep93xx\time.h

2.针对整个Arm体系的时钟相关文件为:

linux-2.4.21\arch\arm\kernel\time.c

时钟中断计时的主要相关函数为如下两个:

1.ep93xx_gettimeoffset()的作用就是返回距最近一次时钟中断发生后, Timer已经累积的时间(但还未满足引发一次时钟中断),这个时间值单位为微秒,获取当前时间时,就是累计已经发生的Timer中断次数所经历的时间,然后加上这个即将要发生中断所过去的时间,这样取得当前时间的精度是相当高的.

2.LATCH的含义是指一次时钟中断要经过多少个Timer时钟周期,TIMER1LOAD寄存器设置的就是这个值,

ep93xx的计时器Timer1会将这个值一直递减直至0,如此就引发一次时钟中断,然后又重LATCH重头开始递减.

3.xtime记载的即为系统自开机以来的当前时间,单位为秒,精确度为微秒.因此在开机时必须从RTC当中取得真实的时间来赋此初值, EP93xx系列直接初始此值为其自身所带RTC模块的时间值,RTCDR寄存器是EP93xx所自带的RTC模块的寄存器,其值单位为秒,基准为相对1970年,此处即为我们要改动的地方.将其值从i2c的RTC实时芯片中取回赋给它.

static unsigned long

ep93xx_gettimeoffset(void)

{

unsigned long

hwticks;

hwticks = LATCH -

(inl(TIMER1VALUE) & 0xffff);

return ((hwticks *

tick) / LATCH);

}

void __init ep93xx_setup_timer(void)//初始化Timer,设定时钟中断周期…

{

gettimeoffset =

ep93xx_gettimeoffset;

outl(0,

TIMER1CONTROL);

outl(LATCH - 1,

TIMER1LOAD);//设定Timer经多少个Timer时钟周期后产生中断…

outl(0xc8,

TIMER1CONTROL);

xtime.tv_sec =

inl(RTCDR);//从ep93xx内部RTC模块读取时间,后将重新从cmos读.

}

以下详细介绍一下时钟点滴计时的几个基本参数,以下定义的出处,除非特别指出,一般是位于各自不同的平台的文件夹下定义:

linux-2.4.21\include\asm-arm\arch-ep93xx

linux-2.4.21\arch\arm\mach-ep93xx\

1.时钟周期(clock cycle)的频率:计时器Timer晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率,要注意这个Timer的时钟周期频率要与时钟中断的频率区别开来,Linux用宏CLOCK_TICK_RATE来表示计时器的输入时钟脉冲的频率(此值在EP93xx上是508KHZ),该宏定义在timex.h头文件中:

#define

CLOCK_TICK_RATE 508000 /* Underlying HZ */

2.时钟中断(clock tick):我们知道当计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟中断,计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度.在EP93xx系统中, Timer1的TIMER1LOAD的值决定过多少时钟周期后产生一次时钟中断.

3.时钟中断的频率(HZ):也即1秒时间内Timer所产生的时钟中断次数。确定了时钟中断的频率值后也就可以确定Timer的计数器初值。Linux内核用宏HZ来表示时钟中断的频率,而且在不同的平台上HZ有不同的定义值。对于SPARC、MIPS、ARM和i386等平台HZ的值都是100。该宏在ARM平台上的定义如下(param.h):

#ifndef HZ

#define HZ 100

#endif

据HZ值,可知每隔(1000ms/HZ)=10ms发生一次时钟中断.

4.时钟中断的时间间隔:Linux用全局变量tick来表示时钟中断的时间间隔长度,其实定义了HZ之后,即决定了此间隔值, tick变量的单位是微妙(μs),该变量定义在kernel/timer.c文件中,如下:

long tick =

(1000000 + HZ/2) / HZ; /* timer interrupt period */.

5.宏LATCH:Linux用宏LATCH来定义要设置到Timer中的值,它表示TImer将没隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算:LATCH=(1秒之内的Timer时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE)÷(HZ).

三.Linux时钟中断的初始化及处理

以下着重描述一下时钟中断时与RTC相关的修改:

文件:linux-2.4.21\arch\arm\kernel\time.c

描述:时钟初始化化,在start_kernel()当中调用.

void __init time_init(void)

{

xtime.tv_usec

= 0;

xtime.tv_sec= 0;

setup_timer();

}

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:安装时钟中断服务程序,从RTC更新初始化系统时钟,在time_init()当中调用.

/*

* Set up timer interrupt, and return the

current time in seconds.

*/

static inline void setup_timer(void)

{

ep93xx_setup_timer();

//houhh 0713...

/

xtime.tv_sec

= get_cmos_time();//从RTC设备中读取当前时间…

set_rtc

= set_cmos_time;//初始化更新系统时间到RTC的函数,在do_set_rtc()中调用…

/

timer_irq.handler

= ep93xx_timer_interrupt;//时钟中断服务程序…

setup_arm_irq(IRQ_TIMER1,

&timer_irq);//安装时钟中断处理…

}

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:读写I2C的RTC设备,软件模拟方式,引自i2c-ep93xx.c,将在下面详细介绍.

extern uchar pcf8563_readdata(uchar

address);

extern int pcf8563_writedata(uchar

address, uchar mdata);

#define CMOS_READ(addr)pcf8563_readdata(addr)

#define CMOS_WRITE(data, addr)pcf8563_writedata(addr, data)

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:设置及读写RTC,主要通过宏CMOS_READ/ CMOS_WRITE完成功能.

unsigned long get_cmos_time(void)

static int set_cmos_time(unsigned long

nowtime)

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:时钟中断服务程序,并检测更新系统时钟到RTC,在setup_timer()当中调用.

/*

* IRQ handler for the timer

*/

static void ep93xx_timer_interrupt(int

irq, void *dev_id, struct pt_regs *regs)

{

outl(

1, TIMER1CLEAR );

do_leds();

do_set_rtc();//houhh 0713,检测是否要更新系统当前时间到cmos rtc设备...

do_timer(regs);

do_profile(regs);

}

一般认为时钟中断计时的精度较高,所以在时钟中断服务程序中会每隔11分钟(660秒)就检测一次是否须要将此时的系统时间写回到RTC当中,所以在ep93xx的时钟中断服务程序中,须要加上do_set_rtc(),关于这个函数具体的功能请具体参见源码.

四.RTC设备驱动程序

主要指出RTC设备及相关操作,这一块相当简单,RTC时钟处理成一简单字符设备.

基础文件结构:

static struct file_operations

pcf8563_fops = {

owner:

THIS_MODULE,

ioctl:

pcf8563_ioctl,

open:

pcf8563_open,

release:

pcf8563_release,

};

文件:linux-2.4.21\drivers\char\pcf8563_rtc.c

描述: RTC设备的文件结构,指文件打开及释放操作,其实最核心的还是ioctl,这里可以进行时间读取以及时间设置操作,具体使用示例可以参考rtctest.c示例文件.

文件:linux-2.4.21\drivers\char\pcf8563_rtc.c

描述:时间设置与读取.

get_rtc_time(struct rtc_time *tm);

Set_rtc_time(struct rtc_time *tm);

文件:linux-2.4.21\drivers\charpcf8563_rtc.c

描述:I2C读写,这两个函数是从文件i2c-ep93xx.c中引用的,定义成宏.

extern int

pcf8563_writedata(uchar address, uchar mdata);

extern uchar

pcf8563_readdata(uchar address);

#define

rtc_reg_read(x)pcf8563_readdata(x)

#define

rtc_reg_write(x,y)pcf8563_writedata(x,

y)

文件:linux-2.4.21\drivers\charpcf8563_rtc.c

描述:模块初始化,负责注册/注消RTC字符设备.

int __init pcf8563_init(void)

void __exit pcf8563_exit(void)

五.I2C总线读写

i2c总线读写方式为软件模拟方式,因为ep93xx没有相关的i2c总线控制器,因此只能通过软件方式来模拟I2C总线的读写,在调试过程中遇到如下问题,注意如下即可:

1.注意延时的时间,I2C的开始条件与读写时都须要一定的延时,根据主设备(即ep93xx cpu)的运行速度,此延时必须是一个稳定的时间,通常采取读取特定外设i/o以达此目的.

2.注意在改变I2C的SDA数据线状态时,必须是在SCL时钟线为低的时候,因为根据开始与结束条件的要求,开始与结束条件是在SCL时钟线高的时候SDA拉高或者拉低,所以如果在传送数据时,SCL为高,则会被当成开始或结束条件,通信失败.

3.在读写I2C总线时,每传送或接收一BYTE数据,必须要进行回应.

Ø写回应:主设备写完八位数据后,在第九个周期等待从设备来拉低SDA作为回应,因此须先将SDA在第八周期SCL低时拉高,之后拉高SCL等从设备回应,等到从设备回应后拉低SCL,第九周期结束,一个BYTE传送完成.

Ø读回应:主设备读从设备八位数据后,也应该在第九周期进行回应,分如下两种情况:连续读n个字节时,前n-1个字节以拉低作回应,第n个字节则为拉高SDA回应,因此如若是每次只读一个字节,则回应为拉高.

4.操作SDA/SCL PIN时,注意在读SDA时,将其设置成输入状态.

文件:linux-2.4.21\drivers\i2ci2c-ep93xx.c

描述:SDA/SCL PIN脚定义,为EP93xx的GPIO口G口的第0, 1位.

#define I2C_SDA_PORTGPIO_PGDR

#define I2C_SDA_DIRGPIO_PGDDR

#define I2C_SDA_MASK0x2//EEDAT...

#define I2C_SCL_PORTGPIO_PGDR

#define I2C_SCL_DIRGPIO_PGDDR

#define I2C_SCL_MASK0x1//EECLK...

文件:linux-2.4.21\drivers\i2c\ i2c-ep93xx.c

描述:SDA/SCL输入输出.

static void bit_ep93xx_setscl(void* data, int state)

{

unsigned

long flags;

save_flags(flags);

outl(inl(I2C_SCL_DIR)

| I2C_SCL_MASK, I2C_SCL_DIR);//

tristate pin

if (state){

cli();

outl(inl(I2C_SCL_PORT)

| I2C_SCL_MASK, I2C_SCL_PORT); // drive pin

}

else{

cli();

outl(inl(I2C_SCL_PORT)

& ~I2C_SCL_MASK, I2C_SCL_PORT); // drive pin

}

restore_flags(flags);

}

//===========================================================================

///write SCL pin

//===========================================================================

static void bit_ep93xx_setsda(void* data, int state)

{

unsigned

long flags;

save_flags(flags);

outl(inl(I2C_SDA_DIR)

| I2C_SDA_MASK, I2C_SDA_DIR);//

output...

if (state){

cli();

outl(inl(I2C_SDA_PORT)

| I2C_SDA_MASK, I2C_SDA_PORT); // drive pin

}

else{

cli();

outl(inl(I2C_SDA_PORT)

& ~I2C_SDA_MASK, I2C_SDA_PORT); // drive pin

}

restore_flags(flags);

}

具体有关I2C总线软件模拟通信部分,参考源码.

六.Linux下的I2C驱动层次结构.

Linux最大的特点,就是将一系列的驱动中共有的东西抽象出来,大大提高代码的共享与利用率,这样使具体硬件设备的驱动作者无须关注驱动中共性的部分,I2C驱动同样如此,这里我不打算详细的分析I2C的层次结构,只是简单的陪析一下其大略的层次,并指出几个不易理解的地方。

1.适配器层(adapters)

描述:这一层简单的理解,可以理解成是I2C总线的抽象,它提供访问I2C总线的方法规则,并包含在物理总线上适用此规则进行I2C通信的I2C设备,而且I2C设备并不须是在同一条总线上,只须满足相同的访问I2C总线方法规则,且设备地址不重重叠.

2.I2C设备驱动层(i2c client driver)

描述:具体到每个挂在I2C上的设备,针对特定的设备可以有不同的驱动,诸如设备标志ID,设备名称,设备属性志Flag,还有就是设备连接及拔除的相应处理.

3.I2C数据传送规则层(algorithms)

描述:针对具体的I2C总线,可以有不同的数据传输规则,分为三种:

ü大体来说如果本身就有I2C控制器的, I2C使用起来就比较简单,传输入数据时就是读写一些寄存器即可.

ü通地软件模拟方式进行数据传输, I2C中这个规则被称为algorithms

for bit-shift,这种编程起来稍微麻烦一些,要用软件模拟I2C的通信协议规则.

ü通过每三方总线间接的连接I2C总线的,如ISA总线,此时I2C是间接连接在系统上,这种方式通常与具体的所连接上的总线相关.

另外,还有一种规则是针对SMBus总线,这个总线是Intel推出的兼容I2C协议的,可能一次传送一个字,两个字节,多个字节等等,这里没有用到,并不详述,由此我们可以理会I2C设计者设计的结构的灵活程度.

以上简述了I2C驱动层次结构,现在具体的就PCF8563 RTC来描述每一层具体由哪些文件组成,结合实际的驱动文件说明:

1.适配器层----------------- i2c-core.c, i2c-dev.c

2.I2C设备驱动层---------pcf8563-rtc.c

3.I2C数据传送规则层--- i2c-algo-bit.c,

i2c-ep93xx

大体对照I2C驱动的层次结构,结合以上文件来看,可以理解I2C的驱动.

I2C驱动代码中不易理解的几点:

结合在代码阅读时我所经历的过程,说明以下几点不易明白的地方:

1.i2c设备的识别

描述:每个I2C设备有自己特定的设备地址,通常为读地址及写地址; 2.4版当中是采取遍历的方式来查找I2C设备应该,每个i2c设备都会标明自己所处的地址,描述i2c设备所处地址范围的结构比较复杂,它包括描述i2c设备所处地址范围,要忽略的地址范围,应当强制检测的范围等,个人感觉些结构设计过于重复,在2.6版中已经简化.

具体的I2C设备检测时,即根据这些地址,从0~0x7f开始检测,检测时会跳出过无须检测的范围.比如一i2c设备写地址为0xa0,则在此描述时给出的地址值是0x50,因此在真实写此设备时,必须将地址左移两位得真实地址.

2.4版i2c地址描述结构:

struct

i2c_client_address_data {

unsigned short *normal_i2c;

unsigned short *normal_i2c_range;

unsigned short *probe;

unsigned short *probe_range;

unsigned short *ignore;

unsigned short *ignore_range;

unsigned short *force;

};

2.6版i2c地址描述结构:

struct

i2c_client_address_data {

unsigned short *normal_i2c;

unsigned short *probe;

unsigned short *ignore;

unsigned short **forces;

};

2.i2c设备设备的读与写

描述:设备地址一般为7位,此地址是字节的高七位,最低的一位用于描述是读设备还是写设备,因此可知I2C设备读写地址必然相连,因此读写地址转换通常为最低位或上1,或者清除1,另外还记得这里描述的地址必须左移两位,因为给出的设备地址是以低七位形式给出.

写地址一般是小于读地址,所以通常读地址都是写地址或上1即得,因此在设备驱动结构i2c_client中,仅用一addr成员来描述设备地址.

具体来说,看i2c-algo-bit.c文件下的bit_doAddress()函数,如果是七位地址的I2C总线,其处理如下,将设备地址左移两位得写地址,然后或上1,即得读地址.

addr

= ( msg->addr << 1 );

if

(flags & I2C_M_RD )

addr

|= 1;

3.i2c adapters及algorithms的管理.

描述:每一个adapters都会有一个唯一的标志,如我们的EP93xx则定义为I2C_HW_B_EP93XX,每一种algorithms都也有一个ID标志, bit-shift algorithms的标志即为: I2C_ALGO_BIT.,关于这些标志的定义,可以查看i2c-id.h文件.

具体的,添加一个bit-shift algorithms的adapters ,可以调用i2c_bit_add_bus()函数,这个调用由用户发出,一般在模块加载的init函数中调用,每当加入一个adapters,都会检测已经注册的i2c driver设备是否适用此adapters,如果适用则调用该i2c设备driver下的attach_adapter,通知设备发现其相应适配器,只有找到相应适配器,该设备才能使用i2c总线.

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。