第3章 GPIO基础:从寄存器到点亮LED
GPIO,说白了就是单片机最基础的“手脚”。你要跟外界打交道,全靠它。我刚开始学单片机时,觉得GPIO不就是输出个高电平低电平嘛,有啥好学的?后来做项目才发现,这里面的坑还真不少。这一章,咱们就把GPIO的底裤扒干净。
3.1 GPIO模块原理
CMS32的GPIO模块,内部结构其实挺讲究的。每个GPIO引脚背后,都有一堆控制逻辑:输出数据寄存器、输入数据寄存器、方向控制、上下拉电阻、还有各种复用功能选择。
我画了个简化的内部结构图,你看一眼就明白了:
从图里能看出来,数据从寄存器到引脚,中间要经过好几道关卡。方向寄存器决定你是“说话”还是“听话”,功能选择决定你是当普通GPIO还是干别的活。
3.2 寄存器映射
CMS32的GPIO寄存器,都映射在特定的地址空间里。我习惯把常用的几个寄存器记在脑子里,查手册时能快不少。
| 寄存器名称 | 偏移地址 | 位宽 | 功能描述 |
|---|---|---|---|
| PxOUT | 0x00 | 8位 | 输出数据寄存器,写1输出高电平,写0输出低电平 |
| PxIN | 0x04 | 8位 | 输入数据寄存器,读取引脚当前电平状态 |
| PxDIR | 0x08 | 8位 | 方向控制寄存器,1为输出,0为输入 |
| PxPU | 0x0C | 8位 | 上拉电阻使能寄存器,1使能上拉 |
| PxPD | 0x10 | 8位 | 下拉电阻使能寄存器,1使能下拉 |
| PxAF | 0x14 | 8位 | 功能选择寄存器,1为复用功能,0为GPIO |
小技巧:操作寄存器时,我建议用“读-改-写”的方式,不要直接赋值。比如要设置P1.3为输出,先读P1DIR,再或上对应位,最后写回去。这样不会影响其他引脚。
3.3 输入输出模式配置
配置GPIO模式,其实就是设置方向寄存器。但这里有个细节——输入模式下,你还要考虑上下拉电阻;输出模式下,你要考虑驱动能力。
我一般这样配置:
// 配置P1.3为输出模式
P1DIR |= (1 << 3); // 设置第3位为1,输出模式
// 配置P1.4为输入模式
P1DIR &= ~(1 << 4); // 清除第4位,输入模式
// 输入模式下使能上拉电阻
P1PU |= (1 << 4); // 使能P1.4上拉
嗯,这里要注意:配置输入模式时,如果引脚悬空,电平是不确定的。我遇到过好几次,按键没按下时电平乱跳,后来才发现是忘了使能上拉电阻。
3.4 推挽输出 vs 开漏输出
这两个概念,很多初学者容易搞混。我简单说说:
- 推挽输出:输出高电平时,PMOS导通;输出低电平时,NMOS导通。说白了就是“能拉能推”,驱动能力强。
- 开漏输出:只有NMOS,输出低电平时导通,输出高电平时高阻。需要外部上拉电阻才能输出高电平。
| 特性 | 推挽输出 | 开漏输出 |
|---|---|---|
| 高电平驱动 | 直接输出VCC | 需要外部上拉 |
| 低电平驱动 | 直接输出GND | 直接输出GND |
| 多引脚线与 | 不支持(会短路) | 支持(线与逻辑) |
| 电平转换 | 不支持 | 支持(通过上拉电阻) |
| 典型应用 | 普通IO、LED驱动 | I2C总线、中断共享 |
避坑指南:我曾经在一个项目里,把两个推挽输出的引脚直接连在一起,结果一个输出高一个输出低,瞬间电流飙升,芯片直接冒烟了。开漏输出就不会有这个问题,因为高阻态不会产生冲突。
3.5 上拉电阻与下拉电阻
上拉和下拉,说白了就是给引脚一个“默认状态”。
- 上拉电阻:引脚悬空时,被拉到高电平。适合按键检测(按键接地)。
- 下拉电阻:引脚悬空时,被拉到低电平。适合按键检测(按键接VCC)。
CMS32内部集成了上拉和下拉电阻,阻值大约在40kΩ~60kΩ之间。我个人习惯用内部上拉,省一个外部电阻,板子能小一点。
// 使能P1.0内部上拉
P1PU |= (1 << 0);
// 使能P1.1内部下拉
P1PD |= (1 << 1);
// 注意:上拉和下拉不能同时使能,否则相当于接了个分压电阻
关键点:上拉和下拉不能同时使能!我见过有人两个都置1了,结果引脚电平被拉到中间值,既不是高也不是低,逻辑判断全乱套了。
3.6 点亮第一个LED
好了,理论知识差不多了。咱们来点实际的——点亮LED。这是嵌入式开发的“Hello World”。
硬件连接很简单:LED正极接VCC(3.3V),负极通过一个限流电阻(330Ω)接到P1.0。为什么这样接?因为单片机输出低电平时,电流从VCC经过LED、电阻流到引脚,这叫“灌电流”驱动。
代码也很简单:
#include "CMS32.h"
void delay(volatile unsigned int count) {
while(count--);
}
void main(void) {
// 1. 配置P1.0为输出模式
P1DIR |= (1 << 0);
// 2. 关闭复用功能,确保是GPIO模式
P1AF &= ~(1 << 0);
// 3. 输出低电平,点亮LED
P1OUT &= ~(1 << 0);
while(1) {
// 闪烁效果
P1OUT ^= (1 << 0); // 翻转P1.0
delay(500000); // 延时
}
}
代码里有个小细节:P1AF &= ~(1 << 0)。为什么要加这一行?因为CMS32上电后,有些引脚默认是复用功能模式。我刚开始就踩过这个坑,配置了半天输出,LED死活不亮,最后发现是功能选择寄存器没设置。
调试技巧:如果LED不亮,先量一下引脚电压。输出低电平时,引脚应该接近0V;输出高电平时,引脚应该接近3.3V。如果电压不对,检查方向寄存器和功能选择寄存器。
你想想看,一个LED从亮到灭,背后涉及了多少寄存器?方向、输出、功能选择,还有延时循环。这就是嵌入式开发的魅力——每一行代码都在直接操控硬件。
这一章的内容就到这里。GPIO看似简单,但它是所有外设的基础。后面的章节,我们会在这个基础上,一步步搭建更复杂的系统。