从头开始创建 cortex-m7 项目从哪里开始

Creating cortex-m7 project from scratch where to start

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

接近最小 bootstrap,当然可以更小。

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 文档,异常和重置 table 没有做得很好,但仍然显示堆栈指针初始值是第一个,重置向量第二个等等内部核心然后异常继续进入中断,其中部分是核心定义的,芯片供应商定义了有多少,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
}

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

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

所有 cortex-ms 所有核心,但不包括 64 位指令集,支持 armv4t 的原始拇指指令,您不需要为了最小的起点而冒险越过它,拥有框架代码也不错你的后袋,稍后选择核心。基本上不要借用您的 cortex-m7 代码并构建不支持同一组 thumb2 扩展的 cortex-m0,它可能无法工作。

build(对于 cortex-m0,现在是 armv6-m,支持原始 thumb 和几十个 thumb2)

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-works arm-linux-gnueabi 等。 ..

为了使其正常启动,矢量 table 需要预先设置并正确形成。 在编程到新部件的闪存之前检查一下是件好事,不想在你拿到它之后就把它变成砖块......

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>

table 上的反汇编当然是伪造的,我使用反汇编程序看到这些项目而不是其他一些转储工具。地址零第一个字是堆栈指针初始化值,有些 bootloaders/chips 要求它是理智的,有些则不需要,你不必使用它作为你的堆栈指针初始化,你总是可以用老式的方式和初始化在重置处理程序中。刚刚阅读了一个对我来说很新的部分(此时已经尝试过大多数供应商),他们确实说在启动之前该值必须在某个范围内。

其余的向量,reset 和其他需要是与 1 的地址 ORRED,所以 reset 是 0x14,0x14|1 = 0x15,检查...我放在那里的其他几个向量也是如此,您通常希望至少涵盖例外情况,然后如果您启用任何重置,则也用这些内容填充 table。这个内存space没什么神奇的,它只是闪存,如果你不使用向量table space,你可以用代码或数据使用向量table space ,但是如果你确实得到了那个中断或异常并且你没有一个理智的处理程序,那就不高兴了。

我喜欢抽象我的访问有很多原因,很多人不这样做。你选择你想要的方式...

如您所见,这将继续在 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 只是 table

.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 vs arm-whatever-linux-whatever ) 和图书馆 C 或其他事项。由于某种原因,gcc 被编译为能够根据 gcc 的路径找到 gcclib,但 ld 不能,尽管它很丑陋,如果你发现自己处于那个位置,你可能会选择使用 gcc 来调用链接器。我让 gcc 调用汇编程序,因为我没有真正的理由不这样做。但是调用链接器,你必须击败默认的 bootstrap 和链接器脚本(如果有的话)。直接调用链接器,您可以控制所有这些,而不需要所有这些 gcc 命令行选项。

在我的职业生涯中,至少有一次处理过这样的工具,如果他们看到 main(),就会添加更多垃圾,因此请使用不称为 main() 的入口点。您可以随意命名您的 C 入口点。或者有多个,如果你想让 bootstrap 调用多个函数...

所以简而言之,这个 core/family 使用向量 table,其他处理器不使用。您必须充分掌握这些工具才能让处理器启动,这意味着正确的矢量 table 在正确的位置。需要知道您的编译器的最低要求,如果您从不 return,通常设置堆栈指针并调用入口点或分支。链接器通常需要一些从 -Ttext=0x0 -Tdata=0x20000000 到 ld 的链接描述文件的 handholding。不要指望从一个工具链到另一个工具链(gnu、kiel、arm 等)的链接器脚本语言是完全相同的,我的建议是,如果您打算移植,请尽可能少地使用特定于工具链的东西。然后在你尝试使用它之前检查二进制文件,有些芯片一旦你从基于闪存的引导中挂在核心上你就无法摆脱它,stm32s 你可以一些其他你可以一些你不能。

将任何形式的二进制文件放到闪存中,这是另一个讨论。从芯片供应商文档开始。

如果你更愿意寻找一种方法来解决这个问题,这就是我所做的:你的 MCU 通常会有一个开发工具包,它带有示例,因此是一个完整的构建环境,希望 MAKE/GCC 支持

然后我会尝试将所有内容隔离开来作为一个简单的例子(您选择的 Blinky 项目)。这使您可以找到链接描述文件和启动代码。这几乎就是您所需要的,因为 main.c 通常不是特定于 MCU 的东西。您可以使用演示中的 makefile 并将其剥离到您需要的内容,或者从头开始,然后简单地交叉检查它们用于您需要的所有 compiler/linker 选项。

启动代码通常是您不需要修改的东西,但它看起来非常有趣并且理解起来很重要,因为它与链接描述文件存在“1:1”关系。此外,您的编译器或您可以修改的 manufacturer/examples 应该有一个链接器脚本。

然后您就可以开始探索了:将第一个原始 make 环境与修改后的链接描述文件和启动代码放在一起,然后尝试到达 main()