【嵌入式開發】時鐘初始化 ( 時鐘相關概念 | 嵌入式時鐘體系 | Lock Time | 分頻引數設定 | CPU 非同步模式設定 | APLL MPLL 時鐘頻率設定 )
本部落格的參考文章及相關資料下載 :
一. 時鐘相關概念解析
1. 相關概念術語
(1) 時鐘脈衝訊號
時鐘脈衝訊號 :
- 1.概念 : 按照 一定的電壓幅度 和 一定的時間間隔 , 連續發出的 脈衝訊號, 就是 時鐘 脈衝訊號;
- 2.重要作用 : 時鐘脈衝訊號 是 時序邏輯的 基礎, 脈衝訊號的間隔是固定的, 可以根據脈衝訊號的個數 及 間隔 計算出對應的時間,
- 3.應用場景 : 晶片 中的 電晶體 工作狀態 都是由 0 1 組成, 即 開和關, 控制這些開關操作都是按照時鐘訊號的節奏進行的;
- 4.說明示例 : 現在的主流 CPU i7 8700K 主頻是 3.7GHz, 其中 1 GHz = 10^3 MHz = 10^6 KHz = 10^9 Hz 即 10億 Hz, 每秒鐘有 37億 次時鐘脈衝訊號; 也就是 經常說的 每秒鐘 運算 37 億次; 當前的超級計算機可以到達 每秒 2億億次;
(2) 時鐘脈衝頻率
時鐘脈衝頻率 :
- 1.概念 : 單位時間內 產生 的 時鐘脈衝個數 就是 時鐘脈衝頻率;
- 2.舉例 : 1秒中 產生 1次, 就是 1Hz, 1秒鐘產生 100 次就是 100Hz, 上面舉例的 i78700K CPU, 一秒鐘產生 37億次, 就是 3.7GHz 的時鐘脈衝頻率;
(3) 時鐘源
時鐘源 : 時鐘脈衝訊號 是由 晶振 或 鎖相環 PLL 產生;
- 1.晶振 : 全稱 晶體振盪器, 是由 石英晶體 製作的, 石英晶體 ① 按照一定的方位角 切割, 並 ② 在其內部新增電子元件組成振盪電路;
- ① 震盪特性 :
- ② 物理特性 : 石英非常穩定, 其控制的震盪頻率也很穩定, 其頻率可以根據集合尺寸精確控制;
- ③ 晶振優勢 : 晶振具有 A. 結構簡單, B. 噪音低, C. 可提供精確定製的頻率, 等優點;
- ④ 晶振缺陷 : A. 生產成本高 , B. 交貨的週期很長;
- ① 震盪特性 :
- 2.PLL(鎖相環) : 鎖相環 比 晶振 更復雜, PLL 需要一個外部晶振作為輸入, PLL 可以對外部晶振產生的頻率進行 加倍 或 分頻 操作, 即 提高 或 降低 頻率;
- ① 使用場景 : 簡單系統一般採用 石英晶振, 複雜的系統採用 晶振 + PLL 合成
- ② 降低成本 : 如果需要特定的時鐘頻率, 可以使用 PLL + 低頻晶振 代替高頻的晶振 , 這樣成本比較低;
- ③ 多時鐘頻率系統 : 如果在一個裝置上需要多個時鐘頻率系統, 可以使用 PLL + 晶振 合成提供時鐘源, PLL 將 晶振頻率 進行 加倍 或 分頻 即可得到不同頻率的時鐘源;
- ④ 與晶振對比 : PLL + 晶振 比 純晶振 成本要低, 並且提供更加靈活多變的時鐘頻率;
- ① 使用場景 : 簡單系統一般採用 石英晶振, 複雜的系統採用 晶振 + PLL 合成
二. 時鐘體系簡介
時鐘體系學習步驟 : 對不不同的時鐘體系, 需要按照下面的學習步驟學習即可;
① 晶振頻率 ;
② PLL 個數 及 種類 ;
③ PLL 產生的時鐘型別;
④ PLL 產生的時鐘作用;
1. 2440 開發板 時鐘體系
參考手冊 : S3C2440.pdf , 章節 : 7 CLOCK & POWER MANAGEMENT , Page 235;
(1) 2440 開發板時鐘體系介紹
2440 開發板時鐘體系介紹 :
- 1.晶振頻率 : 12MHz;
- 2.PLL (鎖相環) : 有 2 個 PLL, 分別是 MPLL 和 UPLL;
- 3.產生時鐘個數 : ① MPLL 鎖相環 產生出 FCLK , HCLK, PCLK 三個時鐘; ② UPLL 鎖相環 產生出 UCLK 時鐘;
- 4.各個時鐘作用 :
- ① FCLK 時鐘作用 : 處理器 中使用, ARM 核使用的時鐘是 FCLK, 該時鐘是 MPLL 產生的;
- ② HCLK 時鐘作用 : AHB 匯流排中使用, 如 LCD, DMA 控制, 該時鐘是 MPLL 產生的;
- ③ PCLK 時鐘作用 : APB 匯流排中使用, 如 Uart 串列埠, GPIO, 該時鐘是 MPLL 產生的;
- ④ UCLK 時鐘作用 : USB 匯流排中使用, 該時鐘是 UPLL 產生的;
- 5.2440 時鐘體系圖示 : 該圖在 S3C2440.pdf 文件中的 7 CLOCK & POWER MANAGEMENT 章節, 237 頁 ;
(2) 6410 開發板時鐘體系介紹
參考手冊 : S3C6410X.pdf , 章節 : 3.3.4.1 Clock selection between PLLs and input reference clock , Page 124;
6410 開發板時鐘體系介紹 :
- 1.晶振頻率 : 12MHz;
- 2.PLL (鎖相環) : 有 3 個 PLL, 分別是 APLL , MPLL 和 EPLL;
- 3.產生時鐘個數 : ① APLL 鎖相環 產生出 ACLK 一個時鐘; ② MPLL 鎖相環 產生出 HCLK, PCLK 時鐘; ③ EPLL 產生 SCLK 時鐘;
- 4.各個時鐘作用 :
- ① ACLK 時鐘作用 : 處理器中使用, ARM核中使用 ACLK 時鐘, 該時鐘是 APLL 鎖相環產生;
- ② HCLK 時鐘作用 : AHB 匯流排中使用, 如 LCD, DMA ; 該時鐘是 MPLL 產生的;
- ③ PCLK 時鐘作用 : APB 匯流排中使用, 如 Uart 串列埠, GPIO 中使用, 該時鐘是 MPLL 產生的;
- ④ SCLK 時鐘作用 : USB 匯流排中使用, 該時鐘是 EPLL 鎖相環產生的;
- 5.6410時鐘體系圖示 : 該圖在 S3C6410X.pdf 文件中的 3.3.4.1 Clock selection between PLLs and input reference clock 章節, 124 頁 ;
(3) S5PV210 開發板時鐘體系介紹
參考手冊 : S5PV210_UM_REV1.1.pdf , 章節 : 3.4 CLOCK GENERATION , Page 361;
S5PV210 開發板時鐘體系介紹 :
- 1.晶振頻率 : 24MHz;
- 2.PLL (鎖相環) : 有 4 個 PLL, 分別是 APLL , MPLL , EPLL 和 VPLL;
- 3.S5PV210 的 時鐘體系分類 : 分為 以下 三類 ;
- ① 主系統時鐘體系 : MSYS , 由 APLL 鎖相環產生, 產生的時鐘有 ARMCLK, HCLK_MSYS, PCLK_MSYS , 應用在 ARM 核 中;
- ② 顯示相關時鐘體系 : DSYS , 由 MPLL 鎖相環產生, 產生的時鐘有 HCLK_DSYS, PCLK_DSYS, 主要應用在顯示相關的部件中 ;
- ③ 外圍裝置時鐘體系 : PSYS , 由 EPLL 鎖相環產生, 產生的時鐘有 HCLK_PSYS, CLK_DPM, 主要用於外設, 如 Uart 串列埠 等;
- ④ VPLL 鎖相環產生的時鐘 : VPLL 產生的時鐘 主要用於視訊處理;
- 4.S5PV210時鐘體系圖示 : 該圖在 S5PV210_UM_REV1.1.pdf 文件中的3.4 CLOCK GENERATION, 361 頁 ;
三. S3C6410 初始化時鐘
1. S3C 6410 時鐘初始化流程簡介
(1) CPU 頻率變化過程
CPU 上電後 從 低頻率 到 高頻率的變化過程 :
- 1.上電後的工作頻率 : 上電後 ARM 核的工作頻率就是晶振頻率, 即 12MHz, 這個速度非常慢;
- 2.配置鎖相環 : 配置鎖相環, 使 頻率 增加;
- 3.Lock Time 時間 : 配置 PLL 鎖相環後, 會出現一段不工作的時間, 此時 CPU 頻率為 0Hz, 這段時間叫 Lock Time;
- 4.正常頻率 : 在 Lock Time 之後, 就會進入正式的 鎖相環 工作頻率, 此時的頻率是 晶振頻率 經過 鎖相環 處理後的 高頻率;
- 5.圖示 : 下圖是處理器上電後各種引數的變化, 橫軸是時間軸, 沒有縱軸, 各個引數在上電後的每個時間段的表現;
整個時鐘初始化流程是需要軟體幫助進行的, 因此這些步驟需要開發者進行開發, 這也是該部落格的主要內容;
(2) 時鐘初始化的四個步驟
時鐘初始化流程 :
- 1.配置 Lock Time : 配置 PLL 鎖相環後會有一段 CPU 頻率為 0 的時間, 這段時間處理器不工作, 這段時間就是 Lock Time;
- 2.設定分頻係數 : 通過 為 不同的時鐘設定不同的分頻係數, 根據這個分頻係數, 來確定每個時鐘的頻率;
- 3.設定 APLL MPLL 頻率 : 設定一個時鐘的頻率, 可以根據分頻係數計算出其它所有時鐘的頻率了;
- 4.設定 CPU 工作模式 : 如果 FCLK 與 HCLK 的頻率不同, 那麼 CPU 需要設定為 非同步工作模式, FCLK 是 ARM 核的時鐘, HCLK 是 匯流排時鐘, 如果兩個時鐘不一致, 需要將 CPU 設定為 非同步工作模式;
- ① 常用設定 : 一般情況下 設定 分頻係數的時候, 不會給 FCLK 與 HCLK 設定相同的分頻係數, 因此, 該步驟大部分情況下都要執行;
2. S3C 6410 時鐘初始化 彙編程式碼編寫
參考手冊 : ARM晶片 手冊 S3C6410X.pdf ( 基於 6410 開發板 ARM 11 )
- 1.手冊對應章節 : 3 SYSTEM CONTROLLER;
- 2.S3C6410X.pdf手冊下載地址 :
(1) 配置 Lock Time
配置 Lock Time :
- 1.文件位置 : S3C6410X.pdf 手冊, Page 141, 3.4.2.1 PLL Control Registers 章節;
- 2.預設設定不變 ( 推薦 ) : 一般情況下, 使用開發板的預設設定即可, 如果有特殊定製需求才修改該 PLL_LOCK 暫存器的值;
(2) 設定分頻係數
設定分頻係數 :
- 1.相關文件位置 : S3C6410X.pdf 手冊, Page 125, 3.3.4.2 ARM and AXI/AHB/APB bus clock generation 章節;
- 2.APLL 鎖相環 : 12 MHz 的晶振頻率, 經過 APLL 鎖相環 產生 輸出時鐘, 該輸出時鐘 經過 DIVARM 分頻, 產生 ARMCLK, 這是 ARM 核使用的時鐘;
- 3.MPLL 鎖相環 : 12MHz 晶振頻率時鐘, 經過 MPLL 鎖相環 產生的 時鐘, 該時鐘 經過 DIVHCLKX2 分頻後, 產生 HCLKx2 時鐘, 同時 DIVHCLKX2 分頻後的時鐘 又 經過不同的 分頻 產生 HCLK, PCLK, CLKJPEG, CLKSECUR 時鐘;
- ① HCLK 時鐘 : HCLKx2 時鐘 經過 DIVHCLK 分頻後, 產生 HCLK 時鐘;
- ② PCLK 時鐘 : HCLKx2 時鐘 經過 DIVPCLK 分頻後, 產生 PCLK 時鐘;
- ③ CLKJPEG 時鐘 : HCLKx2 時鐘 經過 DIVCLKJPEG 分頻後, 產生 CLKJPEG 時鐘;
- ④ CLKSECUR 時鐘 : HCLKx2 時鐘 經過 DIVCLKSECUR 分頻後, 產生 CLKSECUR 時鐘;
- 4.分頻引數參考 : 不同的時鐘分頻器的分頻引數列表 , 來源 S3C6410X.pdf 手冊, Page 126, 3.3.4.2 ARM and AXI/AHB/APB bus clock generation 章節;
- 5.時鐘分頻公式 : 文件位置 S3C6410X.pdf 手冊, Page 126, 3.4.2.3 Clock divider control register 章節;
- ① DIVARM 分頻公式 : ARMCLK = DOUTAPLL / (ARM_RATIO + 1) , DOUTAPLL 是 APLL 鎖相環輸出的時鐘, ARM_RATIO 是設定的分頻係數;
- ② DIVHCLKX2 分頻公式 : HCLKX2 = HCLKX2IN / (HCLKX2_RATIO + 1), HCLKX2IN 是 MPLL 鎖相環的輸出時鐘頻率, HCLKX2_RATIO 是設定的分頻係數;
- ③ DIVHCLK 分頻公式 : HCLK = HCLKX2 / (HCLK_RATIO + 1), HCLKX2 是 DIVHCLKX2 分頻後的時鐘頻率, HCLK_RATIO 是設定的分頻係數;
- ④ DIVPCLK 分頻公式 : PCLK = HCLKX2 / (PCLK_RATIO + 1), HCLKX2 是 DIVHCLKX2 分頻後的時鐘頻率, PCLK_RATIO 是設定的分頻係數;
- 6.PLL 鎖相環輸出頻率 :
- ① APLL 鎖相環輸出頻率 : 533 MHz ;
- ② MPLL 鎖相環輸出頻率 : 533 MHz ;
- 7.具體參考引數設定 : 從 6410 開發板中的 u-boot 原始碼中查詢相關的時鐘 分頻係數 ;
- ① DIVARM 分頻引數 : 0 , APLL 輸出頻率為 533MHz, 根據公式計算 ARMCLK 時鐘頻率為 533MHz;
- ② DIVHCLKX2 分頻引數 : 1 , MPLL 輸出頻率 533MHz, 根據公式計算 HCLKX2 時鐘為 266 MHz;
- ③ DIVPCLK 分頻引數 : 3 , HCLKX2 時鐘為 266 MHz, PCLK 頻率為 66MHz;
- ④ DIVHCLK 分頻引數 : 1 , HCLKX2 時鐘為 266 MHz, HCLK 頻率為 133MHz;
設定分頻係數程式碼編寫 :
- 1.定義分頻控制暫存器( Clock divider control register ) 地址 : 之前說的 分頻引數 都是通過 CLK_DIV0 暫存器設定的, 將 CLK_DIV0 的地址定義成常量,
#define CLK_DIV0 0x7E00F020
;
- 2.定義分頻引數的值 : 參考 CLK_DIV0 值的表格 設定 CLK_DIV0 暫存器的實際值;
- ① 定義 ARM_RATIO 分頻引數 : 這個引數設定成 0, CLK_DIV0 暫存器的 [3:0] 位 設定該引數, 該引數單獨為 0x0 << 0;
- ② 定義 HCLKX2_RATIO 分頻引數 : 這個引數設定成 1, CLK_DIV0 暫存器的 [11:9] 位 設定該引數, 值為 0x1 << 9;
- ③ 定義 HCLK_RATIO 分頻引數 : 這個引數設定成 1, CLK_DIV0 暫存器的 [8] 位 設定該引數, 值為 0x1 << 8;
- ④ 定義 PCLK_RATIO 分頻引數 : 這個引數設定成 3, CLK_DIV0 暫存器的 [15:12] 位 設定該引數, 值為 0x3 << 12;
- ⑤ 設定的值 : 將上面四個值彙總起來為 (0x0 << 0) | (0x1 << 9) | (0x1 << 8) | (0x3 << 12) ;
- ⑥ 程式碼 :
#define CLK_VAL ( (0x0 << 0) | (0x1 << 9) | (0x1 << 8) | (0x3 << 12) )
;
- 3.裝載 CLK_DIV0 地址到通用暫存器中 :
ldr r0, =CLK_DIV0
; - 4.裝載 CLK_DIV0 的暫存器值到通用暫存器中 :
ldr r1, =CLK_VAL
; - 5.設定 CLK_DIV0 暫存器的值 :
str r1, [r0]
, 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中 ; - 6.截止到當前的彙編程式碼展示 :
#define CLK_DIV0 0x7E00F020 @ 定義 CLK_DIV0 暫存器地址, 時鐘的分頻引數都是通過該暫存器進行設定的
#define CLK_VAL ( (0x0 << 0) | (0x1 << 9) | (0x1 << 8) | (0x3 << 12) ) @ 設定 CLK_DIV0 暫存器的值, 即 各個時鐘分頻器的引數
init_clock :
ldr r0, =CLK_DIV0 @ 將 CLK_DIV0 的地址裝載到 r0 通用暫存器中
ldr r1, =CLK_VAL @ 將 要設定給 CLK_DIV0 暫存器的值 CLK_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中
mov pc, lr
(3) 設定 CPU 非同步工作模式
設定 CPU 非同步工作模式 :
- 1.相關文件位置 : S3C6410X.pdf 手冊, Page 169, 3.4.2.14 Others control register 章節;
- 2.Others control register 暫存器 [7] : 第 7 位 [7], 設定為 0 時工作在 非同步模式下, 設定為 1 時 工作在 同步模式 下;
- 3.Others control register 暫存器 [6] : 第 6 位 用於選擇 鎖相環輸出源, 設定為 0 選擇 MPLL 鎖相環 輸出時鐘, 設定為 1 選擇 APLL 鎖相環輸出時鐘, 注意 只有在 CPU 同步模式下才設定為1;
彙編程式碼編寫 :
- 1.定義 OTHERS 暫存器地址 :
#define OTHERS 0x7E00F900
; - 2.將 OTHERS 暫存器地址儲存到 r0 暫存器中 :
ldr r0, =OTHERS
; - 3.讀取 OTHERS 暫存器的值 :
ldr r1, [r0]
, 即 將 r0 暫存器儲存的地址指向的記憶體中的值 裝載到 r1 通用暫存器中; - 4.設定暫存器值 : 將 r1 暫存器中儲存的 OTHERS 暫存器的 6 和 7 位 清零, 設定 CPU 非同步工作模式, 同時設定 MPLL 鎖相環時鐘輸出,
bic r1, r1, #0xc0
; - 5.設定 OTHERS 暫存器值 :
str r1, [r0]
, 將 r1 通用暫存器中的值 儲存到 r0 暫存器中儲存的地址指向的記憶體中, 即 OTHERS 暫存器; - 6.截止到當前的彙編程式碼展示 :
#define CLK_DIV0 0x7E00F020 @ 定義 CLK_DIV0 暫存器地址, 時鐘的分頻引數都是通過該暫存器進行設定的
#define OTHERS 0x7E00F900 @ 定義 OTHERS 暫存器地址, 用於設定 CPU 非同步工作模式
#define CLK_VAL ( (0x0 << 0) | (0x1 << 9) | (0x1 << 8) | (0x3 << 12) ) @ 設定 CLK_DIV0 暫存器的值, 即 各個時鐘分頻器的引數
init_clock :
ldr r0, =CLK_DIV0 @ 將 CLK_DIV0 的地址裝載到 r0 通用暫存器中
ldr r1, =CLK_VAL @ 將 要設定給 CLK_DIV0 暫存器的值 CLK_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中
ldr r0, =OTHERS @ 將 OTHERS 暫存器地址存到 r0 通用暫存器中
ldr r1, [r0] @ 將 r0 暫存器儲存的地址指向的暫存器中的值讀取到 r1 通用暫存器中
bic r1, r1, #0xc0 @ 將 r1 暫存器中的值的 第 6 位 和 第 7 位 設定成 0
str r1, [r0] @ 將 r1 暫存器中的值 寫出到 r0 暫存器儲存的地址指向的記憶體位置 即 OTHERS 暫存器
mov pc, lr
(4) 設定 APLL 和 MPLL 時鐘頻率
設定 APLL 和 MPLL 時鐘頻率 :
- 1.相關文件位置 : *S3C6410X.pdf* 手冊, Page 141, 3.4.2.1 PLL Control Registers 章節;
- 2.APLL 和 MPLL 控制暫存器 : 下面兩張表格 分別說明了 兩個 PLL 控制暫存器對應的地址, 以及暫存器每一位設定的值;
- 3.PLL 輸出頻率公式 : FOUT = MDIV X FIN / (PDIV X 2^SDIV), 該公式在 S3C6410X.pdf 文件 142 頁, 由公式可以得到 PLL 輸出頻率由 MDIV , PDIV, SDIV 三個引數決定, 文件中給出了一個固定的表格示例, 這裡我們選擇 第 5 行的引數進行設定;
彙編程式碼編寫 :
- 1.定義 APLL_CON 暫存器地址常量 :
#define APLL_CON 0x7E00F00C
; - 2.定義 MPLL_CON 暫存器地址常量 :
#define MPLL_CON 0x7E00F010
; - 3.分析 PLL 控制暫存器要設定的位 : 我們要設定 533MHz 的 PLL 輸出頻率, APLL 和 MPLL 都輸出 533MHz 的頻率;
- ① 533MHz 輸出確定的引數 : MDIV 設定成 266, PDIV 設定 3, SDIV 設定 1;
- ② 暫存器值設定 :
#define PLL_VAL ( (0x1 << 31) | (266 << 16) | (3 << 8) | (1 << 0) )
;
- a. 啟用 PLL : [31] 設定成 1 , 設定值為 1 << 31;
- b. 設定 MDIV : [25:16] 位設定成 266, 設定值為 266 << 16;
- c. 設定 PDIV : [13:8] 位 設定成 3, 值為 3 << 8 ;
- d.設定 SDIV : [2:0] 位 設定成 1, 值為 1 << 0;
- e. 最終值為 :
(0x1 << 31) | (266 << 16) | (3 << 8) | (1 << 0)
;
- ① 533MHz 輸出確定的引數 : MDIV 設定成 266, PDIV 設定 3, SDIV 設定 1;
- 4.設定 APLL_CON 暫存器的值 : 首先將 APLL_CON 暫存器地址儲存到 r0 暫存器中, 然後將 要設定的值 儲存到 r1 暫存器中, 之後 使用 str 指令將 r1 暫存器的值 儲存到 r0 暫存器中儲存的地址指向的記憶體中, 即 將 PLL_VAL 值設定給 APLL_CON 暫存器;
ldr r0, =APLL_CON
ldr r1, =PLL_VAL
str r1, [r0]
- 5.設定 MPLL_CON 暫存器的值 : 首先將 MPLL_CON 暫存器地址儲存到 r0 暫存器中, 然後將 要設定的值 儲存到 r1 暫存器中, 之後 使用 str 指令將 r1 暫存器的值 儲存到 r0 暫存器中儲存的地址指向的記憶體中, 即 將 PLL_VAL 值設定給 MPLL_CON 暫存器;
ldr r0, =MPLL_CON
ldr r1, =PLL_VAL
str r1, [r0]
- 6.截止到當前的程式碼 : 目前已完成 ① 分頻引數設定,② CPU 非同步模式設定, ③APLL 和 MPLL 時鐘頻率設定工作;
#define CLK_DIV0 0x7E00F020 @ 定義 CLK_DIV0 暫存器地址, 時鐘的分頻引數都是通過該暫存器進行設定的
#define OTHERS 0x7E00F900 @ 定義 OTHERS 暫存器地址, 用於設定 CPU 非同步工作模式
#define CLK_VAL ( (0x0 << 0) | (0x1 << 9) | (0x1 << 8) | (0x3 << 12) ) @ 設定 CLK_DIV0 暫存器的值, 即 各個時鐘分頻器的引數
#define MPLL_CON 0x7E00F010 @ 定義 MPLL_CON 暫存器地址常量
#define APLL_CON 0x7E00F00C @ 定義 APLL_CON 暫存器地址常量
#define PLL_VAL ( (0x1 << 31) | (266 << 16) | (3 << 8) | (1 << 0) ) @ 設定 PLL 控制暫存器的值
init_clock :
ldr r0, =CLK_DIV0 @ 將 CLK_DIV0 的地址裝載到 r0 通用暫存器中
ldr r1, =CLK_VAL @ 將 要設定給 CLK_DIV0 暫存器的值 CLK_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中
ldr r0, =OTHERS @ 將 OTHERS 暫存器地址存到 r0 通用暫存器中
ldr r1, [r0] @ 將 r0 暫存器儲存的地址指向的暫存器中的值讀取到 r1 通用暫存器中
bic r1, r1, #0xc0 @ 將 r1 暫存器中的值的 第 6 位 和 第 7 位 設定成 0
str r1, [r0] @ 將 r1 暫存器中的值 寫出到 r0 暫存器儲存的地址指向的記憶體位置 即 OTHERS 暫存器
ldr r0, =APLL_CON @ 將 APLL_CON 暫存器地址存到 r0 通用暫存器中
ldr r1, =PLL_VAL @ 將 要設定給 APLL_CON 暫存器的值 PLL_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中, 即 將 PLL_VAL 的值 設定到 APLL_CON 暫存器中
ldr r0, =MPLL_CON @ 將 MPLL_CON 暫存器地址存到 r0 通用暫存器中
ldr r1, =PLL_VAL @ 將 要設定給 MPLL_CON 暫存器的值 PLL_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中, 即 將 PLL_VAL 的值 設定到 MPLL_CON 暫存器中
mov pc, lr
(5) 設定 時鐘源
時鐘源設定 :
- 1.相關文件位置 : S3C6410X.pdf 手冊, Page 145, 3.4.2.2 Clock source control register 章節;
- 2.CLK_SRC 暫存器 : 控制時鐘源;
- ① 控制 APLL 時鐘源 : [0] 位 控制 APLL 時鐘源, 如果設定為 0 , 使用晶振作為時鐘源, 如果設定為 1, 使用 APLL 輸出的時鐘作為時鐘源;
- ② 控制 MPLL 時鐘源 : [1] 位 控制 MPLL 時鐘源, 如果設定為 0 , 使用晶振作為時鐘源, 如果設定為 1, 使用 MPLL 輸出的時鐘作為時鐘源;
- ③ 控制 EPLL 時鐘源 : [2] 位 控制 EPLL 時鐘源, 如果設定為 0 , 使用晶振作為時鐘源, 如果設定為 1, 使用 EPLL 輸出的時鐘作為時鐘源;
彙編程式碼編寫 :
- 1.定義 CLK_SRC 暫存器地址常量 :
#define CLK_SRC 0x7E00F01C
; - 2.設定 CLK_SRC 暫存器的值 : 將 [1:0] 兩位設定成 1; 首先將 CLK_SRC 暫存器地址儲存到 r0 暫存器中, 然後將 要設定的值 0x3 立即數 儲存到 r1 暫存器中, 之後 使用 str 指令將 r1 暫存器的值 儲存到 r0 暫存器中儲存的地址指向的記憶體中, 即 將 0x3 值設定給 CLK_SRC 暫存器;
ldr r0, =CLK_SRC
mov r1, #0x3
str r1, [r0]
(6) 程式碼示例
程式碼示例 : 截止到當前的程式碼示例 ① 設定 MVC 模式 ② 外設基地址初始化, ③ 關閉看門狗, ④ 關閉中斷, ⑤ 關閉 MMU, ⑥ 初始化時鐘, ⑦ 開啟 LED 發光二極體;
@****************************
@File:start.S
@
@BootLoader 初始化程式碼
@****************************
.text @ 巨集 指明程式碼段
.global _start @ 偽指令宣告全域性開始符號
_start: @ 程式入口標誌
b reset @ reset 復位異常
ldr pc, _undefined_instruction @ 未定義異常, 將 _undefined_instruction 值裝載到 pc 指標中
ldr pc, _software_interrupt @ 軟中斷異常
ldr pc, _prefetch_abort @ 預取指令異常
ldr pc, _data_abort @ 資料讀取異常
ldr pc, _not_used @ 佔用 0x00000014 地址
ldr pc, _irq @ 普通中斷異常
ldr pc, _fiq @ 軟中斷異常
_undefined_instruction: .word undefined_instruction @ _undefined_instruction 標號存放了一個值, 該值是 32 位地址 undefined_instruction, undefined_instruction 是一個地址
_software_interrupt: .word software_interrupt @ 軟中斷異常
_prefetch_abort: .word prefetch_abort @ 預取指令異常 處理
_data_abort: .word data_abort @ 資料讀取異常
_not_used: .word not_used @ 空位處理
_irq: .word irq @ 普通中斷處理
_fiq: .word fiq @ 快速中斷處理
undefined_instruction: @ undefined_instruction 地址存放要執行的內容
nop
software_interrupt: @ software_interrupt 地址存放要執行的內容
nop
prefetch_abort: @ prefetch_abort 地址存放要執行的內容
nop
data_abort: @ data_abort 地址存放要執行的內容
nop
not_used: @ not_used 地址存放要執行的內容
nop
irq: @ irq 地址存放要執行的內容
nop
fiq: @ fiq 地址存放要執行的內容
nop
reset: @ reset 地址存放要執行的內容
bl set_svc @ 跳轉到 set_svc 標號處執行
bl set_serial_port @ 設定外設基地址埠初始化
bl disable_watchdog @ 跳轉到 disable_watchdog 標號執行, 關閉看門狗
bl disable_interrupt @ 跳轉到 disable_interrupt 標號執行, 關閉中斷
bl disable_mmu @ 跳轉到 disable_mmu 標號執行, 關閉 MMU
init_clock @ 跳轉到 init_clock 標號, 執行時鐘初始化操作
bl light_led @ 開啟開發板上的 LED 發光二極體
set_svc:
mrs r0, cpsr @ 將 CPSR 暫存器中的值 匯出到 R0 暫存器中
bic r0, r0, #0x1f @ 將 R0 暫存器中的值 與 #0x1f 立即數 進行與操作, 並將結果儲存到 R0 暫存器中, 實際是將暫存器的 0 ~ 4 位 置 0
orr r0, r0, #0xd3 @ 將 R0 暫存器中的值 與 #0xd3 立即數 進行或操作, 並將結果儲存到 R0 暫存器中, 實際是設定 0 ~ 4 位 暫存器值 的處理器工作模式程式碼
msr cpsr, r0 @ 將 R0 暫存器中的值 儲存到 CPSR 暫存器中
mov pc, lr @ 返回到 返回點處 繼續執行後面的程式碼
#define pWTCON 0x7e004000 @ 定義看門狗控制暫存器 地址 ( 6410開發板 )
disable_watchdog:
ldr r0, =pWTCON @ 先將控制暫存器地址儲存到通用暫存器中
mov r1, #0x0 @ 準備一個 0 值, 看門狗控制暫存器都設定為0 , 即看門狗也關閉了
str r1, [r0] @ 將 0 值 設定到 看門狗控制暫存器中
mov pc, lr @ 返回到 返回點處 繼續執行後面的程式碼
disable_interrupt:
mvn r1,#0x0 @ 將 0x0 按位取反, 獲取 全 1 的資料, 設定到 R1 暫存器中
ldr r0,=0x71200014 @ 設定第一個中斷遮蔽暫存器, 先將 暫存器 地址裝載到 通用暫存器 R0 中
str r1,[r0] @ 再將 全 1 的值設定到 暫存器中, 該暫存器的記憶體地址已經裝載到了 R0 通用暫存器中
ldr r0,=0x71300014 @ 設定第二個中斷遮蔽暫存器, 先將 暫存器 地址裝載到 通用暫存器 R0 中
str r1,[r0] @ 再將 全 1 的值設定到 暫存器中, 該暫存器的記憶體地址已經裝載到了 R0 通用暫存器中
mov pc, lr @ 返回到 返回點處 繼續執行後面的程式碼
disable_mmu :
mcr p15,0,r0,c7,c7,0 @ 設定 I-Cache 和 D-Cache 失效
mrc p15,0,r0,c1,c0,0 @ 將 c1 暫存器中的值 讀取到 R0 通用暫存器中
bic r0, r0, #0x00000007 @ 使用 bic 位清除指令, 將 R0 暫存器中的 第 0, 1, 2 三位 設定成0, 代表 關閉 MMU 和 D-Cache
mcr p15,0,r0,c1,c0,0 @ 將 R0 暫存器中的值寫回到 C1 暫存器中
mov pc, lr @ 返回到 返回點處 繼續執行後面的程式碼
set_serial_port :
ldr r0, =0x70000000 @ 將基地址裝載到 r0 暫存器中, 該基地址 在 arm 核 手冊中定義
orr r0, r0, #0x13 @ 設定初始化基地址的範圍, 將 r0 中的值 與 0x13 立即數 進行或操作, 將結果存放到 r0 中
mcr p15, 0, r0, c15, c2, 4 @ 將 r0 中的值設定給 c15 協處理器
mov pc, lr
#define CLK_DIV0 0x7E00F020 @ 定義 CLK_DIV0 暫存器地址, 時鐘的分頻引數都是通過該暫存器進行設定的
#define OTHERS 0x7E00F900 @ 定義 OTHERS 暫存器地址, 用於設定 CPU 非同步工作模式
#define CLK_VAL ( (0x0 << 0) | (0x1 << 9) | (0x1 << 8) | (0x3 << 12) ) @ 設定 CLK_DIV0 暫存器的值, 即 各個時鐘分頻器的引數
#define MPLL_CON 0x7E00F010 @ 定義 MPLL_CON 暫存器地址常量
#define APLL_CON 0x7E00F00C @ 定義 APLL_CON 暫存器地址常量
#define PLL_VAL ( (0x1 << 31) | (266 << 16) | (3 << 8) | (1 << 0) ) @ 設定 PLL 控制暫存器的值
#define CLK_SRC 0x7E00F01C @ 定義 CLK_SRC 時鐘源控制暫存器的地址常量
init_clock :
ldr r0, =CLK_DIV0 @ 將 CLK_DIV0 的地址裝載到 r0 通用暫存器中
ldr r1, =CLK_VAL @ 將 要設定給 CLK_DIV0 暫存器的值 CLK_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中
ldr r0, =OTHERS @ 將 OTHERS 暫存器地址存到 r0 通用暫存器中
ldr r1, [r0] @ 將 r0 暫存器儲存的地址指向的暫存器中的值讀取到 r1 通用暫存器中
bic r1, r1, #0xc0 @ 將 r1 暫存器中的值的 第 6 位 和 第 7 位 設定成 0
str r1, [r0] @ 將 r1 暫存器中的值 寫出到 r0 暫存器儲存的地址指向的記憶體位置 即 OTHERS 暫存器
ldr r0, =APLL_CON @ 將 APLL_CON 暫存器地址存到 r0 通用暫存器中
ldr r1, =PLL_VAL @ 將 要設定給 APLL_CON 暫存器的值 PLL_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中, 即 將 PLL_VAL 的值 設定到 APLL_CON 暫存器中
ldr r0, =MPLL_CON @ 將 MPLL_CON 暫存器地址存到 r0 通用暫存器中
ldr r1, =PLL_VAL @ 將 要設定給 MPLL_CON 暫存器的值 PLL_VAL 立即數 裝載到 r1 通用暫存器中;
str r1, [r0] @ 將 r1 暫存器中的內容 儲存到 r0 儲存的地址 指向的記憶體中, 即 將 PLL_VAL 的值 設定到 MPLL_CON 暫存器中
ldr r0, =CLK_SRC @ 將 CLK_SRC 暫存器地址設定到 r0 通用暫存器中
mov r1, #0x3 @ 將 0x3 立即數設定給 r1 暫存器
str r1, [r0] @ 將 r1 中儲存的立即數設定給 r0 暫存器儲存的地址指向的記憶體中, 即 CLK_SRC 暫存器中
mov pc, lr
#define GPBCON 0x7F008820
#define GPBDAT 0x7F008824
light_led :
ldr r0, =GPBCON @ 將 0x7F008820 GPM 控制暫存器的地址 0x7F008820 裝載到 r0 暫存器中
ldr r1, =0x1111 @ 設定 GPM 控制暫存器的行為 為 Output 輸出, 即每個對應引腳的設定為 0b0001 值
str r1, [r0] @ 將 r1 中的值 儲存到 r0 指向的 GPBCON 0x7F008820 地址的記憶體中
ldr r0, =GPBDAT @ 將 GPBDAT 0x7F008824 地址值 裝載到 r0 暫存器中
ldr r1, =0b110000 @ 計算 GPM 資料暫存器中的值, 設定 0 為 低電平, 設定 1 為高電平, 這裡設定 0 ~ 3 位為低電平, 其它為高電平
str r1, [r0] @ 將 r1 中的值 儲存到 r0 指向的 GPBDAT 0x7F008824 地址的記憶體中
mov pc, lr
3. 連結器指令碼
u-boot.lds 連結器指令碼 程式碼解析 :
- 1.指明輸出格式 ( 處理器架構 ) : 使用
OUTPUT_ARCH(架構名稱)
指明輸出格式, 即處理器的架構, 這裡是 arm 架構的,OUTPUT_ARCH(arm)
; - 2.指明輸出程式的入口 : 設定編譯輸出的程式入口位置, 語法為
ENTRY(入口位置)
, 在上面的 Start.S 中設定的程式入口是_start
, 程式碼為ENTRY(_start)
; - 3.設定程式碼段 : 使用
.text :
設定程式碼段; - 4.設定資料段 : 使用
.data :
設定資料段; - 5.設定 BSS 段 : 使用
.bss :
設定 BSS 段;
- ( 1 ) 記錄 BSS 段的起始地址 :
bss_start = .;
; - ( 2 ) 記錄 BSS 段的結束地址 :
bss_end = .;
;
- ( 1 ) 記錄 BSS 段的起始地址 :
- 6.對齊 : 每個段都需要設定記憶體的對齊格式, 使用
. = ALIGN(4);
設定四位元組對齊即可; - 7.程式碼示例 :
OUTPUT_ARCH(arm) /*指明處理器結構*/
ENTRY(_start) /*指明程式入口 在 _start 標號處*/
SECTIONS {
. = 0x50008000; /*整個程式連結的起始位置, 根據開發板確定, 不同開發板地址不一致*/
. = ALIGN(4); /*對齊處理, 每段開始之前進行 4 位元組對齊*/
.text : /*程式碼段*/
{
start.o (.text) /*start.S 轉化來的程式碼段*/
*(.text) /*其它程式碼段*/
}
. = ALIGN(4); /*對齊處理, 每段開始之前進行 4 位元組對齊*/
.data : /*資料段*/
{
*(.data)
}
. = ALIGN(4); /*對齊處理, 每段開始之前進行 4 位元組對齊*/
bss_start = .; /*記錄 bss 段起始位置*/
.bss : /*bss 段*/
{
*(.bss)
}
bss_end = .; /*記錄 bss 段結束位置*/
}
4. Makefile 編譯指令碼
makefile 檔案編寫 :
- 1.通用規則 ( 彙編檔案編譯規則 ) : 彙編檔案 編譯 成同名的 .o 檔案, 檔名稱相同, 字尾不同,
%.o : %.S
, 產生過程是arm-linux-gcc -g -c $^
, 其中^
標識是所有的依賴檔案, 在該規則下 start.S 會被變異成 start.o ; - 2.通用規則 ( C 檔案編譯規則 ) : C 程式碼編譯成同名的 .o 檔案,
%.o : %.c
, 產生過程是arm-linux-gcc -g -c $^
; - 3.設定最終目標 : 使用
all:
設定最終編譯目標;
- ( 1 ) 依賴檔案 : 產生最終目標需要依賴 start.o 檔案, 使用
all: start.o
表示最終目標需要依賴該檔案; - ( 2 ) 連結過程 :
arm-linux-ld -Tu-boot.lds -o u-boot.elf $^
, 需要使用連結器指令碼進行連線, ①連結工具是 arm-linux-ld 工具, ②使用-Tu-boot.lds
設定連結器指令碼 是剛寫的 u-boot.lds 連結器指令碼, ③輸出檔案是 u-boot.elf 這是個中間檔案, ④ 依賴檔案是$^
代表所有的依賴; - ( 3 ) 轉換成可執行二進位制檔案 :
arm-linux-objcopy -O binary u-boot.elf u-boot.bin
, 使用-O binary
設定輸出二進位制檔案, 依賴檔案是u-boot.elf
, 輸出的可執行二進位制檔案 即 結果是u-boot.bin
;
- ( 1 ) 依賴檔案 : 產生最終目標需要依賴 start.o 檔案, 使用
- 4.makefile 檔案內容 :
all: start.o
arm-linux-ld -Tu-boot.lds -o u-boot.elf $^
arm-linux-objcopy -O binary u-boot.elf u-boot.bin
%.o : %.S
arm-linux-gcc -g -c $^
%.o : %.c
arm-linux-gcc -g -c $^
.PHONY: clean
clean:
rm *.o *.elf *.bin
5. 編譯輸出可執行檔案
編譯過程 :
- 1.檔案準備 : 將 彙編程式碼 ( start.S ) 連結器指令碼 ( gboot.lds ) makefile 檔案 拷貝到編譯目錄 ;
- 2.執行編譯命令 :
make
; - 3.編譯結果 : 可以看到 生成了 編譯目標檔案 start.o, 連結檔案 u-boot.elf, 可執行的二進位制檔案 u-boot.bin ;
6. 將程式燒寫到開發板上執行
燒寫程式並執行 :
- 2.本次執行結果 : 設定的 GPBDAT 暫存器值為 0b110000, 四個 LED 燈都亮起來;
- 3.修改 LED 燈顯示引數後顯示結果 : 設定 GPBDAT 暫存器中值為 0b110101 是 第一個 和 第三個 LED 亮起來;