Synopsys DesignWare APB GPIO (DW_apb_gpio) 模块寄存器详解

以下是针对 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 * 0x0C

port_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

⚠️ 注意 :修改 DRDDR 之前,必须确保 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_LEVELINT_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 控制

五、重要注意事项

  1. 读-修改-写保护 :操作 DR, DDR, CTL 等寄存器时,务必先读出原值,修改特定位后再写回,以免影响其他引脚。示例代码已遵循此原则。

  2. 中断清除

    • 边沿触发模式:中断发生后必须写 PORTA_EOI 寄存器(任意值)来清除中断标志。

    • 电平触发模式:中断由外部电平决定,电平撤销后中断自动消失,但建议也写 PORTA_EOI 以保证统一处理。

  3. 去抖动影响:使能去抖动后,引脚电平变化需要稳定至少 2 个 APB 时钟周期才能被识别,因此中断响应会有微小延迟,但对于按键防抖非常有效。

  4. 未实现引脚的位:若硬件端口实际只有 16 个引脚,则高位(16~31)写操作无效,读返回 0。

  5. 硬件功能优先级 :当 CTL 位为 1 时,引脚完全由内部硬件模块控制,软件配置的 DDRDR 被忽略。切换回 GPIO 模式时需重新配置方向。

  6. 端口A 中断与其它端口的隔离:不要尝试对 Port B/C/D 使用中断相关寄存器,它们要么不存在,要么行为未定义。


六、总结

DW_apb_gpio 模块寄存器设计清晰、功能强大,通过三个基础寄存器(数据、方向、功能选择)即可完全控制任意 GPIO 引脚,而端口 A 额外提供了完整的中断与去抖动支持。掌握上述寄存器定义及驱动代码后,你可以轻松在裸机或 RTOS 中集成该模块,实现各种外设控制。

如需 Linux 内核驱动版本,其原理完全一致,只需替换为 writel/readl 并适配 gpio_chip 框架即可。

相关推荐
危桥带雨1 小时前
PWR代码部分
stm32·单片机·嵌入式硬件
fengfuyao9851 小时前
STM32 定时器程序(标准外设库版本)
stm32·单片机·嵌入式硬件
振南的单片机世界1 小时前
高阻态:GPIO输入的“不打扰”哲学
stm32·单片机·嵌入式硬件
LCG元2 小时前
STM32实战:基于STM32F103的FatFs文件系统移植(SD卡读写)
stm32·单片机·嵌入式硬件
minji...2 小时前
Linux 网络套接字编程(二)从 0 到 1 实现 UDP 回声服务器,recvfrom,sendto
linux·运维·网络·单片机·udp
rit84324992 小时前
基于STM32的RTC(实时时钟)程序设计与实现
stm32·嵌入式硬件·实时音视频
九鼎创展科技2 小时前
联发科 MT8883 核心优势深度解析:对比 MT8385/MT8788/MT8183
人工智能·科技·嵌入式硬件·边缘计算
zmj32032415 小时前
单片机串口收发数据不可靠--用做指令会执行错误动作
单片机·嵌入式硬件·串口
yuan1999715 小时前
STM32 驱动 RC522(MFRC522)实现方案
单片机·嵌入式硬件