《嵌入式linux应用程序开发完全手册》系统时钟和定时器学习笔记系统时钟和定时器

时间:2022-11-15 20:32:55

 《嵌入式linux应用程序开发完全手册》系统时钟和定时器学习笔记 收藏
系统时钟和定时器

 

 

一.系统时钟

(1) FCLK:用于CPU核

      HCLK:用于AHB总线上设备:CPU核、存储器控制器、中断控制器、LCD控制器、DMA和USB主机模块

PCLK:用于APB总线上设备:WATCHDOG、IIS、I2C、PWM定时器、MMC接口、ADC、UART、GPIO、RTC和SPI

 

(2 )开发板时钟频率为12 MHZ,通过PLL提高系统时钟: S3C2440包括MPLL和UPLL,UPLL用于USB设备,MPLL用于FCLK、HCLK、PLCK,他们的设置方法类似。

(3 )上电→FCLK=Fin(外部输入时钟)→设置MPLL相关寄存器→等待(Lock Time:长短由寄存器LOCKTIME设定)→MPLL输出稳定,CPU工作在新的时钟FCLK下。

(4)设置MPLL需要设置下面几个重要寄存器:

LOCKTIME寄存器(LOCK TIME COUNT)用于设置lock time的长度。

      MPLLCON(Main PLL Control)寄存器用于设置FCLK与Fin的倍数

      CLKDIVN(CLOCK DIVIDER CONTROL)寄存器用于设置FCLK、HCLK、PCLK三者的比例

       CAMDIVN某些时钟比例需要设置。

二. PWM(pulse width modulation)定时器

 (1)S3C2440共有5个16位的定时器,其中定时器0、1、2、3有PWM功能,即它们都有一个输出引脚,可以通过定时器来控制引脚周期性的高、低电平变化;定时器4没有输出引脚。

(2)PLCK→2个8位预分频器(定时器0、1共用第一个定时器,2、3、4共用第二个);→第二级分频(输出2分频,4分频,8分频,16分频或者外部时钟TCLK0/TCLK1)。

       这两次预分频都是通过设置TCFG0寄存器完成的。每个定时器工作在哪种频率下可以通过TCFG1寄存器来选择的。定时器内部控制逻辑的详细原理可参考数据文档。

       定时器的使用主要涉及两个寄存器:

TCFG0寄存器:位[7:0],位[15:8]分别用于控制预分频器0,1;它们的值为0~255。经过分频器出来的时钟频率:PLCK/(TCFG0[7:0]+1或TCFG0[15:8]+1)。

TCFG1寄存器 :设定相应定时器为经过分频器出来的时钟频率的几分频。

定时器工作频率= PLCK/(TCFG0[7:0]或TCFG0[15:8]+1)/几分频

TCNTBn/TCMPBn寄存器:这两个寄存器都只用到位[15:0]。TCNTBn中保存定时器的初始计数值,TCMPBn中保存比较值。它们的在启动定时器时,被传到定时器内部寄存器TCNTn,TCMPn中。

TCNTOn寄存器:n为0~4,内部寄存器TCNTn在其工作时钟下不断减1计数,可以通过读取TCNTOn寄存器得知其值。

TCON寄存器:它的功能如下:A.第一次启动定时器时,手动将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn,TCMPn中。B.启动,停止定时器。C.决定在定时器计数到达0时是否自动装入初值 。D.决定定时器的管脚TOUTn的输出电平是否反转。

 

三. WATCHDOG定时器

 

(1)工作原理:PLCK→2个8位预分频器(输出16分频,32分频,64分频,128分频或者外部时钟TCLK0/TCLK1)

   初始计数值写入 WTCNT→while(WTCNT==0)自动重新装载WTCNT=WTDAT,并可以产生中断信号,可以输出复位信号。WATDOG定时器工作频率=PCLK/(WTCON[15:8]+1)/几分频。大部分功能都在WTCON中设定

(2)其相关寄存器:

WTCON:用于设置预分频系数,选择工作频率,决定是否使能中断,是否启用WATDOG功能等,其寄存器具体操作可见数据手册。

WTDAT:用以决定WATCHDOG定时器的超时周期。

