一、s3c2440时钟介绍
s3c2440中有三种时钟:FCLK,HCLK,PCLK。
FCLK用于CPU核;HCLK用于AHB(Advanced High Performance Bus)总线上的设备,如CPU核、存储器控制器、中断控制器、LCD控制器、DMA和USB主机模块等;PCLK用于APB(Advanced Peripheral Bus)总线上的设备,如WATCHDOG、IIS、I2C、PWM定时器、MMC接口、ADC、UART、GPIO、RTC和SPI。
s3c2440中有两个PLL来设置时钟,分别为:MPLL和UPLL。MPLL用于设置上述的三种时钟,而UPLL专用于USB设备。
下面介绍MPLL是如何工作的,先看下图。
(1)在上电时,PLL没有启动,FCLK即等于外部输入的时钟,称为Fin(在我用的mini2440板子上,Fin=12MHz)。
(2)上电几毫秒后,晶振(OSC)输出稳定,FCLK=Fin,nRESET信号恢复高电平以后,CPU开始执行命令。
(3)程序中设置MPLL的相关寄存器,来设定FCLK的值和FCLK、HCLK和PCLK的比例。程序执行到此处后,MPLL启动。此时需要一段时间(Lock Time),MPLL输出才会稳定。
(4)Lock Time之后,MPLL输出正常,CPU工作在新的FCLK之下。
二、时钟设置方式
设置时钟需要设置以下四个寄存器。其中:
LOCKTIME用来设置锁定时长,
MPLLCON用来设置FCLK的值,
CLKDIVN和CAMDIVN这两个寄存器来设置HCLK和PCLK的值(它们为FLCK的分频)。
1.LOCKTIME
这个寄存器时设置上面提到的那个LockTime的长短的,一般设为默认就好。
2.MPLLCON
上面的PLLCON其实代表MPLLCON和UPLLCON两个寄存器,它们的格式一样。
这个寄存器用来设置FCLK的值,它的值是根据这个寄存器和Fin来确定的,确定方式如下公式:
上面的Fout的值即为FCLK的值。上面的MDIV、PDIV、SDIV和上面寄存器中的位相对应。
如果我们要设置FCLK为200MHz,那么根据公式,我们需要设置MPLLCON寄存器为:0x0005c012。
3.CLKDIVN
这个寄存器中的[3]位用来设置UCLK;[2:1]位用来设置HCLK与FCLK的比例;[0]位用来设置PCLK和HCLK的比例。
4.CAMDIVN
这个寄存器一般只需要用到[9]HCLK4_HALF和[8]HCLK3_HALF,这两位结合CLKDIVN寄存器中的[2:1]HDIVN和[0]PDIVN来设置FCLK、HCLK和PCLK的分频比例。这四个参数的设置和比例的关系如下图:
若我们需要配置三者的分频比例为1:2:4,那么就要设置CLKDIVN寄存器为:0x00000003;CAMDIVN为默认。
至此,我们就完成了FCLK HCLK PCLK这三个时钟的设置。
注意:韦东山的《嵌入式linux应用开发完全手册》一书中说“如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode”,这可以通过如下指令来完成。。。”所以我们在程序中需要加上下面的代码。
上面提到的指令:mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #R1_nF : OR : R1_iA @R1_nF : OR : R1_iA 等于 0xC0000000.
mrc p15, 0, r0, c1, c0, 0
三、PWM定时器的使用
s3c2440中有5个16位定时器,其中定时器0、1、2/3具有PWM的功能,即它们都有一个输出引脚,可以通过定时器来控制引脚周期性的高、低电平变化;定时器4没有输出引脚。
定时器的使用步骤:
(1)以PCLK通过设置TCFG0和TCFG1来确定某个定时器的工作频率。
(2)通过TCNTBn、TCMPBn寄存器来确定计数初值和比较值。
(3)通过设置TCON寄存器来设定各个定时器工作方式。
(4)通过设置TCON寄存器来手动装入计数初值和比较值。
(5)通过设置TCON寄存器来开启定时器。
(6)通过读TCNTO寄存器中的数据来获取当前定时器中的数值。
下图为定时器工作流程
定时器的具体使用步骤如下:
1.设置定时器的工作频率
定时器部件的时钟源为PCLK,该时钟信号先经过8位预分频(通过配置TCFG0寄存器来确定预分频倍数),然后再经过一次分频(通过配置TCFG1寄存器来确定分频倍数),这次的分频倍数只能是2、4、8、16中的一个。经过两次分频,得到定时器的工作频率。
若PCLK=50MHz,我们想设置Timer0的工作频率为31500Hz,那么50MHz/(99+1)/16=31500Hz。根据上面的数值我们需要设置TCFG0为99,TCFG1为0x03。
2.设置定时器初值
设定定时器的两个初值,分别为初始计数值、比较值。初始计数值由TCNTBn(n为0-4,表示5个定时器)来决定,比较值由TCMPBn来决定。下面用定时器0为例。
计数初值和比较值需要先手动传入定时器内部的寄存器中,然后当计数器启动后,内部计数寄存器在初值基础上进行不断地减一操作。当减到比较值时,如果我们设定了输出反转(用TCON寄存器设置)的话,输出电平会反转。计数值继续减一,减到0时,如果设置了翻转,那么电平会翻转,如果使能了对应定时器中断(取消INTMSK寄存器中对应定时器的中断屏蔽,并且通过设置状态寄存器CSRP开启总中断),那么会产生中断。然后根据设定(用TCON寄存器设置),确定是否自动装入初值和比较值。这些操作在后面会详细介绍。
上面设置工作频率为315MHz,由于计数初值进行减一操作,如果希望每0.5s为一个计数周期,那么计数初值应设为15625,即TCNTB0=15625。如果我们想控制PWM的占空比,我们可以设置比较值来控制,如占空比为50%,比较值就为初值的一半,即TCMPB0=7812。
3.设置定时器的工作方式,并手动传入初值,然后开启定时器
通过TCON寄存器来设置定时器工作方式的设置包括:输出是否反转,是否自动重装。手动传入初值和开启/关闭定时器也都是通过TCON寄存器完成的。
这里只截取了其中关于Timer0的[0-7]位,它的[8-22]位是关于Timer1-4的。
[0]开启或关闭定时器0
[1]手动更新计数初值和比较值,即:将TCNTB0和TCMPB0的值传入Timer0的内部寄存器。
[2]输出是否反转
[3]是否自动重装,当内部寄存器计数值为零时,如果该位是能,那么计数初值和比较值会自动传入定时器中。
[4]死区使能,这个我没用到,没有研究。
启动定时器0的步骤如下:
(1)用TCON设置好是否反转,是否自动重装
(2)通过INTMSK寄存器开启/屏蔽Timer0的中断请求,通过状态寄存器CPSR开启/屏蔽IRQ/FIQ中断响应。
(3)使能TCON的[1]位,手动更新。
(4)使能TCON的[0]位,开启定时器0,此处注意[1]位应为0(关掉手动更新)。
4.通过TCNTO寄存器来获取当前定时器中的计数数值。
至此,PWM发生器的设置与启动就完成了。下面贴出了上文相关的一些程序代码。完整的程序我已经上传到了CSDN的下载区(不需要下载积分),链接如下:
该程序是以我上上篇博客中的程序为基本写的,有不明白的地方可以点击下面的链接进行查看:
时钟设置程序(head2.S文件):
FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
<span style="color:#996633;">clock_init: ldr r1, =0x00000003 @FCLK:HCLK:PCLK=1:2:4 ldr r0, =0x4c000014 @address of register named CLKDIVN str r1, [r0] mrc p15, 0, r1, c1, c0, 0 orr r1, r1, #0xc0000000 mcr p15, 0, r1, c1, c0, 0 ldr r1, =0x0005c012 @FCLK=200MHZ ldr r0, =0x4c000004 @address of register named MPLLCON str r1, [r0] mov pc, lr</span>
定时器0的初始化程序(init.c文件):
设置Timer0和Timer1两个定时器的定时周期均为0.5s,Timer0设置了比较值,而且板子上Timer0连接到蜂鸣器上,当TOUT0(Timer0的PWM输出端口)输出为高电平时,蜂鸣器响。Timer1对应的中断开启。
/* * enable timer1 interrupt */ void init_irq( ) { INTMSK &= (~(1<<11)); }
<span style="color:#996633;">/* *init timer0 to auto load initial numbers and every 0.5s to produce an interrupt */ void timer_init(void) { GPBCON |= GPB0_TOUT0; TCFG0 = 99; TCFG1 = 0x33; TCNTB0 = 15625; TCMPB0 = 13000; TCNTB1 = 15625; TCON |= ((1<<1) | (1<<9)); TCON = 0x090d; }</span>
Timer1的中断服务程序(timer.c文件):
当中断发生时,连接LED的GPIO口电平会反转,即mini2440上的LED灯会每隔0.5s进行亮灭交替。
<span style="color:#996633;">void Timer1_Handle(void) { if(INTOFFSET ==11) { GPBDAT = ~GPBDAT; } SRCPND = 1<<INTOFFSET; INTPND = INTPND; }</span>