S3C2440之系统时钟(ls_core)

今天笔者开始总结S3C2440的学习笔记,对于S3C2440的学习,笔者的着手点仍然是系统时钟,毕竟时钟是一个微机系统的时间标尺。微机没有了时钟,也就没有了时间尺度,那他也就无法有秩序的运行下去了。如果把CPU的所有工作任务作为纵坐标(y轴),那么时钟就是他的横坐标(x轴)了,如下图1 时钟概念所示。

img

图 1 时钟概念

也就是说时钟就是一个微机(小到8位的51单片机大到32位的S3C2440等)在时域上的一个衡量标准。学过FPGA的朋友都很清楚,微机是以对输入的时钟源的脉冲计数的方式来确定时间的。S3C2440也一样,拿到他之后首先从时钟入手,一定要养成这种系统学习的好方法,否则再学一百款单片机也是感觉再学新的,做不到举一反三。只有把握这其中的通理才能达到事半功倍的效果。

一 、硬件

S3c2440的时钟系统主要由外部晶振和外部时钟,时钟源选择和锁相环三部分组成。如下图2 S3c2440系统时钟框图所示:

img

图2 S3c2440系统时钟框图

1 时钟源

时钟源由外部晶振和外部时钟组成,外部晶振分别接在S3c2440的XTIPLL和XTOPLL引脚上面,外部时钟由S3c2440的EXTCLK引脚输入。

2 时钟源的选择

既然时钟源由两部分组成,那么在系统工作时到底怎么选择所需的时钟源呢。查看 S3c2440的手册不难找到,时钟源的选择由S3c2440的OM2和OM3输入引脚的电平决定,其关系如下表1 时钟源的选择所示:

表1 时钟源的选择

OM3OM2主时钟源USB时钟源00外部晶振外部晶振01外部晶振外部时钟10外部时钟外部晶振11外部时钟外部时钟

在一般的开发板上,主时钟和USB时钟全部是由外部的12M晶体振荡器提供的。所以在定制硬件的时候,直接将OM3和OM2接于地上,以选择外部的晶振作为主时钟源和USB时钟源。

3 锁相环

S3c2440内部集成了2个PLL锁相环以生成内部所需的高频时钟,其中一个锁相环MPLL倍频出的高频时钟有FCLK、PCLK、HCLK 。另一个锁相环UPLL倍频出的高频时钟为UCLK。那么,锁相环的输入时钟和输出时钟的关系怎么确定呢?通过查阅S3c2440的手册可知,锁相环的输入时钟为外部晶振或外部时钟Fin,输出时钟为FCLK。其关系为:FCLK=(2mFin)/(p* )。其中,m=M(分频器M的值)+8;p=(分频器P的值)+2。

4 内部时钟

经锁相环MPLL和UPLL倍频后得到的内部时钟有FCLK、PCLK、HCLK和UCLK。其用途如下表2 内部时钟用途所示:

表2 内部时钟用途

内部时钟应用场合应用举例FCLK—arm920tHCLK高速AHB总线内存控制,中断控制,LCD控制,DMA等PCLK低速APB总线看门狗,IIS,IIC,PWD,MMC接口,ADC,UARTGPIO,RTC以及SPI等UCLKUSB设备USB主、从接口

该内部时钟的关系可通过寄存器CLKDIVN控制得到不同比例的时钟,其关系如下表3 分频比所示:

表3 分频比

HDIVNPDIVNFCLK:HCLK:PCLK比例HDIVNPDIVNFCLK:HCLK:PCLK比例001:1:1301:6:6011:1:2311:6:12101:2:2201:4:4111:2:4211:4:8301:3:3201:8:8311:3:6211:8:16

二、软件

对S3c2440的硬件结构有了整体把握之后,让我们来简单应用一下。手里有S3c2440开发板的朋友,在跑裸机程序的时候大都是用的windows环境下的CodeWarrior for ARM这一开发环境,这个开发环境的强大不再多说。今天笔者,要使用另一种方法对其进行编程开发。笔者使用的环境为:RedHatEnterPrise6与交叉编译环境arm-linux-gcc。在这个环境下,笔者得自己编写由ARM汇编构成的S3c2440初始化汇编文件head.S,编译时所需的Makefile文件和一些C文件以及头文件。

