定时器——S5PV210定时器的理论与操作

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、定时器简介

1、定时器的含义

定时器作为SoC的外设,主要用来实现定时执行代码的功能,它相对SoC而言,就像闹钟相对于人的意义一样。定时器内部的计数器每隔一个固定时间会计一个数,计数值 * 计数时间周期 = 一段时间,这个时间段就是我们定的时间。

2、定时器的作用

定时器具有计时功能,计时结束后,定时器会产生中断提醒CPU。此时CPU会去处理中断,并执行定时器中断的ISR,从而执行预先设定好的事件。定时器就好比CPU的秘书,这个秘书专门用来计时,并到时间后提醒CPU做某件事情。

3、定时器的原理

定时器通过计数来实现定时。定时器内部有一个计数器。计数器的时钟,由ARM的APB总线时钟,经过时钟模块内部的分频器的分频得到。每隔一个时钟周期,计数器就计数一次,定时器的时间 = 计数器计数值×时钟周期。

定时器内部有1个寄存器TCNT。计时开始时,把总的计数值放入TCNT寄存器中,然后每隔一个时钟周期(一高一低)TCNT中的值会自动减1,这减1的操作由硬件自动完成,不需要CPU软件去干预。等到TCNT中减为0时,TCNT就会触发定时器中断。

由此可知,定时时间主要TCNT中的计数值、时钟周期这两个因素决定。

4、看门狗、RTC与蜂鸣器

这几个东西都是和时间有关的部件。

看门狗其实就是一个定时器,只不过定时时间到了之后不只是中断,还可以复位CPU。

RTC是实时时钟,它和定时器的差别就好象闹钟(定时器)和钟表(RTC)的差别一样。

蜂鸣器是一个发声设备,在ARM里面蜂鸣器是用定时器模块来驱动的。

二、S5PV210的四类定时器

1、PWM定时器

这是最常用的定时器,平时说的定时器一般指这个。像51单片机中的定时器就是这类。

因为SoC中一般都是依靠这类定时器产生PWM信号,所以把这类定时器叫PWM定时器。

2、系统定时器

操作系统定时器,也是用来产生固定时间间隔信号的。这个时钟信号称为systick,用来给操作系统提供tick信号,作为操作系统的时间片(time slice)。

一般做操作系统移植时,这里不会由我们来做,原厂提供的基础移植部分就已经包含这部分内容了,所以几乎不需要研究。

3、看门狗定时器

看门狗定时器本质上也是一个定时器,和上面2个没有任何本质区别。

看门狗定时器可以设置成定时结束后产生中断,或者发出复位信号复位CPU。

看门狗定时器在实践中应用很多,尤其是工业领域。环境复杂干扰多的条件下,机器容易出问题,而且出问题的后果很严重,此时一般都会用看门狗来进行系统复位。

4、实时时钟(RTC,real time clock)

时间点是绝对的,是绝无仅有的一个时间点(xx年x月x日……);时间段是相对的,两个时间点相减就会得到一个时间段。

RTC关注的是具体的一个时间点;定时器关注的是时间段,从定时器开始的那一刻开始,到定下的时间段结束为止。RTC和定时器的区别,就相当于钟表和闹钟的区别。

三、S5PV210中的PWM定时器

1、为什么叫PWM定时器?

PWM定时器本质上是定时器,因为它主要用来产生PWM波形,所以叫PWM定时器。

2、PWM定时器介绍

S5PV210有5个PWM定时器。其中PWM定时器0、1、2、3分别对应一个外部GPIO,即GPD0_0\1\2\3,可以通过这些对应的GPIO产生PWM波形信号并输出;PWM定时器4没有对应的外部GPIO,因为它不是为了生成PWM波形而设计的,而是为了产生内部定时器中断而设计的。

3、S5PV210的PWM定时器框图

关于PWM部分的内容,在数据手册第787页。

(1)时钟源

S5PV210的5个PWM定时器的时钟源为PCLK_PSYS。

(2)预分频器与分频器

预分频器prescaler有2个,其中timer0和timer1共用一个预分频器,timer2~4共用一个预分频器。两个预分频器都是8个bit位,因此prescaler value范围为0~255,因此预分频器的分频值范围为1~256(因为实际分频值为prescaler value + 1)。

每个timer还有一个专用的分频器,其实就是是一个MUX开关,多选一开关决定了走哪个分频系数路线,可选的有1/1,1/2,1/4,1/8和1/16。

预分频器和分频器构成了2级分频系统,将 PCLK_PSYS 两级分频后,输入timer模块。由于两级分频是串联的(或者说级联的),因此两级分频的分频数是相乘的,因此总的分频系数最小为1/1(也可能是1/2),最大分频为1/256×16(1/4096)。在PCLK_PSYS为66MHz的情况下(默认时钟设置就是66MHz),两级分频后的时钟周期范围为0.03us到62.061us,再结合TCNTB的值的设置(范围为1至2的32次方),可知最长的定时时间为266548.27s,折合74小时多,远远够用。

两级分频的分频系数分别在TCFG0和TCFG1两个寄存器中设置。

(3)TCNTBn寄存器、TCMPBn寄存器、TCNTOn寄存器、TCNT寄存器

