【嵌入式开发】时钟初始化 ( 时钟相关概念 | 嵌入式时钟体系 | 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. 将程序烧写到开发板上运行


烧写程序并运行 :

  • 1.参考步骤 : 烧写代码到开发板并执行
  • 2.本次运行结果 : 设置的 GPBDAT 寄存器值为 0b110000, 四个 LED 灯都亮起来;
    这里写图片描述
  • 3.修改 LED 灯显示参数后显示结果 : 设置 GPBDAT 寄存器中值为 0b110101 是 第一个 和 第三个 LED 亮起来;
    这里写图片描述

猜你喜欢

转载自blog.csdn.net/han1202012/article/details/81324439