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

str*_*sik 1 gcc arm cortex-m

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

old*_*mer 5

接近最小引导,当然可以更小。

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
Run Code Online (Sandbox Code Playgroud)

阅读 arm 文档,异常和重置表并没有做得那么好,但仍然显示堆栈指针 init 值是第一个,重置向量第二个等等对于内部核心异常然后进入中断部分核心定义和芯片供应商定义有多少,16、32、64、128、更少或更多......

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

不是main.c

void PUT32 ( unsigned int, unsigned int );
void notmain ( void )
{
    unsigned int ra;
    for(ra=0;;ra++) PUT32(0x20000100,ra);
}
Run Code Online (Sandbox Code Playgroud)

不是最小的链接器脚本,但关闭

闪存文件

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

SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}
Run Code Online (Sandbox Code Playgroud)

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

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

所有 cortex-ms 的所有内核(但不支持 64 位指令集)都支持来自 armv4t 的原始拇指指令,您不需要冒险越过它以获得最小的起点,在您的后兜里放一些骨架代码并选择核心稍后。基本上不要借用您的 cortex-m7 代码并为不支持相同的拇指 2 扩展集的 cortex-m0 构建,它可能不起作用。

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

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
Run Code Online (Sandbox Code Playgroud)

不一定需要所有命令行选项,这取决于您的项目、gnu 版本等。编写此代码以便 arm-whatever-works 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>
Run Code Online (Sandbox Code Playgroud)

桌子上的反汇编当然是假的,我是用反汇编器看到这些项目的,而不是其他一些转储工具。地址零的第一个字是堆栈指针 init 值,一些引导加载程序/芯片要求它是理智的,有些则不需要,你不必使用它作为你的堆栈指针 init,你总是可以用老式的方式来做,并在重置处理程序。只是在阅读对我来说是新的部分(此时已经尝试了大多数供应商)并且他们确实说值必须在某个范围内才能启动。

其余的向量,reset 和 other 需要是地址 ORRED 与 1,所以 reset 是 0x14, 0x14|1 = 0x15,检查......我放在那里的其他几个向量也是如此,你通常想要至少涵盖例外情况,然后如果您启用任何重置,那么也将表格填满。这个内存空间没有什么神奇之处,它只是闪存,如果你不使用向量表空间,你可以使用带有代码或数据的向量表空间,但是如果你确实得到了那个中断或异常并且你没有一个健全的处理程序,没有喜悦。

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

如您所见,这将继续写入 sram 的 0x20000100(假设您的 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
Run Code Online (Sandbox Code Playgroud)

将其更改为 cortex-m7 ......而且我在这个项目中没有任何东西可以让拇指 2 指令做得更好。

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

flash.s 只是桌子

.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word notmain
.word hangout
.word hangout
.word hangout
Run Code Online (Sandbox Code Playgroud)

不是main.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;
}
Run Code Online (Sandbox Code Playgroud)

建造

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
Run Code Online (Sandbox Code Playgroud)

逻辑本身确认 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);
}
Run Code Online (Sandbox Code Playgroud)

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

幸运的是,gcc 只是一个编译器将 C 转换为汇编,将汇编转换为对象并 ld 根据命令行或链接器脚本方向链接这些东西。当你开始做需要 gcclib(除法、乘法、浮点)的事情或者你开始使用 C 库时,现在编译器很重要(arm-none-eabi vs arm-whatever-linux-whatever)和库 C 或其他问题. 由于某种原因,gcc 被编译为能够根据 gcc 的路径找到 gcclib,但 ld 不能,尽管它很丑陋,如果你发现自己处于那个位置,你可以选择使用 gcc 来调用链接器。我让 gcc 调用汇编程序,因为我没有真正的理由不这样做。但是调用链接器,如果有的话,你必须打败默认的引导程序和链接器脚本。

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

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

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