Linux入门DAY45 IMX6ULL 裸机开发笔记:汇编点灯、C 语言点灯、GIC 中断梳理

#IMX6ULL #ARM 裸机开发 #Cortex‑A7 #GICV2 # 嵌入式 # 汇编点灯 #Makefile #GPIO 中断

前言

📚学习平台:IMX6ULL(Cortex‑A7 内核)裸机开发 💻开发环境:Windows VSCode + Ubuntu 交叉编译环境 + FileZilla FTP 文件传输 📝学习内容:DDR 内存布局、GPIO 点灯原理、汇编 & C 实现点灯、Makefile 工程管理、GIC V2 中断控制器、协处理器 CP15、异常向量表

最近在学习 NXP i.MX6ULL Cortex‑A7 裸机开发,裸机开发没有操作系统的加持,所有寄存器、时钟、引脚、中断全部需要我们手动配置。从最经典的点灯实验入门,一步步理解 ARM 架构底层,理解链接、编译、内存布局、中断整套体系。本文把课堂学习笔记做完整扩写,适合零基础入门 ARM 裸机的小伙伴,同时可以作为复习笔记。

开发环境部署说明

  1. Windows 端安装 VSCode,安装插件:C/C++C/C++ Extension PackARM AssemblyMakefile Tools。VSCode 只做代码编辑,真正编译工作交给 Ubuntu。
  2. Ubuntu 系统安装arm‑linux‑gnueabihf交叉编译工具链,生成适合 ARM 架构的二进制程序。
  3. 使用 FileZilla FTP Client 实现 Windows 与 Ubuntu 之间文件互传,Windows 写代码,Ubuntu 编译。

一、IMX6ULL 硬件基础认知

1. 存储介质区分:NorFlash、NAND/EMMC、DDR3

很多刚从单片机转 ARM 裸机的同学容易混淆存储介质,这里做对比:

  1. S3C2440 Nor Flash :地址范围 0x0 ~ 0x200000,可以直接 CPU 寻址,CPU 可以直接从 Flash 读取指令运行代码。
  2. IMX6ULL EMMC(8GB) :属于 NAND 类存储,不支持 CPU 直接寻址,不能直接在 EMMC 运行程序。程序需要先拷贝到 DDR 内存,CPU 再在 DDR 中运行代码。
  3. DDR3 :IMX6ULL 搭载 512MB 的 DDR3 内存,起始地址0x80000000,结束地址0xA0000000。DDR 属于运行内存,断电丢失数据,我们裸机程序就是在 DDR 里面运行。

2. DDR3 内存栈划分

Cortex‑A7 有多种工作模式,不同模式拥有独立的栈空间。栈用于函数调用、寄存器压栈保存现场,IRQ 中断模式发生时,必须使用独立 IRQ 栈,不能共用系统栈,否则栈溢出破坏现场。

复制代码
0x80000000  ---------------------------------------------
            系统模式栈 sp_system
0x84000000  ---------------------------------------------
            IRQ模式栈 sp_irq(大小32MB)
0x86000000  ---------------------------------------------
            剩余可用内存
0xA0000000

程序链接运行地址设置为0x87800000,这个地址落在 DDR 内存区间,使用链接参数‑Ttext 0X87800000指定.text代码段的运行地址。

小提示:栈地址是高地址向低地址增长,栈指针 SP 需要预先设置,汇编启动文件必须初始化各个模式下 SP 指针,否则 C 语言无法运行。

3. GPIO 操作三大模块详解

IMX6ULL 总共有 5 组 GPIO:GPIO1、GPIO2、GPIO3、GPIO4、GPIO5。 想要操作 LED,不是直接操作 GPIO 寄存器,必须配置引脚复用 IOMUXC、GPIO 功能寄存器、CCM 时钟门控这三大模块,缺一不可。

① IOMUXC 模块:引脚复用 + 电气属性

一颗 SOC 芯片引脚数量有限,一个物理引脚可以复用为 GPIO、UART、I2C、SPI 等多种功能,由 IOMUXC 控制器管理。

  • IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03:引脚复用选择寄存器,把物理引脚设置为 GPIO 功能。如果不配置复用,引脚可能是其他外设功能,点灯不会有任何效果。
  • IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03:PAD 电气属性寄存器。包含摆率、上下拉、驱动能力等参数。
    • 高压摆率:信号翻转速度快,波形曲线抖动明显,适合高速外设。
    • 低压摆率:信号翻转平缓,波形平滑,适合低速设备例如 LED。
② GPIO 功能寄存器组

表格

