CM3-ARM-GCC编程

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权威指南》

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值