韦东山 3-5 I.MX6ULL 的 LED 编程 · 学习笔记

韦东山 3-5 I.MX6ULL 的 LED 编程 · 学习笔记

所属课程:《ARM 架构与编程(基于 I.MX6ULL)》------裸机开发系列

所在章节:第三章 第一个程序(点亮 LED)

本节名称:3-5_IMX6ULL 的 LED 编程

原视频:https://video.100ask.net/p/t_pc/course_pc_detail/video/v_5f869f4fe4b06aff1a03fedf

整理日期:2026-09-28


摘要

本节把 3-4 的寄存器配置写成工程:汇编设栈、清 BSS 后跳入 C;C 用 volatile 指针封装寄存器,led_init 完成使能、复用、定向三步,led_ctl 位读写 DR 控制亮灭,delay 循环实现闪烁,经 Makefile 与 imxdownload 烧写验证。


目录

  • 摘要
  • [1. 本节在课程中的位置](#1. 本节在课程中的位置)
  • [2. 工程总览:C 裸机程序的结构](#2. 工程总览:C 裸机程序的结构)
  • [3. 启动文件 start.S:C 运行的两个前提](#3. 启动文件 start.S:C 运行的两个前提)
  • [4. 驱动层 led.c:volatile 指针操作寄存器](#4. 驱动层 led.c:volatile 指针操作寄存器)
  • [5. 应用层 main.c:闪烁逻辑](#5. 应用层 main.c:闪烁逻辑)
  • [6. 构建、链接与烧写](#6. 构建、链接与烧写)
  • [7. 常见误区](#7. 常见误区)
  • [8. 本节结论与延伸](#8. 本节结论与延伸)
  • [附录 A:术语速查](#附录 A:术语速查)
  • [附录 B:自查清单](#附录 B:自查清单)
  • 参考

1. 本节在课程中的位置

3-4 完成了"手册 → 寄存器地址与配置值"的推导,本节完成最后一环:把这些配置写成完整的汇编 + C 工程,编译烧录到开发板跑起来。核心新增知识点是"C 语言要跑在芯片上需要哪些准备工作"------栈、BSS 段、启动文件,以及工程化的文件组织(start.S / led.h / led.c / main.c / Makefile)。本节是裸机编程范式的定型课,后续所有裸机实验都沿用这套骨架。

2. 工程总览:C 裸机程序的结构

文件 职责
start.S 启动文件:设置栈指针、清除 BSS 段、跳转 main
led.h 声明 led_init / led_ctl 接口
led.c 寄存器级驱动:初始化引脚、控制亮灭
main.c 应用逻辑:死循环 + 延时闪烁
Makefile / 链接脚本 编译、链接到 DDR 起始地址 0x80000000

分工思想:汇编搭台,C 唱戏------汇编只做 C 干不了的事(栈、BSS),业务逻辑全部交给 C。

3. 启动文件 start.S:C 运行的两个前提

C 函数能执行的两个前提:有效的栈 (局部变量、函数调用需要)与干净的 BSS 段(未初始化全局变量需为 0)。

armasm 复制代码
.text
.global _start          @ 全局标号,链接器入口

_start:
    ldr sp, =0x80200000  @ 设置栈指针(DDR 起始 + 2M)
    bl clean_bss         @ 清除 BSS 段
    bl main              @ 跳入 C 语言 main
halt:
    b halt               @ 原地循环,防止跑飞

clean_bss:
    ldr r1, =__bss_start @ BSS 起始地址(链接脚本提供)
    ldr r2, =__bss_end   @ BSS 结束地址
    mov r3, #0
clean:
    str r3, [r1]         @ 逐字清零
    add r1, r1, #4
    cmp r1, r2
    bne clean
    mov pc, lr

要点:

  • 栈为什么必须先设 :bl main 的返回地址压栈、main 里局部变量都在栈上,sp 无效则第一条 C 语句即崩;
  • 为什么清 BSS:程序烧在 SD/eMMC,加载到 DDR 时 BSS 段不占文件体积,内存里是随机值,必须软件清零,否则全局变量初值不可预期;
  • __bss_start/__bss_end 由链接脚本符号提供,汇编与链接器在此协作。

部分版本课程还会在设栈前显式进入 SVC 模式(mrs/bic/orr/msr 操作 CPSR),保证处理器状态一致。

4. 驱动层 led.c:volatile 指针操作寄存器

韦东山例程(100ASK 板,LED 接 GPIO5_IO03,即 SNVS_TAMPER3 引脚复用):

c 复制代码
static volatile unsigned int *CCM_CCGR1
    = (volatile unsigned int *)(0x20C406C);
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
    = (volatile unsigned int *)(0x2290014);
static volatile unsigned int *GPIO5_GDIR
    = (volatile unsigned int *)(0x020AC000 + 0x4);
static volatile unsigned int *GPIO5_DR
    = (volatile unsigned int *)(0x020AC000);

void led_init(void)
{
    unsigned int val;

    /* a. 使能 GPIO5 时钟: CCM_CCGR1[CG15] bit[31:30] = 11 */
    *CCM_CCGR1 |= (3 << 30);

    /* b. 复用为 GPIO5_IO03: MUX bit[3:0] = 0101 (ALT5) */
    val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
    val &= ~(0xf);
    val |= (5);
    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

    /* c. 方向为输出: GPIO5_GDIR bit[3] = 1 */
    *GPIO5_GDIR |= (1 << 3);
}

void led_ctl(int on)
{
    if (on)
        *GPIO5_DR &= ~(1 << 3);   /* 低电平点亮 */
    else
        *GPIO5_DR |= (1 << 3);    /* 高电平熄灭 */
}

三个代码级要点:

  • volatile 不可省:告诉编译器该内存单元随时被硬件改变,禁止缓存到寄存器或优化掉重复读写------嵌入式驱动的第一铁律;
  • 读-改-写 :|=/&= 保证不影响同寄存器其他位(3-2 铁律的落地);
  • static:把寄存器指针限定在本文件,避免符号污染。

引脚差异说明

100ASK 板的 LED 挂在 GPIO5_IO03 (SNVS 域引脚,复用寄存器在 0x2290014);正点原子等板卡常用 GPIO1_IO03。寄存器地址与组号因板而异,但四步配置方法完全同构------这正是 3-2 抽象出通用框架的价值:换板只改地址宏,不改流程。

5. 应用层 main.c:闪烁逻辑

c 复制代码
#include "led.h"

void delay(volatile unsigned int d)
{
    while (d--);
}

int main(void)
{
    led_init();
    while (1)
    {
        led_ctl(1);           /* 点亮 */
        delay(1000000);
        led_ctl(0);           /* 熄灭 */
        delay(1000000);
    }
    return 0;
}
  • delay 用软件空循环计数实现,无定时器参与------最简但精度差,依赖 CPU 主频;后续课程会改用 GPT 定时器;
  • led_ctl(1) 点亮对应该板低电平亮的硬件事实(3-1 原理图结论);驱动层把极性封装起来,应用层只说"亮/灭"------这是最朴素的硬件抽象层(HAL)思想。

6. 构建、链接与烧写

复制代码
start.S ──gcc -c──> start.o ─┐
                              ├─ld -Ttext 0x80000000──> led.elf ──objcopy──> led.bin
main.c/led.c ──gcc -c──> *.o ─┘
  • 链接地址 :-Ttext 0x80000000 指定代码放在 DDR 起始处,程序按此地址重定位执行;
  • objcopy 把 ELF 抽成纯二进制 led.bin;
  • 烧写 :用 imxdownload 工具把 bin 加上 IVT 头部写入 SD 卡(./imxdownload led.bin /dev/sdb),开发板拨码设为 SD 启动;
  • 上电后 BootROM 按 IVT 引导,把程序加载到 DDR 并跳转 _start,LED 开始闪烁。

7. 常见误区

  • 忘写 volatile:开优化后 delay 被删、寄存器读写被合并,程序"看起来对但现象不对";
  • 忘设栈:进入 main 即异常,且裸机无报错信息,是最隐蔽的坑;
  • 忘清 BSS:全局变量随机初值,现象时好时坏;
  • led_ctl 极性写反:不看原理图直接"写 1 点亮",结果语义颠倒;
  • 把 led.bin 直接 dd 进 SD 卡:缺少 IVT 头部 BootROM 无法引导,必须经 imxdownload 等工具加工。

8. 本节结论与延伸

本节完成第三章点灯实验的最终闭环:原理图(3-1)→ 通用方法(3-2)→ 芯片寄存器(3-3/3-4)→ 可运行工程(3-5)。"汇编搭台、C 唱戏"的工程范式将贯穿后续所有裸机实验;volatile 指针、读-改-写、硬件抽象这些习惯会一直用到 Linux 驱动(届时换成 ioremap + readl/writel,思想不变)。

附录 A:术语速查

术语 含义
BSS 段 存放未初始化全局变量的段,加载后需软件清零
volatile 告知编译器禁止优化该对象的读写
链接脚本 / -Ttext 指定各段在内存中的布局与起始地址
IVT Boot ROM 识别的镜像头(引导向量表),SD 卡启动必需
SNVS_TAMPER3 SNVS 域引脚,复用 ALT5 后为 GPIO5_IO03
imxdownload 百问网烧写工具,为 bin 添加 IVT 头并写入 SD 卡

附录 B:自查清单

  • 能说出 C 程序运行的两大前提及对应的汇编操作;
  • 能解释 volatile 在本例中的两处必要性(寄存器、delay 计数);
  • 能画出 start.S → main → led_init → led_ctl 的调用链;
  • 能说明 __bss_start/__bss_end 由谁定义、谁使用;
  • 能解释为什么 led.bin 不能直接 dd 进 SD 卡;
  • 能说明本板 LED 极性并指出信息来源(原理图)。

参考

  • 韦东山《ARM 架构与编程》3-1 ~ 3-4(本课程前置小节)
  • 100ASK_IMX6ULL 裸板学习 LED 实验(中)------例程与代码分析(博客园)
  • NXP i.MX 6ULL Applications Processor Reference Manual
  • 100ASK I.MX6ULL 开发板原理图
相关推荐
知识分享小能手1 小时前
C++ 学习教程,从入门到精通,C++类继承 — 完整知识点详解(13)
开发语言·c++·学习
Marst Code1 小时前
IBM 开放了 PC 架构,然后把自己“开放“出局了
架构
xingzhemengyou12 小时前
STM32对函数进行CRC校验
stm32·单片机·嵌入式硬件
FPGA小徐2 小时前
STM32引脚详解:PC13-TAMPER-RTC 多功能复用引脚完全剖析(踩坑+配置+布线)
单片机·嵌入式硬件
Joe_Wang52 小时前
【从0到1学习JVM · 25】同样都要停顿所有线程,Parallel比Serial到底强在哪
java·jvm·学习·垃圾回收
M78佐菲2 小时前
ARM学习笔记(9)
linux·arm开发·笔记·嵌入式硬件·学习
铅笔小新z2 小时前
【stm32】中断与异常
stm32·单片机·嵌入式硬件
阳光九叶草LXGZXJ2 小时前
达梦数据库-学习-67-SSL加密认证
linux·运维·数据库·sql·学习·ssl
東隅已逝,桑榆非晚2 小时前
priority_queue的介绍和使用
c++·经验分享·笔记·学习