以下是针对 Synopsys DesignWare APB GPIO (DW_apb_gpio) 模块寄存器的超详细技术说明,附带完整的裸机驱动代码示例。内容涵盖所有标准寄存器、中断与去抖动逻辑(仅端口A)、以及实战操作流程。
一、模块概述
DW_apb_gpio 是一个通过 APB 总线访问的通用输入输出控制器,最多支持 4 个端口(Port A, B, C, D) 。每个端口最多 32 个引脚,实际数量由 IP 配置决定。端口 A 额外支持 中断管理 和 去抖动(debounce) 功能。
所有寄存器都是 32 位宽,且仅支持 32 位对齐访问。
二、寄存器详细列表及位域定义
2.1 端口操作寄存器组(每个端口独立)
基址 =
base_addr + port_index * 0x0Cport_index: 0=Port A, 1=Port B, 2=Port C, 3=Port D
| 偏移 | 寄存器名 | 类型 | 描述 |
|---|---|---|---|
| 0x00 | SWPORTx_DR |
R/W | 软件端口数据寄存器(输出值) |
| 0x04 | SWPORTx_DDR |
R/W | 软件端口方向寄存器(1=输出,0=输入) |
| 0x08 | SWPORTx_CTL |
R/W | 软件端口控制寄存器(0=GPIO模式,1=硬件功能模式) |
位域定义(三者结构相同):
-
位
n(0 ≤ n ≤ 31)对应端口内的第 n 号引脚。 -
未实现的引脚对应的位是 只读 0。
⚠️ 注意 :修改
DR和DDR之前,必须确保CTL对应位为 0(GPIO 模式)。
2.2 外部端口寄存器(读取物理电平)
| 偏移 | 寄存器名 | 类型 | 描述 |
|---|---|---|---|
| 0x50 + port*0x04 | EXT_PORTx |
RO | 读取引脚实际电平(无论方向、模式) |
-
位
n为 1 表示引脚当前为高电平,0 为低电平。 -
即使在输出模式下,也可以回读到引脚上的实际电平(可用于开漏输出时的状态确认)。
2.3 中断相关寄存器(仅端口 A)
基址 =
base_addr(无需端口偏移)这些寄存器只对端口 A 有效,其他端口访问无效或返回 0。
| 偏移 | 寄存器名 | 类型 | 描述 |
|---|---|---|---|
| 0x30 | INTEN |
R/W | 中断使能(1=使能中断) |
| 0x34 | INTMASK |
R/W | 中断屏蔽(1=屏蔽中断,注意与 INTEN 逻辑不同) |
| 0x38 | INTTYPE_LEVEL |
R/W | 中断类型(0=电平触发,1=边沿触发) |
| 0x3C | INT_POLARITY |
R/W | 中断极性(0=低电平/下降沿,1=高电平/上升沿) |
| 0x40 | INTSTATUS |
RO | 原始中断状态(未屏蔽前) |
| 0x44 | RAW_INTSTATUS |
RO | 原始中断状态(同 INTSTATUS) |
| 0x48 | DEBOUNCE |
R/W | 去抖动使能(1=使能去抖动) |
| 0x4C | PORTA_EOI |
WO | 写任意值清除已触发的中断(边沿模式必须写) |
中断触发方式组合 (INTTYPE_LEVEL 与 INT_POLARITY):
| TYPE | POLARITY | 触发方式 |
|---|---|---|
| 0 | 0 | 低电平触发 |
| 0 | 1 | 高电平触发 |
| 1 | 0 | 下降沿触发 |
| 1 | 1 | 上升沿触发 |
去抖动:使能后,引脚电平需稳定超过 2 个 APB 时钟周期才会被采样,用于消除按键抖动。
2.4 配置与识别寄存器
| 偏移 | 寄存器名 | 类型 | 描述 |
|---|---|---|---|
| 0x64 | GPIO_ID_CODE |
RO | IP 标识码,固定为 0x44573030('DW00') |
| 0x70 | GPIO_CONFIG_REG2 |
RO | 硬件配置参数(端口数、各端口引脚数等) |
| 0x6C | GPIO_CONFIG_REG1 |
RO | 另一配置参数(一般用于内部) |
驱动可通过
GPIO_CONFIG_REG2动态检测端口数量及每个端口的引脚数,实现通用性。
三、完整驱动代码示例(裸机 C 语言)
以下代码实现了 DW_apb_gpio 的完整驱动,支持多端口、中断配置、去抖动等。
cpp
#include <stdint.h>
#include <stdbool.h>
/* 寄存器偏移宏 */
#define DW_GPIO_SWPORT_DR 0x00
#define DW_GPIO_SWPORT_DDR 0x04
#define DW_GPIO_SWPORT_CTL 0x08
#define DW_GPIO_INTEN 0x30
#define DW_GPIO_INTMASK 0x34
#define DW_GPIO_INTTYPE_LEVEL 0x38
#define DW_GPIO_INT_POLARITY 0x3C
#define DW_GPIO_INTSTATUS 0x40
#define DW_GPIO_RAW_INTSTATUS 0x44
#define DW_GPIO_DEBOUNCE 0x48
#define DW_GPIO_PORTA_EOI 0x4C
#define DW_GPIO_EXT_PORT 0x50
#define DW_GPIO_ID_CODE 0x64
/* 端口数量及位宽(通常从 config 寄存器读取,此处先定义典型值) */
#define DW_GPIO_PORTS_MAX 4
#define DW_GPIO_PINS_MAX 32
/* 驱动句柄 */
typedef struct {
uint32_t base_addr; // 模块基地址
uint8_t port_num; // 端口数量(实际硬件支持)
uint8_t pin_count[4]; // 每个端口的引脚数
} dw_gpio_t;
/* ---------- 底层寄存器操作 ---------- */
static inline uint32_t dw_read32(uint32_t addr) {
return *(volatile uint32_t *)addr;
}
static inline void dw_write32(uint32_t addr, uint32_t val) {
*(volatile uint32_t *)addr = val;
}
/* 获取指定端口的基地址 */
static inline uint32_t dw_port_base(dw_gpio_t *gpio, int port) {
return gpio->base_addr + port * 0x0C;
}
/* ---------- 初始化与硬件检测 ---------- */
void dw_gpio_init(dw_gpio_t *gpio, uint32_t base) {
gpio->base_addr = base;
/* 读取 ID 码确认硬件存在 */
uint32_t id = dw_read32(base + DW_GPIO_ID_CODE);
if (id != 0x44573030) { // "DW00"
// 硬件不存在或错误,可设置标志或直接返回
gpio->port_num = 0;
return;
}
/* 从 GPIO_CONFIG_REG2 读取配置 (偏移 0x70) */
uint32_t cfg2 = dw_read32(base + 0x70);
// cfg2 位域: [3:0] = 端口数量-1; [7:4] = 端口A引脚数-1; 以此类推
gpio->port_num = (cfg2 & 0x0F) + 1;
if (gpio->port_num > DW_GPIO_PORTS_MAX)
gpio->port_num = DW_GPIO_PORTS_MAX;
/* 解析每个端口的引脚数量 (最多32) */
for (int p = 0; p < gpio->port_num; p++) {
int shift = 4 + p * 4;
int bits = (cfg2 >> shift) & 0x0F;
gpio->pin_count[p] = (bits == 0) ? 32 : bits; // 0 表示 32
}
}
/* ---------- GPIO 基本操作 ---------- */
void dw_gpio_set_direction(dw_gpio_t *gpio, int port, int pin, bool output) {
if (port >= gpio->port_num) return;
if (pin >= gpio->pin_count[port]) return;
uint32_t reg = dw_port_base(gpio, port) + DW_GPIO_SWPORT_DDR;
uint32_t val = dw_read32(reg);
if (output)
val |= (1U << pin);
else
val &= ~(1U << pin);
dw_write32(reg, val);
}
void dw_gpio_set_function(dw_gpio_t *gpio, int port, int pin, bool hw_mode) {
if (port >= gpio->port_num) return;
if (pin >= gpio->pin_count[port]) return;
uint32_t reg = dw_port_base(gpio, port) + DW_GPIO_SWPORT_CTL;
uint32_t val = dw_read32(reg);
if (hw_mode)
val |= (1U << pin);
else
val &= ~(1U << pin);
dw_write32(reg, val);
}
void dw_gpio_write(dw_gpio_t *gpio, int port, int pin, bool high) {
if (port >= gpio->port_num) return;
if (pin >= gpio->pin_count[port]) return;
uint32_t reg = dw_port_base(gpio, port) + DW_GPIO_SWPORT_DR;
uint32_t val = dw_read32(reg);
if (high)
val |= (1U << pin);
else
val &= ~(1U << pin);
dw_write32(reg, val);
}
bool dw_gpio_read(dw_gpio_t *gpio, int port, int pin) {
if (port >= gpio->port_num) return false;
if (pin >= gpio->pin_count[port]) return false;
uint32_t reg = gpio->base_addr + DW_GPIO_EXT_PORT + port * 4;
uint32_t val = dw_read32(reg);
return (val >> pin) & 0x1;
}
/* ---------- 中断配置(仅端口 A) ---------- */
void dw_gpio_int_enable(dw_gpio_t *gpio, int pin, bool enable) {
if (pin >= gpio->pin_count[0]) return; // 仅端口A
uint32_t reg = gpio->base_addr + DW_GPIO_INTEN;
uint32_t val = dw_read32(reg);
if (enable)
val |= (1U << pin);
else
val &= ~(1U << pin);
dw_write32(reg, val);
}
void dw_gpio_int_set_trigger(dw_gpio_t *gpio, int pin, bool level, bool high_polarity) {
if (pin >= gpio->pin_count[0]) return;
uint32_t type_reg = gpio->base_addr + DW_GPIO_INTTYPE_LEVEL;
uint32_t pol_reg = gpio->base_addr + DW_GPIO_INT_POLARITY;
uint32_t type_val = dw_read32(type_reg);
uint32_t pol_val = dw_read32(pol_reg);
if (level)
type_val |= (1U << pin);
else
type_val &= ~(1U << pin);
if (high_polarity)
pol_val |= (1U << pin);
else
pol_val &= ~(1U << pin);
dw_write32(type_reg, type_val);
dw_write32(pol_reg, pol_val);
}
void dw_gpio_debounce_enable(dw_gpio_t *gpio, int pin, bool enable) {
if (pin >= gpio->pin_count[0]) return;
uint32_t reg = gpio->base_addr + DW_GPIO_DEBOUNCE;
uint32_t val = dw_read32(reg);
if (enable)
val |= (1U << pin);
else
val &= ~(1U << pin);
dw_write32(reg, val);
}
uint32_t dw_gpio_get_raw_int_status(dw_gpio_t *gpio) {
return dw_read32(gpio->base_addr + DW_GPIO_RAW_INTSTATUS);
}
void dw_gpio_clear_int(dw_gpio_t *gpio, int pin) {
// 对于边沿触发,写 EOI 寄存器清除
// 对于电平触发,需要外部信号撤销才能清除,但写 EOI 也无害
(void)pin; // 忽略 pin,EOI 写入任意值清除所有已响应的中断
dw_write32(gpio->base_addr + DW_GPIO_PORTA_EOI, 0x0);
}
四、典型使用场景代码示例
4.1 初始化并使能某个输出引脚
cpp
dw_gpio_t gpio;
dw_gpio_init(&gpio, 0xFC000000); // 假设基址 0xFC000000
/* 配置 Port B, Pin 5 为输出模式并输出高电平 */
int port = 1; // B
int pin = 5;
dw_gpio_set_function(&gpio, port, pin, false); // GPIO 模式
dw_gpio_set_direction(&gpio, port, pin, true); // 输出
dw_gpio_write(&gpio, port, pin, true); // 输出高电平
4.2 读取输入引脚
cpp
int port = 0; // A
int pin = 10;
dw_gpio_set_function(&gpio, port, pin, false);
dw_gpio_set_direction(&gpio, port, pin, false); // 输入
bool level = dw_gpio_read(&gpio, port, pin);
4.3 配置端口 A 的引脚 2 为上升沿中断,并使能去抖动
cpp
int pin = 2;
dw_gpio_set_function(&gpio, 0, pin, false);
dw_gpio_set_direction(&gpio, 0, pin, false); // 必须为输入
dw_gpio_debounce_enable(&gpio, pin, true); // 使能去抖动
dw_gpio_int_set_trigger(&gpio, pin, false, true); // 边沿 + 上升沿
dw_gpio_int_enable(&gpio, pin, true); // 使能中断
// 在中断服务函数中:
// uint32_t status = dw_gpio_get_raw_int_status(&gpio);
// if (status & (1<<pin)) { ...; dw_gpio_clear_int(&gpio, pin); }
4.4 将引脚切换为硬件功能(如 UART TX)
cpp
/* 假设 Port C, Pin 0 内部连接到 UART TX */
int port = 2;
int pin = 0;
dw_gpio_set_function(&gpio, port, pin, true); // 硬件模式
// 此时 SWPORTx_DR/DDR 不再影响该引脚,由 UART 控制
五、重要注意事项
-
读-修改-写保护 :操作
DR,DDR,CTL等寄存器时,务必先读出原值,修改特定位后再写回,以免影响其他引脚。示例代码已遵循此原则。 -
中断清除:
-
边沿触发模式:中断发生后必须写
PORTA_EOI寄存器(任意值)来清除中断标志。 -
电平触发模式:中断由外部电平决定,电平撤销后中断自动消失,但建议也写
PORTA_EOI以保证统一处理。
-
-
去抖动影响:使能去抖动后,引脚电平变化需要稳定至少 2 个 APB 时钟周期才能被识别,因此中断响应会有微小延迟,但对于按键防抖非常有效。
-
未实现引脚的位:若硬件端口实际只有 16 个引脚,则高位(16~31)写操作无效,读返回 0。
-
硬件功能优先级 :当
CTL位为 1 时,引脚完全由内部硬件模块控制,软件配置的DDR和DR被忽略。切换回 GPIO 模式时需重新配置方向。 -
端口A 中断与其它端口的隔离:不要尝试对 Port B/C/D 使用中断相关寄存器,它们要么不存在,要么行为未定义。
六、总结
DW_apb_gpio 模块寄存器设计清晰、功能强大,通过三个基础寄存器(数据、方向、功能选择)即可完全控制任意 GPIO 引脚,而端口 A 额外提供了完整的中断与去抖动支持。掌握上述寄存器定义及驱动代码后,你可以轻松在裸机或 RTOS 中集成该模块,实现各种外设控制。
如需 Linux 内核驱动版本,其原理完全一致,只需替换为 writel/readl 并适配 gpio_chip 框架即可。