📝摘要:本文基于IMX6ULL Cortex‑A7裸机开发,梳理DDR内存映射、GPIO点灯三模块(IOMUXC引脚复用、GPIO寄存器、CCM时钟)、SVC软中断、编译工具链Makefile、ELF/bin链接、段分布、GIC通用中断控制器、CP15协处理器、内联函数等核心知识点,附带实操代码片段。
1. IMX6ULL内存布局
IMX6ULL搭载512MB DDR3
- DDR物理起始地址:
0x80000000 - DDR结束地址:
0xA0000000(0x80000000 + 0x20000000)
裸机开发链接脚本常用入口地址:
‑Ttext 0x87800000,把程序运行加载到DDR内存中间区域。
栈空间分配
| 地址区间 | 用途 |
|---|---|
0x80000000 ~ 0x84000000 |
系统模式栈 sp_system |
0x84000000 ~ 0x86000000 |
IRQ中断模式栈 sp_irq,大小32MB |
0x86000000 ~ 0xA0000000 |
其余DDR内存,供用户程序、堆使用 |
ARM32不同处理器模式拥有独立栈指针sp;裸机初始化必须为每一种模式设置单独SP,防止栈冲突。
存储介质差异
- NOR‑Flash :可以按字节随机寻址,支持XIP原地执行代码;S3C2440 NOR Flash:
0x0 ~ 0x200000。 - NAND‑Flash / eMMC(IMX6ULL) :不能直接寻址执行;程序必须拷贝到DDR内存中再运行。
2. GPIO点灯三大核心模块
IMX6ULL一共有5组GPIO:GPIO1 ~ GPIO5。
想要驱动GPIO引脚输出高低电平点亮LED,必须配置3大模块,缺一不可:
模块1:IOMUXC 引脚复用与电气属性
芯片引脚可以复用为GPIO、UART、SPI等多种功能,需要IOMUXC寄存器切换。
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03:引脚复用选择寄存器,选择该PAD作为GPIO功能。IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03:PAD电气属性寄存器 ,配置上下拉、摆率、驱动能力。- 高压摆率:信号边沿陡峭,适合高速外设;
- 低压摆率:信号变化平缓,EMI电磁干扰更小。
模块2:GPIO模块寄存器
| 寄存器 | 功能描述 |
|---|---|
GPIOx_GDIR |
方向寄存器,bit=1输出模式;bit=0输入模式 |
GPIOx_DR |
数据寄存器,写1输出高电平,写0输出低电平 |
GPIOx_PSR |
引脚状态只读寄存器,读取外部引脚实际电平 |
GPIOx_ICR |
中断触发配置:高电平/低电平/上升沿/下降沿 |
GPIOx_IMR |
中断掩码,1开启该引脚中断,0屏蔽中断 |
GPIOx_ISR |
中断状态,发生中断对应bit置1;写1清除该中断标志位 |
GPIOx_EDGE_SEL |
双边沿中断选择,置1开启双边沿检测 |
注意:
GPIOx_ISR是写1清0,不是写0清标志。
模块3:CCM时钟控制模块
外设默认时钟关闭,必须打开时钟门控,外设寄存器才可以正常工作。
CCM_CCGR0 ~ CCM_CCGR6:时钟门控寄存器;写入0xFFFFFFFF打开全部外设时钟。
C语言裸机点灯示例(寄存器直接操作)
#include <stdio.h>
//volatile 阻止编译器优化硬件寄存器访问
#define CCM_CCGR0 (*((volatile unsigned int *)0x020C4068))
#define GPIO1_GDIR (*((volatile unsigned int *)0x0209C004))
#define GPIO1_DR (*((volatile unsigned int *)0x0209C000))
int main(void)
{
CCM_CCGR0 = 0xFFFFFFFF; //打开所有外设时钟
GPIO1_GDIR |= (1 << 3); //GPIO1_IO03 设置输出模式
GPIO1_DR &= ~(1 <<3); //输出低电平,点亮LED
while(1);
return 0;
}
3. SVC软中断指令
SVC(Supervisor Call)软中断,触发异常,进入异常向量表SVC入口。
汇编格式:
SVC{<cond>} #<imm24>
svc #7 ;触发软中断,携带立即数7作为中断号
应用场景:用户模式触发SVC异常,切换进入特权模式,实现系统调用。
异常向量表中SVC对应偏移地址
0x08。
4. 开发环境:VSCode远程开发
开发工作流:Windows安装VSCode,通过FTP(FileZilla)连接Ubuntu服务器。
需要安装VSCode插件:
C/C++C/C++ Extension PackARM Assembly汇编语法高亮Makefile ToolsMakefile语法与调试工具
文件传输:FileZilla FTP客户端,Windows ↔ Ubuntu互传源码。
5. 编译、链接、objcopy工具与程序段
完整裸机编译流水线
#1.编译:源文件编译为.o目标文件,-g加入调试信息
arm‑linux‑gnueabihf‑gcc -g -c start.S -o start.o
arm‑linux‑gnueabihf‑gcc -g -c main.c -o main.o
#2.链接:将多个.o合并elf可执行,‑Ttext指定代码段起始地址
arm‑linux‑gnueabihf‑ld -Ttext 0x87800000 start.o main.o -o start.elf
#3.objcopy:把elf转为二进制bin镜像,烧写到Flash/eMMC
arm‑linux‑gnueabihf‑objcopy -O binary -S -g start.elf start.bin
- elf:带调试信息、段表的可执行格式,不能直接烧写Flash;
- bin:纯粹二进制机器码,无头部,适合烧录存储介质。
ELF中五大段
| 段名 | 作用 |
|---|---|
.text |
存放程序代码、汇编指令,‑Ttext指定该段链接基地址 |
.data |
已经初始化的全局/静态变量 |
.bss |
初始化为0的全局、静态变量;加载时不占用bin文件空间,运行时清零 |
.common |
未初始化的全局变量 |
.rodata |
只读常量字符串,常量数据 |
编译链接参数
-I:指定头文件搜索路径-L:指定库文件搜索路径
工具链程序:
gcc编译、ld链接、objcopy格式转换、objdump反汇编查看elf。
6. Makefile基础三要素
Makefile核心三要素:目标、依赖、执行规则(命令)
目标:依赖
命令
⚠️命令行前面必须是
Tab制表符,不能用空格。
简单示例:
start.bin:start.o main.o
arm‑linux‑gnueabihf‑ld -Ttext 0x87800000 start.o main.o -o start.elf
arm‑linux‑gnueabihf‑objcopy -O binary -S -g start.elf start.bin
start.o:start.S
arm‑linux‑gnueabihf‑gcc -g -c start.S -o start.o
main.o:main.c
arm‑linux‑gnueabihf‑gcc -g -c main.c -o main.c
7. IMX6ULL中断体系与GIC通用中断控制器
IMX6ULL Cortex‑A7使用 GIC‑V2 中断控制器,ARM官方外设,最多支持1020路中断;IMX6ULL实际使用160个中断源。
GIC中断三大分类
- SPI 共享外设中断:来自片内外设GPIO、UART、TIMER;多核环境每个CPU都可以接收该中断。
- PPI私有外设中断:每个CPU私有,核间信号,和外部硬件外设无关。
- SGI软件触发中断:软件指令产生,多用于多核CPU之间通信。
完整中断处理流程
- 外设侧:配置GPIO/定时器等外设,设置中断触发方式,使能外设中断输出;
- GIC控制器侧
- 设置中断使能/屏蔽;
- 设置中断优先级;
- GIC对比优先级,分发中断到对应CPU核;
- CPU内核侧
- CPSR打开IRQ总中断开关;
- CPU跳转到异常向量表IRQ入口;
- 中断服务函数中读取GIC
C_IAR寄存器,获取中断号; - 根据中断号,执行对应外设中断业务处理;
- 处理完毕向
C_EOIR写入中断号,通知GIC中断处理结束; - 恢复现场,返回被打断的程序。
关键寄存器:
C_IAR:中断确认寄存器,只读,获取发生的中断号;C_EOIR:中断结束寄存器,只写,处理完必须写回中断号。
GIC版本迭代:
- GIC V1:Cortex‑A5老芯片;
- GIC V2:IMX6ULL,树莓派,支持虚拟化,最多1020中断;
- GIC V3/V4:64位多核手机芯片;
- GIC V5:大型服务器、车载大算力芯片。
8. CP15协处理器 mrc/mcr指令
CP15是Cortex‑A7最重要系统协处理器,控制MMU、Cache、向量表基地址等系统配置。
MRC:把协处理器寄存器读取到ARM通用寄存器MCR:把通用寄存器写入协处理器寄存器
指令格式
mrc <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
示例:操作SCTLR系统控制寄存器,开启I‑Cache
;读取cp15 c1(SCTLR)到r0
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #(1 <<13)
orr r0, r0, #(1 <<12) ;打开I‑Cache
mcr p15, 0, r0, c1, c0, 0 ;写回cp15
设置异常向量表基地址:
mcr p15,0,r0,c12,c0,0
9. volatile关键字作用
裸机操作硬件寄存器必须加volatile修饰。
-
修饰变量:告诉编译器,该内存内容可能被硬件意外修改,禁止编译器做读写优化,每次都真实访问内存。
-
硬件寄存器指针:
#define CCM_CCGR0 (*((volatile unsigned int *)0x020C4068))
如果去掉volatile,编译器会优化,寄存器读写被直接删掉,外设完全无法工作。
10. 内联函数 inline
inline内联函数:编译阶段,把函数代码直接展开到调用位置,消除函数调用压栈跳转开销,提升运行效率。
注意:编译时必须可以看见函数定义,只声明不定义无法内联。
inline void delay(unsigned int t)
{
while(t--);
}
11. 英文缩写全称对照表
| 缩写 | 英文全称 | 中文释义 |
|---|---|---|
| IOMUXC | Input Output Multiplexer Controller | 输入输出多路复用控制器 |
| CCM | Clock Control Module | 时钟控制模块 |
| GIC | Generic Interrupt Controller | 通用中断控制器 |
| SPI | Shared Peripheral Interrupt | 共享外设中断(GIC中断类型) |
| PPI | Private Peripheral Interrupt | 私有外设中断(GIC中断类型) |
| SGI | Software Generated Interrupt | 软件产生中断(GIC中断类型) |
| SVC | Supervisor Call | 管理调用,软中断指令 |
| XIP | Execute‑In‑Place | 原地执行 |
| ELF | Executable and Linkable Format | 可执行可链接文件格式 |
| BIN | Binary | 原始二进制镜像文件 |
| CP15 | Coprocessor‑15 | 15号系统协处理器 |
| MRC | Move from Coprocessor | 从协处理器读数据到通用寄存器 |
| MCR | Move to Coprocessor | 通用寄存器写入协处理器 |
| DDR | Double‑Data‑Rate | 双倍速率同步内存 |
| FTP | File Transfer Protocol | 文件传输协议 |
💡裸机开发踩坑总结
- IMX6ULL操作GPIO三件套:时钟CCM、IOMUXC引脚复用、GPIO方向数据寄存器,缺一不可。
- 硬件寄存器指针一定要volatile,防止编译器优化。
- GIC中断处理完成必须写C_EOIR,否则会重复触发中断。
- NAND/eMMC不能XIP,程序必须加载到DDR运行;NOR可以直接原地执行。
- Makefile规则行必须是Tab,不能使用空格。