ARM Cortex-M底层技术(四)编写自己的启动代码

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

编写自己的启动代码


    上一篇扯了一些关于启动代码的应用方面的内容,列举了4种我自己遇到过的常见的启动代码应用,当然实际的应用肯定不止上一篇文章中提到的那几种,关键是大家懂了原理后根据实际的需求添加自己的应用,这个才是关键。


    这一片文章主要分享下如何编写自己的启动代码,你可能会说这种脱了裤子放屁的事情没必要的,CMSIS规范了标准的启动代码模板,各个厂商都会提供自己MCU的启动代码,而且很稳定,干嘛要自己写?装B嘛?没错~装B当然是一个主要目的,我一直认为不想在自己的代码里面装个B的程序猿是缺乏上进心的,虚荣心往往是一个人前进的动力,另外更重要的一点,就是不自己亲自动手做过一遍,真正的知识&技能有时候很难被真正掌握。我自认为对启动代码很了解,但真的自己去写的时候,还是遇到了不少问题,当我真正的写出来并且反复测试OK之后,我发现里面用到的很多编程技术是相当实用的,它扩展了我再编写应用代码时的很多手段,废话不多说了,开始装B……


1、首先标准的CMSIS启动代码都是汇编语言的,但是为了装B,小编决定用C来编写(真实的情况是老子不会写汇编o(╥﹏╥)o)。


    用C来编写启动代码之前,我们需要先构思一下C能不能用来编写启动代码?我们回顾下第二篇文章中的启动代码原理&其主要功能:


    (a)正确的在存储空间中摆放异常&中断向量表;


    (b)分散加载&C Library的初始化(调用 __main函数);


    (c)堆栈初始化。


    其中(a)功能在标准的汇编启动代码中是通过声明一个数据段(就是汇编指令DCD xxx)来实现的,这个我们在C中可以用数组来做,关键是要摆放到指定的位置上,这点需要用分散加载来配合,所以(a)应该可以实现;


    其中(b)最好实现,直接调用__main就好了,没什么可说的;


    麻烦的是(c),虽然通过读标准的CMSIS汇编启动代码也可以知道堆栈是怎样初始化的,读下代码可以知道(以LPC54608为例,早期的芯片会有不同的处理方式,比如大家都很熟悉的STM32F103系列,大家可以参照之前的文章(二)去对比参考下,只要你读懂了文章(二)就是启动代码原理分析那一篇后,你可以很轻松的搞清楚其原理,以及与我示例中展示的LPC54608启动代码的区别),是需要启动代码与分散加载配合完成的,在C中是否能搞得通我满就要试试看了。


2、先把异常&中断向量表搞定。


    之前我们说这个可以用数组来实现,但大家只要仔细想一下就知道,普通数组肯定不行,因为这些向量的本质是中断服务函数的入口,也就是“函数指针”所以这个数组必须得是函数指针数组:


    所以我们先声明一个函数指针类型:


typedef void ( *__vector )( void );

    然后定义一个函数指针型数组取名为__vector_table:


__vector __vector_table[] = {};

    这个数组好做,但是最重要的是摆放问题,必须确保这个向量表被摆放到Flash的0x00000000地址上去(在LPC54608这颗芯片中默认的向量表地址),这个才是问题的关键。C与汇编是无法解决这个问题的,管程序在存储空间内摆放的是编译器开发环境中的链接器(不懂的童鞋去参考“编译原理”),指导链接器工作的是分散加载的脚本文件,这里需要分散加载的介入。 这里简单介绍下编译原理的基础知识:


