ARM Cortex-M底层技术(三)启动代码的使用

2020-01-16来源: eefocus关键字:ARM  Cortex-M  底层技术  启动代码

Cortex-M启动代码的使用


    上一篇扯了一些关于启动代码的原理,了解了额原理不去使用意义就没有那么大了,了解了启动代码不是终极目的,我们的目的是深入理解Cortex-M系列MCU的底层原理,并应用到实际的产品中,加速开发,提升产品稳定性;下面就小编我的实际使用经历来看一下,启动代码的具体应用。


    启动代码的本质是在程序进入用户代码(main函数)之前初始化向量表、完成分散加载以及C语言运行环境初始化的一段代码,可以说你需要在进入用户代码(main函数)之前需要搞定的工作都可以放在这里来完成,而且有些代码放到启动代码里面去完成会比在用户代码,main函数最开始完成效果要好得多。


1、如何在启动代码中调用函数?以及什么样的函数不能再启动代码中被调用?


    因为默认的标准的启动代码主要工作是在Reset_Handler里面完成的,调用函数一般也会再这里。


Reset_Handler   PROC


                EXPORT  Reset_Handler               [WEAK]


                IMPORT  SystemInit


                IMPORT  __main



                LDR     r0, =SystemInit


                BLX     r0


                LDR     r0, =__main


                BX      r0


                ENDP


    上面代码展示了如何在Reset_Handler导入并调用SystemInit()函数,首先导入SystemInit()函数的标号


                IMPORT  SystemInit

    然后调用函数,当然这里是用汇编语言来完成的


                LDR     r0, =SystemInit

                BLX     r0

    看不懂的童鞋自己去补汇编课哈。所以在启动代码的汇编语言里调用C语言函数都可以使用以上两步:


    (1)导入函数标号;   


    (2)调用这个函数;


    当然这里有几点注意事项,这里不是所有函数都可以在汇编语言中调用的,因为此时__main还没有运行,C语言运行环境还没有被完整搭建起来,堆栈也没有初始化完成,所以要注意:


    (1)调用的C函数参数不能超过4个,不用可以,但用的话不能超过4个参数,原因是在Cortex-M体系MCU中,函数的1-4个形参会压进R0-R3这4个通用寄存器(Cortex-M系列MCU,M0也好、M3也好、M4也好都只有16个通用寄存器,内部寄存器结构去参照ARM官方的白皮书)如果有第五个参数,这个参数会被压栈,但因为此时__main还没有运行,堆栈没有被初始化所以此时如果函数有超过4个以上的参数,会导致程序跑飞;


    (2)不要把需要调用的函数写到__main之后,因为没有意义,程序不会跑到那里;