首先来梳理一下本次总结所需的内容,该内容是要配置S3c2440的CPU工作频率FCLK为200MHz,FCLK:HCLKimgCLK的比例为1:2:4,这样一来,PCLK的频率为50MHz,然后配置定时器Timer0的输入时钟为PCLK,并配置其为PWM输出,通过示波器可以观察到PWD的频率,从而验证时钟配置的正确性。好了,首先来介绍一下该工程文件组成如下图3 工程文件所示:

img

图3 工程文件

其中,head.S文件为由ARM汇编语言编写的引导系统初始化文件;init.c和main.c为由C语言编写的顶层应用文件,s3c24xx.h为S3c2440的头文件;Makefile为编译、链接所需的文件。接下来首先给出引导系统初始化的汇编文件head.S:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
@**********************************************************************************************

@ File name : head.S

@ Function : 初始化,设置中断模式、系统模式的栈

@ Creating time : 2012-6-7/16:36

@ Author : 李帅

@ Pen-name : 亦然

@ Organization :济南大学(2013本科毕业)

@ E-mail :[ls_core@sina.cn](mailto:ls_core@sina.cn)

@ Contact way : QQ 1021480125 博客 http://blog.sina.com.cn/lscore

**********************************************************************************************

.extern main @定义一个外部符号main 表示该符号不是第一次出现。

.text @表示以下语句表示代码段。

.global _start @定义程序标号_start为全局的。

_start: @起始代码段

b Reset @b指令无条件跳转到Reset代码段

@**********************************************************************************************

@ 7种中断向量的中断向量表

@**********************************************************************************************

@ 0x04:未定义指令终止模式的向量地址

HandleUndef:

b HandleUndef

@ 0x08:管理模式的向量地址,通过SWI指令进入此模式

HandleSWI:

b HandleSWI

@ 0x0c:指令预取终止导致的异常向量地址

HandlePrefetchAbort:

b HandlePrefetchAbort

@ 0x10 :数据访问终止导致的异常向量地址

HandleDataAbort:

b HandleDataAbort

@ 0x14 :保留

HandleNotUsed:

b HandleNotUsed

@ 0x18:中断模式的向量地址

HandleIRQ:

b HandleIRQ

@ 0x1C:快速中断模式的向量地址

HandleFIQ:

b HandleFIQ

@**********************************************************************************************

@ 代码段

@**********************************************************************************************

Reset:

@********************************************************************************

@系统一上电从此处开始在内存中执行,首先控制器为系统模式下设置

@栈指针sp指向4K(steppingstone顶端处),然后关闭看门狗,初始化

@系统时钟,始化定时器0

@********************************************************************************

ldr sp, =4096 @设置堆栈,以便调用C函数

bl disable_watch_dog @调用C文件关闭看门狗

bl clock_init @调用C文件设置系统时钟

bl timer0_init @调用C文件初始化定时器0

halt_loop:

b halt_loop

@********************************** end of file **********************************************

其中,需要将的地方就是堆栈地方,为什么要设置栈指针sp为4096呢?因为此次工程文件在编译链接生成的可执行文件clock.bin文件要在S3c2440的内部ram中执行,而内部ram的总大小为4K。所以此处的sp最大只能设为4096。设置好堆栈后就可以方便地调用C函数了。

接下来给出用C编写的C文件init .c,该文件中的C函数被汇编文件head.S调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#include"s3c24xx.h"

void disable_watch_dog(void);

void clock_init(void);

void timer0_init(void);

/ ********************************************************************

\* 关闭看门狗

*******************************************************************/

void disable_watch_dog(void)

{

WTCON=0;

}

/ *******************************************************************

\* 时钟初始化函数

\* 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV[1:0]为SDIV