WTCNT:在启动WATDOG定时器前,必须往这个寄存器写入初始计数值,启动定时器后,它做减1操作,当计数器值达到0时,如果中断被使能的话,就发出中断,如果WATCHDOG功能被使能的话就发出复位信号,装载WTDAT寄存器的值并重新计数。

四. MPLL的定时器实验

首先启动MPLL,提高系统时钟,初始化存储控制器,使SDRAM工作在新的HCLK下,然后将定时器0设为0.5s产生一次,在中断程序里改变LED状态。

(1)设置/启动MPLL

clock_init函数用于设置MPLL,S3C2440一般输入时钟频率Fin为12MHZ,将FCLK,HCLK,PCLK分别设为200MHZ,100MHZ和50MHZ,三者比例为1:2:4。其中MPLLCON地址为0x4c000004,MDIV[19:12],PDIV[9:4],SDIV[1:0]。

代码如下:

 

view plaincopy to clipboardprint?
#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))  
/* 
 * 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV 
 * 有如下计算公式: 
*  S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s) 
 *  其中: m = MDIV + 8, p = PDIV + 2, s = SDIV 
 * 对于本开发板,Fin = 12MHz 
 * 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4, 
 * FCLK=200MHz,HCLK=100MHz,PCLK=50MHz 
 */ 