TCNT寄存器和TCNTB寄存器在SoC内部是对应的。TCNT没有寄存器地址,程序员不能编程访问这个寄存器;TCNTB是有地址的寄存器,可供程序员操作。TCNT寄存器功能就是用来减1的,它是内部的不能读写。要向寄存器TCNT中写数值时,需要通过TCNTB寄存器来写入;要读取TCNT寄存器中的数值时,需要通过TCNTO寄存器来读取。

单纯的定时功能(也就是定时一次)只需要TCNT、TCNTB这两个寄存器即可。TCNTO寄存器用来做一些捕获计时,TCMPB用来生成PWM波形。

(4)如何设置定时

先根据时钟周期与定时的时长,计算出TCNT寄存器中开始的数字,然后把它写入TCNTB寄存器。在启动timer前,将TCNTB中的值刷到TCNT寄存器(有一位寄存器专门用来将数据刷过去),然后启动定时器开始计时。在定时的过程中,如果想获取TCNT寄存器中的值,可以读取相应的TCNTO寄存器。

4、自动重载和双缓冲机制

定时器默认只运行一次,也就是说定时结束时产生中断后,这一次定时就算完成了。下次如果还要再定时中断,需要另外设置。

现实中往往需要循环使用定时器,我们可以写代码(在每次中断处理的isr中再次给TCNTB中赋值,再次刷到TCNT中再次启动定时器)重置定时器寄存器的值,比如早期的单片机定时器就是这样的。但高级SoC比如S5PV210中的定时器,内置有循环定时工作模式,即自动装载(auto-reload)机制。

自动装载机制,就是当定时器完成初始化并开始计时后,就不需再进行设置,当一次定时结束时,硬件会自动地将TCNTB寄存器中的值再次写到TCNT寄存器中,再次启动定时器以开始下一个循环。

5、PWM的含义及运用

PWM的全称是pulse wide modulation,中文意思是“脉宽调制”。

PWM波形是周期性波形,每个周期内波形是完全相同的,由一个高电平和一个低电平组成。

PWM波形有2个重要参数,一个是周期T,另一个是占空比(Duty Ratio,一个周期内高电平的时间与周期时间的比值)。

PWM波形的用处包括:通信上用PWM来进行脉宽调制对基波进行载波调制;在LED照明领域可以用PWM波形来调制电流进行调光,见博客PWM信号用于调节台灯亮度;用来驱动蜂鸣器等设备。

6、PWM波形的生成原理

早期的单片机(比如51单片机)没有专用的PWM定时器,需要自己结合GPIO和定时器模块来生产PWM波形:先将GPIO引脚电平拉高,同时启动定时器定T*duty时间,时间到了在isr中将电平拉低,然后定时T*(1-duty)后再次启动定时器,然后时间到了后在isr中将电平拉高,然后再定时T*duty时间再次启动定时器,如此反复。

因为定时器经常和PWM的产生有关,所以在设计SoC的时候就直接把定时器和一个GPIO引脚绑定起来,然后在定时器内部设置PWM产生的机制,可以更方便地产生PWM波形。利用PWM定时器来产生PWM波形时,可以直接生成PWM而不使用中断。

在S5PV210中,PWM波形产生有2个关键寄存器,一个是TCNTB,一个是TCMPB。TCNTB决定PWM波形的周期,TCMPB决定PWM波形的占空比。最终生成的PWM波形的周期为T=TCNTB×时钟周期(PCLK_PSYS经过两极分频后得到的时钟周期);最终生成的PWM波形的占空比是TCMPB/TCNTB。

注意,如果使用PWM定时器只做一次定时,定时结束时会产生中断给CPU。而这里因为要产生PWM波形,所以要启用自动重载机制,定时结束时不产生中断(?),而是立刻开始下一次的计时。PWM定时器如果不启用自动重载而作为一次定时,则定时的时长为TCNTB*时钟周期;PWM定时器用来产生PWM波形时,周期等于TCNTB*时钟周期。可知,一次定时的时长与PWM波形的周期相等。

7、输出电平翻转器

PWM定时器可以规定:当TCNT>TCMPB时为低电平,当TCNT<TCMPB时为高电平。也可以规定:当TCNT>TCMPB时为高电平,当TCNT<TCMPB时为低电平。在这两种规定下,计算时TCMP寄存器的值会变化。

基于上面讲的,当duty从30%变到70%时,我们TCMPB寄存器中的值就要改(比如TCNTB中是300时,TCMPB就要从210变化到90)。这样的改变可以满足需要,但是计算有点麻烦,于是S5PV210的PWM定时器帮我们提供了一个友好的工具叫做电平翻转器。

电平翻转器在电路上的实质就是一个电平取反的部件,在编程上反映为一个寄存器位。写0就关闭输出电平反转,写1就开启输出电平反转。开启后和开启前输出电平刚好高低反转。输出电平一反转30%的duty就变成70%了。

实战中到底是TCNT和TCMPB谁大谁小时高电平还是低电平,一般不用理论分析,只要写个代码然后用示波器实际看一下出来的波形就知道了,如果反了就直接开启电平翻转器即可。

8、死区生成器