\* 计算公式如下:

\* 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

\* 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4

\* 由于开发板的输入时钟为12MHz,而且设置MDIV PDIV SDIV分别为

\* S3C2410 : MDIV=0x5C PDIV=0x04 SDIV=0x00

\* S3C2440 :MDIV=0x12 PDIV=0x01 SDIV=0x02

\* 则有:FCLK=200MHz HCLK=100MHz PCLK=50MHz

*******************************************************************/

\#define S3C2410_MPLL_200MHZ ((0x5C<<12) | (0x04<<4) | (0x00<<0))

\#define S3C2440_MPLL_200MHZ ((0x5C<<12) | (0x01<<4) | (0x02<<0))

void clock_init(void)

{

CLKDIVN=0x03;

/ ***********************************************************

\* 如果HDIVN非0,CPU总线模式应该从“fast mode”转变成“

\* asynchronous bus mode”

\* ARM920T有三种时钟总线模式:快速总线模式、同步总线

\* 模式和异步总线模式。

\* 当处理器上电或是复位时一开始使用快总线模式,由BCLK提供时钟。

\* 然后设置PLL使得FCLK达到目标频率并且稳定之后,通过设置CP15

\* 寄存器的c1寄存器来改变时钟驱动为同步驱动或是异步驱动。

\* 1.如果FCLK是BCLK的整数倍,则采用同步模式

\* 2.如果FCLK不是BCLK的整数倍,则采用异步模式

************************************************************/

__asm__

(

"mrc p15, 0, r1, c1, c0, 0\n"

"orr r1, r1, #0xC0000000\n"

"mcr p15, 0, r1, c1, c0, 0\n"

);

MPLLCON=S3C2440_MPLL_200MHZ;

}

/ ********************************************************************

\* 初始化定时0

\* 定时器0输入时钟Fin=PCLK/{prescaler value+1}/{divider value}

\* 其中:{prescaler value}=0~255

\* {divider vaalue}=2 4 8 16

*******************************************************************/

void timer0_init(void)

