从头开始创建cortex-m7项目

时间:2018-07-17 21:19:31

标签: gcc arm cortex-m

我想创建自己的启动程序,链接程序脚本和init文件,配置makefile和gcc-toolchain。我在哪里可以找到有关它的资源,教程等?也许有一些最小的示例实现?

2 个答案:

答案 0 :(得分:3)

接近最小的引导程序,肯定会更小。

flash.s

.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hangout
.word hangout
.word hangout

.thumb_func
reset:
    bl notmain
    b hangout

.thumb_func
hangout:   b .

.align

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.thumb_func
.globl dummy
dummy:
    bx lr

阅读arm文档,异常和重置表虽然做得不好,但仍然显示堆栈指针初始化值是第一个,重置向量是第二个,对于内部核心异常,依此类推,然后继续到中断部分由核心定义和芯片供应商定义的数量,即有16个,32个,64个,128个,更少或更多...

示例程序演示C入口点并调用asm。

notmain.c

void PUT32 ( unsigned int, unsigned int );
void notmain ( void )
{
    unsigned int ra;
    for(ra=0;;ra++) PUT32(0x20000100,ra);
}

不是很简单的链接描述文件,而是关闭

flash.ld

MEMORY
{
    rom : ORIGIN = 0x00000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

从技术上讲,向量表重置为0x00000000(VTOR),但是一些芯片供应商在启动该闪存时将应用程序闪存映射到另一个地址以及零,因此STM32家族树通常为0x08000000,另一些为0x01000000我认为可能是0x10000000 ,无论如何,但是它们需要映射为零以进行重置(如果此代码在重置中确实被调用,并且没有引导加载程序伪造的重置)。因此您可以将0x00000000保留为rom或尝试对其进行更改。

最小示例,因此将堆栈指针和内存大小设置得较小。对于cortex-m7,这些数字应适用于cortex-m0,也许有些数字实际上可能太大而失败。

所有cortex-ms的所有内核(但不包括64位指令集)都支持armv4t的原始Thumb指令,并且您无需冒险就将其作为一个最小的起点,也可以在您的背上拥有骨架代码口袋里,然后选择核心。基本上不要从您的cortex-m7代码中借用并构建一个不支持相同的thumb2扩展集的cortex-m0,这可能无法正常工作。

构建(对于现在的armv6-m为cortex-m0,支持原始拇指加上几十个thumb2s)

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -o notmain.elf -T flash.ld flash.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary

不一定所有命令行选项都是必需的,取决于您的项目,gnu的版本等。编写此代码是为了使arm-whatever- work arm-linux-gnueabi等...

要使其正确启动,向量表必须在前面并且结构正确。 编程成新零件的闪存之前需要检查的好事,不要在得到它后立即砌成东西...

Disassembly of section .text:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   0000001b    andeq   r0, r0, r11, lsl r0
   c:   0000001b    andeq   r0, r0, r11, lsl r0
  10:   0000001b    andeq   r0, r0, r11, lsl r0

00000014 <reset>:
  14:   f000 f808   bl  28 <notmain>
  18:   e7ff        b.n 1a <hangout>

0000001a <hangout>:
  1a:   e7fe        b.n 1a <hangout>

桌上的反汇编当然是伪造的,我用反汇编器看到的不是其他一些转储工具。地址为零的第一个字是堆栈指针init值,某些引导程序/芯片要求它是合理的,有些不要,您不必将其用作堆栈指针init,则始终可以使用旧方法并在其中进行初始化。重置处理程序。只是阅读了我的新手(目前已经尝试了大多数供应商),他们确实说,启动前值必须在一定范围内。

其余的向量,reset和其他向量必须为ORRED,地址为1,所以reset为0x14、0x14 | 1 = 0x15,请检查...对我放入的其他几个向量也一样,您会通常至少要涵盖例外情况,然后,如果启用任何重置,则也要用这些填充表。关于此存储空间,它只是闪存,没有什么神奇的,如果您不使用向量表空间,则可以将向量表空间与代码或数据一起使用,但是如果您确实得到了该中断或异常并且没有健全的处理程序,则不会欢乐。

出于许多原因,我喜欢抽象我的访问权限,很多人却不这样做。您选择想要的方式...

如您所见,这将继续以0x20000100的速度写入sram(假设您的sram以0x20000000而不是0x40000000开始,0x20000000是使用cortex-m内核的供应商中非常受欢迎的选择)。

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m7 flash.s -o flash.o
arm-none-eabi-gcc -Wall -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m7 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -o notmain.elf -T flash.ld flash.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary

将其更改为cortex-m7。...在这个项目中,我没有任何东西可以用thumb2指令做得更好。

关于cortex-m架构设计的一件好事

flash.s只是桌子

.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word notmain
.word hangout
.word hangout
.word hangout

notmain.c

#define SOME_RAM (*((volatile unsigned int *)0x20000100))
void notmain ( void )
{
    unsigned int ra;
    for(ra=0;;ra++) SOME_RAM=ra;
}
void hangout ( void )
{
    while(1) continue;
}

构建

Disassembly of section .text:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   00000025    andeq   r0, r0, r5, lsr #32
   c:   00000025    andeq   r0, r0, r5, lsr #32
  10:   00000025    andeq   r0, r0, r5, lsr #32

00000014 <notmain>:
  14:   2300        movs    r3, #0
  16:   4a02        ldr r2, [pc, #8]    ; (20 <notmain+0xc>)
  18:   6013        str r3, [r2, #0]
  1a:   3301        adds    r3, #1
  1c:   e7fc        b.n 18 <notmain+0x4>
  1e:   bf00        nop
  20:   20000100    andcs   r0, r0, r0, lsl #2

00000024 <hangout>:
  24:   e7fe        b.n 24 <hangout>
  26:   bf00        nop

逻辑本身会确认ARM的调用约定,因此,如果编译器也执行了此操作,并且您不想包装不需要的重置处理程序,那么

在我的项目中,我不需要将.bss或init .data设置为零,但是很多人都这样做了,这使得链接描述文件更加复杂,不需要像大多数人那样疯狂。还有更多的汇编来执行.bss的零和.data的副本。

用于特定cortex-m7微控制器的LED指示灯工作正常。

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );

#define RCCBASE 0x40023800
#define RCC_AHB1ENR   (RCCBASE+0x30)
#define RCC_AHB1LPENR (RCCBASE+0x50)

#define GPIOABASE 0x40020000
#define GPIOA_MODER     (GPIOABASE+0x00)
#define GPIOA_OTYPER    (GPIOABASE+0x04)
#define GPIOA_BSRR      (GPIOABASE+0x18)

#define GPIOBBASE 0x40020400
#define GPIOB_MODER     (GPIOBBASE+0x00)
#define GPIOB_OTYPER    (GPIOBBASE+0x04)
#define GPIOB_BSRR      (GPIOBBASE+0x18)

//PA5 or PB0 defaults to PB0
//PB7
//PB14

int notmain ( void )
{
    unsigned int ra;
    unsigned int rx;

    ra=GET32(RCC_AHB1ENR);
    ra|=1<<1; //enable GPIOB
    PUT32(RCC_AHB1ENR,ra);

    ra=GET32(GPIOB_MODER);
    ra&=~(3<<(0<<1)); //PB0
    ra|= (1<<(0<<1)); //PB0
    ra&=~(3<<(7<<1)); //PB7
    ra|= (1<<(7<<1)); //PB7
    ra&=~(3<<(14<<1)); //PB14
    ra|= (1<<(14<<1)); //PB14
    PUT32(GPIOB_MODER,ra);
    //OTYPER
    ra=GET32(GPIOB_OTYPER);
    ra&=~(1<<0); //PB0
    ra&=~(1<<7); //PB7
    ra&=~(1<<14); //PB14
    PUT32(GPIOB_OTYPER,ra);

    for(rx=0;;rx++)
    {
        PUT32(GPIOB_BSRR,((1<<0)<<0)|((1<<7)<<16)|((1<<14)<<0));
        for(ra=0;ra<200000;ra++) dummy(ra);
        PUT32(GPIOB_BSRR,((1<<0)<<16)|((1<<7)<<0)|((1<<14)<<16));
        for(ra=0;ra<200000;ra++) dummy(ra);
    }
    return(0);
}

您不必使用HAL或CMSIS或其他第三方资源。从专业上讲,您应该知道如何尝试或定期尝试,但是有关裸机编程的最好的事情之一是,您仅受硬件及其规则的真正限制,您可以做任何想要生成的函数,只要它符合芯片和电路板逻辑规则。

值得庆幸的是,

gcc只是编译器将C转换为汇编,而将汇编转换为对象并ld根据命令行或链接描述文件的方向链接了这些东西。当您开始执行需要gcclib(除法,乘法,浮点数)的事情或开始使用C库时,现在编译器很重要(arm-none-eabi与arm-whatever-linux-whatever)以及库C或其他事项。出于某种原因,gcc被编译为能够基于gcc的路径来找到gcclib,但是ld却是如​​此,尽管如此,如果您发现自己处于该位置,则可以选择使用gcc来调用链接程序。我让gcc调用汇编程序,因为我没有真正的理由不这样做。但是,调用链接程序时,如果存在默认引导程序和链接程序脚本,则必须取消它。直接调用链接器,您可以控制所有链接,而无需所有gcc命令行选项。

在我的职业生涯中,至少有一次使用过工具,如果他们看到main()会添加更多的垃圾,因此请使用不称为main()的入口点。您可以随意命名C入口点。或者,如果您希望引导程序调用多个函数,则有多个...

因此,简而言之,此核心/系列使用向量表,而其他处理器则不使用。您必须掌握足够的工具才能启动处理器,这意味着在正确的位置放置了正确的向量表。需要知道您的编译器的最低要求,通常设置堆栈指针,并在永不返回时调用入口点或分支。链接程序通常需要从-Ttext = 0x0 -Tdata = 0x20000000到ld的链接程序脚本进行一些处理。不要期望链接程序脚本语言从一个工具链到另一种工具链(gnu,kiel,arm等)在远程上是相同的,我的建议是,如果您打算移植,请尽可能少使用特定于工具链的东西。然后,在尝试使用二进制文件之前,请检查二进制文件。一旦从基于闪存的启动中挂起了内核,您将无法使用某些芯片;对于stm32,则可以使用一些其他的芯片。

以任何形式将二进制文件下载到部件的Flash中,这是另一个讨论。从芯片供应商的文档开始。

答案 1 :(得分:1)

如果您通常正在寻找一种解决方法,那就是我要做的:您的MCU通常会具有一个devkit,其中包含示例,因此是一个完整的构建环境,希望有MAKE / GCC支持。 / p>

然后,我尝试为一个简单的示例(您选择的Blinky项目)隔离所有内容。这使您可以找到链接程序脚本和启动代码。这几乎就是您所需要的,因为main.c通常不是特定于MCU的。您可以使用演示中的makefile并将其剥离为所需的内容,也可以从头开始,只需交叉检查它们用于所需内容的所有编译器/链接器选项即可。

启动代码通常不需要修改,但由于链接器脚本与代码之间存在“ 1:1”的关系,因此非常有趣且易于理解。此外,您的编译器或来自您可以修改的制造商/示例都应该附带一个链接描述文件。

然后,您就可以开始探索了:将第一个原始make环境与修改后的链接程序脚本和启动代码放在一起,然后尝试访问main()