在功率电路中,可以用PWM来对交流电压进行整流。整流时2路整流分别在正电平和负电平时导通工作,不能同时导通(同时导通会直接短路,瞬间的同时导通都会导致电路烧毁)。大功率的开关电源、逆变器等设备广泛使用了整流技术。特别是逆变器,用SoC的GPIO输出的PWM波形来分别驱动2路整流的IGBT。

PWM波形用来做整流时,电平不能同时高或低,因为会短路。但是实际电路是不理想的,不可能同时上升/下降,所以比较安全的做法是留死区。

死区少了容易短路,而死区多了控制精度低,不利于产品性能的提升。

S5PV210提供了自带的死区生成器,只要开启死区生成器,生产出来的PWM波形就自带死区控制功能,用户不用再自己去操心死区问题。

大部分人工作是用不到这个的,直接关掉死区生成器即可。

四、PWM定时器编程实践

1、蜂鸣器的工作原理

蜂鸣器里面有两片金属片,离的很紧但没挨着。没电的时候两个片在弹簧本身张力作用下分开彼此平行。有电的时候两边分别充电,在异性电荷的吸力作用下两个片挨着。我们只要以快速的频率给蜂鸣器的正负极供电与断电,蜂鸣器的两个弹簧片就会挨着分开挨着分开,从而敲击发出声音。

频率高低会影响音频,一般音频越低声音听起来越低沉,音频越高听起来越尖锐。因为人的耳朵能听见的声音频率范围是20Hz-20000Hz,我们做实验时一般是2KHz的频率。

只要用PWM波形的电压信号来驱动蜂鸣器,把PWM波形的周期T设置为要发出的声音信号的1/频率即可;PWM的占空比只要确保能驱动蜂鸣器即可(一般引脚驱动能力都不够,所以蜂鸣器会额外用三极管来放大流来供电)。

2、蜂鸣器原理图和硬件信息

 由原理图可知,蜂鸣器通过GPD0_2(XpwmTOUT2)引脚连接在SoC上。GPD0_2引脚通过限流电阻接在三极管基极上,引脚有电则三极管导通,蜂鸣器就会有电;引脚没电则三极管关闭,蜂鸣器就会没电。但这些都是硬件问题,软件工程师不用理会,他们只要写程序控制GPD0_2引脚的电平产生PWM波形即可。

另外,由GPD0_2这个引脚的信息可推出蜂鸣器使用的是timer2这个PWM定时器。所以涉及的寄存器的后缀2,表示与timer2相关的意思。

3、PWM定时器的主要寄存器

与蜂鸣器相关的寄存器有TCFG0、TCFG1,CON,TCNTB2、TCMPB2、TCNTO2。

 (1)TCFG0、TCFG1寄存器

见上一节的描述,两级分频的分频系数分别在TCFG0TCFG1两个寄存器中设置。

(2)TCON寄存器

这个寄存器用来设置5个PWN定时器是否启用循环工作模式、是否更新TCNTBn、是否启动或者停止定时器、电平是否反转、死区功能是否使能等内容。

(3)TCNTO2寄存器

TCNTO2寄存器用来做一些捕获计时,也就是说,如果要读取TCNT寄存器中还剩多少数值,可以通过TCNTO寄存器来读取。

(4)TCNTB2寄存器

TCNTB2寄存器用来写入计数值,也就是说,要向寄存器TCNT中写数值时,需要通过TCNTB2寄存器来写入。

(5)TCMPB2寄存器

TCMPB2寄存器主要用来设置PWM波形的占空比。

已知占空比 = TCMPB / TCNTB ,PWM波形的周期为T=TCNTB×时钟周期(PCLK_PSYS经过两极分频后得到的时钟周期)。所以TCMPB这个寄存器,存储的应该是高电平持续时长所对应的计数值;TCNTB这个寄存器,存储的应该是一个PWM周期所对应的计数值。所以占空比才会等于(TCMPB*时钟周期 )/ (TCNTB*时钟周期 ),约去时钟周期这个因数,就等于TCMPB / TCNTB。

以上寄存器的结合很巧妙。如果只用一个寄存器,让这个寄存器既用来减数,又能用来读写,那么可能会误操作。这里设计了3个寄存器:TCNT/TCNTB/TCNTO,TCNT负责减1,TCNTB负责写入,TCNTO负责读取。TCNTB中的值写入后是不会改变的,TCNT对外部不可见,TCNTO读取到的值会随着读取的时刻不同而不同。

4、蜂鸣器和PWM定时器编程实践

(1)关键代码如下,完整代码见链接。

