接近最小的引导程序,当然可以更小。
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 你可以,有些你可以,有些你不能。
将二进制文件以任何形式放到部件的闪存上,这是另一个讨论。从芯片供应商文档开始。