AT91RM9200 的时钟源有4个:
慢时钟(SLK)
主时钟(Main Clock)
PLLA,PLLB
在这里需要区别一个概念:主时钟和主机时钟
主时钟(main clock)是指输入主振荡器的时钟
主机时钟(mck)指CPU的时钟频率。
主机时钟可以在4个时钟源中选择(时钟选择器)一个作为本身的时钟
时钟设置流程
启动代码中一些预定义的值
;-----------------------------------------------------------------------------
;- ARM Core Mode and Status Bits ARM内核模式和状态位
;------------------------------------------------------------------------------
ARM_MODE_USER EQU 0x10
ARM_MODE_FIQ EQU 0x11
ARM_MODE_IRQ EQU 0x12
ARM_MODE_SVC EQU 0x13
ARM_MODE_ABORT EQU 0x17
ARM_MODE_UNDEF EQU 0x1B
ARM_MODE_SYS EQU 0x1F
I_BIT EQU 0x80
F_BIT EQU 0x40
T_BIT EQU 0x20
;------------------------------------------------------------------
;- Stack Area Definition 堆栈段区域定义
;------------------------------------------------------------------
IRQ_STACK_SIZE EQU 0x04
FIQ_STACK_SIZE EQU 0x04
ABT_STACK_SIZE EQU 0x04
UND_STACK_SIZE EQU 0x04
SVC_STACK_SIZE EQU 0x04
USER_STACK_SIZE EQU 0x100
;----------------------------------------------------------------------------
9200启动后时钟变化
由于系统复位后进入慢时钟状态,由慢时钟提供主机时钟源,但由于内置boot程序可能已经启动,需要在启动时重新设置慢时钟为主机时钟源。但主机时钟寄存器要求写入的值不能和当前值相同,所以要分成两步,代码如下:
//第一步,将PMC_MCKR 修改一下,这里使其选择PLLB作为主机时钟源
ldr r1, = AT91C_BASE_PMC ;取得PMC的基地址
ldr r0, = AT91C_PMC_CSS_PLLB_CLK
str r0, [r1, #PMC_MCKR] ;向PMC_MCKR寄存器写入数值
//通过测试状态寄存器 PMC_SR的第3位来判断时钟设置是否成功
mov r4, #0x8
MCKR_Loop
ldr r3, [r1, #PMC_SR]
and r3, r4, r3
cmp r3, #0x8
bne MCKR_Loop
//第二步,修改PMC_MCKR使其选择慢时钟SCK作为主机时钟源
ldr r0, = AT91C_PMC_CSS_SLOW_CLK :OR:AT91C_PMC_PRES_CLK
str r0, [r1, #PMC_MCKR]
mov r4, #0x8
MCKR_Loop2
ldr r3, [r1, #PMC_SR]
and r3, r4, r3
cmp r3, #0x8
bne MCKR_Loop2
//为了节省功耗关闭PLLs。
//实际上,在机器复位后PLLs 是被默认关闭的,但是在以前有一个boot程序已经运行过的情况//下,PLLs可能已经被打开,所以要在这个boot程序开始的时候要先关闭PLLs。
ldr r1, = AT91C_BASE_CKGR ; 取得CKGR 的基地址
ldr r0, = AT91C_CKGR_DIVA_0
str r0, [r1, #CKGR_PLLAR] : 关闭 PLLA
ldr r0, = AT91C_CKGR_DIVB_0
str r0, [r1, #CKGR_PLLBR] : 关闭 PLLB
以上动作完成之后,系统的主机时钟源就是慢时钟。但是,系统正常运行时不可能用慢时钟作为时钟源,(比如此时用plla作为时钟源),就需要进一步的设置。
对于PLL时钟,它的时钟源是主振荡器时钟(即主时钟 main clock),所以要先将主时钟打开,代码如下:
//打开主时钟
//MOSCEN 主时钟使能位;OSCOUNT 指定慢时钟周期数作为主振荡器启动时间
ldr r0, = AT91C_CKGR_MOSCEN:OR:AT91C_CKGR_OSCOUNT
str r0, [r1, #CKGR_MOR]
选择主时钟作为主机时钟,设置时钟频率等工作等到后面用C语言来完成。这里将继续用汇编进行其余的初始化动作,直到运行至AT91F_LowLevelInit函数。
//为各种处理器模式设置堆栈
; 加载堆栈基地址
add r0, pc,#-(8+.-StackData) ; r0 = pc – 8 - . + StackData ,让r0指向StackData
ldmia r0, {r1-r6} ;从r0指向的地址单元加载数据到r1~r6中
;- Set up Supervisor Mode and set SVC Mode Stack
msr cpsr_c, #ARM_MODE_SVC : OR : I_BIT : OR : F_BIT
bic r1, r1, #3 ; Insure word alignement
mov sp, r1 ; Init stack SYS
;- Set up Interrupt Mode and set IRQ Mode Stack
msr CPSR_c, #ARM_MODE_IRQ : OR : I_BIT : OR : F_BIT
bic r2, r2, #3 ; 确保字对齐
mov sp, r2 ; 设置IRQ模式堆栈指针
;- Set up Fast Interrupt Mode and set FIQ Mode Stack
msr CPSR_c, #ARM_MODE_FIQ : OR : I_BIT : OR : F_BIT
bic r3, r3, #3 ; Insure word alignement
mov sp, r3 ; Init stack FIQ
;- Set up Abort Mode and set Abort Mode Stack
msr CPSR_c, #ARM_MODE_ABORT : OR : I_BIT : OR : F_BIT
bic r4, r4, #3 ; Insure word alignement
mov sp, r4 ; Init stack Abort
;- Set up Undefined Instruction Mode and set Undef Mode Stack
msr CPSR_c, #ARM_MODE_UNDEF : OR : I_BIT : OR : F_BIT
bic r5, r5, #3 ; Insure word alignement
mov sp, r5 ; Init stack Undef
;- Set up user Mode and set Undef Mode Stack
msr CPSR_c, #ARM_MODE_SYS : OR : I_BIT : OR : F_BIT
bic r6, r6, #3 ; Insure word alignement
mov sp, r6 ; Init stack Undef
b EndInitStack
StackData
DCD AT91_SVC_Stack_Begin
DCD AT91_IRQ_Stack_Begin
DCD AT91_FIQ_Stack_Begin
DCD AT91_ABT_Stack_Begin
DCD AT91_UND_Stack_Begin
DCD AT91_USER_Stack_Begin
EndInitStack
; 补偿主振荡器启动时间
ldr r0, =0x00000010
LoopOsc
subs r0, r0, #1
bhi LoopOsc
; 调用C函数 AT91F_LowLevelInit()
IMPORT AT91F_LowLevelInit
ldr r0, = AT91F_LowLevelInit
mov lr, pc
bx r0
;-----------------------------------------------------
; 读/改/写 CP15 控制寄存器
;-----------------------------------------------------
MRC p15, 0, r0, c1, c0,0 ; read cp15 control register (cp15 r1) in r0
ldr r3, =0xC0000080 ; Reset bit :Little Endian end fast bus mode
ldr r4, =0xC0000000 ; Set bit :Asynchronous clock mode, Not Fast Bus
BIC r0, r0, r3
ORR r0, r0, r4
MCR p15, 0, r0, c1, c0,0 ; write r0 in cp15 control registre (cp15 r1)
;-----------------------------------------------------
; 初始化C变量
;-----------------------------------------------------
add r2, pc,#-(8+.-CInitData) ; @ where to read values (relative)
ldmia r2, {r0, r1, r3, r4}
cmp r0, r1 ; Check that they are different
beq EndRW
LoopRW
cmp r1, r3 ; Copy init data
ldrcc r2, [r0], #4 ;从r0指向的地址加载数据到r2后,r2+4
strcc r2, [r1], #4 ;将r2中数据存储到r1指向的地址后,r1+4
bcc LoopRW
EndRW
mov r2, #0
LoopZI
cmp r3, r4 ; Zero init
strcc r2, [r3], #4
bcc LoopZI
b EndInitC
CInitData
IMPORT |Image$$RO$$Limit| ; ROM 代码结束处 (=ROM 数据开始处)
IMPORT |Image$$RW$$Base| ; Base of RAM to initialise
IMPORT |Image$$ZI$$Base| ; Base and limit of area
IMPORT |Image$$ZI$$Limit| ; Top of zero init segment
DCD |Image$$RO$$Limit| ; End of ROM code (=start of ROM data)
DCD |Image$$RW$$Base| ; Base of RAM to initialise
DCD |Image$$ZI$$Base| ; Base and limit of area
DCD |Image$$ZI$$Limit| ; Top of zero init segment
EndInitC
;------------------------------------------------------------------------------
;- Branch on C code Main function (with interworking)
;----------------------------------------------------
;- Branch must be performed by an interworking call as either an ARM or Thumb
;- main C function must be supported. This makes the code not position-
;- independant. A Branch with link would generate errors
;------------------------------------------------------------------------------
IMPORT main
_main
__main
EXPORT _main
EXPORT __main
ldr r0, =main
mov lr, pc
bx r0 ; 跳到mian() 函数中
;------------------------------------------------------------------------------
;- Loop for ever
;---------------
;- End of application. Normally, never occur.
;- Could jump on Software Reset ( B 0x0 ).
;------------------------------------------------------------------------------
End
b End
END
由于之前打开了主时钟,但没有判断打开是否成功,所以AT91F_LowLevelInit首先通过AT91F_WaitForMainClockFrequency函数判断主时钟是否打开。代码略
主时钟打开之后,就可以进行真正的PLL时钟源设置,代码主体为AT91F_InitClocks函数。
由于时钟设置要求在一定的范围条件下,所以要首先判断是否满足时钟要求。主要分成4步:
1.先计算主时钟的频率,算法见datasheet。
2.通过MainClock和预设值(见函数的输入参数)来判断频率是否满足要求。
3.如果满足要求则把预设值写入到PLL控制寄存器。
4.最后一步当然是修改主机时钟源为需要的PLL,同样,需要分成两步。
慢时钟(SLK)
主时钟(Main Clock)
PLLA,PLLB
在这里需要区别一个概念:主时钟和主机时钟
主时钟(main clock)是指输入主振荡器的时钟
主机时钟(mck)指CPU的时钟频率。
主机时钟可以在4个时钟源中选择(时钟选择器)一个作为本身的时钟
时钟设置流程
![](http://blog.chinaunix.net/photo/106983_100116223606.jpg)
启动代码中一些预定义的值
;-----------------------------------------------------------------------------
;- ARM Core Mode and Status Bits ARM内核模式和状态位
;------------------------------------------------------------------------------
ARM_MODE_USER EQU 0x10
ARM_MODE_FIQ EQU 0x11
ARM_MODE_IRQ EQU 0x12
ARM_MODE_SVC EQU 0x13
ARM_MODE_ABORT EQU 0x17
ARM_MODE_UNDEF EQU 0x1B
ARM_MODE_SYS EQU 0x1F
I_BIT EQU 0x80
F_BIT EQU 0x40
T_BIT EQU 0x20
;------------------------------------------------------------------
;- Stack Area Definition 堆栈段区域定义
;------------------------------------------------------------------
IRQ_STACK_SIZE EQU 0x04
FIQ_STACK_SIZE EQU 0x04
ABT_STACK_SIZE EQU 0x04
UND_STACK_SIZE EQU 0x04
SVC_STACK_SIZE EQU 0x04
USER_STACK_SIZE EQU 0x100
;----------------------------------------------------------------------------
9200启动后时钟变化
由于系统复位后进入慢时钟状态,由慢时钟提供主机时钟源,但由于内置boot程序可能已经启动,需要在启动时重新设置慢时钟为主机时钟源。但主机时钟寄存器要求写入的值不能和当前值相同,所以要分成两步,代码如下:
//第一步,将PMC_MCKR 修改一下,这里使其选择PLLB作为主机时钟源
ldr r1, = AT91C_BASE_PMC ;取得PMC的基地址
ldr r0, = AT91C_PMC_CSS_PLLB_CLK
str r0, [r1, #PMC_MCKR] ;向PMC_MCKR寄存器写入数值
//通过测试状态寄存器 PMC_SR的第3位来判断时钟设置是否成功
mov r4, #0x8
MCKR_Loop
ldr r3, [r1, #PMC_SR]
and r3, r4, r3
cmp r3, #0x8
bne MCKR_Loop
//第二步,修改PMC_MCKR使其选择慢时钟SCK作为主机时钟源
ldr r0, = AT91C_PMC_CSS_SLOW_CLK :OR:AT91C_PMC_PRES_CLK
str r0, [r1, #PMC_MCKR]
mov r4, #0x8
MCKR_Loop2
ldr r3, [r1, #PMC_SR]
and r3, r4, r3
cmp r3, #0x8
bne MCKR_Loop2
//为了节省功耗关闭PLLs。
//实际上,在机器复位后PLLs 是被默认关闭的,但是在以前有一个boot程序已经运行过的情况//下,PLLs可能已经被打开,所以要在这个boot程序开始的时候要先关闭PLLs。
ldr r1, = AT91C_BASE_CKGR ; 取得CKGR 的基地址
ldr r0, = AT91C_CKGR_DIVA_0
str r0, [r1, #CKGR_PLLAR] : 关闭 PLLA
ldr r0, = AT91C_CKGR_DIVB_0
str r0, [r1, #CKGR_PLLBR] : 关闭 PLLB
以上动作完成之后,系统的主机时钟源就是慢时钟。但是,系统正常运行时不可能用慢时钟作为时钟源,(比如此时用plla作为时钟源),就需要进一步的设置。
对于PLL时钟,它的时钟源是主振荡器时钟(即主时钟 main clock),所以要先将主时钟打开,代码如下:
//打开主时钟
//MOSCEN 主时钟使能位;OSCOUNT 指定慢时钟周期数作为主振荡器启动时间
ldr r0, = AT91C_CKGR_MOSCEN:OR:AT91C_CKGR_OSCOUNT
str r0, [r1, #CKGR_MOR]
选择主时钟作为主机时钟,设置时钟频率等工作等到后面用C语言来完成。这里将继续用汇编进行其余的初始化动作,直到运行至AT91F_LowLevelInit函数。
//为各种处理器模式设置堆栈
; 加载堆栈基地址
add r0, pc,#-(8+.-StackData) ; r0 = pc – 8 - . + StackData ,让r0指向StackData
ldmia r0, {r1-r6} ;从r0指向的地址单元加载数据到r1~r6中
;- Set up Supervisor Mode and set SVC Mode Stack
msr cpsr_c, #ARM_MODE_SVC : OR : I_BIT : OR : F_BIT
bic r1, r1, #3 ; Insure word alignement
mov sp, r1 ; Init stack SYS
;- Set up Interrupt Mode and set IRQ Mode Stack
msr CPSR_c, #ARM_MODE_IRQ : OR : I_BIT : OR : F_BIT
bic r2, r2, #3 ; 确保字对齐
mov sp, r2 ; 设置IRQ模式堆栈指针
;- Set up Fast Interrupt Mode and set FIQ Mode Stack
msr CPSR_c, #ARM_MODE_FIQ : OR : I_BIT : OR : F_BIT
bic r3, r3, #3 ; Insure word alignement
mov sp, r3 ; Init stack FIQ
;- Set up Abort Mode and set Abort Mode Stack
msr CPSR_c, #ARM_MODE_ABORT : OR : I_BIT : OR : F_BIT
bic r4, r4, #3 ; Insure word alignement
mov sp, r4 ; Init stack Abort
;- Set up Undefined Instruction Mode and set Undef Mode Stack
msr CPSR_c, #ARM_MODE_UNDEF : OR : I_BIT : OR : F_BIT
bic r5, r5, #3 ; Insure word alignement
mov sp, r5 ; Init stack Undef
;- Set up user Mode and set Undef Mode Stack
msr CPSR_c, #ARM_MODE_SYS : OR : I_BIT : OR : F_BIT
bic r6, r6, #3 ; Insure word alignement
mov sp, r6 ; Init stack Undef
b EndInitStack
StackData
DCD AT91_SVC_Stack_Begin
DCD AT91_IRQ_Stack_Begin
DCD AT91_FIQ_Stack_Begin
DCD AT91_ABT_Stack_Begin
DCD AT91_UND_Stack_Begin
DCD AT91_USER_Stack_Begin
EndInitStack
; 补偿主振荡器启动时间
ldr r0, =0x00000010
LoopOsc
subs r0, r0, #1
bhi LoopOsc
; 调用C函数 AT91F_LowLevelInit()
IMPORT AT91F_LowLevelInit
ldr r0, = AT91F_LowLevelInit
mov lr, pc
bx r0
;-----------------------------------------------------
; 读/改/写 CP15 控制寄存器
;-----------------------------------------------------
MRC p15, 0, r0, c1, c0,0 ; read cp15 control register (cp15 r1) in r0
ldr r3, =0xC0000080 ; Reset bit :Little Endian end fast bus mode
ldr r4, =0xC0000000 ; Set bit :Asynchronous clock mode, Not Fast Bus
BIC r0, r0, r3
ORR r0, r0, r4
MCR p15, 0, r0, c1, c0,0 ; write r0 in cp15 control registre (cp15 r1)
;-----------------------------------------------------
; 初始化C变量
;-----------------------------------------------------
add r2, pc,#-(8+.-CInitData) ; @ where to read values (relative)
ldmia r2, {r0, r1, r3, r4}
cmp r0, r1 ; Check that they are different
beq EndRW
LoopRW
cmp r1, r3 ; Copy init data
ldrcc r2, [r0], #4 ;从r0指向的地址加载数据到r2后,r2+4
strcc r2, [r1], #4 ;将r2中数据存储到r1指向的地址后,r1+4
bcc LoopRW
EndRW
mov r2, #0
LoopZI
cmp r3, r4 ; Zero init
strcc r2, [r3], #4
bcc LoopZI
b EndInitC
CInitData
IMPORT |Image$$RO$$Limit| ; ROM 代码结束处 (=ROM 数据开始处)
IMPORT |Image$$RW$$Base| ; Base of RAM to initialise
IMPORT |Image$$ZI$$Base| ; Base and limit of area
IMPORT |Image$$ZI$$Limit| ; Top of zero init segment
DCD |Image$$RO$$Limit| ; End of ROM code (=start of ROM data)
DCD |Image$$RW$$Base| ; Base of RAM to initialise
DCD |Image$$ZI$$Base| ; Base and limit of area
DCD |Image$$ZI$$Limit| ; Top of zero init segment
EndInitC
;------------------------------------------------------------------------------
;- Branch on C code Main function (with interworking)
;----------------------------------------------------
;- Branch must be performed by an interworking call as either an ARM or Thumb
;- main C function must be supported. This makes the code not position-
;- independant. A Branch with link would generate errors
;------------------------------------------------------------------------------
IMPORT main
_main
__main
EXPORT _main
EXPORT __main
ldr r0, =main
mov lr, pc
bx r0 ; 跳到mian() 函数中
;------------------------------------------------------------------------------
;- Loop for ever
;---------------
;- End of application. Normally, never occur.
;- Could jump on Software Reset ( B 0x0 ).
;------------------------------------------------------------------------------
End
b End
END
由于之前打开了主时钟,但没有判断打开是否成功,所以AT91F_LowLevelInit首先通过AT91F_WaitForMainClockFrequency函数判断主时钟是否打开。代码略
主时钟打开之后,就可以进行真正的PLL时钟源设置,代码主体为AT91F_InitClocks函数。
由于时钟设置要求在一定的范围条件下,所以要首先判断是否满足时钟要求。主要分成4步:
1.先计算主时钟的频率,算法见datasheet。
2.通过MainClock和预设值(见函数的输入参数)来判断频率是否满足要求。
3.如果满足要求则把预设值写入到PLL控制寄存器。
4.最后一步当然是修改主机时钟源为需要的PLL,同样,需要分成两步。