附加一:强势插入的编译原理基础知识

    比如大家常用的开发MCU的各种开发环境(Keil、IAR、Eclipse……等)其中真正起到编译作用的就是编译内核,Keil的编译内核是ARMCC(有时写成CCARM)、IAR编译内核是ICCARM、Eclipse一般大多是GCC,编译内核实际上是分成4个功能模块:预处理器(Preprocessor)、编译器(Compiler)、汇编器(Assembler)、链接器(Linker);如上图所示:预处理器负责把各种头文件与对应的C文件结合在一起,负责删除注释、展开宏定义等工作;编译器负责把C语言转换为对应的汇编语言;汇编器负责把汇编语言转换为可重定位的目标文件*.o,这个就已经是二进制文件了;链接器负责把所有.o文件链接成一个整体并分配真实的物理地址(就是上文反反复复提到的分散加载)。之后我会单独开一篇文章专门介绍这部分内容,这里大家有个概念就好。


    好回归正题,如何摆放异常&中断向量表呢?


    这里以Keil MDK为例(IAR、GCC在这部分语法不同,不过原理相同),指导链接器如何工作有一个专门的描述脚本文件,在Keil中此文件以.sct/.scf为扩展名,在这里可以找到分散加载描述文件:

    说明:勾选1,则使用系统默认生成的分散加载文件,好处是你不用自己动手写,缺点是,只能生成最简单的分散加载,没有办法适应用户需要的很多功能;2的红色方框内就是分散加载的路径,这里是我自己工程的路径,大家可以根据自己需求去弄,点击Edit可以编辑;


    这里直接列出来我针对自己写的启动代码编写的分散加载文件,关于分散加载以后我会专门写几篇文章来教大家如何使用,这是非常非常有用的功能,这里直接列出最简单的写法,比较暴力,在后续的文章中我们再去完善这部分内容:


load_rom 0x00000000 0x00080000

{

vector_rom 0x00000000 0x400

{

*( vector_table, +first)

}

execute_rom 0x400 FIXED 0x0007FC00

{

*( InRoot$$Sections )

.any( +ro )

}

execute_data 0x20000000 0x00010000   

{

.any ( +rw +zi )

}

ARM_LIB_HEAP  +0 empty 0x400 {}

ARM_LIB_STACK 0x20020000  empty -400 {}

}

    其中用*( vector_table, +first)中的+first来确保项链表被放置到Flash地址的最前端,这里需要注意的是,分散加载文件中的语言是脚本语言,既不是C也并非汇编,这个文件无法编译更无法调试,如果写错了,只能通过经验来修改不能DEBUG。如果你看不懂,不要着急,我们以后专门来介绍这部分内容。


    我们要放置一个数组,那么我们需要声明一个数据段Symbol,然后用分散加载来指定这个数据段Symbol的放置方式也就是+first。所以我们要先让这个数组生成一个段:


const __vector __vector_table[] __attribute__( ( section ( "vector_table" ), used ) ) = {};

    于是我们使用__attribute__来实现。


    堆栈的指定我们在分散加载中完成,即:


ARM_LIB_HEAP  +0 empty 0x400 {}

ARM_LIB_STACK 0x20020000  empty -400 {}

    具体细节以后再介绍,这里简单说一下,这两句话的意思就是声明一个大小为0x400的堆和一个大小为0x400的栈,注意堆和栈是不同的,简单来说函数的调用会占用栈空间,而使用malloc这样的函数则会产生堆分配,具体细节大家自己去百度。


*( InRoot$$Sections )

    这部分指定了C Library以及分散加载部分代码的放置;


    总之,这个分散加载文件最终把代码在存储空间内放置成了类似下图的形态:

附加二:__attribute__是纳尼????


    __attribute__关键字用于指定函数、变量等Symbol的属性,是编译器可以识别的C语言关键字,当然这个要看使用的是什么编译器,不同的C编译器语法上略有差别,有的C编译器甚至还不支持__attribute__关键字,以下是Keil的帮助文档里面的关于__attribute__关键字可实现的功能:

     __attribute__关键字具体玩法大家可以去keil的帮助文档里面找,很多有用的功能。


    回归正题:__attribute__( ( section ( "vector_table" ), used ) ),其中的“section("vector_table")”意思是在elf(可重定位的目标文件)文件中放置一个名字为vector_table的section。used关键字指定了编译器该变量要在OBJ文件中保持为static类型。再配合分散加载的*( vector_table, +first)的+first属性我们就成功的定义了一个放置在Flash最前端地址0x00000000地址上并且不会被编译器优化掉,数值不会被改掉,名字为vector_table的向量数据段。


    写到这里小编已累吐血,但我们还得继续;


    还记得栈顶如何处理吗:

    这个是编译器识别的特殊栈标号,可以在分散加载或者汇编或者C语言中使用,还记得我们在第二篇文档里面讲到的,0x00000000地址(向量表首地址)放置主堆栈指针MSP,0x00000004地址放置PC指针,所以我们先导入这个标号:


    CSDN的编辑器会把两个放在一起的美元符号识别为一个关键字改变之后的字体以及段落形态,所以有些代码部分我只能贴图片出来,郁闷中……