void clock_init(void)  
{  
    // LOCKTIME = 0x00ffffff;   // 使用默认值即可  
    CLKDIVN  = 0x03;            // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1  
 
    /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */ 
__asm__(  
    "mrc    p15, 0, r1, c1, c0, 0/n"        /* 读出控制寄存器 */   
    "orr    r1, r1, #0xc0000000/n"          /* 设置为“asynchronous bus mode” */ 
    "mcr    p15, 0, r1, c1, c0, 0/n"        /* 写入控制寄存器 */ 
    );  
 
MPLLCON = S3C2440_MPLL_200MHZ;  /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */ 

#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))
/*
 * 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV
 * 有如下计算公式:
*  S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
 *  其中: m = MDIV + 8, p = PDIV + 2, s = SDIV
 * 对于本开发板,Fin = 12MHz
 * 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4,
 * FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
 */
void clock_init(void)
{
    // LOCKTIME = 0x00ffffff;   // 使用默认值即可
    CLKDIVN  = 0x03;            // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1

    /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
    "mrc    p15, 0, r1, c1, c0, 0/n"        /* 读出控制寄存器 */
    "orr    r1, r1, #0xc0000000/n"          /* 设置为“asynchronous bus mode” */
    "mcr    p15, 0, r1, c1, c0, 0/n"        /* 写入控制寄存器 */
    );

MPLLCON = S3C2440_MPLL_200MHZ;  /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
 

(2)设置存储控制器

 

view plaincopy to clipboardprint?
/* 
 * 设置存储控制器以使用SDRAM 
 */ 
void memsetup(void)  
{  
    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;  
 
    /* 这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值 
     * 写在数组中,是因为要生成”位置无关的代码”,使得这个函数可以在被复制到 
     * SDRAM之前就可以在steppingstone中运行 
     */ 
    /* 存储控制器13个寄存器的值 */ 
    p[0] = 0x22011110;     //BWSCON  
    p[1] = 0x00000700;     //BANKCON0  
    p[2] = 0x00000700;     //BANKCON1  
    p[3] = 0x00000700;     //BANKCON2  
    p[4] = 0x00000700;     //BANKCON3    
    p[5] = 0x00000700;     //BANKCON4  
    p[6] = 0x00000700;     //BANKCON5  
    p[7] = 0x00018005;     //BANKCON6  
    p[8] = 0x00018005;     //BANKCON7  
      
    /* REFRESH, 
     * HCLK=12MHz:  0x008C07A3, 
     * HCLK=100MHz: 0x008C04F4 
     */   
    p[9]  = 0x008C04F4;  
    p[10] = 0x000000B1;     //BANKSIZE  
    p[11] = 0x00000030;     //MRSRB6  
    p[12] = 0x00000030;     //MRSRB7  

/*
 * 设置存储控制器以使用SDRAM
 */
void memsetup(void)
{
    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;

    /* 这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值
     * 写在数组中,是因为要生成”位置无关的代码”,使得这个函数可以在被复制到
     * SDRAM之前就可以在steppingstone中运行
     */
    /* 存储控制器13个寄存器的值 */
    p[0] = 0x22011110;     //BWSCON
    p[1] = 0x00000700;     //BANKCON0
    p[2] = 0x00000700;     //BANKCON1
    p[3] = 0x00000700;     //BANKCON2
    p[4] = 0x00000700;     //BANKCON3 
    p[5] = 0x00000700;     //BANKCON4
    p[6] = 0x00000700;     //BANKCON5
    p[7] = 0x00018005;     //BANKCON6
    p[8] = 0x00018005;     //BANKCON7
   
    /* REFRESH,
     * HCLK=12MHz:  0x008C07A3,
     * HCLK=100MHz: 0x008C04F4
     */
    p[9]  = 0x008C04F4;
    p[10] = 0x000000B1;     //BANKSIZE
    p[11] = 0x00000030;     //MRSRB6
    p[12] = 0x00000030;     //MRSRB7
}
  

 

(3)初始化定时器0

view plaincopy to clipboardprint?
/* 
 * Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value} 
 * {prescaler value} = 0~255 
 * {divider value} = 2, 4, 8, 16 
 * 本实验的Timer0的时钟频率=100MHz/(99+1)/(16)=62500Hz 
 * 设置Timer0 0.5秒钟触发一次中断: 
 */ 
void timer0_init(void)  
{  
    TCFG0  = 99;        // 预分频器0 = 99          
    TCFG1  = 0x03;      // 选择16分频  
    TCNTB0 = 31250;     // 0.5秒钟触发一次中断  
    TCON   |= (1<<1);   // 手动更新  
    TCON   = 0x09;      // 自动加载,清“手动更新”位,启动定时器0  

/*
 * Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
 * {prescaler value} = 0~255
 * {divider value} = 2, 4, 8, 16
 * 本实验的Timer0的时钟频率=100MHz/(99+1)/(16)=62500Hz
 * 设置Timer0 0.5秒钟触发一次中断:
 */
void timer0_init(void)
{
    TCFG0  = 99;        // 预分频器0 = 99       
    TCFG1  = 0x03;      // 选择16分频
    TCNTB0 = 31250;     // 0.5秒钟触发一次中断
    TCON   |= (1<<1);   // 手动更新
    TCON   = 0x09;      // 自动加载,清“手动更新”位,启动定时器0
}

 

 

(4)定时器中断

前面调用timer0_init函数后,定时器0就开始工作,调用init_irq函数使能定时器0中断,设置CPSR寄存器,开启IRQ中断后,每当定时器0计数达到0时就触发中断。


view plaincopy to clipboardprint?
/* 
 * 定时器0中断使能 
 */   
void init_irq(void)  
{          
    // 定时器0中断使能  
    INTMSK   &= (~(1<<10));  

/*
 * 定时器0中断使能
 */
void init_irq(void)
{       
    // 定时器0中断使能
    INTMSK   &= (~(1<<10));
}
 

(5)ISP定时器0计数到达时就调用其服务程序:

view plaincopy to clipboardprint?
void Timer0_Handle(void)  
{  
    /* 
     * 每次中断令4个LED改变状态 
     */ 
    if(INTOFFSET == 10)  
    {  
        GPBDAT = ~(GPBDAT & (0xf << 5));  
    }  
    //清中断  
    SRCPND = 1 << INTOFFSET;  
    INTPND = INTPND;       

void Timer0_Handle(void)
{
    /*
     * 每次中断令4个LED改变状态
     */
    if(INTOFFSET == 10)
    {
        GPBDAT = ~(GPBDAT & (0xf << 5));
    }
    //清中断
    SRCPND = 1 << INTOFFSET;
    INTPND = INTPND;    
}
 

 

定时器0的中断使用SRCPND,INTPND寄存器中的位10来表示,中断服务程序Timer0_Handle先判断是否定时器0的中断,若是则反转LED状态。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ipromiseu/archive/2009/10/29/4745421.aspx