本篇总结
使用 GPIO 位带操作,控制 LED 闪烁。
🔑 KEIL MDK、UART调试、GPIO
本示例使用位带操作控制 GPIO 引脚输出,实现 LED 闪烁,展示位带操作的简洁与高效。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口收发测试工具:点击下载
硬件连接
本例程使用 PD14 引脚控制 LED 闪烁。W55MH32L-EVB 开发板上 PD14 通常连接用户 LED(低电平点亮)。USART1 用于输出调试信息。
| 引脚 | 功能 | 方向 | 模式 |
| PD14 | LED 控制输出(低电平点亮) | 开发板 → LED | 推挽输出 (Out_PP) |
| PA9 | USART1_TX | 开发板 → PC | 复用推挽输出 (AF_PP) |
| PA10 | USART1_RX | PC → 开发板 | 浮空输入 (IN_FLOATING) |
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\GPIO_BitBand\,核心文件如下:
GPIO_BitBand/
├── USER/
│ ├── main.c ← 位带宏定义 + GPIO配置 + LED闪烁
│ ├── w55mh32_conf.h ← 外设头文件集中包含
│ ├── w55mh32_it.c/h ← 中断服务函数
│ ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│ └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
└── delay/
└── delay.c/h ← SysTick 延时函数
步骤1:位带操作宏定义与主函数
文件:USER/main.c — 定义位带操作宏,将 GPIO 输出寄存器的每个比特映射到独立的别名地址,通过 PDout(14) = 1 直接控制 PD14 输出。
/* 位带操作宏定义 */
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + \
((addr & 0xFFFFF) << 5) + (bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
/* GPIO 输出寄存器地址映射 */
#define GPIOA_ODR_Addr (GPIOA_BASE + 12)
#define GPIOD_ODR_Addr (GPIOD_BASE + 12)
#define GPIOC_ODR_Addr (GPIOC_BASE + 12)
/* PAout(n) → 操作 GPIOA 第 n 位 */
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr, n)
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr, n)
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr, n)
int main(void)
{
RCC_ClocksTypeDef clocks;
GPIO_InitTypeDef GPIO_InitStructure;
delay_init();
UART_Configuration(115200);
RCC_GetClocksFreq(&clocks);
printf("GPIO Bit-Band Test.\n");
/* 使能 GPIOD 时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
/* PD14: 推挽输出 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
while (1)
{
/* 位带操作:直接对单个位赋值,无需读-改-写 */
PDout(14) = 1; /* PD14 = 1,LED 灭 */
delay_ms(500);
PDout(14) = 0; /* PD14 = 0,LED 亮 */
delay_ms(500);
}
}
位带映射公式:AliasAddr = 0x42000000 + (ByteOffset × 32) + (BitNumber × 4)。位带区地址范围 0x40000000~0x400FFFFF(外设区),每个比特映射到位带别名区的一个 32 位字地址。通过 PDout(n) 宏可直接对 GPIOD 的第 n 位赋值,等价于原子操作,无需 GPIO_SetBits/GPIO_ResetBits。
如果你想自定义
- 控制其他引脚 → 使用
PAout(n)、PCout(n) 等宏操作其他 GPIO
- 改闪烁速度 → 修改
delay_ms() 参数
- 读取输入 → 定义
PBin(n) 宏映射 GPIO 输入数据寄存器(IDR)
编译与下载
Keil MDK 中打开 GPIO_BitBand.uvprojx,F7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。
运行验证
下载固件并按 RESET 后,PD14 连接的 LED 以 500ms 间隔闪烁。串口打印 “GPIO Bit-Band Test.” 提示信息。通过示波器测量 PD14 可观察到方波信号,频率约 1Hz。
本例程的运行效果为 LED 闪烁,可通过观察板载 LED 或示波器测量 PD14 引脚波形验证。无串口输出截图。
常见问题
Q: LED 不闪烁
确认 PD14 引脚连接了 LED。检查 GPIO 是否配置为推挽输出模式(GPIO_Mode_Out_PP)。确认 RCC_APB2PeriphClockCmd 已使能 GPIOD 时钟。
Q: 位带操作编译报错
检查宏定义是否正确。BITBAND、MEM_ADDR、BIT_ADDR 宏需在文件顶部定义。确保 GPIOx_ODR_Addr 宏的偏移量正确(ODR 偏移为基地址 + 12)。
Q: 位带操作和 GPIO_SetBits/ResetBits 有什么区别
位带操作是单条STR指令的原子操作,不会被中断打断。GPIO_SetBits 实际是读-改-写操作(先读 ODR,修改位,再写回),在多任务或中断环境中可能产生竞态条件。位带操作更安全且效率更高。
总结
本文基于 W55MH32L 的 Cortex-M3 位带机制实现了 GPIO 单比特原子操作:
- 位带映射:将外设区 0x40000000~0x400FFFFF 的每个比特映射到位带别名区的独立字地址
- 通过
PDout(n) = 1/0 宏直接对 GPIO 某一位赋值,等价于原子操作
- 相比 GPIO_SetBits/ResetBits 的读-改-写流程,位带操作更简洁、更安全(无竞态条件)
- 示例使用 PD14 控制 LED 以 500ms 间隔闪烁