1. 程式人生 > >【嵌入式開發】時鐘初始化 ( 時鐘相關概念 | 嵌入式時鐘體系 | Lock Time | 分頻引數設定 | CPU 非同步模式設定 | APLL MPLL 時鐘頻率設定 )

【嵌入式開發】時鐘初始化 ( 時鐘相關概念 | 嵌入式時鐘體系 | 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 產生的時鐘作用;

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 , EPLLVPLL;
  • 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) ;
        這裡寫圖片描述
  • 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 = .; ;
  • 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 ;
  • 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 亮起來;
    這裡寫圖片描述