寄存器名称 功能说明
GPIOx_GDIR GPIO 方向寄存器。bit 位 = 1:输出模式;bit 位 = 0:输入模式。点灯必须配置为输出。
GPIOx_DR 数据输出寄存器。对应 bit 写 1 输出高电平;写 0 输出低电平,用来点亮熄灭 LED。
GPIOx_PSR 引脚状态寄存器,只读。读取外部引脚真实电平状态,做按键输入的时候使用。
GPIOx_ICR 中断触发配置寄存器,配置是电平触发(高 / 低电平)还是边沿触发(上升沿 / 下降沿)。
GPIOx_IMR 中断屏蔽寄存器。1 代表打开该引脚中断,0 屏蔽中断。
GPIOx_ISR 中断状态寄存器。判断引脚是否产生中断,写 1 清零,读取为 1 表示发生中断。
GPIOx_EDGE_SEL 双边沿中断选择寄存器,可以配置为同时捕获上升沿和下降沿。
③ CCM 时钟控制模块

SOC 的外设默认时钟是关闭的,用来降低功耗。GPIO、UART、TIMER 全部受 CCM 时钟门控管理。 CCM_CCGR0 ~ CCM_CCGR6共 7 个寄存器,管理所有外设时钟。写入0xFFFFFFFF就打开全部外设时钟门。时钟不打开,GPIO 寄存器读写无效,LED 无法点亮。

点灯操作流程总结:

  1. CCM 打开 GPIO 外设时钟
  2. IOMUXC 设置引脚复用为 GPIO 功能,配置电气属性
  3. GPIOx_GDIR 设置引脚为输出模式
  4. GPIOx_DR 输出高低电平,控制 LED 亮灭

4. 点灯三种实现方式

  1. 汇编点灯:纯汇编语言直接操作寄存器,不依赖 C 库,最贴近 CPU 底层,适合理解 ARM 汇编指令。
  2. C 语言点灯:汇编只做启动初始化(设置栈、关闭看门狗),硬件寄存器用 C 指针访问,开发效率高,实际裸机开发最常用。
  3. NXP 官方固件库点灯:NXP 封装好寄存器操作,直接调用库函数,不用记忆寄存器地址,类似 STM32 库开发。

二、交叉编译、链接、Makefile 工程体系

1. 完整交叉编译命令解析

裸机程序没有操作系统,不能使用 x86 平台 gcc,必须使用 ARM 交叉编译器arm‑linux‑gnueabihf‑gcc

复制代码
#1.编译汇编启动文件start.S,生成start.o目标文件,-g加入调试信息
arm‑linux‑gnueabihf‑gcc -g -c start.S -o start.o

#2.编译C语言主程序main.c生成main.o
arm‑linux‑gnueabihf‑gcc -g -c main.c -o main.o

#3.链接:把多个.o文件合并,指定代码段运行地址,输出elf格式文件
arm‑linux‑gnueabihf‑ld -Ttext 0x87800000 start.o main.o -o start.elf

#4.objcopy:elf文件转换为二进制bin文件,烧录到EMMC。-S -g去掉调试符号信息
arm‑linux‑gnueabihf‑objcopy -O binary -S -g start.elf start.bin

工具链分工:

  1. gcc:编译 C / 汇编,输出.o目标文件。
  2. ld链接器:把多个目标文件、库文件合并,排布各个段的内存地址,输出 elf。可以使用自定义链接脚本实现复杂段管理。编译参数 -I 指定头文件搜索路径,‑L 指定库文件搜索路径。
  3. objcopy:elf 格式转 bin 原始二进制镜像,bin 是真正烧录运行的文件。
  4. objdump:反汇编工具,调试时查看汇编代码。

2. ELF 文件五大段含义

elf 文件内部会划分不同段,不同数据存放在不同段:

表格

段名 存放内容
.text 代码段,存放汇编、C 语言编译出来的指令,‑Ttext就是修改该段运行地址
.data 数据段,存放已经初始化完成的全局变量、静态局部变量
.bss 存放初始化为 0 的全局 / 静态变量,bin 文件中不占用空间,程序启动时自动清零
.common 存放没有初始化的全局变量
.rodata 只读数据段,存放字符串常量、const 修饰只读数据

注意:bss 和 common 段不会占用 bin 镜像大小,程序运行起来之后在 DDR 内存分配空间。

3. volatile 关键字(裸机开发重中之重)

C 语言访问硬件寄存器的时候,一定要加volatile修饰。 编译器会对代码做优化,如果不加 volatile,编译器会认为寄存器的值没有变化,直接使用寄存器缓存,不去真实读取硬件地址。

