- ARM 交叉工具链各工具作用
基于你使用的 arm-linux-gnueabihf-xxx 工具链,各工具核心功能如下,均运行在 x86 主机上,生成 ARM 架构可执行程序:
(1)arm-linux-gnueabihf-gcc ------ 交叉 C 编译器
-
核心作用:将 C 语言源文件(.c)、汇编源文件(.S)编译、汇编生成目标文件(.o),也可直接完成编译 + 链接。
-
常用参数:
-
-c:只编译不链接,生成单个.o 目标文件 -
-I:指定头文件搜索路径 -
-g:加入调试信息,配合 JTAG 调试 -
-Wall:开启编译警告 -
-nostdlib:不链接系统标准 C 库,裸机开发必用
-
-
裸机场景:把每个驱动、主函数源文件编译成独立的目标文件,后续交给链接器合并。
(2)arm-linux-gnueabihf-ld ------ 链接器
-
核心作用:将多个.o 目标文件、库文件按照 \\ 链接脚本 (.lds)\\ 的规则,合并成一个完整的 ELF 格式可执行文件,完成地址重定位、符号解析、段排布。
-
常用参数:
-
-T xxx.lds:指定自定义链接脚本,安排代码 / 数据在内存中的起始地址 -
-Ttext 0x87800000:直接指定代码段运行地址(简化写法)
-
-
裸机场景:决定程序在 DDR / 内部 RAM 中的运行地址,指定程序入口点,是裸机程序地址分配的核心工具。
(3)arm-linux-gnueabihf-objcopy ------ 二进制格式转换工具
-
核心作用:在 ELF、纯二进制 (.bin)、Intel HEX 等多种文件格式之间转换,同时可裁剪符号、调试信息。
-
常用参数:
-
-O binary:输出纯二进制文件(去掉 ELF 的文件头、符号表等冗余信息) -
-S:移除所有符号和调试信息,减小文件体积
-
-
裸机场景:LD 生成的 ELF 文件不能直接烧录到 SD 卡 / NAND Flash 运行,必须用 objcopy 转换成.bin 纯二进制文件,才是芯片能直接加载执行的程序。
(4)arm-linux-gnueabihf-objdump ------ 反汇编与段分析工具
-
核心作用:对 ELF 文件进行反汇编、查看段信息、符号表、地址分布,是调试裸机程序的核心工具。
-
常用参数:
-
-D:反汇编所有段(代码 + 数据段) -
-h:查看所有段的大小、地址、属性 -
-t:查看所有符号的地址和类型
-
-
裸机场景:排查地址错误、段排布异常、程序跑飞、变量地址不对等问题,也可分析编译器生成的汇编指令优化效果。
2. 各内存段的区别
这些段是 ELF 文件的标准分段,对应程序运行时在 Flash 和 RAM 中的不同存储区域,核心区别如下表:
|----------|--------------------------|------|------------|--------------------------------|
| 段名 | 存储内容 | 读写属性 | 初始化状态 | 存储介质(裸机典型场景) |
| .text | 程序执行指令、中断向量表 | 只读 | 编译时确定,固定 | 存放于 Flash(ROM),运行时可在 Flash/RAM |
| .rodata | 只读常量:const 全局变量、字符串常量 | 只读 | 编译时确定,固定 | 存放于 Flash,与.text 段相邻 |
| .data | 已初始化的全局变量、已初始化静态局部变量 | 可读写 | 编译时给定初始值 | 初始值存在 Flash;运行时拷贝到 RAM |
| .bss | 未初始化的全局变量、未初始化静态局部变量 | 可读写 | 运行时自动清 0 | 不占 Flash 空间,运行时占用 RAM |
| common 段 | 多文件重复声明的未初始化全局变量(编译期临时段) | 可读写 | 链接后合并到.bss | 编译期临时标记,最终不存在于可执行文件 |
补充说明:
-
common 段本质 :是编译阶段的临时标记。单个.c 文件中未初始化的全局变量,编译器不会直接分配地址,而是标记为 common 符号;链接阶段会把所有编译单元的同名 common 符号合并,最终统一分配到.bss 段。如果编译时加
-fno-common参数,未初始化全局变量会直接放入.bss,不生成 common 段。 -
体积差异:.bss 段不占用 Flash 存储空间,只记录大小,程序启动时由启动代码把对应 RAM 区域清零,以此节省 Flash 空间。
3. LED + 蜂鸣器裸机程序实现(IMX6ULL 平台)
基于当前的工程结构(bsp/ 存放驱动文件、project/ 存放主程序和启动文件、imx6ull/ 存放芯片头文件),以下提供一套完整可运行的寄存器版代码。该代码适配主流 IMX6ULL 开发板(LED 连接 GPIO1_IO03,蜂鸣器连接 GPIO1_IO19,若使用其他开发板,请根据实际引脚定义进行相应修改)。
工程目录结构
├── bsp/
│ ├── led.h
│ ├── led.c
│ ├── beep.h
│ └── beep.c
├── project/
│ ├── start.S
│ └── main.c
├── imx6ull/
│ └── MCIMX6Y2.h
├── imx6ull.lds
└── Makefile
(1)芯片寄存器头文件 imx6ull/MCIMX6Y2.h
c
#ifndef __MCIMX6Y2_H
#define __MCIMX6Y2_H
/* 外设基地址 */
#define CCM_BASE 0x020C4000
#define IOMUXC_BASE 0x020E0000
#define GPIO1_BASE 0x0209C000
/* CCM时钟使能寄存器 /
#define CCM_CCGR1 ((volatile unsigned int *)(CCM_BASE + 0x6C))
/* IOMUXC引脚复用与电气属性寄存器 /
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 ((volatile unsigned int )(IOMUXC_BASE + 0x068))
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 ((volatile unsigned int )(IOMUXC_BASE + 0x2F4))
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO19 ((volatile unsigned int )(IOMUXC_BASE + 0x0AC))
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO19 ((volatile unsigned int *)(IOMUXC_BASE + 0x338))
/* GPIO寄存器 /
#define GPIO1_DR ((volatile unsigned int )(GPIO1_BASE + 0x00))
#define GPIO1_GDIR ((volatile unsigned int *)(GPIO1_BASE + 0x04))
#endif
(2)启动文件 project/start.S
assembly
.global _start
_start:
ldr pc, =_reset_handler
ldr pc, =_undefine_handler
ldr pc, =_svc_handler
ldr pc, =_prefetch_aboart_handler
ldr pc, =_data_abort_handler
ldr pc, =_reserved_handler
ldr pc, =_irq_handler
ldr pc, =_fiq_handler
_fiq_handler:
ldr pc, =_fiq_handler
_irq_handler:
ldr pc, =_irq_handler
_reserved_handler:
ldr pc, =_reserved_handler
_data_abort_handler:
ldr pc, =_data_abort_handler
_prefetch_aboart_handler:
ldr pc, =_prefetch_aboart_handler
_svc_handler:
ldr pc, =_svc_handler
_undefine_handler:
ldr pc, =_undefine_handler
_reset_handler:
// 切换工作模式:IRQ
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x12
msr cpsr, r0
// 设置IRQ模式下的栈
ldr sp, =0x86000000
// 切换工作模式:System
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x1F
msr cpsr, r0
// 设置System模式下的栈
ldr sp, =0x84000000
// 清空bss段和common段
bl _bss_clear
// 跳转到main函数执行
b main
b finished
_bss_clear:
ldr r0, =__bss_start
ldr r1, =__bss_end
loop:
mov r2, #0
str r2, [r0]
add r0, r0, #4
cmp r0, r1
blt loop
bx lr
finished:
b finished
(3)LED 驱动 bsp/led.h + bsp/led.c
led.h
c
#ifndef _LED_H_
#define _LED_H_
extern void led_init(void);
extern void led_on(void);
extern void led_off(void);
extern void led_nor(void);
#endif
led.c
c
#include "led.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
void led_init(void)
{
// 1. 引脚复用
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
// 2. 电气属性
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);
// 3. GPIO方向:输出模式
GPIO1->GDIR |= (1 << 3);
}
void led_on(void)
{
GPIO1->DR &= ~(1 << 3);
}
void led_off(void)
{
GPIO1->DR |= (1 << 3);
}
void led_nor(void)
{
GPIO1->DR ^= (1 << 3);
}
(4)蜂鸣器驱动 bsp/beep.h + bsp/beep.c
beep.h
c
#ifndef _BEEP_H_
#define _BEEP_H_
extern void beep_init(void);
extern void beep_on(void);
extern void beep_off(void);
extern void beep_nor(void);
#endif
beep.c
c
#include "beep.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
/**
* @brief 蜂鸣器初始化函数
* @note 该函数用于初始化蜂鸣器对应的GPIO引脚,配置其复用功能、电气属性和方向
*/
void beep_init(void)
{
// 1. 复用功能配置
// 将SNVS_TAMPER1引脚复用为GPIO5_IO01功能
IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0);
// 2. 电气属性配置
// 设置GPIO5_IO01引脚的电气属性,0x10B0表示适当的驱动能力和速度
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0);
// 3. GPIO方向:输出模式
GPIO5->GDIR |= (1 << 1);
}
void beep_on(void)
{
GPIO5->DR &= ~(1 << 1);
}
void beep_off(void)
{
GPIO5->DR |= (1 << 1);
}
void beep_nor(void)
{
GPIO5->DR ^= (1 << 1);
}
(5)主函数 project/main.c
c
include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "led.h"
#include "beep.h"
void enable_clocks(void)
{
CCM->CCGR0 = 0xFFFFFFFF;
CCM->CCGR1 = 0xFFFFFFFF;
CCM->CCGR2 = 0xFFFFFFFF;
CCM->CCGR3 = 0xFFFFFFFF;
CCM->CCGR4 = 0xFFFFFFFF;
CCM->CCGR5 = 0xFFFFFFFF;
CCM->CCGR6 = 0xFFFFFFFF;
}
void delay(unsigned int n)
{
while (n--);
}
int main(void)
{
// 使能外设时钟
enable_clocks();
// LED初始化
led_init();
// 蜂鸣器初始化
beep_init();
while (1)
{
led_nor();
beep_nor();
delay(0x7FFFF);
}
return 0;
}
(6)链接脚本 imx6ull.lds
ld
SECTIONS
{
. = 0x87800000;
.text :
{
obj/start.o
obj/led.o
obj/main.o
*(.text)
}
.rodata ALIGN(4) : { *(.rodata*)}
.data ALIGN(4) : { *(.data)}
. = ALIGN(4);
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON)}
__bss_end = .;
}
(7)Makefile
makefile
target = beep
cross_compiler = arm-linux-gnueabihf-
cc = $(cross_compiler)gcc
ld = $(cross_compiler)ld
objcopy = $(cross_compiler)objcopy
objdump = $(cross_compiler)objdump
incdirs = bsp imx6ull
srcdirs = bsp project
include = $(patsubst %, -I%, $(incdirs))
cfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.c))
sfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.S))
cfilenodir = $(notdir $(cfiles))
sfilenodir = $(notdir $(sfiles))
cobjs = $(patsubst %, obj/%, $(cfilenodir:.c=.o))
sobjs = $(patsubst %, obj/%, $(sfilenodir:.S=.o))
objs = $(cobjs) $(sobjs)
VPATH = $(srcdirs)
$(target).bin : $(objs)
$(ld) -Timx6ull.lds -o$(target).elf $^
$(objcopy) -O binary -S -g $(target).elf $@
$(objdump) -D $(target).elf > $(target).dis
$(sobjs) : obj/%.o : %.S
@mkdir -p obj
$(cc) -Wall -nostdlib -c $(include) -o $@ $<
$(cobjs) : obj/%.o : %.c
@mkdir -p obj
$(cc) -Wall -nostdlib -c $(include) -o $@ $<
.PHONY : clean
clean:
rm -rf $(objs) $(target).elf $(target).bin $(target).dis
root: $(target).bin
chmod +x ./imxdownload
./imxdownload $(target).bin /dev/sdb
编译运行步骤
-
创建编译产物目录:执行命令
mkdir obj。 -
执行编译:运行
make命令,系统将自动调用交叉编译工具链完成编译、链接和格式转换。 -
将生成的
beep_led.bin文件烧录到 SD 卡,插入开发板并上电。此时即可观察到 LED 闪烁,同时蜂鸣器同步鸣叫。