#define 	GPD0CON		(0xE02000A0) //设置输出模式为PWM	
#define 	TCFG0		(0xE2500000)
#define 	TCFG1		(0xE2500004)//设置分频系数
#define 	CON			(0xE2500008) //设置PWM定时器的一些属性
#define 	TCNTB2		(0xE2500024) //设置PWM波形周期=TCNTB2*时钟周期
#define 	TCMPB2		(0xE2500028) //设置占空比=TCMPB2*时钟周期/(TCNTB2*时钟周期)#define 	rGPD0CON	(*(volatile unsigned int *)GPD0CON)
#define 	rTCFG0		(*(volatile unsigned int *)TCFG0)
#define 	rTCFG1		(*(volatile unsigned int *)TCFG1)
#define 	rCON		(*(volatile unsigned int *)CON)
#define 	rTCNTB2		(*(volatile unsigned int *)TCNTB2)
#define 	rTCMPB2		(*(volatile unsigned int *)TCMPB2)// 初始化PWM timer2,使其输出PWM波形:频率是2KHz、duty为50%
void timer2_pwm_init(void)
{// 设置GPD0_2引脚,将其配置为XpwmTOUT_2rGPD0CON &= ~(0xf<<8);rGPD0CON |= (2<<8);// 设置PWM定时器的相关寄存器,使其工作rTCFG0 &= ~(0xff<<8);rTCFG0 |= (65<<8);			// prescaler1 = 65, 预分频后频率为1MHzrTCFG1 &= ~(0x0f<<8);rTCFG1 |= (1<<8);			// MUX2设置为1/2,则2级分频后时钟频率为500KHz// 时钟设置好,我们的时钟频率是500KHz,对应的时钟周期是2us。也就是说每隔2us// 计一次数。如果要定的时间是x,则TCNTB中应该写入x/2usrCON |= (1<<15);		// 使能auto-reload,反复定时才能发出PWM波形//rTCNTB2 = 250;			// 0.5ms/2us = 500us/2us = 250//rTCMPB2 = 125;			// duty = 50%rTCNTB2 = 50;			rTCMPB2 = 25;	// 第一次需要手工将TCNTB中的值刷新到TCNT中去,以后就可以auto-reload了rCON |= (1<<13);		// 打开自动刷新功能rCON &= ~(1<<13);		// 关闭自动刷新功能rCON |= (1<<12);		// 开timer2定时器。要先把其他都设置好才能开定时器
}

(2)测试。将完整的例子在linux中编译之后,通过usb启动方式下载到开发板。可以看到SCRT中不断输出字符“a”,同时蜂鸣器响起。

五、看门狗定时器

1、看门狗定时器的简介

看门狗定时器和普通的定时器并无本质区别。看门狗定时器可以设定一个定时,定时到之后定时器会复位CPU而重启系统。

系统正常工作的时候当然不希望被重启,但是当系统受到干扰,或者极端环境的情况下,系统可能会产生异常工作或者不工作,这种状态可能会造成不良影响,此时的解决方案就是重启系统。普通设备的重启不是问题,但是有些设备人工重启存在困难,我们希望系统能够自检是否失常,并且在意识到自己失常的时候,可以很快地自我重启。这个功能就要依靠看门狗定时器来实现。

看门狗定时器的典型应用的情景:我们在应用程序中打开看门狗设备,初始化好给它一个定时时间,然后应用程序使用一个线程来喂狗。当系统异常后,喂狗线程停止工作,因而不能及时喂狗,系统就会复位。

实战中有时候为了绝对的可靠,我们并不会用SoC中自带的看门狗,而是使用专门的外置的看门狗芯片来实现看门狗。

2、S5PV210看门狗定时器的结构框图

由图可知,PCLK_PSYS经过两级分频后生成WDT(watchdog timer)的时钟周期,然后把要定的时间写到WTDAT寄存器中,刷到WTCNT寄存器中去减1,减到0时(即定时结束)产生复位信号或中断信号。典型应用是配置为产生复位信号,我们应该在WTCNT寄存器减到0之前给WTDAT寄存器中重新写值以喂狗。

看门狗定时器的时钟信号的周期为:PCLK / (Prescaler value + 1) / Division_factor。

注意,和PWM定时器有5个不同,S5PV210只有一个看门狗定时器。

3、看门狗定时器的主要寄存器

与看门狗定时器有关的寄存器有:WTCON、WTDAT、WTCNT、WTCLRINT寄存器。

 (1)WTCON寄存器

该寄存器用来设置是否使能看门狗定时器、选择预分频的分频系数、是否启用中断、是否产生复位信号等等内容。

(2)WTDAT寄存器

这个寄存器用来设置定时计数值(刚开始减1的那个数值)。注意WTDAT寄存器的值不能自动写入WTCNT寄存器中,因此也需要初始化WTCNT寄存器。(除非使用默认的初始化计数值,即0x8000。)

(3)WTCNT寄存器

这个寄存器用来存储实时的定时计数值。注意WTDAT寄存器的值不能自动写入WTCNT寄存器中,因此也需要初始化WTCNT寄存器。(除非使用默认的初始化计数值,即0x8000。)

(4)WTCLRINT寄存器

这个寄存器用来清除看门狗定时器的中断。

4、看门狗定时器的编程实践

(1)产生中断信号

关键代码如下,完整的代码见链接。

