1 概述
DW_apb_gpio是新思科技推出的基于AMBA 2.0APB总线的通用输入输出知识产权核,属于DesignWare库中的可合成组件。它广泛应用于SoC设计中,用于通过APB接口控制芯片引脚的输入/输出状态。
核心特性:
- 总线接口:遵循AMBA 2.0APB协议,作为从设备挂载在APB总线上,适用于低带宽外设控制。
- 端口配置:最多支持4个独立端口(Port A、B、C、D),每个端口最多可配置32个引脚,总引脚数可达128个,具体数量由IP配置决定。
- 功能模式:每个引脚支持软件控制(通过寄存器读写)或硬件控制(外部信号直接驱动),部分引脚可混合配置。
- 中断支持:仅Port A支持中断功能,包括电平/边沿触发、极性配置、去抖动(Debounce)及中断屏蔽/使能逻辑。
2 寄存器概述
端口操作寄存器(每个端口独立):
基址 = base_addr + port_index * 0x0C
port_index: 0=Port A, 1=Port B, 2=Port C, 3=Port D
位 n(0 ≤ n ≤ 31)对应端口内的第 n 号引脚。
| 偏移量 | 寄存器名称 | 属性 | 功能描述 |
|---|---|---|---|
| 0x00 | SWPORTx_DR | R/W | 软件端口数据寄存器(输出值,一般仅在输出时配置) |
| 0x04 | SWPORTx_DDR | R/W | 软件端口方向寄存器(1=输出,0=输入) |
| 0x08 | SWPORTx_CTL | R/W | 软件端口控制寄存器(0=GPIO模式,1=硬件功能模式) |
外部端口寄存器:
| 偏移量 | 寄存器名称 | 属性 | 功能描述 |
|---|---|---|---|
| 0x50 + port_index*0x04 | EXT_PORTx | RO | 读取引脚实际电平(一般在输入时使用,比如配置为输入时,实时查看引脚的输入值) |
- 位n为1表示引脚当前为高电平(3.3v/5v等),0为低电平。
- 即使在输出模式下,也可以回读到引脚上的实际电平(可用于开漏输出时的状态确认)。
中断相关寄存器(仅端口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时钟周期才会被采样,用于消除按键抖动。
中断清除:
- 边沿触发模式:中断发生后必须写PORTA_EOI寄存器(任意值)来清除中断标志。
- 电平触发模式:中断由外部电平决定,电平撤销后中断自动消失,但建议也写PORTA_EOI以保证统一处理。
- 如果是双边沿触发时,INTTYPE_LEVEL和INT_POLARITY寄存器的值是不起作用的,此时以GPIO_INT_BOTHEDGE寄存器为主。
配置与识别寄存器
| 偏移量 | 寄存器名称 | 属性 | 功能描述 |
|---|---|---|---|
| 0x64 | GPIO_ID_CODE | RO | IP版本的标识码 |
| 0x70 | GPIO_CONFIG_REG2 | RO | 读该寄存器各端口引脚数等 |
| 0x74 | GPIO_CONFIG_REG1 | RO | 读该寄存器的bit2-bit3确定端口数量 |
| ... | ... | ... | ... |
3 编程概要
寄存器偏移量定义:
/* 寄存器偏移宏 */
#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方向,配置mode等:
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;
}
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);
}
中断相关的函数操作:
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);
}
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);
}