【问题标题】:Creating cortex-m7 project from scratch where to start从头开始创建 cortex-m7 项目从哪里开始
【发布时间】:2018-07-17 21:19:30
【问题描述】:

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

【问题讨论】:

  • 虽然我很想知道答案,但对于堆栈溢出问题可能有点宽泛
  • @hetepeperfan,有什么建议更适合这类问题吗?我在想arm-community论坛,但是板子明显死了,大部分问题不仅没有答案,而且几乎没有意见。
  • @old_timer,您能否提供一些指向您以前帖子的链接?我浏览了 github 项目(确实,我发现了一些例子,但对我来说仍然有太多的“魔法”),主要是查看供应商提供的启动、链接器文件和初始化的 HAL 实现,并试图理解自动生成的 makefile stm32Qube。实际上我有某种不闪烁 LED 的科学怪人项目(我假设是因为编译器标志不正确)。
  • 那你没找到我的……咧嘴笑……等等……
  • @hetepeperfan 见下文,至少在这个问题被关闭并删除之前,因为它真的不是一个 stackoverflow 问题......

标签: gcc arm cortex-m


【解决方案1】:

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

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 的原始拇指指令,并且您不需要冒险超越它以获得最小的起点,在您的背后有骨架代码也不错口袋并稍后选择核心。基本上不要从你的 cortex-m7 代码中借用并为不支持同一组 thumb2 扩展的 cortex-m0 构建,它可能不起作用。

构建(用于 cortex-m0,现在为 armv6-m,原始拇指加上几十个 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-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>

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

其余的向量,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 或初始化 .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 调用汇编器,因为我没有真正的理由不这样做。但是调用链接器时,如果有默认引导程序和链接器脚本,则必须取消它。直接调用链接器,您可以控制所有这些,而不需要所有这些 gcc 命令行选项。

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

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

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

【讨论】:

  • 天哪!非常感谢!
  • 没问题...先付款。
【解决方案2】:

如果您更愿意寻找解决此问题的方法,我通常会这样做:您的 MCU 通常会有一个 devkit,它带有示例,因此是一个完整的构建环境,希望支持 MAKE/GCC。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-21
    相关资源
    最近更新 更多