#define		WTCON		(0xE2700000)
#define		WTDAT		(0xE2700004)
#define		WTCNT		(0xE2700008)
#define 	WTCLRINT	(0xE270000C)#define 	rWTCON		(*(volatile unsigned int *)WTCON)
#define 	rWTDAT		(*(volatile unsigned int *)WTDAT)
#define 	rWTCNT		(*(volatile unsigned int *)WTCNT)
#define 	rWTCLRINT	(*(volatile unsigned int *)WTCLRINT)// 初始化WDT使之可以产生中断
void wdt_init_interrupt(void)
{// 第一步,设置好预分频器和分频器,得到时钟周期是128usrWTCON &= ~(0xff<<8);rWTCON |= (65<<8);				// 1MHzrWTCON &= ~(3<<3);rWTCON |= (3<<3);				// 1/128 MHz, T = 128us// 第二步,设置中断和复位信号的使能或禁止rWTCON |= (1<<2);				// enable wdt interruptrWTCON &= ~(1<<0);				// disable wdt reset// 第三步,设置定时时间// WDT定时计数个数,最终定时时间为这里的值×时钟周期rWTDAT = 10000;					// 定时1.28srWTCNT = 10000;					// 定时1.28s// WTDAT中的值不会自动刷到WTCNT中去,// 因此如果只设置WTDAT而没有设置WTCNT,// 则WTCNT中的值就是默认值0x8000,然后以这个默认值开始计数,时间比较久。// 第四步,先把所有寄存器都设置好之后,再去开看门狗rWTCON |= (1<<5);				// enable wdt
}

运行结果是:SCRT 的显示表明不断地有中断发生。这是因为(1)的代码中设置了1.28s的定时,在这个定时结束前都没有喂狗,所以系统在定时结束时产生一次中断,然后重新定时,在定时结束前还是没有喂狗,因此定时结束又产生了一次中断,然后重新定时……不断地循环重复,所以SCRT才会显示那么多 的中断。不过这个只是我的猜想,因为不知道看门狗定时器是不是也会自动重载,另外 i 的输出情况也不对,为何?

-------------wdt interrupt test--------------
wdt interrupt, i = 1602283875...
wdt interrupt, i = 1602283876...
wdt interrupt, i = 1602283877...
wdt interrupt, i = 1602283878...
wdt interrupt, i = 1602283879...
wdt interrupt, i = 1602283880...
wdt interrupt, i = 1602283881...
wdt interrupt, i = 1602283882...
wdt interrupt, i = 1602283883...
wdt interrupt, i = 1602283884...
wdt interrupt, i = 1602283885...

(2)产生复位信号

关键代码如下(其实就是关中断启动复位,与上面设置相反),完整代码见链接。

#define		WTCON		(0xE2700000)
#define		WTDAT		(0xE2700004)
#define		WTCNT		(0xE2700008)
#define 	WTCLRINT	(0xE270000C)#define 	rWTCON		(*(volatile unsigned int *)WTCON)
#define 	rWTDAT		(*(volatile unsigned int *)WTDAT)
#define 	rWTCNT		(*(volatile unsigned int *)WTCNT)
#define 	rWTCLRINT	(*(volatile unsigned int *)WTCLRINT)// 初始化WDT使之可以产生中断
void wdt_init_reset(void)
{// 第一步,设置好预分频器和分频器,得到时钟周期是128usrWTCON &= ~(0xff<<8);rWTCON |= (65<<8);				// 1MHzrWTCON &= ~(3<<3);rWTCON |= (3<<3);				// 1/128 MHz, T = 128us// 第二步,设置中断和复位信号的使能或禁止rWTCON &= ~(1<<2);				// disable wdt interruptrWTCON |= (1<<0);				// enable wdt reset// 第三步,设置定时时间// WDT定时计数个数,最终定时时间为这里的值×时钟周期rWTDAT = 10000;					// 定时1.28srWTCNT = 10000;					// 定时1.28s// 第四步,先把所有寄存器都设置好之后,再去开看门狗rWTCON |= (1<<5);				// enable wdt
}

运行结果如下,就只显示这么一行,表示仅有一次复位操作。因为这是裸机实验,而且设置成usb启动方式。复位之后,没有继续从usb下载程序到开发板,所以不会执行任何操作,因此就只显示一次。这里的 i 还是显示得不正确,为何?

---wdt reset test---, i = -10647390.

 关于i显示不正确的原因,目前还没有弄明白。不过课程里面是使用SD卡作为启动方式的,而且这种方式启动的情况下 i 的显示是正确的。另外课程提示,因为移植printf的缘故,不能再用usb启动方式,这是为何?我按照SD卡启动方式试了一下,结果上面两种情况下的 i 值显示正确。这是为何?有时间回顾一下printf移植的过程。

-------------wdt interrupt test--------------
wdt interrupt, i = 0...
wdt interrupt, i = 1...
wdt interrupt, i = 2...
wdt interrupt, i = 3...
wdt interrupt, i = 4...
wdt interrupt, i = 5...
wdt interrupt, i = 6...
wdt interrupt, i = 7...
……

六、实时时钟RTC

1、实时时钟的简介

RTC是real time clock的缩写,它表示真实时间,即xx年x月x日x时x分x秒。

RTC是SoC中一个内部外设,拥有独立的晶振提供RTC时钟源(32.768KHz),内部有一些寄存器用来记录时间(年月日时分秒星期)。为了在系统关机时让时间继续流逝,一般会给RTC提供一个电池供电。

2、S5PV210实时时钟的结构框图

从这个框图可以看出,关于时间的寄存器有7个,分别记录着年、月、day(星期几)、时、分、秒、date(几号);另外还有一个闹钟发生器。闹钟发生器可以定闹钟时间,到时间会产生alarm interrupt,通知系统闹钟定时到了。注意闹钟定时,定的是时间点;而timer定时,定的是时间段。