2、什么样的功能或者函数适合放在启动代码中启动或者执行?在main()函数里面执行不行吗?有什么区别?


    这个问题的关键就是__main(不是main函数是__main,在上一篇文章中有对他们两个区别的详细论述),说白了启动代码中以及main()函数或用户代码中的最大区别就是是在__main之前还是之后。


    下面小编我罗列几种适合在启动代码中执行即__main之前执行,效果好过在main()函数中执行即__main之后执行的几种典型情况:


    (1)FPU(浮点协处理单元)初始化:


    准确的说这个是必须在启动代码里面也就是__main之前执行的代码,FPU只在Cortex-M4&Cortex-M7以及刚刚发布的Cortex-M33中才有,Cortex-M3&Cortex-M0/M0+中没有,原因是__main中会初始化浮点C的浮点库,如果在这之前浮点硬件没有被初始化,程序会直接挂掉跑飞。当然你也可以选择不用FPU,在开发环境的编译器选项中都会有这个选项,即不启用FPU,这样你就可以不去初始化FPU了,程序也不会有问题,但只要你用FPU,你就要在__main之前初始化FPU,以LPC54608为例FPU初始化代码集成在了SystemInit函数中,我也用过有单独函数去初始化FPU的MCU,代码基本上都是厂商SDK里面会提供的,你只需要调用就好了;


    (2)片外SRAM/SDRAM初始化:


    有很多有过开发经验的老同志马上跳起来了“老子之前用过片外SDRAM,是在main()函数里面才初始化的,也没出过问题,你别在这瞎BB。”原因是这样的,看情况,看你是否要把一部分RW/ZI数据段放到片外片外SRAM/SDRAM上去初始化,就是说看你是否需要要分散加载过程作用到你的片外SRAM/SDRAM。如果要,那就需要在启动代码__main之前调用片外SRAM/SDRAM的初始化函数;如果不要,则无所谓,在main函数里面初始化片外SRAM/SDRAM也行。


    你也许会问,什么情况下我们需要分散加载作用到片外SRAM/SDRAM?(分散加载以后会专门开几篇文章来讨论,这里先过)其实有很多种情况,以后讨论分散加载再细说,这里先举个例子,还是拿LPC54608来举例,其他Cortex-M类的MCU情况相同,LPC54608片内有200KB的SRAM,最大连续的SRAM有128KB,另外72KB与这128KB的SRAM在地址上不连续,这是一个细节(大家在选择MCU时也要注意地址的连续性)比如我要开一个150K的BUFFER,来缓存我程序中间的数据,看似LPC54608片内有200KB的SRAM可以实现150K的BUFFER,但实际不然,编译器无法跨地址来分配这种BUFFER,也就是说实际的课分配单一最大BUFFER为128KB,但是你要150KB,所以没办法,你要外扩SDRAM,因为这个BUFFER为编译器去分配空间以及初始化的,所以必然会被计算为RW/ZI数据段,这样这个数据段就需要被分散加载,这就需要把外扩SDRAM的初始化代码放到__main之前,这样__main才会知道片外有一个SDRAM的空间需要被初始化(这也需要分散加载脚本文件的配合,以后文章再细说)。也有其他情况需要在启动代码中初始化SDRAM的,所以个人建议片外SRAM/SDRAM初始化最好放到启动代码里面就是__main之前,因为这样基本不会错,放到用户代码里面会有很多情况Cover不到。


    (3)看门狗的开关、低电压检测等初始化:


    有些厂商的MCU会在芯片的BOOTROM里面打开看门狗,可以理解为一上电看门狗就开了,有些厂商则不然,需要根据应用自己去开关开门狗;这里的核心问题是__main的运行时间,考虑到这个因素,我强烈建议把看门狗,低电压检测等功能放到启动代码中也就是__main之前来初始化。


    __main的运行时间???纳尼??什么鬼??没错,这是一个很重要的隐形要素,__main的运行时间是根据你的用户代码来决定的,还记得上篇文章中提到__main的功能吗?


    初始化堆栈


    初始化C Library


    分散加载


    所以使用MacroLib速度就会快一些,使用标准C Lib会慢一点,分散加载内容多会慢一点,反之会快一些,当然也跟MCU的主频相关,主频高速度也会快,大概__main的运行时间从100-200微秒到几十个毫秒之间。所以如果程序在这中间发生一些BUG,比如你在__main之前调用了几个初始化函数里面有BUG,或者__main运行时电压发生跌落等情况时,MCU会对这些紧急情况有所反应,比如复位,或者保护重要数据等,增加系统稳定性。因为如果你的程序比较复杂__main可能要运行几十个毫秒,对MCU来讲,几十个毫秒以及是很长的时间了,尤其是你的产品出货量很大的情况下,各种小概率事件都会被用量放大为大的品质问题,所以这段时间MCU的运行安全也是需要考虑的。所以我建议启动代码的结构如下,把看门狗&低电压检测放到最前面:    


Reset_Handler   PROC

                EXPORT  Reset_Handler               [WEAK]

                IMPORT  SystemInit

                IMPORT  LVDInit

                IMPORT  WDTInit

                IMPORT  OtherInit

                IMPORT  __main

 

                LDR     r0, =WDTInit                ; 看门狗初始化

                BLX     r0,

                LDR     r0, =LVDInit                ; 低电压检测初始化

                BLX     r0

                LDR     r0, =SystemInit             ; 系统初始化

                BLX     r0