0x00000004放置PC指针,我们需要把复位向量放到此处,所以代码是这样的:

    上面代码省略了中断向量表部分。ECRP为LPC54608的加密位,0xffffffff为不加密。这里不同的MCU处理方式不同,不具备什么代表性。


3、搞定__reset_handler复位函数


这个比较简单,最重要的是调用__main,另外记得我们在上一篇文档中介绍的启动代码的使用吗?有一些初始化要放到__main之前的,LPC54608的FPU以及一些SRAM的开关要放

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

上一篇:Debian for ARM
下一篇:ARM Cortex-M底层技术(三)启动代码的使用

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

推荐阅读

ARM嵌入式学习--OK6410裸板程序--1.GPIO控制LED
;  mov pc, lrMakefile: 1 led.bin: start.o 2         arm-linux-ld -Ttext 0x50008000 -o led.elf $^ 3         arm-linux-objcopy -O binary led.elf led.bin 4         arm-linux-objdump -D led.elf > led_elf.dis 5 %.o
发表于 2020-01-09
ARM嵌入式学习--OK6410裸板程序--1.GPIO控制LED
移植mysql到arm平台
最近需要将一个程序移植到arm平台上,程序调用了MySQL数据库,所以就牵扯到将MySQL数据库移植到ARM平台上面,所以在网上大量查阅资料。在baidu文库发现了一篇文档,是wlzxlc上传的文档名称为:《编译ARM平台的QtEmbedded的MySQL插件和移植MySQL》。下面说明里面介绍到:ARM平台下的QtEmbedded所需mysql插件,及MySQL的移植。我的整个移植过程参考了这篇文档,但是遇到了很多的问题,所以又上网搜寻其它资料。现在我已经移植成功,回头看这篇文档,就发现里面有很多疏漏甚至是错误。这些东西如果不详细注明出来,更多的新手绝对还会重蹈我的覆辙,所以在这里将mysql 的arm+linux移植
发表于 2020-01-09
ARM Linux.2.6.34内核移植
ARM-LINUX-GCC版本4.3.2.以安装在/usr/local/arm/4.3.2下.第一步:修改linux-2.6.34/Makefile文件,在makefile中找到以下两条信息并做修改ARCH ? =arm  CROSS_COMPILE ?=/usr/local/arm/4.3.2/bin/arm-linux-第二步:修改平台输入时钟修改平台的时钟频率,修改内核源码“arch/arm/mach-s3c2440/mach-smdk2440.c”把时钟初始化中(S3C24XX_init_clocks(16934400 ))的16934400 改为12000000,因为我们的开发板上为12M第三步:修改
发表于 2020-01-09
编译基于ARM LINUX的驱动模块的Makefile
KERNELDIR = /home/wenhao/platform/linux-2.6.34PWD := $(shell pwd)CROSS_COMPILE = /usr/local/arm/4.3.2/bin/arm-linux-CC    = $(CROSS_COMPILE)gccobj-m := key.o modules:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean:    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c
发表于 2020-01-09
GNU ARM汇编快速入门
以前用ARM的IDE工具,使用的是ARM标准的汇编语言。现在要使用GNU的工具,当然要了解一点GNU ARM汇编的不同之处。其实非常的简单,浏览一下文档然后再看看程序就完全可以搞定了,或者你硬着头皮看GNU ARM的汇编程序,用不了多少时间你就就可以无师自通了。ARM汇编语言源程序语句 ,一般由指令、伪操作、宏指令和伪指令作成。ARM汇编语言的设计基础是汇编伪指令,汇编伪操作和宏指令。 目前常用的ARM编译环境有2种:ARMASM: ARM公司的IDE中使用了CodeWarrior的编译器,绝大多数windows下的开发者都在使用这一环境,完全按照ARM的规定;GNU ARM ASM:GNU工具的ARM版本
发表于 2020-01-09
交叉编译器 arm-linux-gnueabi 和 arm-linux-gnueabihf 的区别
自己之前一直没搞清楚这两个交叉编译器到底有什么问题,特意google一番,总结如下,希望能帮到道上和我有同样困惑的兄弟…..一. 什么是ABI和EABI1) ABI: 二进制应用程序接口(Application Binary Interface (ABI) for the ARM Architecture)在计算机中,应用二进制接口描述了应用程序(或者其他类型)和操作系统之间或其他应用程序的低级接口.ABI涵盖了各种细节,如:数据类型的大小、布局和对齐;调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最
发表于 2020-01-08
小广播
何立民专栏 单片机及嵌入式宝典

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

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