3、实时时钟的主要寄存器

主要的寄存器有:INTP、RTCCON、RTCALM ALMxxx、BCDxxx等寄存器。

 

(1)INTP,中断挂起寄存器

(2)RTCCON,RTC控制寄存器

(3)RTCALM、ALMxxx,闹钟功能有关的寄存器


(4)BCDxxx,与时间有关的寄存器

4、BCD码的简介

RTC中所有的时间(年月日时分秒星期,包括闹钟)都是用BCD码编码的。BCD码本质上是对数字的一种编码,用4位二进制数表示1位十进制数字,比如十进制的56被编码成0b0101_0110,即0x56。

BCD码的作用:将十进制数拆成组成这个十进制数的各个数字的编码,变成编码后就没有位数的限制了。比如123456789123456789,这个数字超出了int的范围,计算机无法直接处理。想让计算机处理这个数,计算机首先得能表达这个数,表达的方式就是先把这个数转成对应的BCD码。

BCD码在计算机中可以用十六进制的形式来表示。也就是说十进制的56转成BCD码后是0x56,在计算机中用0x56来表达(暂时存储与运算)。需要写2个函数,一个是bcd转十进制,一个是十进制转bcd。当要设置时间的时候(譬如要设置为23分),需要将这个23转成0x23,然后再赋值给相应的寄存器BCDMIN;当从寄存器BCDMIN中读取一个时间时(譬如读取到的是0x59),需要转成十进制再去显示(0x59当作BCD码就是59,转成十进制就是59,所以显示就是59分)。

5、RTC的编程实战

rtc.c文件内容如下,是一些与rtc相关的函数操作。

#include "main.h"#define 	RTC_BASE	 (0xE2800000)#define		rINTP      	 (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define		rRTCCON    	 (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define		rTICCNT    	 (*((volatile unsigned long *)(RTC_BASE + 0x44)))#define		rRTCALM    	 (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define		rALMSEC    	 (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define		rALMMIN    	 (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define		rALMHOUR  	 (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define		rALMDATE     (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define		rALMMON    	 (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define		rALMYEAR  	 (*((volatile unsigned long *)(RTC_BASE + 0x68)))#define		rRTCRST      (*((volatile unsigned long *)(RTC_BASE + 0x6c)))#define		rBCDSEC    	 (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define		rBCDMIN   	 (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define		rBCDHOUR     (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define		rBCDDATE     (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define		rBCDDAY      (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define		rBCDMON      (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define		rBCDYEAR     (*((volatile unsigned long *)(RTC_BASE + 0x88)))#define		rCURTICCNT   (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define		rRTCLVD    	 (*((volatile unsigned long *)(RTC_BASE + 0x94)))// 函数功能:把十进制num转成bcd码,譬如把56转成0x56
static unsigned int num_2_bcd(unsigned int num)
{// 第一步,把56拆分成5和6 // 第二步,把5和6组合成0x56return (((num / 10)<<4) | (num % 10));//这里的与表明是2进制,或者说16进制
}// 函数功能:把bcd码bcd转成十进制,譬如把0x56转成56
static unsigned int bcd_2_num(unsigned int bcd)
{// 第一步,把0x56拆分成5和6 // 第二步,把5和6组合成56return (((bcd & 0xf0)>>4)*10 + (bcd & (0x0f)));
}// 函数功能:设置rtc的时间
void rtc_set_time(const struct rtc_time *p)
{// 第一步,打开RTC读写开关rRTCCON |= (1<<0);// 第二步,写RTC时间寄存器rBCDYEAR = num_2_bcd(p->year - 2000);rBCDMON = num_2_bcd(p->month);rBCDDATE = num_2_bcd(p->date);rBCDHOUR = num_2_bcd(p->hour);rBCDMIN = num_2_bcd(p->minute);rBCDSEC = num_2_bcd(p->second);rBCDDAY = num_2_bcd(p->day);// 最后一步,关上RTC的读写开关rRTCCON &= ~(1<<0);
}// 函数功能:读取rtc的时间
void rtc_get_time(struct rtc_time *p)
{// 第一步,打开RTC读写开关rRTCCON |= (1<<0);// 第二步,读RTC时间寄存器p->year = bcd_2_num(rBCDYEAR) + 2000;p->month = bcd_2_num(rBCDMON);p->date = bcd_2_num(rBCDDATE);p->hour = bcd_2_num(rBCDHOUR);p->minute = bcd_2_num(rBCDMIN);p->second = bcd_2_num(rBCDSEC);p->day = bcd_2_num(rBCDDAY);// 最后一步,关上RTC的读写开关rRTCCON &= ~(1<<0);
}void rtc_set_alarm(void)
{rALMSEC = num_2_bcd(23);rRTCALM |= 1<<0;rRTCALM |= 1<<6;
}void isr_rtc_alarm(void)
{static int i = 0; printf("rtc alarm, i = %d...", i++);rINTP |= (1<<1);intc_clearvectaddr();
}

main.c文件内容如下。

