ARM Cortex-M底层技术(2)—单片机的启动代码详解
提示:若转载请备注来源,谢谢
文章目录
- 启动文件
- 1. 什么是启动代码
- 2. 启动代码主要干了什么
- 启动文件分析
- 一、设置堆栈
- 二、定义中断向量表
- 三、初始化系统时钟
- 总结
启动文件
1. 什么是启动代码
启动代码是系统上电或者复位后运行的第一段代码,是进入C 语言的main 函数之前需要执行的那段汇编代码。或者说用户程序运行之前对系统硬件及软件环境进行必要的初始化并在最后使程序跳转到用户程序。
将启动文件理解为一种描述性的代码,不要拘泥于它的实现机制,我之前一直在想启动代码中汇编代码的执行顺序,后来没明白,估计和keil工具有关系,只需要记住一个道理,程序上电执行的第一条代码(我们可以控制的代码),就是启动文件中Reset Handler函数,Reset Handler函数 的地址存放在flash的0x00000004处。
2. 启动代码主要干了什么
对于 Cortex-M系列的芯片而言,启动代码大同小,明白一个几乎所有的也就都明白了。
- 设置堆栈
- 定义中断向量表
- 初始化系统时钟(Reset_Handler中做)
- 初始化堆栈,有的启动代码不一定做(Reset_Handler中做)
- 执行_main,准备c语言的运行环境,初始化程序计数器指针PC指向main,进而来到c语言的世界
启动文件分析
一、设置堆栈
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>Stack_Size EQU 0x00000800AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>Heap_Size EQU 0x00000100AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limitPRESERVE8THUMB
以上仅仅定义了堆栈的大小,堆栈的起始地址在keil中设置方式有两种,
第一种:
第二种,分散加载文件(将keil的Use Memory Layout from Target Dialog去掉勾选就会有该文件),在实际项目中,我通常使用该文件,可以理解问链接文件
在上图(第一幅图片中),启动文件的最后几行,有关于Use MicoLIB的描述,还有关于堆栈的设置,代码为,主要是初始化堆栈的地址范围:
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************IF :DEF:__MICROLIBEXPORT __initial_spEXPORT __heap_baseEXPORT __heap_limitELSEIMPORT __use_two_region_memoryEXPORT __user_initial_stackheap__user_initial_stackheapLDR R0, = Heap_MemLDR R1, =(Stack_Mem + Stack_Size)LDR R2, = (Heap_Mem + Heap_Size)LDR R3, = Stack_MemBX LRALIGNENDIF
二、定义中断向量表
1. 当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了―向量表查表机制‖(是ARM的实现机制)。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。
2. 向量表在地址空间中的位置默认存放在0x00000000处,但是还可以设置,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。
代码为:
; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLYEXPORT __Vectors__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_IRQHandler ; NMI HandlerDCD HardFault_IRQHandler ; Hard Fault HandlerDCD 0 ; ReservedDCD 0 ; Reserved............AREA |.text|, CODE, READONLY
附加:常常在涉及到升级loader中使用
- 向量表重定位 + 跳转代码如下:
__asm void SoftReset(u32 address)
{LDR R1, =0xE000ED08 // 0xE000ED08 重定位地址STR R0, [R1, #0] // R0为形参address/* 重定位完后,向量表地址为address */ LDR r1, [r0] // address内前4个直接存放的 Top of Stack msr MSP,r1 // 给MSP赋值ADDS r0, r0, #4 // Reset_Handler 的地址 LDR r1, [r0] NOPBX R1 // 执行 Reset_HandlerNOPNOPNOP
}
- 若仅仅想实现向量表重定位,例如将向量表定位到0x00000200,代码如下:
// SCB->VTOR就等于 0xE000ED08
//SCB->VTOR = 0x00000000; // 等价于0xE000ED08 = 0x00000000;
SCB->VTOR = 0x00000200;
三、初始化系统时钟
还是要在重申一下,复位子程序Reset_Handler是系统上电后第一个执行的程序。这里分析两个Reset handler,一个是STM32 demo上的,一个是我们使用的thk88芯片的
stm32代码为:
; Reset handler
Reset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __mainIMPORT SystemInitLDR R0, =SystemInit // 系统初始化,里面包含了初始化时钟BLX R0 LDR R0, =__main // 最后跳转到main()BX R0ENDP
thk88代码为
Reset_Handler PROC EXPORT Reset_Handler [WEAK]IMPORT main; 开启内部时钟30MLDR R3, =SCUBASEADDRLDR R0, =0x00000000STR R0, [R3,#SCUSCK]LDR R0, =0x00000000STR R0, [R3,#SCUINCKD]; 关闭USB PHY,禁止DP上拉电阻 LDR R0, =0x00000100STR R0, [R3, #SCUCM3] ;OPEN USB CLKLDR R2, =USBBASEADDRLDR R0, =0x00000001STR R0, [R2,#USBPCON] ; 关闭USB PHYLDR R0, =0x00000000LDR R2, =USBPHYCON STR R0, [R2] ;禁止DP上拉;STR R0, [R2,#USBPHYCON] ;禁止DP上拉; 屏蔽 NMI 中断LDR R0, =0XFFFFFFFFLDR R2, =SCUNMISTR R0, [R3,R2] ; DMA使用event方式LDR R2, = 0x40000100 ;SCUEVTSET1 = 0x02;DMA event modeLDR R0, = 0x00000002STR R0, [R2];初始化用到的RAM区,4字节对齐,包括RW和ZI区域BL CLEAR_ZIIMPORT __mainLDR R0,= __mainBX R0ENDP
每款芯片的启动代码大同小异,熟悉一个其他的都可以看懂。
总结
具体启动流程如图,两种方式
- 启动流程1(使用标准库,不使用Microlib)如下图:
- 启动流程2(使用Microlib)如下图:
假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x00000000(映射到0x8000000,两个是一个东西),则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处(复位地址在栈顶地址4字节后)。当STM32上电后,遇到复位信号,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。
重申:
- 绝大部分ARM-M协议的芯片,复位之后先进入厂商boot,此时所有的用户均无法接入处理器;厂商boot主要负责芯片最初级的初始化,对MCU进行一些差异性设置等,BOOT完成后,会把主动权交给用户,也就是启动代码;
- 启动代码(执行汇编语言不需要此启动代码),在启动文件中,会设置MSP(主堆栈指针)和PC(程序计数器)的值,MSP的地址默认是0x00000000,PC的地址默认是0x00000004,这两个地址可以通过CORTEX-M中的VTOR寄存器来进行重映射,修改。
ARM Cortex-M底层技术(2)—单片机的启动代码详解相关推荐
- cortex m0启动代码详解
转自:http://www.cnblogs.com/mddblog/p/4920063.html 阅读目录 概述 1.堆栈空间定义 2.存放中断向量表 3. 复位中断函数(Reset_Handler) ...
- TI Cotex M3/4单片机关于寄存器操作详解
TI Cotex M3/4单片机关于寄存器操作详解 前备知识 寄存器与偏移量 位带操作.位带区 TI单片机寄存器操作详解 快速在TI的手册里面确认某外设某寄存器的确切地址 寄存器操作介绍 本文参考 前 ...
- Qt开发技术:QCharts(三)QCharts样条曲线图介绍、Demo以及代码详解
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108022984 各位读者,知识无穷而人力有穷 ...
- Qt开发技术:Q3D图表开发笔记(二):Q3DBar三维柱状图介绍、Demo以及代码详解
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/130150728 各位读者,知识无穷而人力有穷,要么改需 ...
- MATLAB与51单片机进行串口通信详解
目录 一.51单片机与电脑进行串口通信 二.MATLAB串口通信函数 三.串口属性 四.示例Demo 4.1 MATLAB接收单片机发来的数据 4.2 MATLAB向单片机发送数据控制LED 五.总结 ...
- Qt开发技术:Q3D图表开发笔记(三):Q3DSurface三维曲面图介绍、Demo以及代码详解
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/130264470 各位读者,知识无穷而人力有穷,要么改需 ...
- 单片机 rs232 c语言,完全手册 51单片机C语言开发详解系列之第8章 串口通讯实战—RS232...
完全手册 51单片机C语言开发详解系列 技术凝聚实力 专业创新出版 第8章 串口通讯实战-RS232接口电路与软件设计 本章主要通过实际的例子讲解AT89S51单片机的RS232串行通讯接口设计. 随 ...
- 容器编排技术 -- Kubernetes kubectl rollout resume 命令详解
容器编排技术 -- Kubernetes kubectl rollout resume 命令详解 1 kubectl rollout resume 2 语法 3 示例 4 Flags kubectl ...
- 容器编排技术 -- Kubernetes kubectl rollout undo 命令详解
容器编排技术 -- Kubernetes kubectl rollout undo 命令详解 1 kubectl rollout undo 2 语法 3 示例 4 Flags kubectl roll ...
最新文章
- Nautilus启动报错
- 几则常用的BASIS技巧整理
- boost::endian模块宏BOOST_SCOPED_ENUM的测试程序
- linux cd 命令案例,15个关于Linux的‘cd’命令的练习例子
- 直流有刷减速电机结构及其工作原理
- idea可以正常启动无法进入断点_IDEA Debug 无法进入断点的解决方法
- LoadRunner截取字符串操作
- 洛谷 P3745 [六省联考2017]期末考试
- 【SQL Server】入门教程(总结篇)
- DSP 6678 多核CACHE一致性操作
- 豆瓣读书TOP250书籍信息爬虫脚本
- STM32CUBEMX_定时器控制LED闪烁
- 利用matlab制作光栅动画(莫尔条纹动画)
- 服务器系统事件id1001,WIN10事件查看器,ID1000,ID1001 問題
- Python结巴中文分词工具使用过程中遇到的问题及解决方法
- 未成年人勿进 谨以献给1980~1990出生的人(二)
- 直流电机参数术语中英文对照及解释
- 查询计算机系统操作工,计算机系统操作工B卷
- 使用Docker搭建自己的在线密码管理器软件-Bitwarden
- 肖仰华:知识图谱如何解决行业智能化的工程问题?