tag 标签: system_boot

相关博文
  • 热度 18
    2013-8-5 15:02
    1770 次阅读|
    4 个评论
    Kinetis L系列的启动过程及其实现(三)   (10) 在 start() 中建立 system clock (10.1)  FEI (core/system default clock 21MHz, bus/flash clock 10.5MHz) 任何系统的建立, 都离不开 system clock 的确立. 在 Freescale 的 Kinetis KL serial 中, clock 控制器是 MCG. 无论是否建立 clock, 系统首先进入 FEI, 利用内部 IRC 建立振荡, 这意味着系统可靠性的提高, 外部振荡激励成功与否, 都不会妨碍系统 BOOT 代码的执行. Tip2: 系统 RESET 后首先进入的状态 当我们没有建立任何 clock 时, 与没有内部 RC 振荡器的 MCU 不同. KL25Z 的 MCG 控制器首先建立的是内部 RC 振荡的 FEI 模式. 从图例18 中, 我们可以得知无论我们利用内部 or 外部振荡构建怎样的模式, 我们首先进入的是 FEI 状态. 在论坛上, 我们见过某个答复, 认为 FEI 即系统默认的 core clock 是 internal slow IRC(32K) 倍频后得到的 20.97MHz. 我们觉得这个回答是欠妥的, 因为在 24.5.2 章节(of Reference Manual) 提到的是, 只有在 FEE 或者 FBE 模式下, 并从外部的 32.768k 振荡参考下, 根据默认的 FLL 倍频因子, 才得到 20.97M. 实际上, 我们觉得, 正确的 SYSTEM BOOT 后, KL25Z 进入 FEI, 是内部大约 32KHz 经过FLL(默认) 640 倍乘因子的结果, 也就是如图例19中提到的, 是 RESET 后 的 21MHz.     Tip3: 关于 bus clock 的测量. 对于 Freescale 的 KL25Z 系列, 时钟有三种类型: core/system clock: 无疑的, 在上面 Tip2 中, 我们提到了系统 RESET 默认进入的 FEI 的 core clock 是 MCGOUTCLK 也就是 21M Hz. bus clock: 总线时钟与外围相关, 比如 SPI, 比如 COP, 比如 Timer(在Freescale World 中被称为 PIT) flash clock: FLASH clock(这里与 bus clock 相同, From Renference Manual). 默认的, bus/flash clock 总是 system clock/2, 除非有必要时, 我们对 SIM_CLKDIV1 在启动后的程序内进行操作 OUTDIV4. 因此我们计算得到 bus clock 在 RESET 进入 FEI mode 后, 应该是: 21M/2 = 10.5M. 对于 bus clock 的测量, 我们可以使用指定 PIN 的 clkout, 在 KL25Z 中它是 RC3. 我们可以用示波器测试到这个频率.   (10.2) 利用 internal IRC 建立 freescale KL serial(cortex-m0+) 最大之 48MHz 如果我们不利用 external osc, 我们完全可以建立在 internal IRC 之基础上的最大 48MHz 的 system clock. 正如 sample project 我们见到的, 利用 inernal IRC slow 32KHz, 在设定 1464 FLL 倍频后, 我们得到48Mhz. 那么一个质朴的问题就是, 利用内部的 IRC, 我们建立的 48M core clock, 是否符合 USB 的通讯标准? 在一份查询到的论坛问题中,可以利用这个 clock source 实现 USB, 但是不被推荐. 让我们在这里引出这个帖子的讨论地址, 留作参考: https://community.freescale.com/thread/305970   (10.3) 利用外部 8M 晶振建立 48M system clock (a) 在 pll init 之前, 设定 DIV1, 通常为1, 表明被 MCG 控制器生成的 MCGOUTCLK 直接等于 core/system clock, 没有分频. 设定 DIV4, 通常是2,表明 (bus/flash clock) = (core/syste clock) /2. (b) 确认保持在 FEI 状态中.   (10.4) 从 FEI - FBE (system clock 8M under current 8M crystal, bus clock 4Mhz) (a) 根据图例18, 从初始状态, RESET后默认进入 IRC 之 32k 的 FLL 倍频得到 21M Hz core clock, 这是 FEI mode. 我们最终希望获得外部晶振 8MHz 的PLL 倍频到达 48MHz 的 PEE mode. (b) 根据图例18, 也就是 Kinetis 的 MCG 特性, 我们不得不首先从 FEI mode - FBE mode. 我们倾向认为 FBE mode 是一种过度阶段, 核心就是利用 external crystal(8M), 通过 div 因子, 获得 32K 左右频率, 可以替代原有的 FLL 倍频模式(也就是 FEI mode). 具体的说, 就是我们必须保持使用 external crytal 8M clock, 通过分频得到 FBE mode 下需要的 31.25 ~ 39.0625KHz. 这个变化, 核心就是设定 MCG_C1 register, 对于 8M external crystal, 恰好设定分频因子为 256, 我们计算得到: 8M/256 = 31.25k Hz, 满足 FBE下的(31.25~39.0625Khz) 需求. (c) 因此, 在对 C1, C6, C2 register 进行相应设定后(from reference manual), 特别是我们上面提到的 C1 之分配因子, 我们进入了 FBE mode(利用 external crystal 8m 的分频之后获得 FLL 的倍频). (d) 最终的, 我们计算 core/system clock 现在应该是: 31.25(内部 show IRC 置换为 外部8M分频) * 640 = 20MHz, 这应该就是 FLL 倍频后的结果. (e) 应当注意到, 设定 FEB 模式后, 应等待 crystal osc 初始化完成, 我们用 MCG_S register 可以检查基于external crystal 的 osc 是否建立. 并应检查是否 external reference clock 已作为 FLL source, 也检查 MCGOUTCLK 是否选用了外部 reference clock. (f) 另外, 在脱离默认的 FEI 后, 进入 external crystal 分频后(32.25KHz)再 FLL 倍频得到 FBE mode 后, 我们应该设定 CME, 如果外部频率脱离了当前设定 high freq(3~32MHz, from reference manual), 则控制器 RESET. (g) 应注意到, 因 C6 使用 FLL as MCGOUTCLK source, 并且默认 RESET 后C5 为0, 表 MCGPLLCLK 未使能, 故在 FBE 状态下, PLL 模式disable, 系统仍然处于 FLL 模式下. (h) 那么, 是否这里 system clock 可以合理假设为: 由于 FLL 倍频被使用, 则是 32.25K*640 = 20MHz 呢? 答案是 : 并非如此, 我们理解, 尽管这里 FLL 倍频产生, 而其输出不被使用. 这样, MCGOUTCLK(这里是 core/system clock) 就不是 FLL 倍频的结果, 而是 OSCCLK, 也就是 8Mhz, 同时 bus/flash 为 4MHz.   Tip4: 如何理解 FBE? FEI: FLL Engaged Internal, 这是系统 RESET 后进入的默认模式, 我们已经读出了关键字, FLL, internl, 从 slow IRC(32KHz) 中进行 FLL 我们获得了默认 21Mhz 的 system clock(10.5MHz bus clock). FBE: FLL Bypassed External, 这里的关键字被我们上面的分析所熟知, 我们利用 external reference clock, 这里是 8M crystal 的分频(分频后进入了 31.25 ~ 39.0625KHz 参数域后), 替代 FEI 的 slow IRC 进行了 FLL 倍频. 但是我们饶有兴趣地注意到, 这里 FLL 倍频尽管在工作, 但是最终的 OUTPUT(MCGOUTCLK, 这里是 system clock) 却没有使用到 FLL 倍频, 简单说是 FLL OUTPUT 被 BYPASS. 而最终的 system clock, 是 external crystal 的直接输出. 这里就是外部晶体频率为 8M Hz, 我们合理假设在 FBE 的 bus/flash clock 也就是 8/2 = 4Mhz.   (10.5) 从 FBE - PBE (system clock 8M under current 8M crystal, bus clock 4Mhz) (a) 根据图例18, 为了最终抵达 PEE mode, 我们在 FBE mode 下, 需要进入 PBE mode. (b) 对于 Freescale Kinetis 的复杂繁琐的 MCG 的状态管理, 我们颇有微词. 但是也不得不遵守(因为 Freescale 的手册教育说, 这是建立 system clock 的必须过程```). 那么, 当我们理解, 我们之前, 成功从 external reference clock 完成了 FLL(尽管不用实际输出被bypass), 也就是从 FEI-FBE. 那么此时在 FBE-PEB(PLL Bypassed External) 过程开始前, 从关键字的分析中, 我们已经判断出 PBE 又是一个过渡mode, 其目的是将 external reference clock 的 FLL 模式更换为 PLL 模式. (c) 选择合适的分频, 产生 PLL reference clock 在 Mhz, 这里我们选择 4. 选择合适的 VCO 倍频, 这里我们选择 24. 因此, 计算得知, PLL 倍频的结果是: (8M/4)*24 = 48M Hz (d) 应注意, 启用 PLL(同时将禁止 FLL), 也需要跟踪状态, 等待启用PLL 的同步过程完成. 也应等待 PLL 的锁定完成. (e) 同样的, 在 PEB(PLL Bypassed External) mode 下, 尽管 PLL 倍频锁定成功, 我们 bypass 其输出, 而 system clock 仍取 OSCCLK 为 8MHz(bus clock 4MHz).   (10.6) 从 PBE - PEE (system clock 48M under current 8M crystal, bus clock 24Mhz) (a) 根据图例18, 我们需要最终抵达 PEE mode. (b) 在完成了 PBE, 也即是成功PLL 倍频并锁定后, 我们开启 PLL OUTPUT 作为 system clock(也就是 MCGOUTCLK) 即可完成全部过程. (c) 应注意, 开启 PLL OUTPUT, 我们仍然应在 MCG_S register 中等待确认 PLL 输出启用成功.   (10.7) Reference Manaul 给出的 4M crystal - PEE 的流程例子 尽管使用的 crystal 值不同, 且使用了 high gain 设定等小小差异, 我们仍然可以几乎完整地比拟为我们的 setup clock 的 例子. 参考图例20:   11. 完成 system boot  的 setup   至此, 我们完成了 system boot 的过程. 值得一提的是, 等我们把 size 的优化级别提到最高, 然后构建一个 NULL 内容的 main(), 生成 .bin 文件的大小是: 0x944(大约 2k bytes). 这就是说, 最小系统初始化的 size 的代价. (我们没有任何的调试输出, 产生多余字节的 code).   1. 我们在 Freescale kinetis 的 L serials, 以 KL25Z128 为例, 对 system boot 的过程进行了解析, 这个完整的过程, 如上所述, 包括: (1) 定义汇编代码, 进入 __startup 程序入口点. 要提及我们在汇编中, 初始化了通用寄存器, 并使能了中断, 完成了 start c代码的函数入口点. (2) 在 start 函数中, 我们进行了 RAM 初始化, 并对 .data 进行初始化. 如前所述, 这个过程核心是将 flash 中异常向量表 copyto RAM. 如果有定义的 ram function, 也在这里完成copy. (3) 系统 clock 的建立, 我们详述了从 system 内部的 IRC 默认 FLL 倍频的 FEI mode, 通过 FEI - FBE - PBE - PEE, 最终在 8MHz crystal 的 external reference clock, 使用 PLL 倍频, 完成了 kinetis 的 cortex-m0+ 的最大允许 48M core/system clock 以及 24M bus/flash clock 的过程. (1) 完成一份 BLOG 的过程, 好比作为列车长, 引领观光的旅客们, 共同完成这段曼妙的旅程. 在终点站台上, 与各位珍重道别之际, 我想感谢各位的阅读, 那么也要对这份匆匆急就的 BLOG中, 可能的错误致歉. (2) FREESCALE 的 MCU, 与 NXP 之类同等 ARM(cortex-m) 比较, 似乎略显繁琐, 熟悉与上手的进度都会嫌慢. 一方面, 我们少少郁闷着这种不便(诸如这种可怕的 MCG 的 clock 的建立过程). 另一方面, 因其彻底服从开发者的初始化过程, 使我们对 IDE 的 LINKER 脚本, 对程序进入点, 对 memory space 的形象构建, 以及对 SYSTEM RESET 的初始化过程的完全掌控, 都有着与比如说 NXP ARM 的内置 bootloader 机制的较大不同. 因此, 在这个完成 system reset 的过程后, 于恍恍惚惚间, 于此专业领域, 我们似乎若有所得... Allen Zhan allen_zhan@163.com 2013.08.04 于深圳福田 Release On EETC     Kinetis L系列的启动过程及其实现(一) Kinetis L系列的启动过程及其实现(二) Kinetis L系列的启动过程及其实现(三)  
  • 热度 17
    2013-8-5 14:51
    2459 次阅读|
    1 个评论
    Kinetis L系列的启动过程及其实现(二)   (7) 编译器放置 __startup 的ADDRESS 与真实程序入口点 质朴的思路就是, 作为初始代码, __startup 的 ADDRESS 应该位于 0x000000C0. 事实上不是, 我们假设这完全取决 linker 的安排, 包括运行效率, SIZE的共同优化与平衡的考虑. 实际上, 0x000000C0 是 main() 函数的首地址(见图例13). 分析的依据来源于我们生成的 Linker list 文件, 事实上它被表达为 .MAP 文件. 从源代码编译的 list 文件中, 对应的机器码为: 注意到 THUMB 的方式为 16-bit 机器码. 从 __startup 对应的地址: 0x00000129, 我们找到对应的机器码, 并进行标识(见图例13). 从 .bin 文件中, 我们读取 PC 的地址是: 0x0000181, 这说明真正的程序入口点在此, 参考 .MAP 文档, 0x00000181 address 对应程序入口点为: _iar_program_start. 事实上, 我们进入 debug 后, 顺序执行, 我们将分析到: _iar_program_start - _main - _call_main - main  的过程. 这与我们设计的程序入口点的初衷并不一致. 我们希望程序进入的是 __startup 执行 BOOT 的过程.   (8) 修改程序入口点为 __startup 修改 Project 增加 vectors.h 与 vectors.c 文件 分析 linker 脚本语言, .intvec 被定义在 __ICFEDIT_intvec_start__ (也就是首地址 0x00000000). 分析 vectors.c 将异常中断向量表写在了 .intvec 的位置. 分析 vectors.h 决定了中断向量表中第二个量 VECTOR_001 被定义为 __startup, 也就是我们成功将 IAR 默认 _iar_program_start  为 __startup 最终的, 我们从生成的 .bin 文件中, 确认 PC 指向了新的入口点: 0x00000129, 也就是 __startup 在 debug 中进行调试, 我们也确认单步调试进入了 __startup.   (9) 构建C代码 start() 函数执行 SYSTEM SETUP 在步骤(4)中, 我们完成了 .s 汇编函数 __startup, 根据 vectors.h 的定义, 指示这是 PC 指针的"入口点". (事实上, 如上对 .bin 文件和.map 文件分析, IAR 通过 _iar_program_start 实现的是真正的入口点. 我们猜测, 这里不影响我们对这个 "赝程序入口点 __startup" 的理解). 在 __startup 中, 我们清零通用寄存器, 并开启中断后, 跳转至 start() 函数, 也就进入了 SYSTEM SETUP 的过程. (9.1) Disable watchdog 在 debug mode 下我们会没有机会喂狗, 因此 watchdog 需要disable. 如果没有做 disable, 我们将在 debug mode 下见到不断重启的提示. 进行 diable 后该现象消失.   (9.2) Init RAM 包括 (a) 从 flash copy 向量表 to RAM. 我们至少能够作两个功能假定: 首先这个 copyto 过程, 能够保证我们从 RAM BOOT 成功(假设我们有从 RAM BOOT 的需求, 比如说某种机制下的 upgrade firmware). 其次可以想象 Interrupt 的响应速度增加(在 RAM 中寻址而不必从 FLASH 中读取). (b) 初始化数据从 flash copyto RAM. (c) data section 的 zero 初始化数据清零. (d) copy flash 中定义的 ram function 到 RAM(如果它们因某种加速目的或者 IAP 目的存在且被我们所定义). Tip1: 有时我们无法在C源文件中加入调试断点 我们在调试的过程中, 偶尔有问题为何可以在反汇编窗口调试, 但是 breakpoint 却无法建立在c语言的源代码中, 这里我们需要选中 IAR IDE 的 Options - C/C++ compiler - Output - Check "Generate Debug Information" 即可. 可能有调试者会宣称 .out 文件(用于下载到 target board 进行调试) 会显著增加, 但是, 毕竟 .bin 文件不会因为这个 check 而发生任何变化.   Kinetis L系列的启动过程及其实现(一) Kinetis L系列的启动过程及其实现(二) Kinetis L系列的启动过程及其实现(三)  
  • 热度 25
    2013-8-5 14:49
    2310 次阅读|
    6 个评论
    Kinetis L系列的启动过程及其实现(一)   众所周知, Kinetis L 的 MCU 不存在 bootloader, 这与 NXP 的 ARM 系列有明显的不同. 在 NXP 的 LPC 系列(ARM7)中, 存在 bootloader, 这个 bootloader 将执行一系列的初始化, 也会将 flash 中断向量表 copyto ram, 同时也包括定义的 ram function. 而且, 在 bootloader 的启动中, 还会比较 ISP PIN(一般这个 PIN 是 P0.14), 如果 P0.14 被拉低, 那么 LPC MCU 将进入 ISP 状态, 也就是初始化 uart, 并可以通过 uart 直接下载 firmware. 而 Freescale 的 Kinetis L serials(CORTEX MO+) 是没有类似的 bootloader 的存在. 这可能带来两个方面的不同效果的影响: 作为项目设计者, 我们不得不自行设计系统 startup 的过程, 一手操办 system reset 中初始化的代码, 比如说, 数据初始化过程, 中断向量表与 ram function 向 RAM 的 copyto 等等. 没有经过设计者包办的启动过程, 我们甚至无法正确的运行 main() 函数. 一方面, 这带来了我们工作量的增加; 但是另一方面讲, 这也确实是一个机会, 使得项目设计者, 清晰了解 KL MCU 中的 RESET 过程, 并必须尊重 MCU 的 startup 的规则.   网络上, 至少有两种中文开发者参考资源, 描述了 Kinetis L serial 的 startup 的过程, 但是都未曾明确指出, 这个 startup process 描述的出处 -- 实际上, 它来源于官方的 Freescale 的可下载帮助文档, 文档名为: Kinetis L Peripheral Module Quick Reference 之中第一章: Chapter 1: General System Setup(Software Considerations)   我们使用 IAR 6.50.3, 并借鉴 IAR 自带的 FREEDOM KL25Z 的 SAMPLE(FOR KL25Z128) 来说明 BOOT 过程, 不意外地, 通过对 BOOT 过程的理解, 以及 startup 规则的要求, 我们将跟随完成 startup 实际代码, 建立一个成功 BOOT 过程: (1) START POINT: FLASH 的起始地址被定义为: 0x00000000, 初始 stack pointer(SP) 位于向量表的首地址, 而 4-byte offsets 位置也就是向量表的第二个量是 Program counter(PC). 设备支持从 internal flash 做 boot, 也就是从地址 0x00000000 开始, 获得 SP 与 PC(VECTOR_001) 后, 并跳转于 PC address 开始执行指令. 当然设备也只从从 RAM 启动, 比方说, 如果在默认从 flash 启动后, 我们在 startup 中完成, 将 flash 中的异常向量表 copyto 到 ram, 那么我们将能实现从 RAM 启动的过程. 另外要提到的是, 我们记得曾经了解过, 将异常向量表在 startup 中 copy头 ram, 不仅仅可以 do ram boot, 也可以加速 interrput 的过程. 那么不意外的, 从向量表的 3rd 量开始, 这将是 interrupt isr 的入口地址(图例1中, 我们见到3rd 的值为 VECTOR_002, 实际上是不可屏蔽中断 NMI 对应的地址, 而之后的 VECTOR_003 开始, 是我们 interrupt 例程中使用到的地址. 在之后的图例中, 我们将看到对应的注释).     (2) 异常向量表的ADDRESS: 让我们检查 IAR complier 的 linker 安排, 无疑的, 异常向量表的初始地址是: 0x00000000   在 IDE 中, 我们使用 config options - linker 的 config TAB 中, 对 ICF file 的 edit 按钮也可以读到: 准确的, Linker 脚本文件, 定义异常向量表的起始地址, 就是 FLASH 的起始地址:0x00000000   (3) RESET 过程与 MCU 状态 在 RESET 过程中, MCU 的 digital I/O PIN 将进入 disable state, 也就是高阻态(内部上拉下拉禁止). analog PIN 将进入默认的 analog function. 而一旦 RESET 完成后, digital I/O PIN 继续保持 disable state, 中断被禁止, 并且大部分的 modules 被关闭. 时钟模式进入 FEI 模式. 且 watchdog 被激活. 对于 kinetis L serial, 上述 RESET 执行后的 device state, 则暗示着我们, 我们需要在 startup 中主动设计: core clock, system clock 以及 flash clock (事实上, 默认 flash 与 bus 的 clock 相同). 这是 kinetis L serial 赋予我们设计者的神圣使命(其实, 如果不完成这些任务, 我们事实上无法正常启动这颗 MCU). ;P   (4) low-level 的汇编(启动)代码的实现 通过图例1, 我们已知, 设备(默认)从 FLASH 执行后, 从起始地址 0x00000000 处开始, 载入了 4-byte 的 SP 值后, 并将位于 0x00000004 的 PC 值读取后, 跳转到 PC 值指向的指令执行地址, 进行指令执行. 向量表中, PC 对应的定义 VECTOR_001. 上面的图例清楚说明了, 我们需要实现 __startup 对应于 (VECTOR_001), 也就是 PC 的执行地址. 我们需要实现 __startup 函数, 以实现 MCU 的正确 BOOT.   在上面的图例中, 我们见到了 __startup 的建立, 简单来说, 内容如代码注释, 通用寄存器被清零, 启用中断后, 跳转到了 C 代码形式的 start() 函数. 当然, 对于任何一个 IAR 构建的 NULL KINETIS L project, 这可能意味着, 我们不得不遵守完成 __startup 这个事实上的程序入口点.(题外话, 此时要想编译正常通过, 我们也需要定义 start()函数, 当然在函数内部我们需要 call main() ).   (5) REBUILL ALL 得到 OUTOUT FILE 这样, 我们在 IAR IDE 中, 通过正确设定 target mcu 后, 即可 rebuild all, 并生成输出文件格式(设为二进制文件 .bin).  在 IAR 下, 也就是设定:   (6) 为何 CODE 的起始地址是: 0x000000C0 默认从 FLASH 启动的 .bin 文件, 将异常向量表放在最前, 即从 0x00000000 的地址开始. 跟随着异常向量表, 才是 CODE 的放置区. 异常向量表的 size 有多大? 在 kinetis sample 中, 我们也找到下面的证明: 这里, 48*4 = 0xC0.   Kinetis L系列的启动过程及其实现(一) Kinetis L系列的启动过程及其实现(二) Kinetis L系列的启动过程及其实现(三)