#include "stdio.h"
#include "int.h"
#include "main.h"int main(void)
{uart_init();system_init_exception();rtc_set_alarm();intc_setvectaddr(NUM_RTC_ALARM, isr_rtc_alarm);intc_enable(NUM_RTC_ALARM);struct rtc_time tRead;while (1){rtc_get_time(&tRead);printf("The time read is: %d.", tRead.second);volatile int i, j;for (i=0; i<10000; i++)for (j=0; j<1000; j++);printf("-------");}/*printf("---rtc write time test---");struct rtc_time tWrite = {.year = 2015,.month = 8,.date = 9,.hour = 18,.minute = 11,.second = 3,.day = 0,};rtc_set_time(&tWrite);printf("---rtc read time test---");struct rtc_time tRead;while (1){rtc_get_time(&tRead);printf("The time read is: %d:%d:%d:%d:%d:%d:%d.", tRead.year,\tRead.month, tRead.date, tRead.hour, tRead.minute, tRead.second, tRead.day);// 读写之间做点延时volatile int i, j;for (i=0; i<10000; i++)for (j=0; j<1000; j++);printf("-------");}*/	while (1);return 0;
}

完整代码见链接,该代码主要完成时间的写入与读取测试、闹钟实验。

由于设置闹钟的时候,在rtc_set_alarm函数中设定秒=23时中断,因此每到23秒则中断进而进到中断处理函数中打印中断次数。

The time read is: 71.//很奇怪,每次开机都是从71开始
The time read is: 75.
The time read is: 0.
The time read is: 4.
The time read is: 8.
The time read is: 13.
The time read is: 17.
The time read is: 22.
rtc alarm, i = 0...//中断服务
The time read is: 26.
The time read is: 30.
The time read is: 35.
The time read is: 39.
The time read is: 43.
The time read is: 48.
The time read is: 52.
The time read is: 57.
The time read is: 1.
The time read is: 5.
The time read is: 10.
The time read is: 14.
The time read is: 18.
rtc alarm, i = 1...//中断服务
The time read is: 23.
The time read is: 27.
---rtc read time test---
The time read is: 2090:25:38:34:76:71:7.
The time read is: 2090:25:38:34:76:75:7.
The time read is: 2090:25:38:34:76:0:7.
The time read is: 2090:25:38:34:76:4:7.
The time read is: 2090:25:38:34:76:8:7.
The time read is: 2090:25:38:34:76:13:7.
The time read is: 2090:25:38:34:76:17:7.
The time read is: 2090:25:38:34:76:22:7.
The time read is: 2090:25:38:34:76:26:7.
The time read is: 2090:25:38:34:76:30:7.
……

阅读代码的时候,注意以下细节。

(1)为了安全,默认情况下RTC读写是禁止的,此时读写RTC的时间是不允许的;当我们要更改RTC时间时,应该先打开RTC的读写开关,然后再进行读写操作,操作完了后立即关闭读写开关。

(2)读写RTC寄存器时,一定要注意BCD码和十进制之间的转换。

(3)BCDYEAR寄存器存的并不是完整的年数,而是基于2000年的偏移量来存储的。比如今年2015年,实际存的就是15(2015-2000=15);还有些RTC芯片是以1970年为基点的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461633.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uboot的移植——DM9000移植的理论基础

以下内容源于朱有鹏嵌入式课程的学习&#xff0c;如有侵权请告知删除。 一、网卡相关的基本知识 1、DM9000网卡芯片和SoC的连接 如上图所示&#xff0c;DM9000网卡芯片是通过SROM总线接口&#xff0c;或者说SROM控制器接入SoC的。下面分别介绍SROM控制器、DM9000网卡芯片的相关…

javascript深入浅出

第一章 数据类型 1&#xff0c;六种数据类型&#xff1a;原始类型&#xff08;number&#xff0c;string&#xff0c;boolean&#xff0c;null&#xff0c;undefined&#xff09; object对象&#xff08;Function Array Date&#xff09; 2&#xff0c;隐式转换&#xff1a;Na…

网络通信基础常识

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 1、网络通信属于进程间通信 进程间通信的一种方法是使用套接字socket&#xff0c;网络通信其实就是位于网络中不同主机上面的2个进程之间的通信。 2、网络通信的层次 网络通信的层次&#xff0c;即…

Android NDK工程创建与编译运行

首发地址&#xff1a;http://www.eoeandroid.com/thread-201993-1-1.html一、 工程创建1. 创建一个Android工程配置好开发环境并加载好对应版本sdk后&#xff0c;选择菜单File->New-> Project创建工程&#xff0c;工程类型选择Android Project&#xff0c;如下图所示&…

[Quatsch]Quantum Or Optics

小生预言&#xff0c;鉴于目前CG产业中学术界与生产界之间的差异&#xff0c;未来必将有一门独立的学科&#xff0c;专门用于研究自然界的材质特性与表现&#xff0c;从此艺术界与学术界将呈现完美之统一&#xff0c;应该先在MIT或者Cornell出现。本人姑且将这门学科命名为“应…

C#事件 的讲解

一、事件的本质事件是软件系统里的两个子系统之间&#xff0c;或者两个模块之间&#xff0c;或者两个对象之间发送消息&#xff0c;并处理消息的过程。在面向对象的世界里&#xff0c;就可以统一认为是两个对象之间的行为。两个对象之间发送的这种消息&#xff0c;对发送方来讲…

