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”,这可以通过如下指令来完成:
1
# MMU_SetAsyncBusMode
2
mrc p15, 0, r0, c1, c0, 0
3
orr r0, r0, #R1_nF:OR:RL_iA
4
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\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,所以:
1
    MPLL(FCLK) = (m * Fin)/(p * 2^s) = (0x5c + 8) * 12MHz/((0x04 + 2)*2^0) = 200MHz
2
    HCLK = FCLK/2 = 100MHz
3
    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中,代码如下:
1
行号
2
140/*
3
141行*定时器0中断使能
4
142行*/
5
143行void init_irq(void)
6
144{
7
145    //定时器0中断使能
8
146    INTMSK &= (~(1 << 10));
9
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值