复制代码
//正确写法,volatile告诉编译器每次都访问真实硬件地址,禁止优化
#define CCM_CCGR0 (*((volatile unsigned int *)0x020C4068))
  • volatile 修饰变量:该变量读写不能被编译器优化。
  • volatile 修饰指针:指针指向的硬件内存空间,每次必须真实读写。

4. Makefile 规则

裸机工程文件变多之后,手动敲编译命令效率很低,使用 Makefile 自动化编译。 Makefile 的三要素:目标、依赖、规则 语法格式:

复制代码
目标:依赖文件
	#shell命令,前面必须是Tab键,不能空格
  • 目标:想要生成的文件,例如 elf、bin、.o
  • 依赖:生成目标需要哪些源文件
  • 规则:执行的编译链接 shell 命令

5. 推荐工程目录结构

实际项目不要把所有文件堆在同一个文件夹,做模块化分层,参考下面目录结构:

复制代码
project/                #项目根目录
├─ start.S              #汇编启动文件,设置栈、异常向量表
├─ main.c               #主业务逻辑
├─ Makefile             #编译脚本
├─ imx6ull/             #芯片库文件,寄存器定义
├─ bsp/                 #板级外设驱动层
│   ├─ led.c
│   ├─ led.h
│   ├─ beep.c
│   └─ beep.h
└─ obj/                 #编译中间产物,存放.o目标文件

三、IMX6ULL GIC V2 中断系统深度讲解

1. 对比 51 单片机中断

51 单片机中断流程相对简单: 外设产生中断 → 设置外设中断允许 → CPU总中断开关 → 设置优先级 → 跳转到中断服务函数

ARM Cortex‑A7 的中断体系复杂很多,引入专门的GIC 通用中断控制器外设来统一管理全部中断源。

2. GIC 介绍

GIC(Generic Interrupt Controller,通用中断控制器),是 ARM 设计的 IP 核,不是 NXP 设计。不同版本 GIC 支持不同 CPU:

  • GIC V1:Cortex‑A5 老款芯片
  • GIC V2:IMX6ULL、树莓派,32 位处理器,最多支持 1020 路中断,IMX6ULL 实际只用 160 个中断源,支持虚拟化,最多 8 核。
  • GIC V3/V4:面向 64 位多核处理器,手机、高端嵌入式芯片。
  • GIC V5:面向服务器、车载算力芯片,支持数百核。

IMX6ULL 整体架构:

  • Cortex‑A7 内核(ARM 设计)
  • GICV2 中断控制器(ARM 设计)
  • GPIO、UART、TIMER 等片内外设(NXP 设计)
GICV2 三类中断源
  1. SPI 共享外设中断:所有片上外设(GPIO、UART、定时器)产生的中断,多个 CPU 核都可以接收处理,裸机开发接触最多。
  2. PPI 私有中断:CPU 核私有中断,多核之间核间通信,和外部外设无关。
  3. SGI 软件中断:软件指令主动触发产生的中断,多核通信使用。

3. IMX6ULL 完整中断执行流程

  1. 外设层 :配置外设寄存器。以 GPIO 按键中断举例:配置GPIO1_ICR2设置触发方式;GPIO1_IMR打开引脚中断使能。外设检测信号变化,向 GIC 发出中断请求。
  2. GIC 控制器处理:GIC 接收中断请求,判断中断屏蔽状态,读取中断优先级,对比优先级,把最高优先级中断发送给 CPU。
  3. CPU 内核处理阶段
    1. CPSR 寄存器打开 CPU 全局 IRQ 中断总开关。
    2. CPU 硬件自动切换进入 IRQ 异常模式,保存 CPSR 到 SPSR_irq,PC 跳转至异常向量表 IRQ 入口
    3. 异常向量表跳转执行 IRQ 异常处理汇编代码。
    4. 汇编代码读取 GIC 寄存器C_IAR,拿到中断号,区分是哪一个外设触发中断。
    5. 根据中断号调用对应外设中断服务函数。
    6. 中断处理完成,必须把中断号写入C_EOIR(中断结束寄存器)通知 GIC 本次中断处理完毕。不写 EOIR 会导致 GIC 不会接收下一次同类型中断。
    7. 恢复寄存器现场,从 IRQ 模式返回到被打断的程序。

重要寄存器总结 外设侧 (GPIO)

  • GPIO1_ICR2:中断触发方式
  • GPIO1_IMR:中断使能
  • GPIO1_ISR:中断状态,写 1 清零

GIC 控制器侧

  • C_IAR只读:获取中断号
  • C_EOIR只写:通知 GIC 中断处理结束

4. CP15 协处理器