{

GPBCON &=~(0x03);

GPBCON |=(0x02);

/ ****************************************************************

\* TCFG0寄存器

\* 31~24 23~16 15~8 7~0

\* Reserved DeadZoon Prescaler1 Prescaler0

\* DeadZoon :死区长度

\* Prescaler1 :定时器2、3、4的预分频值(0~255)

\* Prescaler0 :定时器0、1的预分频值(0~255)

\* ************************************************************/

TCFG0=99;

/ ***************************************************************

\* TCFG1寄存器

\* 31~24 23~20 19~16 15~12

\* Reserved DMA mode MUX4 MUX3

\* 11~8 7~4 3~0

\* MUX2 MUX1 MUX0

\* DMA mode ![img](http://www.dnsnat.com/bbs/static/image/smiley/default/biggrin.gif)MA请求通道选择

\* MUX4 :为Timer4选择输入时钟的分频系数。

\* 0000 2分频 0001 4分频 0010 8分频

\* 0011 16分频 01xx 外部时钟TCLK1作为Timer4的时钟

\* *************************************************************/

TCFG1=0x03;

/ **************************************************************

\* TCNTB0计数缓存寄存器

\* 该值决定了PWM的周期(16位)。

\* ************************************************************/

TCNTB0=32;

/ *************************************************************

\* TCMPB0 比较缓冲寄存器

\* 该值在PWM模式下,决定了PWM的占空比。

\* ***********************************************************/

TCMPB0=24;

/ *************************************************************

\* TCON 定时器的控制寄存器

\* [4] :设置死区长度

\* [3] :timer0自动重装的开关

\* [2] :timer0输出PWD开关

\* [1] :人工更新

\* [0] :Timer0的启动、停止开关

\* ************************************************************/

TCON |=(1<<1); //手动更新各寄存器

TCON =0x09; //自动加载,启动定时器0

}

/ *************** end of file **********************************/

最后来看看编译链接文件Makefile。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
objs := head.o init.o main.o

clock.bin: $(objs)

arm-linux-ld -Ttext 0x00000000 -o clock_elf $^

arm-linux-objcopy -O binary -S clock_elf $@

arm-linux-objdump -D -m arm clock_elf > clock.dis

%.o:%.c

arm-linux-gcc -Wall -nostdlib -O2 -c -o $@ $<

%.o:%.S

arm-linux-gcc -Wall -nostdlib -O2 -c -o $@ $<

clean:

rm -f clock.bin clock_elf clock.dis *.o

拿到Makefile文件之后,初试者肯定会很头疼,总感觉这既不是C语言也不是其他常用语言,这到底是什么东西啊?呵呵,请不要着急,这个文件中只是安排了一些编译的规则而已。见得多了,读的多了,写的多了,自然就熟悉了。

首先来看第1行,该行定义了一个变量objs并为其赋值为3个编译后的.o文件。那这些.o文件从何而来呢?来看第6~9行,便知,通过arm-linux-gcc编译器将工程中的所有.s和.c文件编译之后形成对应的.o文件。

再来看第2行,第2行讲的是要根据三个.o文件最终要生成clock.bin二进制文件。那么这个过程的规则又是什么呢?来看第3~5行,第3行讲要将编译得到了3个.o文件从地址0x00000000开始通过命令arm-linux-ld连接起来生成一个clock_elf格式文件。第4行讲将clock_elf文件通过命令arm-linux-objcopy转化成为二进制文件clock.bin。第5行讲将clock_elf文件通过命令arm-linux-objdump转化成为汇编文件clock.dis以便查看。

最10~11行讲的是编写一个make clean命令将编译生成中间文件全部删除。

这里只给出重要的文件程序进行讲解,s3c24xx.h和main.c不再做详细讲解。编写好这几个文件之后执行make命令,即可得到Makefile文件中提到的所有文件包括可执行的二进制文件clock.bin文件。编译过程如下图4 编译过程所示:

img

图4 编译过程

然后,将生成clock.bin二进制文件烧写到NandFlash中,运行并用普源示波器测试由S3c2440的TOUT0引脚输出的PWM的频率如下图5 实测PWM所示:

img

图5 实测PWM

三 数据分析

通过示波器可以清晰的看到频率为947.052Hz,占空比为75%的PWM波。回顾init.c文件中对定时器的配置有TCFG0=99; TCFG1=0x03; TCNTB0=32; TCMPB0=24;可以计算出:

PWM的频率:PWM_CLK=(PCLK/( TCFG0+1))/16/TCNTB0=976.5625Hz

PWM占空比:PWM_D=TCMPB0/TCNTB0=75%

通过理论数据和实际数据对比得知,PWM的实际频率小于理论值,推究其原因,由于计算理论值时PCLK的取值为50MHz,分析其原因可知,PCLK的实际值应小于50MHz。那么PCLK的实际值到底是多大呢?根据PWM的实际频率和配置相关系数安照PWM的频率公式可反推出PCLK=48.5MHz。那么根据FCLK:HCLK:PCLK=1:2:4可得出,FCLK、HCLK、PCLK的频率分别为194MHz、97Hz和48.5MHz。

结论:时钟系统的各个频率值的实际大小和理论值还是存在一定的偏差。

好啦,讲了这些希望初学者对对系统时钟有一个整体把握,希望前辈能多多指正,谢谢!我在后面还会将我的学习心得整理成文,包括FPGA和ARM等。再次声明,文中若有技术错误或是不足望各位多多指正,多多交流。共同交流使得我们共同进步!希望将我的学习心得与您分享,谢谢!下次总结再见。

Blog :http://blog.sina.com.cn/lscore

E-mail :ls_core@sina.cn

QQ号 :1021480125

新浪微博:lis_core


S3C2440之系统时钟(ls_core)
http://blog.uanet.cn/EMBEDDED/mini2440/S3C2440之系统时钟(ls_core).html
作者
dnsnat
发布于
2022年5月30日
许可协议