phpMyAdmin密码设置

2019独角兽企业重金招聘Python工程师标准>>> 在使用phpMyAdmin操作数据库时&#xff0c;首先需要设置mysql的root用户的密码。具体操作参考前一篇博文。设置完mysql的root密码之后&#xff0c;需要对phpMyAdmin的登录进行一些配置。 未经配置的phpMyAdmin很不安全&a…

抽取网络信息进行数据挖掘 建立语料库

最近的实习项目需要做一个大数据库&#xff08;语料库&#xff09;&#xff0c;采集博客、微博、问答库的信息。将数据库的内容进行训练&#xff0c;最后应该是做成一个类似中文siri的模型吧。 第一步新闻抓取器已经稳定运行了&#xff0c;基本原理用的是爬虫去爬新闻门户网站的…

四旋翼无人机调研结果

P.S&#xff1a;作者曾在大学某次小班课上看到该视频&#xff0c;深深被震撼了&#xff0c;因此对无人机念念不忘。 恰逢某课程要求讲解自己喜欢的领域的内容&#xff0c;因此对四旋翼无人机进行了调研。

选购四轴飞行器的部件

内容截于&#xff1a;http://www.loveuav.com/article-191-1.html

重复编辑命令行

为什么80%的码农都做不了架构师&#xff1f;>>> 要想重复前面已经输入的命令&#xff0c;请按向上方向键。每按这个键一次&#xff0c;shell都会显示前一个命令行。要想重新执行所显示的命令行&#xff0c;请按回车键。按向下方向键&#xff0c;则可以沿着相反的方…

hadoop2.2.0 分布式存储hdfs完全分布式搭建及功能测试记录(一)----架构及原理介绍...

0.文档说明&#xff1a;本文是围绕hadoop2.2的分布式文件系统hdfs进行分布式存储功能测试&#xff0c;形成的hdfs分布式存储功能测试报告&#xff0c;其中主要包括三大部分内容&#xff1a;第一部分介绍了hdfs的基本原理&#xff1b;第二部分介绍了hadoop2.2的完全分布式集群安…

宏定义函数container_of的解释

从kernel里面抠出的一些与宏container_of有关的代码&#xff0c;如下&#xff1a; 1、此宏作用是从结构体的某元素&#xff08;member&#xff09;出发&#xff0c;得到结构体的首地址&#xff1b; 2、container_of的参数解释 &#xff08;1&#xff09;type&#xff1a;指的是…

【ASP.NET Web API教程】2.3 与实体框架一起使用Web API

2.3 Using Web API with Entity Framework 2.3 与实体框架一起使用Web API 本小节是ASP.NET Web API第2章的第3小节&#xff0c;原文共分为7个部分&#xff0c;分成了7篇文章&#xff0c;故这里也分为7个帖子贴出&#xff0c;以下是本小节的第1部分 — 译者注。 Part 1: Overvi…

ITTC数据挖掘平台介绍(综述)——平台简介

数据挖掘方兴未艾&#xff0c;大量新事物层出不穷。本系列将介绍我们自主设计的数据挖掘软件平台。与大家共同分享对知识&#xff0c;微博&#xff0c;人际等复杂网络的分析&#xff0c;以及对自然语言处理的见解。 一、我们需要怎样的数据挖掘系统 一直以来&#xff0c;以高校…

Linux—程序包安装与管理

1、软件包是对于一种软件所进行打包的方式。在不同的操作系统中&#xff0c;软件包的类型有很大的区别。对于Linux系统中&#xff0c;软件包主要以两种形式出现&#xff1a;二进制包以及源代码包。二进制包&#xff1a;1&#xff09;传统的red hat linux二进制包2&#xff09;d…

Master-Detail(主表明细),确认可以出货的SQL指令 -- Not Exists

这是我文章的备份&#xff0c;原文请看&#xff1a; http://www.dotblogs.com.tw/mis2000lab/archive/2011/08/18/master_detail_finish_and_shipping.aspx [补充]下集&#xff0c;第十四章。Master-Detail&#xff08;主表明细&#xff09;&#xff0c;确认可以出货的SQL指令…

开发板——X210开发板的SD卡启动方式

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 前言与总结 这里说的SD卡启动方式&#xff0c;指的是uboot在SD卡中或者在inand里&#xff0c;且启动介质拨码开关选择SD卡启动方式&#xff08;对于X210&#xff0c;是拨到远离电源键的一侧&#xf…

使用iBATIS3.0完成增删改查

为什么80%的码农都做不了架构师&#xff1f;>>> 使用iBATIS3.0完成增删改查 iBATIS3.0和以前的版本有一些改变&#xff0c;不过学过以前版本的再学习3.0应该不是太难&#xff0c;3.0要求JDK1.5支持&#xff0c;因为其中增加了注解和泛型&#xff0c;这些都是JDK1.5…

oracle-11g-R2监听文件配置

客户端连接oracle数据库时出现如下错误&#xff1a; Listener refused the connection with the following error: ORA-12514, TNS:listener does not currently know of service requested in connect descriptor 首先看看Oracle服务是否开启&#xff1a; 然后找到listener.or…