Cortex‑A7 内核内置协处理器 cp0~cp15,CP15 是系统控制协处理器,没有内存地址,不能通过 C 指针访问,只能用汇编指令访问,用来控制 MMU、Cache、异常向量表重映射等底层功能。

两条核心汇编指令:

  1. mrc:将协处理器寄存器读取到 CPU 通用寄存器
  2. mcr:将 CPU 通用寄存器写入协处理器寄存器

指令格式:

复制代码
mrc <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}

示例:操作 SCTLR 系统控制寄存器,打开 I‑Cache

复制代码
mrc p15, 0, r0, c1, c0, 0    ;读取cp15 c1(SCTLR)寄存器到r0
bic r0, r0, #(1 <<13)        ;清除对应bit
orr r0, r0, #(1 << 12)       ;置位bit12,打开Icache
mcr p15, 0, r0, c1, c0, 0    ;把r0写回cp15 c1寄存器

mcr p15,0,r0,c12,c0,0        ;异常向量表重映射,设置向量表基地址

5. SVC 软中断 & 异常向量表

异常向量表是一片内存,存放各个异常的入口地址。当发生异常(复位、SVC 软中断、IRQ、FIQ、数据中止等)CPU 自动跳转到向量表对应位置。

软中断指令:SVC #<imm24>,例如svc #7。执行这条指令立刻触发 SVC 异常,跳转到异常向量表 SVC 入口,常用于模拟系统调用。

注意:裸机开发需要手动重映射异常向量表,告诉 CPU 异常向量表放在 DDR 内存哪个地址,通过 cp15 c12 寄存器设置。

6. inline 内联函数

C 语言inline内联函数,编译的时候,会把函数代码直接展开到调用处,省去函数调用跳转开销,提升执行效率。使用要求:调用 inline 函数之前必须看到函数定义,不能只有声明。裸机中断处理中可以用来封装简短寄存器操作。


四、整体学习总结

  1. IMX6ULL 裸机点灯三步走:开启 CCM 外设时钟 → IOMUXC 配置引脚复用与电气属性 → GPIO 配置方向,输出电平。任何一步配置错误 LED 都无法正常工作。
  2. 裸机 C 语言直接操作硬件寄存器,volatile 必不可少,防止编译器做优化,屏蔽硬件真实读写
  3. 编译链路流程:源文件 (.S/.c) 编译生成.o 目标文件 → ld 链接排布内存地址生成 elf → objcopy 转为 bin 镜像烧录运行。不同段 (.text/.data/.bss 等) 各司其职,存放不同类型数据。
  4. Makefile 管理工程,三要素目标、依赖、规则,简化重复编译命令。项目做好模块化分层,区分启动文件、驱动、主逻辑。
  5. IMX6ULL 中断由三层协同工作:外设寄存器配置中断条件;GICV2 控制器管理所有中断源,分发中断;CPU 内核配合 CPSR、异常向量表、CP15 协处理器完成异常处理。中断处理结束必须向 GIC 的 C_EOIR 寄存器写入中断号。
  6. CP15 协处理器只能汇编指令 mrc/mcr 访问,控制 Cache、MMU、异常向量表基地址等底层硬件。SVC 指令触发软中断,属于 ARM 异常中的一类。

📖后续实践方向

相关推荐
离凌寒1 小时前
一、关于linux开发板上移植ubuntu根文件系统并配置桌面环境笔记记录
驱动开发·单片机
临期冰淇淋1 小时前
Unisoc 展锐平台Camera摄像头分辨率适配
linux·c语言·驱动开发
Rsingstarzengjx2 小时前
第二十八章 Linux 并发与竞争实验
驱动开发·嵌入式硬件·stm32mp157
辰域电子2 小时前
STM32项目开源:实验室消防预警控制系统(代码 + 原理图 + 仿真)
stm32·单片机·嵌入式硬件·开源·proteus
流浪0012 小时前
Linux系统篇36——线程(一) 线程的概念、本质和Linux的实现方式
linux·面试·操作系统·线程
szxinmai主板定制专家2 小时前
NXP LS1046A 硬件方案: LS1046A+FPGA 异构加速架构实践
大数据·图像处理·人工智能·嵌入式硬件·fpga开发
从零开始的嵌入式之旅2 小时前
day46
linux·arm开发·笔记·嵌入式硬件
老当益壮梁奶奶3 小时前
ARM汇编学习笔记(四):i.MX6ULL裸机开发:从汇编点灯到C语言BSP工程管理
c语言·arm开发·单片机·嵌入式硬件·51单片机
虚无的纽扣3 小时前
【Linux】将进程知识与基础IO融会贯通——简单自定义Shell的实现
linux·ubuntu