[1] [2]
关键字:ARM  Cortex-M  底层技术  启动代码 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic485852.html 本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:ARM Cortex-M底层技术(四)编写自己的启动代码
下一篇:Arm中main()和_main()的区别

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

ARM linux内核在内存中的布局
Kernel Memory Layout on ARM Linux Russell King <rmk@arm.linux.org.uk>      November 17, 2005 (2.6.15)This document describes the virtual memory layout which the Linuxkernel uses for ARM processors.  It indicates which regions arefree for platforms to use, and which are used by generic
发表于 2020-01-19
ARM命令LDREX和STREX实现spinlock
在 include/asm-arm/spinlock.h 下有這麼一段#if __LINUX_ARM_ARCH__ < 6#error SMP not supported on pre-ARMv6 CPUs#endif好啦,前提就是:只有 ARM core 版本 >=6 才可以繼續:all spin lock primitives 到最後都是使用下面這個基本型: static inline void __raw_spin_lock(raw_spinlock_t *lock){    unsigned long tmp;1 
发表于 2020-01-19
ARM用户层发生异常后软硬件协同处理流程
我这里是要简单说一下,在ARM平台的用户层发生异常后的软硬件协同处理流程,是个大致的概况,对宏观了解后,具体细节内容网上有很多,可以自行查询。用户层程序正在执行时,遇到未定义的指令(ARM不是别的指令)或者SWI软件中断指令(产生系统调用),就会产生异常,这里以未定义指令异常为例进行说明:一旦出现未定义指令异常,CPU会自动做如下操作:(1)未定义模式(ARM七种运行模式的一种)下对应的lr(即R14,不同的运行模式有不同的lr寄存器)寄存器保存当前发生异常的指令下一条指令的地址。例如,在用户态有A B C 三条指令顺序执行,指令A发生未定义指令异常,则指令B的地址就会由CPU保存到未定义模式下的lr寄存器中,用于异常返回
发表于 2020-01-19
ARM处理器各个模式之间是如何切换的?
1、ARM处理器各个模式之间是如何切换的?答:除用户模式外的其他6种模式称为特权模式,这些模式中,程序可以访问所有系统资源,也可以任意进行处理器模式的切换。处理器模式可以通过软件控制进行切换(直接设置CPSR寄存器的后五位就可以在6种特权模式之间互相切换),也可以通过外部中断或异常处理过程进行切换(例如,在USR模式下,发生中断后切换到IRQ模式)。2、ARM各个模式之间切换时,上下文的保存哪些是硬件在做?哪些是操作系统在做?答:CPU做的:(1)把返回地址保存到相应模式的lr寄存器中,例如从usr模式切换到irq模式,CPU会将usr模式下的pc值,保存到irq模式下的lr寄存器中。(2)保存CPSR到相应模式的SPSR寄存器中
发表于 2020-01-19
ARM处理器的运行模式和ARM寄存器
一、ARM处理器共有7种运行模式 处理器模式描述用户模式(User,usr)正常程序执行的模式快速中断模式(FIQ,fiq)用于高速数据传输和通道处理外部中断模式(IRQ,irq)用于通常的中断处理特权模式(Supervisor,sve)供操作系统使用的一种保护模式数据访问中止模式(Abort,abt)用于虚拟存储及存储保护未定义指令中止模式(Undefined,und)用于支持通过软件仿真硬件的协处理器系统模式(System,sys)用于运行特权级的操作系统任务usr是普通模式,其他六种是特权模式(Privileged Modes),在这些模式下,程序可以访问所有的系统资源,也可以任意地进行处理器模式的切换。除了usr
发表于 2020-01-18
ARM处理器的运行模式和ARM寄存器
ARM裸机驱动中的main函数调用前的准备工作
硬件方面1.关闭CPU看门狗2 配置CPU的工作时钟3.程序要在SDRAM中运行,因此必须初始化SDRAM软件方面1 函数要运行,需要栈空间,因此必须初始化栈指针SP2 设置main函数的返回地址3 调用main4 清理工作
发表于 2020-01-18
小广播
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2020 EEWORLD.com.cn, Inc. All rights reserved