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
保存比较值。它们的值在启动定时器时,被传到定时器内部寄存器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
"    //读出控制寄存器
22 43行     "orr r1, r1, #0xc0000000
"      //设置为“asynchronous bus mode”
23 44行     "mcr p15, 0, r1, c1, c0, 0
"    //写入控制寄存器
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行 
init.c->clock_init.c()
    如果处理器是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行
init.c->memsetup()
    除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行
init.c->timer0_init()
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行}
init.c->init_irq()
    发生定时器中断时,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行}
interrupt.c
    定时器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
原文地址:https://www.cnblogs.com/sz189981/p/7712295.html