目录
[一、点亮 LED 的三步法](#一、点亮 LED 的三步法)
[二、看原理图:LED 怎么连到芯片的?](#二、看原理图:LED 怎么连到芯片的?)
[2.1 LED 在原理图中的抽象](#2.1 LED 在原理图中的抽象)
[2.2 两种最基本的驱动方式](#2.2 两种最基本的驱动方式)
[2.3 引脚驱动能力不足怎么办?------ 三极管驱动](#2.3 引脚驱动能力不足怎么办?—— 三极管驱动)
[三、GPIO 通用操作方法](#三、GPIO 通用操作方法)
[3.1 GPIO 模块的五大要素](#3.1 GPIO 模块的五大要素)
[3.2 寄存器操作的两种方式](#3.2 寄存器操作的两种方式)
[方式B:set-and-clear 协议(硬件原子操作)](#方式B:set-and-clear 协议(硬件原子操作))
[四、实战:i.MX6ULL 的 GPIO 操作](#四、实战:i.MX6ULL 的 GPIO 操作)
[4.1 总体流程](#4.1 总体流程)
[4.2 步骤详解 + 如何查手册](#4.2 步骤详解 + 如何查手册)
[步骤1:使能 GPIO1 时钟](#步骤1:使能 GPIO1 时钟)
[步骤2:将引脚复用为 GPIO 功能](#步骤2:将引脚复用为 GPIO 功能)
[4.3 完整代码示例(裸机风格)](#4.3 完整代码示例(裸机风格))
[5.1 必备的两份文档](#5.1 必备的两份文档)
[5.2 查手册的典型路径](#5.2 查手册的典型路径)
[5.3 小技巧](#5.3 小技巧)
学习 C 语言时,第一个程序是 "Hello World"。
学习 ARM 裸机编程时,第一个程序就是 点亮 LED 。
这篇文章带你从看原理图开始,一步步掌握 i.MX6ULL 的 GPIO 操作,顺便学会怎么查芯片手册。
一、点亮 LED 的三步法
点亮一个 LED 只需要三步:
-
看原理图 -- 找到控制 LED 的引脚是哪个。
-
看芯片手册 -- 确定如何设置这个引脚(时钟、复用、方向、电平)。
-
写程序 -- 配置寄存器,点亮或熄灭 LED。
下面我们就按这三步展开。
二、看原理图:LED 怎么连到芯片的?
2.1 LED 在原理图中的抽象
实际 LED 长得五花八门(插脚、贴片),原理图中统一抽象成下图的样子:
text
+3.3V
│
╭┴╮
│ │ 电阻(限流,一般 470Ω ~ 1kΩ)
╰┬╯
│
┌┴┐
│▼│ LED(箭头指向GND表示正极接电源)
└┬┘
│
GND
重点:
-
LED 需要 正向电压 才能点亮(阳极 > 阴极)。
-
必须串联电阻 限制电流,否则 LED 会烧坏。
2.2 两种最基本的驱动方式
| 方式 | 连接方法 | 芯片引脚动作 |
|---|---|---|
| 方式1 | 芯片引脚 → 电阻 → LED → GND | 引脚输出 3.3V 点亮,输出 0V 熄灭 |
| 方式2 | VCC(3.3V) → 电阻 → LED → 芯片引脚 | 引脚输出 0V 点亮,输出 3.3V 熄灭 |
方式1也叫 "高电平点亮" ,方式2是 "低电平点亮" 。
很多开发板喜欢用低电平点亮,因为芯片引脚的 灌电流 能力通常比拉电流强。
2.3 引脚驱动能力不足怎么办?------ 三极管驱动
有些芯片引脚输出电流很小(比如 <5mA),直接推 LED 可能很暗。这时可以加一颗 三极管 或 MOSFET 来驱动:
-
方式3:NPN 三极管,基极接芯片引脚,集电极接 LED 负极。引脚输出 1.2V 左右(高电平)→ 三极管导通 → LED 亮。
-
方式4:PNP 三极管,基极接芯片引脚,发射极接电源。引脚输出 0V(低电平)→ 三极管导通 → LED 亮。
💡 在你的学习板原理图上,先看清 LED 是接到哪个 GPIO 引脚,以及是高电平点亮还是低电平点亮。这是编程的第一步。
三、GPIO 通用操作方法
GPIO = General Purpose Input/Output ,通用输入输出口。
任何芯片的 GPIO 操作都逃不出下面几个步骤:
3.1 GPIO 模块的五大要素
-
使能(时钟/电源) -- 先打开 GPIO 模块的时钟,否则寄存器无法读写。
-
模式(Mode) -- 引脚可能复用为 UART、I2C、SPI 等,需要先设为 GPIO 模式。
-
方向(Direction) -- 设为输入还是输出。
-
数值(Data) -- 输出时写高/低电平;输入时读电平状态。
-
其他 -- 中断、防抖、唤醒等(后续再学)。
3.2 寄存器操作的两种方式
方式A:读-改-写(Read-Modify-Write)
适用于普通的数据寄存器(写 1 置位,写 0 清零,不能只改某一位)。
cs
c
// 设置 bit n 为 1
val = *reg;
val |= (1 << n);
*reg = val;
// 清除 bit n 为 0
val = *reg;
val &= ~(1 << n);
*reg = val;
方式B:set-and-clear 协议(硬件原子操作)
部分芯片提供 SET 和 CLR 寄存器,向 SET 写 1 就置位,向 CLR 写 1 就清零,互不影响。
cs
c
// 设置 bit n
*set_reg = (1 << n);
// 清除 bit n
*clr_reg = (1 << n);
i.MX6ULL 的 GPIO 数据寄存器
GPIOx_DR不支持 set-and-clear,只能用读-改-写。但它的 IOMUXC 部分有类似设计,后面会见到。
四、实战:i.MX6ULL 的 GPIO 操作
我们以最常见的 i.MX6ULL 芯片为例,演示如何点亮一个 LED。
假设 LED 接在 GPIO1_IO00 引脚,且是 高电平点亮(方式1)。
4.1 总体流程
cs
text
1. 使能 GPIO1 时钟 → 操作 CCM_CCGR1 寄存器
2. 将引脚复用为 GPIO1_IO00 → 操作 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00
3. 设置方向为输出 → 操作 GPIO1_GDIR
4. 输出高电平或低电平 → 操作 GPIO1_DR
4.2 步骤详解 + 如何查手册
步骤1:使能 GPIO1 时钟
为什么? 如果不打开时钟,GPIO 模块无法工作,寄存器读写无效(可能引起总线错误)。
查手册 :
打开 i.MX6ULL 参考手册,找到 Chapter 18 Clock Controller Module (CCM) 。
搜索 GPIO1_CLK_ENABLE,会看到如下信息:
| 时钟信号 | 控制位 | 所在寄存器 |
|---|---|---|
| gpio1_clk_enable | CG13 | CCM_CCGR1 |
| gpio2_clk_enable | CG15 | CCM_CCGR0 |
| gpio3_clk_enable | CG13 | CCM_CCGR2 |
| gpio4_clk_enable | CG6 | CCM_CCGR3 |
| gpio5_clk_enable | CG15 | CCM_CCGR1 |
注意:每个 CG 位占 2 bits,编码含义:
00:所有模式下关闭时钟
01:Run 模式开,Wait/Stop 关
11:所有模式(除 Stop)开我们一般设为
11,让时钟一直开着。
实际操作:
cs
c
#define CCM_CCGR1 (*(volatile unsigned int *)0x020C406C) // 地址见手册
// 使能 GPIO1 时钟:设置 CG13 两位为 11
CCM_CCGR1 |= (3 << 26); // CG13 从 bit26 开始,占2位
💡 教你找地址 :
手册中搜索 "CCM_CCGR1" 即可看到它的基址和偏移。i.MX6ULL 的 CCM 基址是
0x020C4000,CCGR1 偏移0x6C,所以地址0x020C406C。
步骤2:将引脚复用为 GPIO 功能
一个物理引脚可能有多个功能(UART、I2C、GPIO...)。我们需要选择 GPIO 功能。
查手册 :
找到 Chapter 30 IOMUX Controller (IOMUXC) 。
搜索引脚名称,比如 GPIO1_IO00,找到 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00 寄存器。
其中关键字段 MUX_MODE:
| MUX_MODE 值 | 功能 |
|---|---|
| 0101 (5) | ALT5 -- GPIO1_IO00 |
| 0000 | I2C2_SCL |
| 0001 | GPT1_CAPTURE1 |
| ... | ... |
要设置为 GPIO,就写入 5。
实际操作:
cs
c
#define SW_MUX_GPIO1_IO00 (*(volatile unsigned int *)0x020E005C)
// 将 MUX_MODE 设置为 5 (ALT5, GPIO 功能)
SW_MUX_GPIO1_IO00 = 5;
💡 地址怎么来的?
IOMUXC 基址
0x020E0000,GPIO1_IO00的 SW_MUX 偏移0x005C→0x020E005C。
另外这个寄存器里还有一个 SION 位(Software Input On),如果希望输入路径一直打开(比如读引脚状态),可以置1。输出操作一般不需要。
步骤3:设置方向为输出
每个 GPIO 端口有一组 GDIR 寄存器,每 bit 控制一个引脚的方向:
-
0= 输入 -
1= 输出
查手册 :
Chapter 28 General Purpose I/O (GPIO) 。
找到 GPIOx_GDIR 寄存器描述,bit n 对应 GPIO[n] 的方向。
实际操作:
cs
c
#define GPIO1_GDIR (*(volatile unsigned int *)0x0209C004)
// 设置 bit0 为 1,表示 GPIO1_IO00 为输出
GPIO1_GDIR |= (1 << 0);
步骤4:输出高/低电平
输出电平通过 数据寄存器 GPIOx_DR 控制。
写 1 则引脚输出高电平(3.3V),写 0 输出低电平(0V)。
⚠️ 特别注意 :当方向为 输入 时,读取
GPIOx_DR返回的不是你写入的值,而是 引脚实际电平 (等同于GPIOx_PSR状态寄存器)。手册原文:While the GPIO direction is set to input, a read access to GPIO_DR returns the GPIO_PSR data.
实际操作:
cs
c
#define GPIO1_DR (*(volatile unsigned int *)0x0209C000)
// 点亮 LED(高电平点亮)
GPIO1_DR |= (1 << 0);
// 熄灭 LED
GPIO1_DR &= ~(1 << 0);
4.3 完整代码示例(裸机风格)
cs
c
// 定义寄存器地址
#define CCM_CCGR1 (*(volatile unsigned int *)0x020C406C)
#define SW_MUX_GPIO1_0 (*(volatile unsigned int *)0x020E005C)
#define GPIO1_GDIR (*(volatile unsigned int *)0x0209C004)
#define GPIO1_DR (*(volatile unsigned int *)0x0209C000)
void delay(volatile int t) { while(t--); }
int main(void)
{
// 1. 使能 GPIO1 时钟 (CG13 = 11)
CCM_CCGR1 |= (3 << 26);
// 2. 复用为 GPIO1_IO00
SW_MUX_GPIO1_0 = 5; // ALT5
// 3. 设置为输出
GPIO1_GDIR |= (1 << 0);
while (1) {
// 点亮
GPIO1_DR |= (1 << 0);
delay(500000);
// 熄灭
GPIO1_DR &= ~(1 << 0);
delay(500000);
}
return 0;
}
五、怎么查手册?我教你方法
很多初学者觉得手册几百页很恐怖,其实你只需要学会 有目的地搜索。
5.1 必备的两份文档
-
i.MX6ULL 参考手册 (
IMX6ULLRM.pdf) -- 详细寄存器描述。 -
数据手册 (
IMX6ULLCEC.pdf) -- 引脚定义、电气特性。
5.2 查手册的典型路径
| 任务 | 搜索关键词 | 去哪一章 |
|---|---|---|
| 找某个引脚的复用配置 | GPIO1_IO00 或 SW_MUX_CTL_PAD_ |
IOMUXC 章节 |
| 打开 GPIO 时钟 | GPIO1_CLK_ENABLE 或 CCM_CCGR1 |
CCM 章节 |
| GPIO 方向寄存器 | GPIOx_GDIR |
GPIO 章节 |
| GPIO 数据寄存器 | GPIOx_DR |
GPIO 章节 |
| 引脚在哪个 GPIO 组? | 查看原理图或数据手册的 Ball Map | 数据手册 |
5.3 小技巧
-
手册 PDF 的 书签 是最快导航。
-
善用 Ctrl+F,但尽量搜准确的寄存器名或信号名。
-
看到
ALT5这样的复用值,就在附近找表格,里面列出了所有复用功能。
七、总结
| 步骤 | 寄存器类别 | 作用 |
|---|---|---|
| 1 | CCM_CCGRx | 使能 GPIO 模块时钟 |
| 2 | IOMUXC_SW_MUX | 把引脚切换为 GPIO 功能 |
| 3 | GPIOx_GDIR | 设置方向(输入/输出) |
| 4 | GPIOx_DR | 输出高低电平 / 读取输入 |
掌握了这套 "时钟 → 复用 → 方向 → 数据" 的套路,任何 Cortex-A 芯片的 GPIO 你都能轻松驾驭。
下一步,你可以试试读取按键输入、控制蜂鸣器,甚至用 GPIO 模拟 I2C 协议。
动手是学会的唯一途径。现在就去打开你的开发板,把第一个 LED 点起来吧!