简单!
全部博文(366)
分类: 嵌入式
2011-06-05 17:55:48
系统时钟和定时器
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。
上电时,PLL没被启动,FCLK即等于外部输入的时钟,称为Fin。若要提高系统时钟,需要软件来启动PLL,可跟随FCLK的图像了解启动过程。
(1)上电几毫秒后,晶振输出稳定,FCLK=Fin(晶振频率),nRESET信号恢复高电平后,CPU开始执行指令。
(2)可以在程序开头启动MPLL,设置MPLL的几个寄存器后,需要等待一段时间(Lock Time),MPLL输出才稳定。在这段时间内,FCLK停振,CPU停止工作。Lock Time的长短由寄存器LOCKTIME设定。
(3)Lock Time之后,MPLL输出正常,CPU工作在新的FCLK下。
FCLK、HCLK和PCLK的比例是可以改变的,设置它们三者的比例,启动MPLL只需设置3个寄存器(对于S3C2440的一些时钟比例,还需要额外设置一个寄存器)。
(1)LOCKTIME寄存器:用于设置“Time Lock”的长度。
(2)MPLLCON寄存器:用于设置FCLK与Fin的倍数。
(3)CLKDIVN寄存器:用于设置FCLK、HCLK、PCLK三者的比例。对于S3C2440的一些时钟比例,还需要额外设置一个寄存器CAMDIVN。
#define S3C2410_MPLL_200MHZ ((0x5c<<12)|(0x04<<4)|(0x00))
#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
/*
* 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV
* 有如下计算公式:
* S3C2410: MPLL(FCLK) = (m * Fin)/(p * 2^s)
* S3C2410: 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" /* 写入控制寄存器 */
);
/* 判断是S3C2410还是S3C2440 */
if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
{
MPLLCON = S3C2410_MPLL_200MHZ; /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
else
{
MPLLCON = S3C2440_MPLL_200MHZ; /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
}
PWM定时器
S3C2410/S3C2440的定时器部件完全一样,共有5个16位的定时器。其中定时器0、1、2有PWM功能,即它们都有一个输出引脚,可以通过定时器来控制引脚周期性的高、低电平变化;定时器4没有输出引脚。
定时器部件的时钟电源位PCLK,首先通过两个8位的预分频器降低频率:定时器0、1共用第一个预分频,定时器2、3、4共用第二个预分频器。预分频器的输出将进入第二级分频器,它们输出5种频率的时钟:2分频、4分频、8分频、16分频或者外部时钟TCLK0/TCLK1。每个定时器的工作时钟可以从这5种频率中选择。
这两个预分频都可以通过TCFG0寄存器来设置,每个定时器工作在哪种频率下也可以通过TCFG1寄存器来选择。
上面只是确定了定时器的工作频率,至于定时器如何工作还得了解其内部结构。
定时器内部控制逻辑的工作流程如下。
(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设为“自动加载”,则TCMPBn和TCNTBn寄存器的值被自动装入TCMPn和TCNTn寄存器中,下一个计数流程开始。
定时器n的输出管脚TOUTn初始状态为高电平,以后在TCNTn的值等于TCMPn的值、TCNTn的值等于0时反转。也可以通过TCON寄存器设置其初始电平,这样TOUTn的输出就完全相反了。通过设置TCMPBn、TCNTBn的值可以设置管脚TOUTn输出信号的占空比,这就是所谓的可调制脉冲,所以这些定时器又被称为PWM定时器。
下面讲解定时器的寄存器使用方法。
(1)TCFG0寄存器:位[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个频率中哪一个之下。
(3)TCNTBn/TCMPBn寄存器:n为0~4,这两个寄存器都只用到位[15:0],TCNTBn中保存定时器的初始计数值,TCMPBn中保存比较值。它们的值在启动定时器时,被传到定时器内部寄存器TCNTn、TCMPn中。没有TCMPB4,因为定时器4没有输出引脚。
(4)TCNTOn寄存器:n位0~4,定时器n被启动后,内部寄存器TCNTn在其工作时钟下不断减1计数,可以通过读取TCNTOn寄存器得知其值。
(5)TCON寄存器:有四个作用,a、第一次启动定时器时“手动”将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中;b、启动、停止定时器;3、决定在定时器计数达到0时是否自动将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中;4、决定定时器的管脚TOUTn的输出电平是否反转。
在第一次使用定时器时,需要设置“手动更新”位为1以使TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中。下一次如果还要设置这一位,需要先将它清0。
#define PCLK 50000000
void delay_ms(int time)
{
int val = (PCLK>>3)/1000-1;
TCFG0 &= ~(0xff<<8); //TCFG0设置预分频器0、1,TCFG1用于设置二次分频
TCFG0 |= 3<<8; //prescaler = 3+1
TCFG1 &= ~(0xf<<12);
TCFG1 |= 0<<12; //mux = 1/2
TCNTB3 = val;
TCMPB3 = val>>1; // 50%
TCON &= ~(0xf<<16);
TCON |= 0xb<<16; //interval, inv-off, update TCNTB3&TCMPB3, start timer 3
TCON &= ~(2<<16); //clear manual update bit
while(time--)
{
while(TCNTO3>=val>>1);
while(TCNTO3
};
}
#define GPB5_out (1<<(5*2)) // LED1
#define GPB6_out (1<<(6*2)) // LED2
#define GPB7_out (1<<(7*2)) // LED3
#define GPB8_out (1<<(8*2)) // LED4
/*
* 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
}
// 定时器0中断使能
void init_irq_timer0(void)
{
INTMSK &= (~(1<<10)); // 定时器0中断使能
}
void Timer0_Handle(void)
{
if(INTOFFSET == 10)
{
GPBDAT = ~(GPBDAT & (0xf << 5)); // 每次中断令4个LED改变状态
}
SRCPND = 1 << INTOFFSET; //清中断
INTPND = INTPND;
}
void Timer0_test(void)
{
char c;
GPBCON = GPB5_out | GPB6_out | GPB7_out | GPB8_out ;
GPBDAT = 0xffff;
timer0_init();
printf("\nKey Scan Test, press ESC key to exit !\n");
ClearPending(BIT_TIMER0);
pISR_TIMER0 = (int)Timer0_Handle;
EnableIrq(BIT_TIMER0);
while( (c=get_c())!=0x1b);
DisableIrq(BIT_TIMER0);
}
//***************************[ BOARD BEEP ]*******************************
void Buzzer_Freq_Set( int freq )
{
GPBCON &= ~3; //set GPB0 as tout0, pwm output
GPBCON |= 2;
TCFG0 &= ~0xff;
TCFG0 |= 15; //prescaler = 15+1
TCFG1 &= ~0xf;
TCFG1 |= 2; //mux = 1/8
TCNTB0 = (PCLK>>7)/freq;
TCMPB0 = TCNTB0>>1; // 50%
TCON &= ~0x1f;
TCON |= 0xb; //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
TCON &= ~2; //clear manual update bit
}
void Buzzer_Stop( void )
{
GPBCON &= ~3; //set GPB0 as output
GPBCON |= 1;
GPBDAT &= ~1;
}
//***************************[ BOARD BEEP ]*******************************
void Beep(int freq, int ms)
{
Buzzer_Freq_Set( freq ) ;
delay_ms( ms ) ;
Buzzer_Stop() ;
}
/****************************************************************************
【功能说明】蜂鸣器PWM测试
****************************************************************************/
void Buzzer_Pwm_Test( void )
{
int freq =8000;// lci 1000 ;
Buzzer_Freq_Set( freq ) ;
while( 1 )
{
char key = get_c();
if( key == '+' )
{
if( freq < 2000 ) //lci 20000
freq += 10 ;
Buzzer_Freq_Set( freq ) ;
}
if( key == '-' )
{
if( freq > 11 )
freq -= 10 ;
Buzzer_Freq_Set( freq ) ;
}
if( key == 0x1b )
{
Buzzer_Stop() ;
return ;
}
}
}
WATCHDOG定时器
WATCHDOG定时器可以像一般16位定时器一样用于产生周期性的中断,也可以用于发出复位信号以重启失常的系统。
WATCHDOG定时器的8位预分频器将PCLK分频后,被再次分频得到4种频率:16分频、32分频、64分频、128分频,WATCHDOG定时器可以选择工作于哪种频率之下。WTCNT寄存器按照其工作频率减1计数,当达到0时,可以产生中断信号,可以输出复位信号。在第一次使用WATCHDOG定时器时,需要往WTCNT寄存器中写入初始计数值,以后在计数值到达0时自动从WATDAT寄存器中装入,重新开始下一个计数周期。
使用WATCHDOG定时器的“WATCHDOG功能”时,在正常的程序中,必须不断重新设置WTCNT寄存器使得它不为0,这样可以保证系统不被重启,这称为“喂狗”;当程序崩溃时不能正常“喂狗”,计数值达到0后系统将被重启,这样程序将重新运行。为了克服各种干扰、避免各类系统错误时系统彻底死机,经常使用WATCHDOG功能。
WATCHDOG定时器所涉及的寄存器如下。
(1)WTCON寄存器:用于设置预分频系数,选择工作频率,决定是否使能中断、是否启动WATCHDOG功能(即是否输出复位信号)。
(2)WTDAT寄存器:被用来决定WATCHDOG定时器的超时周期,在定时器启动后,当计数达到0时,WTDAT寄存器的值会自动传入WTCNT寄存器。不过,第一次启动WATCHDOG定时器时,WTDAT寄存器的值不会自动传入WTCNT寄存器。
(3)WTCNT寄存器:在启动WATCHDOG定时器前,必须往这个寄存器写入初始计数值。启动定时器后,它减1计数,当计数值达到0时:如果中断被使能的话发出中断,如果WATCHDOG功能被使能的话发出复位信号,装载WTDAT寄存器的值并重新计数。