gnu arm工具下载
https://launchpad.net/gcc-arm-embedded/+download
https://developer.arm.com/downloads/-/gnu-rm
本文案使用的工具链:gcc-arm-none-eabi-10-2020-q4-major.zip
链接:https://pan.baidu.com/s/1mcJejVnhGx3rr47UE3WeuQ?pwd=d5kb
提取码:d5kb
使用ARM GCC开发
基于GNU工具链的开发流程
GNU工具链:
汇编器 arm-none-eabi-as
编译器 arm-none-eabi-gcc
链接器 arm-none-eabi-ld
二进制映像产生器 arm-none-eabi-objcopy
反汇编器 arm-none-eabi-objdump
第一个程序
计算10+9+8+…+1
;//example1.s
.equ STACK_TOP, 0x20000800
.text
.global _start
.code 16
.syntax unified
_start:
.word STACK_TOP, start
.type start, function
start:
movs r0, #10
movs r1, #0
loop:
adds r1, r0
subs r0, #1
bne loop
deadloop:
b deadloop
.end
与ARM汇编器不同的是,GNU汇编器中的标号要以":“结尾,注释可以使用/**/,指示字要以”."作为前缀。
要注意: 在thumb代码(.code16)里面,复位向量(start)被定义成了一个函数
(.type start, function)。这是为了使复位向量的LSB被强制为1,从而表示这是以Thumb状态执行。否则,处理器就会尝试以ARM态开始,从而引起一个硬fault.
注:在CM3中,程序计数器PC/R15的LSB位总是读回0,但是,如果要在Thumb状态下执行代码,无论直接还是间接给PC赋值时都必须保证加载到PC的数值是奇数(即LSB=1)。若LSB=0,则视为企图转入ARM模式, CM#将产生一个fault异常。
汇编,链接,生成二进制文件,反汇编 使用下面命令之前,需要把gcc-arm-none-eabi工具链的bin目录加入PATH环境变量。
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example1.s -o example1.o
arm-none-eabi-ld -Ttext 0x0 -o example1.out example1.o
arm-none-eabi-objdump -x -d -S example1.out > example1.out.txt
arm-none-eabi-objcopy -Obinary example1.out example1.bin
连接多个文件
example2a.s只包含向量表,example2b.s包含正常的程序代码。.global指事字在文件之前传递全局符号。
;//example2a.s
.equ STACK_TOP, 0x20000800
.global vectors_table
.global start
.global nmi_handler
.code 16
.syntax unified
vectors_table:
.word STACK_TOP, start, nmi_handler, 0x00000000
.end
;//末尾有一行空行
;//example2b.s
.text
.global _start
.global start
.global nmi_handler
.code 16
.syntax unified
.type start, function
.type nmi_handler, function
_start:
/*程序入口*/
start:
movs r0, #10
movs r1, #0
/*计算10+9+8+...+1*/
loop:
adds r1, r0
subs r0, #1
bne loop
/*结果存储在R1中*/
deadloop:
b deadloop
/*为演示而设置的空NMI服务例程*/
nmi_handler:
bx lr
.end
;//末尾有一行空行
编译
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example2a.s -o example2a.o
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example2b.s -o example2b.o
arm-none-eabi-ld -Ttext 0x0 -o example2.out example2a.o example2b.o
arm-none-eabi-objdump -x -d -S example2.out > example2.out.txt
arm-none-eabi-objcopy -Obinary example2.out example2.bin
一个简单的"Hello World"程序
在这里为了突出主题,省去了UART初始化代码。
;//example3a.s
.equ STACK_TOP, 0x20000800
.global vectors_table
.global _start
.code 16
.syntax unified
vectors_table:
.word STACK_TOP, _start
.end
;//example3b.s
.text
.global _start
.code 16
.syntax unified
.type _start, function
_start:
/*程序入口*/
movs r0, #0
movs r1, #0
movs r2, #0
movs r3, #0
movs r4, #0
movs r5, #0
ldr r0, =hello
bl puts
movs r0, #0x4
bl putc
deadloop:
b deadloop
hello:
.ascii "Hello, World!\n"
.byte 0
.align
puts:
/*该子程序向UART发送字符串
入口条件: r0=字符串的起始地址
字符串要以零结尾*/
push {r0, r1, lr} /*保存寄存器*/
mov r1, r0 /*把地址拷贝到R1, 因为R0还要用作putc的参数*/
putsloop:
ldrb.w r0, [r1], #1 /*取出一个字符并且自增地址*/
cbz r0, putsloopexit /*如果字符串为NULL, 则跳转到结束*/
bl putc
b putsloop
putsloopexit:
pop {r0, r1, pc} /*返回*/
.equ UART0_DATA, 0x4000C000
.equ UART0_FLAG, 0x4000C018
putc:
/*该子程序向UART发送一个字符*/
/*入口条件: r0=要发送的字符*/
push {r1, r2, r3, lr} /*保存寄存器*/
ldr r1, =UART0_FLAG
putcwaitloop:
ldr r2, [r1] /*读UART状态寄存器*/
tst.w r2, #0x20 /*判断是否发送完毕 检查发送缓冲区满标志*/
bne putcwaitloop /*如果已满则循环等待*/
ldr r1, =UART0_DATA /*否则继续往发送缓冲区里送数据*/
str r0, [r1]
pop {r1, r2, r3, pc} /*返回*/
.end
编译
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example3a.s -o example3a.o
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example3b.s -o example3b.o
arm-none-eabi-ld -Ttext 0x0 -o example3.out example3a.o example3b.o
arm-none-eabi-objdump -x -d -S example3.out > example3.out.txt
arm-none-eabi-objcopy -Obinary example3.out example3.bin
把数据放到RAM中
.equ STACK_TOP, 0x20000800
.text
.global _start
.code 16
.syntax unified
_start:
.word STACK_TOP, start
.type start, function
start:
movs r0, #10
movs r1, #0
/*计算10+9+...+1*/
loop:
adds r1, r0
subs r0, #1
bne loop
/*结果存储在R1中*/
ldr r0, =result
str r1, [r0]
deadloop:
b deadloop
/*数据区*/
.data
result:
.word 0
.end
;//末尾需要留空一行
编译
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example4.s -o example4.o
arm-none-eabi-ld -Ttext 0x0 -Tdata 0x20000000 -o example4.out example4.o
arm-none-eabi-objdump -x -d -S example4.out > example4.out.txt
arm-none-eabi-objcopy -Obinary -R .data example4.out example4.bin
纯C程序
example5.c
#define STACK_TOP 0x20000800
#define NVIC_CCR ((volatile unsigned long *)(0xe000ed14))
void myputs(char *string1);
void myputc(char c);
int main(void);
void nmi_handler(void);
void hard_fault_handler(void);
//定义向量表
__attribute__ ((section("vectors"))) void (* const VectorArray[])(void) =
{
STACK_TOP,
main,
nmi_handler,
hard_fault_handler
};
//主程序入口点
int main(void)
{
char *helloworld="Hello World\n";
*NVIC_CCR = *NVIC_CCR | 0x200; /*设置NVIC的STKALIGN*/
myputs(helloworld);
while(1);
return 0;
}
void myputs(char *string1)
{
char mychar;
int j;
j=0;
do{
mychar=string1[j];
if(mychar!=0)
{
myputc(mychar);
j++;
}
}while(mychar!=0);
return;
}
void myputc(char c)
{
#define USART0_DATA ((volatile unsigned long *)(0x4000c000))
#define USART0_FLAG ((volatile unsigned long *)(0x4000c018))
//Wait until busy flag is clear
while(((*USART0_FLAG)&0x20)!=0);
// Output character to UART
*USART0_DATA=c;
return;
}
//空的服务例程
void nmi_handler(void)
{
return;
}
void hard_fault_handler(void)
{
return;
}
simple.ld
/*MEMORY:
它是用来补充SECTIONS命令的,用来描述目标CPU中可用的内存区域。
它是可选的,如果没有这个命令,LD会认为SECTIONS描述的相邻的内存块之间有足够可用的内存。
在SECTIONS中每个段的分布都没有考虑ARM能够寻址的地址中,ROM,RAM,FLASH是不是连续的。 */
/*`R' Read-only sections.
`W' Read/write sections.
`X' Sections containing executable code.
`A' Allocated sections.
`I' Initialized sections.
`L' Same as I.
`!' Invert the sense of any of the following attributes. */
MEMORY
{
/*ROM是可读的(r)和可执行的(x)*/
rom (rx) : ORIGIN = 0x0, LENGTH = 2M
/*RAM是可读的(r), 可写的(w),可执行(x)*/
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4M
}
/*定义各输入段到输出段的映射*/
SECTIONS
{
. = 0x0; /*从0x00000000开始*/
.text : {
*(vectors) /*向量表*/
*(.text) /*程序代码*/
*(.rodata) /*只读数据*/
}
. = 0x20000000; /*从0x20000000开始*/
.data : {
*(.data) /*数据存储器*/
}
.bss : {
*(.bss) /*预留的数据存储器,必须初始化为零*/
}
}
编译
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb example5.c -nostartfiles -T simple.ld -o example5.o
arm-none-eabi-ld -T simple.ld -o example5.out example5.o
arm-none-eabi-objdump -S example5.out > example5.txt
arm-none-eabi-objcopy -Obinary example5.out example5.bin
使用STM32CubeMX生成启动代码和链接脚本
上图就是生成的启动代码和链接脚本。修改Core中的代码和Makefile即可。
使用make工具,可以直接编译,也可以用VScode打开项目进行编程。
编译后在build目录下生成bin文件,可以通过烧录软件烧录。
修改链接脚本和启动代码,可以实现《Cortex-M3权威指南》中19.3.6 示例6的实验。
Makefile注意事项
VScode输入tab键时是4个空格而非制表符,所以在编写Makefile文件时,需要使用制表符的地方可能报错。
参考文献
ywMaqd5-1709893418797)]
[外链图片转存中…(img-F57LCW6M-1709893418798)]
[外链图片转存中…(img-K1ealFTG-1709893418798)]
[外链图片转存中…(img-GQSmxzvZ-1709893418799)]
上图就是生成的启动代码和链接脚本。修改Core中的代码和Makefile即可。
使用make工具,可以直接编译,也可以用VScode打开项目进行编程。
编译后在build目录下生成bin文件,可以通过烧录软件烧录。
修改链接脚本和启动代码,可以实现《Cortex-M3权威指南》中19.3.6 示例6的实验。
Makefile注意事项
VScode输入tab键时是4个空格而非制表符,所以在编写Makefile文件时,需要使用制表符的地方可能报错。
参考文献
《Cortex-M3权威指南》