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,同樣,需要分成兩步。