S3C2440的datesheet上说,可以达到400M,但是也不是说,必须在400M的频率下工作,主时钟晶振来自于外部晶振(XTIPLL)或者是外部时钟(EXTCLK)。时钟生成器包含了一个振荡器(振荡放大器),其连接外部晶振,并且还有2个PLL,可以产生需要的高频。
通过引脚OM[3:2]来决定时钟源是Crystal还是EXTCLK,不过我用的开发板将OM[3:2]固定接地了,都是用外部晶振。有一点值得注意,在对MPLLCON写入有效值之前,系统使用外部晶振或外部时钟源的时钟。即使用户不准备改变MPLLCON的值,也应当重新写一次。
S3C2440的时钟构成:S3C2440具有2个PLL(Phase Locked Loop:用来产生高频的电路),一个是MPLL, 用于产生FCLK, HCLK, PCLK三种频率, 这三种频率分别有不同的用途:FCLK是CPU提供的时钟信号,如果提到CPU的主频是400MHz,就是指的这个时钟信号。HCLK是为AHB总线提供的时钟信号, Advanced High-performance Bus,主要用于高速外设,比如内存控制器,中断控制器,LCD控制器, DMA 以及USB host 。 PCLK是为APB总线提供的时钟信号,Advanced Peripherals Bus,主要用于低速外设,比如WATCHDOG,IIS, I2C, SDI/MMC, GPIO,RTC ,UART,PWM,ADC and SPI等等。 另外一个是UPLL,专门用于驱动USB host/Device。并且驱动USB host/Device的频率必须为48MHz。
在系统复位之后,如果没有设定PLL,则采用外部晶振的频率作为FCLK,同时FCLK:HCLK:PCLK的比例关系为1:1:1。
下面说一些跟时钟有关的寄存器设置:通过改变CLKDIVN可以改变FCLK,HCLK,PCLK的分频比。锁定时间计数寄存器LOCKTIME(0x4c000000):一般使用默认就可以。
锁相环控制寄存器[MPLLCON(0x4c000004)&UPLLCON(0x4c000008)]:MPLL=(2*m*Fin)/(p*2^s) UPLL=(m*Fin)/(p*2^s)其中m=(MDIV+8),p=(PDIV+2),s=SDIV
P,M范围:1<=P<=62,1<=M<=248
说到设置,在内核当中查找关于上述寄存器的设置颇费了一番功夫,结果却只是在s3c2410_init_clocks()中找到了__raw_readl(S3C2410_MPLLCON),全是读没写。郁闷!最后发现原来写操作是在bootloader中:/board/smdk2410.c board_init()。不光是对该寄存器写操作,对所有IO口控制寄存器及常用的控制寄存器都进行了些操作。
注意:MDIV[19:12],PDIV[9:4],SDIV[1:0],当设置MPLL和UPLL值的时候,需要先设置UPLL再设置MPLL。例如:MPLLCON = (92<<12) | (1<<4) |(1);//FCLK=400M
这里MDIV=92,PDIV=1,SDIV=1,那么m=100,p=3,s=1,且Fin=12M,所以FCLK=400M
再设置CLKDIVN=0x03;//FCLK:HCLK:PCLK=1:2:4
这里CLKDIVN(0x4c000014)用于决定三者的分配比例
一般设置这两个就可以了。还有一个时钟控制寄存器CLKCON(0x4c00000c)向相应位写1使能相应时钟,不过一般默认为1.
S3C2440的定时器设置
S3C2440有5个16位定时器,定时器0-3有PWM功能,定时器4有一个没有输出引脚的内部定时器,定时器0有一个用于大电流设备的死区生成器。
有2个8位预分频器和2个4位分频器。定时器0和定时器1共用一个8位预分频器。定时器2,定时器3,定时器4共用另一个8位预分频器。
定时器的时钟源是PCLK,首先经过预分频器降低频率后,进入第二个分频,可以生成5种不同的分频信号(1/2,1/4,1/8,1/16和TCLK)。其中TCLK0,TCLK1是S3C2440的外部时钟信号输入管脚。
所有定时器都是递减计数。
预分频器值(prescaler value)在TCFG0(0X5100 0000)中设置,0-7位设置TIMER0,TIMER1的预分频值。8-15位设置TIMER2,3,4的预分频值。 第二个分频器的值(divider value)在TCFG1(0X5100 0004)中设置 ,0-3位设置TIMER0的分频值,4-7位设置TIMER1的分频值,8-11位设置TIMER2的分频值 ,12-15位设置TIMER3的分频值,16-19位设置TIMER4的分频值。 定时器输入频率的计算公式是: Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
{prescaler value} = 0~255
{divider value} = 2, 4, 8, 16
TCON(0X5100 0008)是5个定时器的控制位寄存器,以TIMER0为例:
0-4位是TIMER0的控制位
Bit0:停止或开始TIMER0计时
Bit1:手动更新TCNTB0和TCMPB0
Bit2:TOUT0逻辑电平是否翻转
Bit3:0-一次性脉冲模式,1-自动装载模式
Bit4:使能或禁止死区功能
Timer0-3各有TCNTBn,TCNTn,TCMPBn,TCMPn,TCNTOn共5个寄存器,其中TCNTn,TCMPn是内部寄存器,没有对应的地址,通过读TCNTOn的值可以得到TCNTn的值。当定时器计数到0,TCNTBn和TCMPBn的值装入TCNTn和TCMPn,如果中断使能,同时产生中断。在计数过程中,TCNTBn和TCMPBn的值是不变的,变的是TCNTn的值。Timer0-3各有一个对应的输出脚TOUT0-3。
Timer4有TCNTB4,TCNT4,TCNTO4共3个寄存器,其中TCNT4是内部寄存器,Timer4没有对应的输出脚。
按下面的步骤启动一个定时器:
1、 初始化TCNTBn和TCMPBn。
2、 把相应定时器的手动更新位置1,不管是否使用TOUTn极性转换功能,推荐都配置一下极性转换功能位。
3、 设置相应定时器的启动位启动定时器,同时清除手动更新位。
不管定时器是否运行,只要TOUTn极性转换位改变,TOUTn逻辑电平也会变改变。所以推荐极性转换位和人工加载位一起设置。
定时器运行时,当TCNTn等于TCMPBn时,TOUTn输出的电平会翻转,而当TCNTn减为0时,产生中断,TOUTn的电平又会翻转过来。
S3C2440的datasheet里举了一个例子,很好的说明的定时器的工作过程,过程图如下:
1、使能自动装载功能,TCNTBn设为160(50+110),TCMPBn设为110,置为手动更新标志,把TCNTBn,TCMPBn的值装入TCNTn,TCMPn。TOUTn翻转功能关闭,然后把TCNTBn设为80(40+40),TCMPBn设为40,这是为下一次装载设置的。
2、使能定时器开始计时位,清零手动更新位,定时器开始向下计时。
3、当TCNTn的值和TCMPn的值相等时,TOUTn从低变高。
4、当TCNTn等于0时,产生中断,TCNTBn,TCMPBn重新装载进TCNTn,TCMPn,这次的值是80和40,TOUTn从高变低。
5、在定时器中断程序中,TCNTBn和TCMPBn的值设置成80(20+60)和60,这是为下一次装载准备的。
6、当TCNTn的值和TCMPn相等时,TOUTn从低变高。
7、当TCNTn等于0,产生中断,TCNTBn,TCMPBn重新装载进TCNTn,TCMPn,这次的值是80和60.
8、在定时器中断程序中,关闭定时器自动装载和中断功能。
9、当TCNTn的值和TCMPn相等时,TOUTn从低变高。
10、TCNTn等于0,TCNTn不在自动装载,定时器停止计时。
11、不会有中断产生。
经过测试:使用外部时钟输入时输入给定时器的频率信号仅与外部时钟的频率有关,与预分频寄存器的值无关。输出任意频率PWM方波代码如下: |
void start_timer2(void)
{
ulong val, tmp, m, n;
val = __raw_readl(S3C2410_TCFG0) & (~S3C2410_TCFG_PRESCALER1_MASK);
val |= (0 << S3C2410_TCFG_PRESCALER1_SHIFT);
__raw_writel(val, S3C2410_TCFG0);
tmp = __raw_readl(S3C2410_TCFG0);
printk("S3C2410_TCFG0=0x%x\n", tmp);
/*设置分频寄存器的值*/
val = __raw_readl(S3C2410_TCFG1) & (~S3C2410_TCFG1_MUX2_MASK);
// val |= S3C2410_TCFG1_MUX2_TCLK1;
val |= S3C2410_TCFG1_MUX2_DIV16;
__raw_writel(val, S3C2410_TCFG1);
tmp = __raw_readl(S3C2410_TCFG1);
printk("S3C2410_TCFG1=0x%x\n", tmp);
/*使能自动重载位*/
tmp = __raw_readl(S3C2410_TCON);
val = __raw_readl(S3C2410_TCON) & (~(0xf << 12));
val |= S3C2410_TCON_T2RELOAD;
__raw_writel(val, S3C2410_TCON);
tmp = __raw_readl(S3C2410_TCON);
printk("S3C2410_TCON22222222222=0x%x\n", tmp);
/*设置TCNTB和TCMPB寄存器*/
// __raw_writel(TIMERCOUNT, S3C2410_TCNTB(2));
// __raw_writel(TIMERCOUNT / 4, S3C2410_TCMPB(2));
__raw_writel(799, S3C2410_TCNTB(2));
__raw_writel(399, S3C2410_TCMPB(2));
m = __raw_readl(S3C2410_TCNTB(2));
n = __raw_readl(S3C2410_TCMPB(2));
printk("S3C2410_TCNTB=0x%x,S3C2410_TCMPB=0x%x\n", m, n);
/*置位手动更新位*/
val = __raw_readl(S3C2410_TCON) & (~(0x3 << 12));
val |= S3C2410_TCON_T2MANUALUPD;
__raw_writel(val, S3C2410_TCON);
tmp = __raw_readl(S3C2410_TCON);
printk("S3C2410_TCON--S3C2410_TCON_T3MANUALUPD=0x%x\n", tmp);
/*设置起始位置位反相位,关闭手动更新位*/
val = __raw_readl(S3C2410_TCON) & (~(0x3 << 12));
val |= (S3C2410_TCON_T2START | S3C2410_TCON_T2INVERT);
// val |= S3C2410_TCON_T2START;
tmp = __raw_writel(val, S3C2410_TCON);
printk("S3C2410_TCON========0x%x\n", tmp);
}
在初始化函数中s3c2410_gpio_cfgpin(S3C2410_GPB2, S3C2410_GPB2_TOUT2);
s3c2410_gpio_pullup(S3C2410_GPB2, 0);
通过设置TCNTB和TCMPB寄存器得到PWM,前者调整频率(pclk经过预分频、分频之后除以(TCNTB+1)即可得到输出频率)。后者调整占空比(当TCNTB的值和TCMPB相等时就会翻转)。
阅读(2081) | 评论(0) | 转发(0) |