JZ2440 裸机驱动 第10章 系统时钟和定时器
本章目标
了解S3C2410/S3C2440的时钟体系结构
掌握通过设置MPLL改变系统时钟的方法
掌握在不同的频率下设置存储控制器的方法
掌握PWM定时器的用法
了解WATCHDOG定时器的用法
10.1 时钟体系及各类时钟部件
10.1.1 S3C2410/S3C2440时钟系统
S3C2410/S3C2440的时钟控制逻辑既可以外接晶振,然后通过内部电路产生时钟源;也
可以直接使用外部提供的时钟源,它们通过引脚的设置来选择。时钟控制逻辑给整个芯片提
供3种时钟:FCLK用于CPU核;HCLK用于AHB总线上的设备,比如CPU核、存储控制器、
中断控制器、LCD控制器、DMA和USB主机模块等;PCLK用于APB总线上的设备,比如
WATCHDOG、IIS、I2C、PWM定时器、MMC接口、ADC、UART、GPIO、RTC和SPI。
为了降低电磁干扰、降低板间布线的要求,S3C2410/S3C2440外接的晶振频率通常很低,
本开发板上为12MHz,需要通过时钟控制逻辑的PLL提高系统时钟。
S3C2410/S3C2440有两个PLL:MPLL和UPLL。UPLL专用于USB设备,MPLL用于设置
FCLK、HCLK、PCLK。它们的设置相似,本书以MPLL为例。
上电时,PLL没被启动,FCLK即等于外部输入时钟,称为Fin。
若要提高系统时钟,需要开启PLL。
PLL设置过程如下所示,请参考图10.1,跟随FCLK的图像了解启动过程。
(1)上电几毫秒后,晶振输出稳定,FCLK = Fin(晶振频率),nRESET信号恢复高电平后,
CPU开始执行命令。
(2)可以在程序开头启动MPLL,设置MPLL的几个寄存器后,需要等待一段时间(Lock Time),
MPLL输出才稳定。在这段时间(Lock Time)内,FCLK停振,CPU停止工作。Lock Time的长短由
寄存器LOCKTIME设定。
(3)Lock Time之后,MPLL输出正常,CPU工作在新的FCLK之下。
FCLK、HCLK和PCLK的比例是可以改变的,设置它们三者的比例,启动MPLL只需要设置3个
寄存器(对于S3C2440的一些时钟比例,还需要额外设置一个寄存器)。
【1】LOCKTIME寄存器(LOCK TIME COUNT):用于设置“Lock Time”的长度。
前面说过,MPLL启动后需要等待一段时间,使得其输出稳定。S3C2410中,位[23:12]用于UPLL,
位[11:0]用于MPLL。S3C2440中,位[31:16]用于UPLL,位[15:0]用于MPLL。一般而言,使用它的
默认值即可,S3C2410中默认值为0x00FF FFFF,S3C2440中默认值为0xFFFF FFFF。
【2】MPLLCON寄存器(Main PLL Control):用于设置FCLK与Fin的倍数。
位[19:12]的值称为MDIV,位[9:4]的值称为PDIV,位[1:0]的值称为SDIV。FCLK与Fin的关系
有如下计算公式:
① 对于S3C2410: MPLL(FCLK) = ( m * Fin) / (p * 2^s)
② 对于S3C2440: MPLL(FCLK) = (2 * m * Fin) / (p * 2^s)
其中:m = MDIV + 8,p = PDIV + 2,s = SDIV。
当设置MPLLCON之后——相当于图10.1中的“首先使用软件设置PLL”,Lock Time就被自动插入。
Lock Time之后,MPLL输出稳定,CPU工作在新的FCLK下。
【3】CLKDIVN寄存器(CLOCK DIVIDER CONTROL):用于设置FCLK、HCLK、PCLK三者的比例。
对于S3C2410、S3C2440,这个寄存器表现稍有不同,请参考表10.1和图10.2.
对于S3C2440的一些时钟比例,还需要额外的设置一个寄存器CAMDIVN。图10.2中,
HDIVN为CLKDIVN寄存器为位[2:1],PDIVN为位[0];HCLK4_HALF、HCLK3_HALF分
别为CAMDIVN寄存器的位[9]、[8]。各种时钟对比对应的寄存器设置如图10.2所示。
对于S3C2410,HDIVN是CLKDIVN寄存器的位[1]; 对于S3C2440,HDIVN是CLKDIVN寄存器的位[2:1],如果HDIVN非0,CPU的总线模式
应该从“fast bus mode”变为“asynchronous bus mode”,这可以通过如下指令来完成:
# MMU_SetAsyncBusMode mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #R1_nF:OR:RL_iA mcr p15, 0, r0, c1, c0, 0
其中的“R1_nF:OR:R1_iA”等于0xC000 0000。如果HDIVN非0时,而CPU的总线模式仍是
“fast bus mode”,则CPU的工作频率将自动变为HCLK,而不再是FCLK。
10.1.2 PWM定时器
S3C2410/S3C2440的定时器部件完全一样,共有5个16位定时器。其中定时器0、1、2、3
有PWM功能;定时器4没有输出引脚。
定时器部件的时钟源为PCLK,首先通过两个8位的预分频器降低频率:定时器0、1共用第
一个预分频器,定时器2、3、4共用第二个预分频器。预分频器的输出将进入第二部分分频器,
它们输出5种频率的时钟:2分频、4分频、8分频、16分频或者外部时钟TCLK0/TCLK1。每个
定时器的工作时钟可以从这5种频率中选择。
这两个预分频都可以通过TCFG0寄存器来设置,每个定时器工作在哪种频率下也可以通过
TCFG1寄存器来选择。如图10.3所示,形象地说明定时器的结构。
图10.3 定时器结构图
上面只是确定了定时器的工作频率,至于定时器如何工作还得了解其内部结构,如图
10.4所示。
定时器内部控制逻辑的工作流程如下:
(1)程序初始,设定TCMPBn、TCNTBn这两个寄存器,它们表示定时器n的比较值、
初始计数值。
(2)随之设置TCON寄存器启动定时器n,这时,TCMPBn、TCNTBn的值将被装入其
内部寄存器TCMPn、TCNTn中。在定时器n的工作频率下,TCNTn开始减1计数,其值可
以通过读取TCNTOn寄存器得知。
(3)当TCNTn的值等于TCMPn的值时,定时器n的输出管脚TOUTn反转;TCNTn继续
减1计数。
(4)当TCNTn的值达到0时,其输出管脚TOUTn再次反转,并触发定时器n的中断(如果
中断使能了的话)。
(5)当TCNTn的值达到0时,如果TCON寄存器中将定时器n设为“自动加载”,则TCMPB0
和TCNTB0寄存器的值将被自动装入TCMP0和TCNT0寄存器中,下一个计数流程开始。
定时器n的输出管脚TOUTn初始状态为高电平,以后在TCNTn的值等于TCMPn的值、
TCNTn的值时反转。也可以通过TCON寄存器设置其初始电平,这样TOUTn的输出就完全
反相了。通过设置TCMPBn、TCNTBn的值可以设置管脚TOUTn输出信号的占空比,这就
是所谓的PWM,所以这些定时器又被称为PWM定时器。
下面讲解定时器时寄存器的使用方法。
(1)TCFG0寄存器(TIMER CONFIGURATION)
位[7:0]、位[15:8]分别被用于控制预分频器0、1,它们的值为0~255。经过预分频器出来
的时钟频率为:PCLK/{prescaler value + 1}。
(2)TCFG1寄存器
经过预分频器得到的时钟将被2、4、8、16分频,除了这4种频率外,额外的,定时器0、1
还可以工作在外接的TCLK0时钟下,定时器2、3、4还可以工作在外接的TCLK1时钟下。
通过TCFG1寄存器来设置这5个定时器,分别工作于这5个频率中哪一个下,如表10.2所示。
这样,定时器n的工作频率或者外接的TCLK0或TCLK1可以通过这个公式计算:
定时器工作频率 = PCLK / {prescaler value +1} / {divider value}
{prescaler value} = 0~255
{divider value} = 2、4、8、16
(3)TCNTBn/TCMPBn寄存器(COUNT BUFFER REGISTER & COMPARE BUFFER REGISTER)。
n为0~4,这两个寄存器都只用到位[15:0],TCNTBn中保存定时器的初始值,TCMPBn
n为0~4,这两个寄存器都只用到位[15:0],TCNTBn中保存定时器的初始值,TCMPBn
中保存比较值。它们的值在启动定时器时,被传到定时器内部寄存器TCNTn、TCMPn中。
没有TCMPB4,因为定时器4没有输出管脚。
(4)TCNTOn寄存器(COUNT OBSERVATION)
n为0~4,定时器n被启动后,内部寄存器TCNTn在其工作时钟下不断减1计数,可以通过
读取TCNTOn寄存器得知其值。
(5)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。
除了定时器因为没有输出引脚在没有“输出反转”位外,其他位的功能相似。表10.3以定时器
0为例说明这些位的作用。
在第一次使用定时器时,需要设置“手动更新”位为1,以使TCNTBn/TCMPBn寄存器的
值装入内部寄存器TCNTn、TCMPn中。下一次如果还要设置这一位,需要先将它清0。
定时器还有其他功能,比如DMA、Dead zone等,需要了解的读者清参考数据手册。
寄存器中涉及它们的部分这里就省略了。
10.1.3 WATCHDOG定时器 WATCHDOG定时器可以像一般16位定时器一样用于产生周期性中断,也可以用于发
出复位信号以重启失常的系统。它与PWM定时器的结构类似,如图10.5所示。
同样,WATCHDOG定时器的8位分频器将PCLK分频后,被再次分频得到4种频率:
16、32、64、128分频,WATCHDOG定时器可以选择工作在哪种频率之下。WTCNT
寄存器按照其工作频率减1计数,当达到0时,可以产生中断信号,可以输出复位信号。
在第一次使用WATCHDOG定时器时,需要向WTCNT寄存器中写入初始计数值,以后
在计数值达到0时,自动从WATDAT寄存器中寄存器中装入,重新开始下一个计数周期。
使用WATCHDOG定时器的“WATCHDOG功能”时,在正常的程序中,必须不断重新
设置WTCNT寄存器使得它不为0,这样可以保证系统不被重启,这称为喂狗。
WATCHDOG定时器所涉及的寄存器如下:
(1)WTCON寄存器(WATCHDOG TIMER CONTROL)
用于设置预分频系数,选择工作频率,决定是否使用中断、是否启用WATDOG功能(即
是否输出复位信号),各位的作用如表10.4所示。
与PWM定时器相似,WATDOG定时器的工作频率通过这个公式计算:
WATDOG定时器工作频率 = PCLK / {prescaler value + 1} / {divider value}
{prescaler value} = 0~255;{divider value} = 16、32、64、128 (2)WTDAT寄存器(WATCHDOG TIMER DATA)。
用于决定WATCHDOG定时器的超时周期,在定时器启动后,当计数达到0时,WTDAT
寄存器的值会自动传入WTCNT寄存器。不过,第一次启动WATDOG定时器时,WTDAT
寄存器的值会自动传入WTCNT寄存器。
(3)WTCNT寄存器(WATCHDOG TIMER COUNT)。
在启动WATDOG定时器前,必须往这个寄存器写入初始值。启动定时器后,它减1计数,
当计数值到达0时:
如果中断被使能的话发出中断;
如果WATCHDOG功能被使能的话,发出复位信号,装载WTDAT寄存器的值并重新计数。
10.2 MPLL和定时器操作实例
10.2.1 程序设计
本实例讲解MPLL、定时器的使用。首先启动MPLL提高系统时钟,初始化存储控制器使
SDRAM工作在新的HCLK下,然后将定时器0设为0.5s产生一次中断,在中断程序中改变
LED的状态。
10.2.2 代码详解
源码在/work/hardware/timer目录下。
本实验的重点在4点:
① 设置/启动 MPLL;
② 根据HCLK设置存储控制器;
③ 初始化定时器0;
④ 初始化定时器中断。
相关函数在init.c中。
1.设置/启动 MPLL
clock_init函数用于设置MPLL,本开发板的输入时钟频率Fin为12MHz,将FCLK、HCLK、
PCLK分别设为200MHz、100MHz和50MHz,代码如下:
1 行号 2 23行 #define s3c2410_MPLL_200MHz ((0x5c << 12) | (0x04 << 4) | (0x00)) /*MDIV = 0x5c, PDIV = 0x04, SDIV = 0*/ 3 24行 #define s3c2440_MPLL_200MHz ((0x5c << 12) | (0x01 << 4) | (0x02)) 4 25行 /* 5 26行 *对于MPLLCON寄存器,[19:12]为MDIV、[1:0]为SDIV 6 27行 *有如下公式: 7 28行 * s3c2410:MPLL(FCLK) = (m * Fin)/(p * 2^s) 8 29行 * s3c2440:MPLL(FCLK) = (2*m*Fin)/(p * 2^s) 9 30行 * 其中:m = MDIV + 8,p = PDIV +2, s = SDIV 10 31行 *对于本开发板,Fin = 12MHz 11 32行 *设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK = 1:2:4 12 33行 *FCLK = 200MHz,HCLK = 100MHz,PCLK = 50MHz 13 34行 */ 14 35行 void clock_init(void) 15 36行 { 16 37行 //LOCKTIME = 0x00ff ffff //使用默认值即可 17 38行 CLKDIVN = 0x03; //FCLK:HCLK:PCLK = 1:2:4,HDIVN = 1, PDIVN = 1 18 39行 19 40行 /*如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode”*/ 20 41行 __asm__( 21 42行 "mrc p15, 0, r1, c1, c0, 0\n" //读出控制寄存器 22 43行 "orr r1, r1, #0xc0000000\n" //设置为“asynchronous bus mode” 23 44行 "mcr p15, 0, r1, c1, c0, 0\n" //写入控制寄存器 24 45行 ) 25 46行 26 47行 /*判断是s3c2410还是s3c2440*/ 27 48行 if((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002)) 28 49行 { 29 50行 MPLLCON = S3C2410_MPLL_200MHz; /*现在,FCLK = 200MHz,HCLK = 100MHz,PCLK = 50MHz*/ 30 51行 } 31 52行 else 32 53行 { 33 54行 MPLLCON = S3C2440_MPLL_200MHz; 34 55行 } 35 56行 } 36 57行
如果处理器是S3C2410,使用第50行设置MPLL寄存器,令MDIV = 0x5c,PDIV = 0x04,
SDIV = 0,所以:
MPLL(FCLK) = (m * Fin)/(p * 2^s) = (0x5c + 8) * 12MHz/((0x04 + 2)*2^0) = 200MHz HCLK = FCLK/2 = 100MHz PCLK = FCLK/4 = 50MHz
如果处理器是S3C2440,使用第54行设置MPLL寄存器,使用第29行的公式可以计算
出MPLL = 200MHz,所以FCLK、HCLK、PCLK分别为200MHz、100MHz和50MHz。
2.设置存储控制器
memsetup函数被用来设置存储控制器,代码如下:
1 行号 2 58行/* 3 59行*设置存储控制器以使用SDRAM 4 60行*/ 5 61行void memsetup(void) 6 62行{ 7 63行 volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE; 8 64行 9 65行 /*这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值 10 66行 *写在数组中,是因为要生成位置无关代码,使得这个函数可以被复制到 11 67行 *SDRAM之前就可以在Steppingstone中运行 12 68行 */ 13 69行 /*存储控制器13个寄存器的值*/ 14 70行 p[0] = 0x22011110; //BWSCON 15 71行 P[1] = 0x00000700; //BANKCON0 16 72行 p[2] = 0x00000700; //BANKCON1 17 73行 p[3] = 0x00000700; //BANKCON2 18 74行 p[4] = 0x00000700; //BANKCON3 19 75行 p[5] = 0x00000700; //BANKCON4 20 76行 p[6] = 0x00000700; //BANKCON5 21 77行 p[7] = 0x00018005; //BANKCON6 22 78行 p[8] = 0x00018005; //BANKCON7 23 79行 24 80行 /*REFRESH, 25 81行 *HCLK = 12MHz :0x008c 07a3 26 82行 *HCLK = 100MHz:0x008c 04f4 27 83行 */ 28 84行 p[9] = 0x008c04f4; 29 85行 p[10] = 0x000000b1; //BANKSIZE 30 86行 p[11] = 0x00000030; //MRSRB6 31 87行 p[12] = 0x00000030; //MRSRB7 32 88行} 33 89行
除REFRESH寄存器外,其他寄存器的值与第6章的实验程序一样。现在HCLK等于
100MHz,REFRESH寄存器的值需要重新计算。参考第6章的公式可以计算:
R_CNT = 2^11 + 1 - 100MHz * 7.8125uS = 0x04F4,
所以,REFRESH = 0x008c0000 + R_CNT = 0x008c04f4。
在连接脚本timer.lds中,全部代码的起始运行地址都被设为0x3000 0000,但是在执行
memsetup函数时,代码还在内部SRAM(steppingston)中,为了能在此处运行这个函数,
它应该是位置无关的。
3.初始化定时器0
timer0_init函数用于初始化定时器0,根据相关寄存器的格式并参考代码中的注释就可
以理解这个函数,代码如下:
1 行号 2 124行/* 3 125行*Timer input clock Frequency = PCLK / (prescaler value + 1) / (divider value) 4 126行*(prescaler value) = 0~255 5 127行*(divider value) = 2、4、8、16 6 128行*本实验的Timer0的时钟频率 = 100MHz/(99 + 1)/(16) = 62500Hz 7 129行*设置Timer0 0.5s触发一次中断 8 130行*/ 9 131行void timer0_init(void) 10 132行{ 11 133行 TCFG0 = 99; //预分频器 0 = 99 12 134行 TCFG1 = 0x03; //选择16分频 13 135行 TCNTB0 = 31250; //0.5s触发一次中断 14 136行 TCON |= (1 << 1); //手动更新 15 137行 TCON = 0x09; //自动加载,清除“手动更新”位,启动定时器0 16 138行} 17 139行
4.定时器中断
head.S中调用timer0_init函数之后,定时器开始工作;调用init_irq函数使能定时器0
中断、设置CPSR寄存器开启IRQ中断后,每当定时器0计数达到0时,就会触发中断。
init_irq函数很简单,在init.c中,代码如下:
行号 140行/* 141行*定时器0中断使能 142行*/ 143行void init_irq(void) 144行{ 145行 //定时器0中断使能 146行 INTMSK &= (~(1 << 10)); 147行}
发生定时器中断时,CPU将调用其中断服务程序Timer0_Handler,它在interrupt.c中:
1 行号 2 03行void Timer0_Handler(void) 3 04行{ 4 05行 /* 5 06行 *每次中断令3个LED改变状态 6 07行 */ 7 08行 if(INTOFFSET == 10) 8 09行 { 9 10行 GPFDAT = ~(GPFDAT & (0x7 << 4)); 10 11行 } 11 12行 //清除中断 12 13行 SRCPND = 1 << INTOFFSET; 13 14行 INTPND = INTPND; 14 15行}
定时器0的中断使用SRCPND、INTPND寄存器中的位10来表示。中断服务程序
Timer0_Handler先判断是否定时器0的中断,若是则反转3个LED的状态。
10.2.3 实例测试
在timer目录下执行make命令生成timer.bin可执行程序,烧入NAND Flash中执行,
即可看到3个LED每1s闪烁一次。
将head.S中对clock_init函数的调用去掉,不启动MPLL;并随之将init.c中的
memsetup函数的REFRESH寄存器改为12MHz对应的0x008c 07a3.重新编译、烧写,
可以看到差不多8sLED闪烁一次。
附:代码:
链接: https://pan.baidu.com/s/1kV24a9L 密码: tfab