系统时钟和定时器
《嵌入式Linux应用完全开发手册》第2篇第10章总结归纳
本章目标
- 了解S3C2410/S3C2440的时钟体系结构
- 掌握通过设置MPLL改变系统时钟的方法
- 掌握在不同频率下设置存储控制器的方法
- 掌握PWM定时器的用法
- 了解WATCHDOG定时器的用法
时钟体系以及各类时钟部件
S3C2410/S3C2440时钟体系
S3C2410/S3C2440的时钟控制逻辑既可以外接晶振,然后通过内部电路产生时钟源;也可以直接使用外部提供的时钟源,它们通过引脚的设置来选择。时钟控制逻辑给整个芯片提供了3种时钟。FCLK用于CPU核,HCLK用于AHB总线上的设备,比如CPU核、存储控制器、中断控制器、LCD控制器、DMA和USB主机模块等,PCLK用于APB总线上的设备,比如WATCHDOG、IIS、IIC、PWM定时器、MMC接口、ADC、UART、GPIO、RTC、SPI。
AHB(Advanced High performance Bus)总线主要用于高性能模块(CPU、DMA和DSP等)之间的连接;APB(Advanced Peripheral Bus)总线主要用于低带宽的周边外设之间的连接,例如UART,IIC等。
S3C2410核的工作电压为1.8V时,主频可达200MHz;工作电压为2.0V,主频可达266MHz。S3C2440核的工作电压为1.2V时,主频可达300MHz;工作电压为1.3V时,主频可达300MHz。为了降低电磁干扰,降低板间布线的要求,S3C2410/S3C2440外接晶振的频率一般很低,本开发板上的为12MHz,需要通过时钟控制逻辑的PLL提高系统时钟。
S3C2410/S3C2440有两个PLL,MPLL和UPLL。UPLL专用于USB设备,MPLL用于设置FCLK、HCLK、PCLK。它们的设置方式类似。以MPLL为例。
上电时,PLL没启动,FCLK即等于外部输入的时钟,称为Fin。若要提高系统时钟,需要软件来启动PLL。结合下图来介绍PLL的设置过程。
- 上电几毫秒后,晶振(图中的OSC)输出稳定,FCLK=Fin(晶振频率),nRESET信号恢复高电平之后,CPU开始执行指令。
- 可以在程序开头启动MPLL,设置MPLL的几个寄存器之后,需要等待一段时间(Lock Time),MPLL的输出才稳定。这段时间(Lock Time)内,FCLK停振,CPU停止工作。Lock Time的长短由寄存器LOCKTIME设定。
- Lock Time之后,MPLL的输出才稳定,CPU工作在新的FCLK下。
FCLK、HCLK、PCLK的比例是可以改变的,设置它们三者的比例,启动MPLL只需要设置3个寄存器。
- LOCKTIME寄存器(LOCK TIME COUNT),用于设置“Lock Time”的长度。
MPLL启动后需要等待一段时间(Lock Time),使得其输出稳定。S3C2410中,位[23:12]用于UPLL,位[11:0]用于MPLL。S3C2440中,位[31:16]用于UPLL,位[15:0]用于MPLL。一般而言。使用它的默认值即可,S3C2410中默认值为0x00FFFFFF,S3C2440中的默认值为0xFFFFFFFF。 - MPLLCON寄存器(Main PLL Control),用于设置FCLK与Fin的倍数,
位[19:12]的值称为MDIV,位[9:4]的值称为PDIV,位[1:0]的值称为SDIV。FCLK与Fin的关系有如下计算公式:设置MPLLCON就相当于之前图中说的“首先使用软件来设置PLL”,Lock Time就被自动插入。Lock Time之后,MPLL输出稳定,CPU工作在新的FCLK下。1
2
3
4
5
6
7//对于S3C2410
MPLL(FCLK) = (m*Fin)/(p*2^s)
其中:m = MDIV + 8,p = PDIV + 2,s = SDIV。
//对于S3C2440
MPLL(FCLK) = (2*m*Fin)/(p*2^s)
其中:m = MDIV + 8,p = PDIV + 2,s = SDIV。 - CLKDIVN寄存器(CLOCK DIVIDER CONTROL),用于设置FCLK、HCLK、PCLK三者的比例。
对于S3C2410、S3C2440这个寄存器表现稍有不同CLKDIVN 位 说明 HDIVN1 2 0表示保留,1表示FCLK:HCLK:PCLK=1:4:4,此时HDIVN、PDIVN必须设为0b00 HDIVN 1 HCLK的分频系数,0-HCLK=FCLK,1-HCLK=FCLK/2 PDIVN 0 PCLK的分频系数,0-PCLK=HCLK,1-PCLK=HCLK/2
对于S3C2440的一些时钟比例,还需要额外设置一个寄存器CAMDIVN。下图中,HDIVN为CLKDIVN寄存器的位[2:1],PDIVN为位[0];HCLK4_HALF、HCLK3_HALF分别为CAMDIVN寄存器的位[9]、[8]。各种时钟比例对应的寄存器设置如下图所示:
对于S3C2410,HDIVN是CLKCIVN寄存器的位[1],对于S3C2440,HDIVN是CLKDIVN寄存器的位[2:1]。如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode”,这可以通过如下指令来完成。
1 | #MMU_SetAsyncBusMode |
其中的“#R1_nF:OR:R1_iA”等于0xC0000000。如果HDIVN非0时,而CPU的总线模式仍是“fast bus mode”,则CPU的工作频率将自动变为HCLK,而不再是FCLK。
PWM定时器
S3C2410/S3C2440的定时器部件完全一样,共有5个16位的定时器。其中定时器0、1、2、3有PWM(Pulse Width Modulation)功能,即它们都只有一个输出引脚,可以通过定时器来控制引脚周期性的高低电平变化;定时器4没有输出引脚。
定时器部件的时钟源为PCLK,首先通过两个8位的预分频器降低频率:定时器0、1共用第一个预分频器,定时器2、3、4共用第一个预分频器。预分频器的输出将进入第二级分频器。它们输出5种频率的时钟,2、4、8、16分频或者外部时钟TCLK0/TCLK1。每个定时器的工作时钟可以从这5种频率中选择。
两个预分频都可以通过TCFG0寄存器来设置,每个定时器工作在哪个频率下也可以通过TCFG1寄存器来选择。下图形象的说明了定时器的结构:
上面只是确定了定时器的工作频率,至于定时器如何工作还得了解其内部结构。定时器内部控制逻辑的工作流程如下:
- 程序初始化,设定TCMPBn、TCNTBn这两个寄存器,它们表示定时器n的比较值,初始计数值。
- 随之设置TCON寄存器启动定时器n,这时,TCMPBn、TCNTBn的值将被装入其内部寄存器TCMPn、TCNTn中。在定时器n的工作频率下,TCNTn开始减1计数,其值可以通过读取TCNTOn寄存器得知。
- 当TCNTn的值等于TCMPn的值时,定时器n的输出管脚TOUTn反转;TCNTn继续减1计数。
- 当TCNTn的值到达0时,其输出管脚TOUTn再次反转,并触发定时器n的中断(如果使能了中断的话)。
- 当TCNTn的值到达0时,如果在TCON寄存器中将定时器n设为自动加载,则TCMPB0和TCNTB0寄存器的值被自动装入TCMP0和TCNT0寄存器中,下一个计数流程开始。
定时器n的输出管脚TOUTn初始状态为高电平,以后在TCNTn的值等于TCMPn的值、TCNTn的值时反转。也可以通过TCON寄存器设置其初始电平,这样TOUTn的输出就完全反相了。通过设置TCMPBn、TCNTBn的值可以设置管脚TOUTn输出信号的占空比,这就是所谓的可调制脉冲(PWM),所以这些定时器就可以称为PWM定时器。
定时器的寄存器:
- TCFG0寄存器(TIMER CONTFIGURATION)
位[7:0]、位[15:8]分别用来控制预分频器0、1。它们的值为0-255。经过预分频器出来的时钟频率为PCLK/{prescaler value + 1}。 - TCFG1寄存器(TIMER CONTFIGURATION)
经过预分频器的得到的时钟将被2分频、4分频、8分频和16分频,除这4种频率外,额外的,定时器0、1还可以工作在外接的TCLK0时钟下,定时器2、3、4还可以工作在外接的TCLK1时钟下。
通过TCFG1寄存器来设置这5个定时器,分别工作于这5个频率的哪一个之下,如下表所示:
这样,定时器n的工作频率或者是外接的TCLK0或TCLK1可通过这个公式进行计算:1
2
3定时器工作频率 = PCLK / {presacaler value + 1} / {divider value}
{prescaler value} = 0-255
{divider value} = 2,4,8,16 - TCNTBn/TCMPBn寄存器(COUNT BUFFER REGISTER & COMPARE BUFFER REGISTER)
n为0-4,这四个寄存器都只用到位[15:0],TCNTBn中保存定时器的初始计数值,TCMPBn中保存比较值。它们的值在启动定时器时,被传到定时器内部寄存器TCNTn、TCMPn中。
没有TCMPB4因为定时器4没有输出引脚。 - TCNTOn寄存器(COUNT OBSERVATION)。
n为0-4,定时器n被启动后,内部寄存器TCNTn在其工作时钟下不断减1计数,可以通过读取TCNTOn寄存器得知其值。 - TCON寄存器(TIMER CONTROL)。
它有以下4个作用:- 第一次启动定时器时“手动”将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中。
- 启动、停止定时器。
- 决定在定时器计数到达0时是否自动将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中。
- 决定定时器的管脚TOUTn的输出电平是否反转。
TCON寄存器位[3:0]、位[11:8]、位[15:12]、位[19:16]、位[22:20]分别用于定时器0-4。除定时器因为没有输出引脚在没有“输出反转”位外,其他位的功能相似,下表以定时器0为例说明这些寄存器:功能 位 位置 开启/停止 0 0:停止定时器 1:开启定时器 手动更新 1 0:无用 1:将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中 输出反转 2 0:TOUT0不反转 1:TOUT0反转 自动加载 3 0:不自动加载 1:在定时器计数达到0时,TCNTBn/TCMPBn寄存器的值自动装入内部寄存器TCNTn、TCMPn中
第一次使用定时器时,需要设置“手动更新”位为1以使TCNTBn/TCMPBn寄存器的值装入TCNTn、TCMPn中。下一次如果还要设置这一位,需要先将它清0。
WATCHDOG 定时器
WATCHDOG定时器可以像一般16位定时器一样用于产生周期性的中断,也可以用于发出复位信号以重启失常的系统。它与PWM定时器结构类似。
同样的,WATCHDOG定时器的8位预分频器将PCLK分频后,将再次分频得到4种频率。16、32、64、128分频。WATCHDOG 定时器可以选择工作于哪种频率之下。WTCNT寄存器按照其工作频率减1计数,当达到0时,可以产生中断信号,可以输出复位信号。在第一次使用WATCHDOG 定时器时,需要往WTCNT寄存器中写入初始计数值,以后在计数值达到0时自动从WATDAT寄存器中装入,重新开始下一个计数周期。
使用WATCHDOG 定时器的“WATCHDOG 功能”时,在正常的程序中,必须不断重新设置WTCNT寄存器使得它不为0,这样可以保证系统不被重启,称为“喂狗”。当程序崩溃时不能正常喂狗,计数值达到0后系统将被重启,这样程序将程序运行。为了克服各种干扰、避免各类系统错误时系统彻底死机,经常使用WATCHDOG 功能。
WATCHDOG 定时器涉及到的寄存器如下:
- WTCON寄存器(WATCHDOG TIMER CONTROL)
用于设置预分频系数,选择各种频率,决定是否使能中断,是否启用WATCHDOG 功能(即是否输出复位信号)。功能 位 说明 WATCHDOG功能 0 当定时器达到0时,0:不输出复位信号 1:输出复位信号 中断使能 1 0:禁止中断 1:使能中断 时钟选择 [4:3] 选择分频系数 0b00:16 0b01:32 0b10:64 0b11:128 定时器启动 5 0:停止 1:启动 预分频系数 [15:8] 预分频系数:0-255
与PWM定时器相似,WATDOG定时器的工作频率可以通过这公式计算:
1 | WATDOG定时器工作频率=PCLK/{presacler value + 1}/{divider value} |
- WTDAT寄存器(WATCHDOG TIMER DATA)
WTDAT寄存器被用来决定WATCHDOG定时器的超时周期,在定时器启动后,当计数达到0时,WTDAT寄存器的值会自动传入WTCNT寄存器。不过,第一次启动WATCHDOG定时器时,WTDAT寄存器的值会自动传入WTCNT寄存器。 - WTCNT寄存器(WATCHDOG TIMER COUNT)
在启动WATCHDOG前,必须往这个寄存器写入初始计数值。启动定时器后,它减1计数,当计数值达到0时:如果中断被使能的话发出中断,如果WATCHDOG功能被使能的话就发出复位信号,装载WTDAT寄存器的值并重新计数。
MPLL和定时器操作实例
程序设计
本实例讲解MPLL、定时器的使用。首先启动MPLL提高系统时钟,初始化存储控制器使SDRAM工作在新的HCLK下,然后将定时器0设为0.5s产生一次中断,在中断程序里改变LED的状态。
代码详解
4个关键点:设置/启动MPLL、根据HCLK设置存储控制器、初始化定时器0、定时器中断。
设置/启动MPLL
clock_init函数用于设置MPLL,本开发板的输入时钟频率Fin为12MHz,将FCLK、HCLK、PCLK分别设为200MHz、100MHz和50MHz。代码如下:
1 |
|
第17行设置FCLK、HCLK、PCLK三者分频比为1:2:4。
当HDIVN非0时,需要将CPU总线模式从“fast bus mode”设为“asynchronous bus mode”,第21-23行的汇编代码即完成此事。
第27-34行代码判断芯片是S3C2410还是S3C2440,它们的MPLL计算公式稍有不同,需要区分开来。如果处理器为S3C2410,使用第29行设置MPLL寄存器,令MDIV=0x5C,PDIV=0x04,SDIV=0,所以
1 | MPLL(FCLK)=(m * Fin)/(p * 2 ^ s) = (0x5c + 8)*12MHz / ((0x04+2)*2^0) = 200MHz |
类似的,S3C2440的FCLK = 200MHz,HCLK = 100MHz,PCLK = 50MHz。
设置存储控制器
memsetup函数被用来设置存储控制器,代码如下:
1 | /* |
现在HCLK的值等于100MHz,REFRESH寄存器的值需要重新计算。
R_CNT = 2^11 + 1 - 100MHz * 7.8125μS = 0x04F4,所以REFRESH = 0x008C0000 + R_CNT = 0x008C0000 + 0x04F4 = 0x008C04F4。
1 | REFRESH = 0x008c0000 + R_CNT |
对于第12到第20行为何用这么笨拙的方式设置存储控制器的13个寄存器,在连接脚本timer.lds中,全部代码的起始运行地址都被设为0x30000000,但是在执行memsetup函数时,代码仍在SRAM(Steppingstone)中,为了能够在Steppingstone中运行这个函数,它应该是位置无关的代码,而第12到第20行得手工赋值可以达到这个要求。
初始化定时器0
tiemr0_init函数用于初始化定时器0,根据相关寄存器的格式并参考代码中的注释就可以理解这个函数,代码如下:
1 | /* |
定时器中断
head.S中调用timer0_init函数之后,定时器0即开始工作;调用init_irq函数使能定时器0中断,设置CPSR寄存器开启IRQ中断之后,每当定时器0达到0就会触发中断。init_irq函数很简单,在init.c中,代码如下:
1 | /* |
发生定时器中断时,CPU将调用其中断服务程序Timer0_Handle,它在interrupt.c中:
1 | void Timer0_Handle(void) |
定时器0的中断使用SRCPND、INTPND寄存器中的位10来表示。中断服务程序Timer0_Handle先判断是否定时器0的中断,若是则反转4个LED的状态。
实例测试
编译生成的bin文件烧入NAND Flash后,上电运行,即可看到4个LED每1S闪烁一次。
将head.S中对clock_init函数的调用去掉,不启用MPLL,并随之将init.c中的memesetup函数的REFRESH寄存器改为12MHz对应的0x008C07A3。重新编译、烧写。可以看到差不多8S这4个LED才闪烁一次。