
简介面向嵌入式与物联网开发者压缩包提供AW9523 GPIO扩展芯片的I2C驱动源码用于解决微控制器GPIO引脚不足的问题。驱动以C/C编写适合学习I2C通信、驱动编程以及底层硬件控制的开发者参考。包内共1个c源文件大小仅3KB代码精简便于快速阅读与移植内容通常涵盖I2C初始化、寄存器读写、GPIO方向与电平设置、中断处理及错误检查等模块可对照AW9523数据手册理解寄存器映射与操作流程。已有244人学习下载这份源码能帮助开发者理清从总线配置到引脚操作的完整链路也可作为在Linux等系统上集成同类I2C GPIO扩展芯片时的参考模板。1. 拿到 gpio-aw9523.zip 之后这份 I2C 扩展 GPIO 驱动源码到底能干什么做嵌入式的人大概率遇到过这种场景MCU 的 GPIO 不够用I2C 总线上却还空闲着一堆地址。AW9523 就是为这种场景设计的 16 路 GPIO 扩展芯片而 gpio-aw9523.zip 这个压缩包里的 gpio-aw9523.c 文件正是读写这颗芯片的核心驱动实现。我拆完这份源码后最大的感触是它不是一个完整的内核驱动框架而是一份能直接抄进自己工程的 C 语言驱动模块I2C 初始化、寄存器读写、GPIO 方向与电平控制、中断处理这些骨架全都在你只需要按自己的平台改改 I2C 适配层。适合谁用手里正好有 AW9523 芯片、想快速跑通 GPIO 扩展功能的嵌入式工程师和学生。这份资源解决的核心问题是不用从 AW9523 数据手册的寄存器定义开始一点点啃直接拿着源码对照着改就能出效果。2. AW9523 驱动源码的底细I2C 通信机制与源码文件结构拆解2.1 AW9523 是什么一颗芯片如何通过 I2C 扩展出 16 路 GPIOAW9523 是一颗 I2C 接口的 GPIO 扩展芯片内部集成了 16 路可配置为输入或输出的 GPIO还带了 LED 呼吸灯控制功能。它的 I2C 从机地址是 7 位地址 0x58加上读写位之后写地址是 0xB0读地址是 0xB1。这一点在写驱动的时候要格外注意因为很多 I2C 控制器驱动里填的地址格式不一样有的填 7 位地址有的填 8 位地址。我在这上面吃过亏后面避坑章节会细说。芯片内部寄存器空间分为几个区域0x00 到 0x0F 是全局控制寄存器包括芯片软复位、GPO 输出模式选择、GPIO 方向配置等0x10 到 0x2F 是 GPIO 输入输出数据寄存器0x30 到 0x4F 是中断相关寄存器0x50 到 0x7F 是 LED 控制寄存器。驱动源码里的寄存器操作函数本质上就是通过 I2C 总线对这几个区域做读写。理解了这个寄存器布局读源码的时候就不会迷路。2.2 gpio-aw9523.c 源码文件里有什么函数模块与调用关系梳理打开 gpio-aw9523.c你会发现它的结构非常清晰基本是照着 Linux 内核驱动的风格写的但去掉了内核 API 依赖可以直接移植到 RTOS 或裸机环境。核心函数大致可以分为这几个模块第一个是初始化模块函数名一般是 aw9523_init 或类似命名。它做的事情包括初始化 I2C 控制器、设置时钟频率、配置从机地址、检测芯片是否在线通常通过读取芯片 ID 寄存器 0x02 来判断、执行软复位、把默认方向配置为输入。源码里 I2C 初始化部分用的抽象接口是 i2c_write 和 i2c_read 这类自定义函数这层是可移植性的关键所在。第二个是寄存器读写模块。AW9523 的寄存器操作规定写寄存器时先发送寄存器地址字节再发送数据字节读寄存器时先发送寄存器地址字节然后重新发起读操作。源码里读单个寄存器的典型实现如下static int aw9523_read_reg(uint8_t reg, uint8_t *val) { uint8_t cmd reg; int ret; ret i2c_write(aw9523_dev, cmd, 1); // 先写寄存器地址 if (ret 0) return ret; ret i2c_read(aw9523_dev, val, 1); // 再读取数据 if (ret 0) return ret; return 0; }这段代码的逻辑是标准的 I2C 寄存器读流程分两次传输完成。第一行先发送要访问的寄存器地址第二次读取芯片返回的数据。i2c_write 和 i2c_read 是平台相关的底层接口你在 STM32 上用 HAL 库就替换为 HAL_I2C_Master_Transmit 和 HAL_I2C_Master_Receive在 Linux 上用 ioctl 就替换为 read/write 系统调用。参数 reg 是寄存器地址val 是存放读取结果的指针返回值用负数表示错误码0 表示成功。这里有一个容易被忽略的细节i2c_write 发送的地址字节不含读写位因为在这一步只是定位寄存器。第三个是 GPIO 操作模块包括设置方向、设置电平、读取电平三个核心函数int aw9523_gpio_direction(uint8_t pin, uint8_t is_output) { uint8_t reg, mask, val; if (pin AW9523_GPIO_NUM) return -EINVAL; reg 0x02 (pin / 8); // 方向寄存器分两组每路 GPIO 占 1 位 mask 1 (pin % 8); if (aw9523_read_reg(reg, val) 0) return -EIO; if (is_output) val | mask; else val ~mask; return aw9523_write_reg(reg, val, 1); } int aw9523_gpio_write(uint8_t pin, uint8_t level) { uint8_t reg, mask, val; reg 0x10 (pin / 8); // 输出数据寄存器 mask 1 (pin % 8); if (aw9523_read_reg(reg, val) 0) return -EIO; if (level) val | mask; else val ~mask; return aw9523_write_reg(reg, val, 1); } int aw9523_gpio_read(uint8_t pin, uint8_t *level) { uint8_t reg, mask, val; reg 0x12 (pin / 8); // 输入数据寄存器 mask 1 (pin % 8); if (aw9523_read_reg(reg, val) 0) return -EIO; *level (val mask) ? 1 : 0; return 0; }三个函数的逻辑一脉相承都是“先算出目标寄存器地址和位掩码再读改写”。direction 函数操作方向寄存器注意这里的位含义是 1 表示输出、0 表示输入。write 函数操作输出数据寄存器但这里有一个坑AW9523 的 GPO 输出模式有开漏和推挽两种配置默认是开漏模式如果你要驱动 LED 或者做推挽输出需要在初始化时把 0x12 寄存器的对应位配好否则输出高电平时外部必须接上拉电阻。read 函数读的是输入寄存器返回的是引脚当前电平但要注意如果引脚方向配成了输出读输入寄存器可能读到的是输出状态的回读而不是真实电平。2.3 I2C 时序与地址问题的源码级验证方法当你拿到这份源码准备移植时建议先用逻辑分析仪或者示波器抓一次 I2C 波形验证芯片是否正常工作。具体做法是在初始化函数里加上一段读芯片 ID 的代码AW9523 的芯片 ID 寄存器是 0x02读到的值应该是 0x23 或 0x25不同批次可能略有差异。如果读不到预期的 ID优先检查 I2C 地址是否填对了——很多人在 0x58 和 0xB0 之间反复横跳花了半天时间最后发现是地址格式的问题。另外I2C 时钟频率建议设置在 400kHz 以下AW9523 虽然支持高速模式但走线过长的 PCB 上容易因为信号质量导致读回的数据偶发错误。3. 驱动移植实战把 gpio-aw9523.c 跑在 STM32 裸机环境上3.1 移植前的准备工作硬件接线与工程文件放置移植前先确认硬件接线是否正确。AW9523 需要四根线VCC、GND、SCL、SDA。VCC 接 3.3V 电源注意某些 AW9523 型号支持 5V 供电但 I2C 电平要匹配 MCU 的 I/O 电平如果 MCU 是 3.3V 而芯片供电是 5VSCL 和 SDA 需要用电平转换电路或者确认芯片的 SCL/SDA 引脚是否耐受 5V 输入。工程文件的放置建议是新建一个 aw9523 目录把 gpio-aw9523.c 和对应的头文件 gpio-aw9523.h 放进去然后在你的 I2C 平台适配文件里实现 i2c_write 和 i2c_read 两个函数。这里我一般会这么做在源码里定义一个结构体包含设备地址和底层操作函数指针typedef struct { uint8_t i2c_addr; // 7 位 I2C 地址 int (*i2c_write)(uint8_t addr, uint8_t *buf, uint16_t len); int (*i2c_read)(uint8_t addr, uint8_t *buf, uint16_t len); } aw9523_dev_t;这个结构体的好处是让驱动层和平台层解耦你在 STM32 上实现这两个函数时第一个参数就是结构体里的 i2c_addr。注意这里的 i2c_addr 填的是 7 位地址 0x58还是 8 位地址 0xB0取决于你的底层实现怎么往 I2C 控制器的 DR 寄存器里填。STM32 HAL 库的 HAL_I2C_Master_Transmit 函数第二个参数 DevAddress 填的是 7 位地址左移一位后的值也就是 0xB0因为它内部会自动把地址和数据拼在一起发送。这是我移植过程中最容易翻车的地方建议定义结构体时注释写清楚每种平台填什么。3.2 移植的四个核心步骤初始化、方向配置、读写电平、中断处理第一步是初始化芯片。在你的板级初始化代码里调用 aw9523_initvoid aw9523_init_platform(void) { aw9523_dev_t dev { .i2c_addr 0x58, // 7 位地址写地址为 0xB0读地址为 0xB1 .i2c_write stm32_i2c_write, .i2c_read stm32_i2c_read }; if (aw9523_init(dev) 0) { // 初始化失败一般是 I2C 不通或者地址不对 printf(AW9523 init failed\r\n); } else { printf(AW9523 init success\r\n); } }第二步是配置 GPIO 方向。假设你要把 P0_0 到 P0_3 配置为输出P0_4 到 P0_7 配置为输入for (int i 0; i 4; i) { aw9523_gpio_direction(i, 1); // 输出 } for (int i 4; i 8; i) { aw9523_gpio_direction(i, 0); // 输入 }第三步是读写电平。输出一路高电平控制 LED读取一个外部按键的电平aw9523_gpio_write(0, 1); // P0_0 输出高电平 uint8_t key_level 0; aw9523_gpio_read(4, key_level); // 读取 P0_4 的电平第四步是中断处理。AW9523 支持 GPIO 电平变化中断初始化时在 0x10 到 0x13 的中断掩码寄存器里配置哪些引脚使能中断然后在中断服务函数里读取 0x10 的中断状态寄存器处理完后写 1 清除中断标志。移植时要注意这个中断清除机制如果只读状态不写清除标志中断会一直触发。3.3 验证移植结果的三个自测点移植完不要急着跑业务逻辑先做三个自测。第一个自测是回读方向寄存器确认你配置的方向值确实写进去了。第二个自测是做一个 GPIO 回环测试把一个输出引脚和一个输入引脚短接输出高电平后读输入引脚确认能读到高电平。第三个自测是中断功能测试把按键接到使能了中断的引脚上按下按键后确认中断状态寄存器置位了。这三个自测都通过说明底层 I2C 通信和寄存器读写链路是通的。4. 避坑指南移植 AW9523 驱动时最常见的六个坑与排查方法4.1 坑一I2C 地址填错导致设备找不到现象初始化时读芯片 ID 返回错误或者读回来的值全是 0xFF。原因AW9523 的 I2C 地址有多个来源可配默认 7 位地址是 0x58但有些模块上通过 A0、A1 引脚把地址改成了 0x59 甚至 0x5A。更常见的是 7 位地址和 8 位地址混用在代码里填了 0x58 但底层 I2C 驱动把它当 8 位地址直接发送导致多了一位。解决先查硬件上 A0、A1 引脚接法确认实际地址。然后确认你的 I2C 驱动层的地址参数格式。STM32 HAL 库填的是 7 位地址左移一位即 0xB0裸机寄存器操作通常填 7 位地址0x58。如果不确定用逻辑分析仪抓波形看设备地址字节到底是 0xB0 还是 0x58。4.2 坑二I2C 时钟频率过高导致读回的数据偶发错误现象现象很玄学GPIO 读写偶尔失败用示波器看波形发现 SDA 线上的信号边沿不够陡峭。原因AW9523 支持 400kHz 快速模式但 PCB 走线长、上拉电阻阻值不合适时信号上升时间会超过 I2C 协议规定的限制。这种情况在高速模式下更容易暴露低速模式下反而不出错。解决把 I2C 时钟频率降到 100kHz 标准模式试一下如果问题消失说明是信号完整性问题。检查 SCL 和 SDA 的上拉电阻通常 4.7kΩ 在 400kHz 下比较稳妥如果走线长可以换成 2.2kΩ。实在不行就保持 100kHz 运行GPIO 扩展场景对速率要求没那么高。4.3 坑三开漏输出模式下高电平电压异常现象GPIO 配置为输出高电平但用万用表量引脚电压只有 1.5V 左右不是预期的 3.3V。原因AW9523 的 GPO 输出模式默认是开漏开漏模式下输出高电平时引脚实际处于高阻状态需要外部上拉电阻才能拉到 VCC 电平。如果外部没有接上拉引脚电平就处于不确定状态。解决检查 0x12 寄存器的高位配置把对应 GPO 组设置为推挽输出模式。推挽模式下输出低电平时能主动灌电流输出高电平时能主动提供驱动能力。注意这个寄存器是按组配置的每组 8 路 GPIO 用的是同一个配置位不能单独配置某一路。4.4 坑四中断标志没清除导致中断风暴现象系统上电后中断一直触发CPU 负载异常高进中断服务函数读中断状态寄存器发现有值但处理后下次又立即触发。原因AW9523 的中断清除机制是“写 1 清除”也就是向中断状态寄存器的对应位写 1而不是写 0。初学者很容易按常规思维写 0 去清除结果标志位一直没清掉芯片持续拉低 INT 引脚。解决读中断状态寄存器后把读到的值原样写回对应的中断清除寄存器。注意 AW9523 的清除操作是在状态寄存器本身还是独立的清除寄存器不同批次的数据手册描述有差异以你手头芯片型号的官方数据手册为准。4.5 坑五方向配置后电平不生效现象调用方向配置函数设置为输出后接着设置电平但外部测量发现电平没有变化。原因AW9523 的方向寄存器和输出数据寄存器是分开的而且有些版本要求输出使能还需额外配置。另外如果配置方向时先向寄存器写入值但这个值没有正确回写也可能导致方向没生效。解决先读方向寄存器确认当前值再修改对应位后写回。写回后再次读出来确认是否写入成功。如果确认方向正确但电平仍不对检查输出数据寄存器的地址是否正确——AW9523 的输出数据寄存器地址是 0x10 和 0x11输入数据寄存器是 0x12 和 0x13不要搞混了。4.6 坑六硬件 I2C 和软件模拟 I2C 的时序差异导致不稳定现象用硬件 I2C 跑得好好的代码换成 GPIO 软件模拟 I2C 后偶尔出现读写失败。原因软件模拟 I2C 的时序精度不如硬件 I2C特别是起始条件和停止条件的建立时间容易不满足要求。AW9523 对这个时序比较敏感。解决软件模拟 I2C 时在 SCL 翻转之间加上适当的延时保证 SCL 高电平和低电平的持续时间都不小于 1.3 微秒对应 400kHz 的半周期。如果还是不稳定把时钟频率降到 100kHz即延时调整为 5 微秒左右。另外注意软件模拟 I2C 时 SDA 的切换要在 SCL 低电平期间完成。5. 把这份驱动玩出进阶效果中断驱动的按键检测与 LED 呼吸灯实现5.1 中断驱动的按键检测从轮询到事件触发的优化驱动源码里自带了中断处理框架但默认是轮询模式。想把它改成中断驱动模式核心改动点在三个地方。第一处是初始化时配置 0x10 到 0x13 的中断掩码寄存器使能你要监听的引脚。第二处是写中断服务函数读取中断状态寄存器判断是哪个引脚触发的然后调用对应的回调函数。第三处是在主循环里不再轮询读取引脚电平而是靠中断回调函数置一个事件标志位。我自己习惯的做法是定义一个回调函数指针注册机制让业务层代码不用关心中断细节typedef void (*aw9523_irq_cb_t)(uint8_t pin); void aw9523_irq_register_cb(uint8_t pin, aw9523_irq_cb_t cb) { if (pin AW9523_GPIO_NUM) { irq_callbacks[pin] cb; } } void aw9523_irq_handler(void) { uint8_t status 0; aw9523_read_reg(0x10, status); // 读中断状态寄存器 for (int i 0; i 8; i) { if (status (1 i)) { if (irq_callbacks[i]) { irq_callbacks[i](i); } } } aw9523_write_reg(0x10, status, 1); // 写 1 清除中断标志 }这段代码把中断状态遍历和回调分发逻辑封装好了你只需要在业务代码里注册回调函数就能在按键按下时拿到通知而不需要在主循环里忙等。注意中断状态寄存器 0x10 对应 P0_0 到 P0_70x11 对应 P1_0 到 P1_7两个都要读取和清除。5.2 LED 呼吸灯AW9523 专用硬件 PWM 的配置方法AW9523 相比普通 GPIO 扩展芯片最大的卖点就是内置了 12 位 PWM 呼吸灯控制器。实现 LED 呼吸灯效果时不需要 MCU 额外输出 PWM 信号只需要配置芯片内部的 LED 控制寄存器组。每个 LED 通道有三个关键寄存器电流控制寄存器选择最大电流档位PWM 占空比寄存器设置亮度呼吸控制寄存器设置呼吸周期和渐变步长。配置步骤如下先把对应引脚的模式从普通 GPIO 切换为 LED 模式通过 0x12 寄存器的高 4 位设置然后选择电流档位0x40 到 0x47 寄存器控制每路 LED 的电流上限最后配置呼吸参数包括上电渐变时间和下降渐变时间。源码里没有单独封装 LED 控制函数需要对照数据手册自行添加但寄存器操作函数的框架直接用现成的即可。5.3 从这份源码延伸到其它 I2C 器件的驱动写法沉淀拆过这份 gpio-aw9523.c 之后我形成了一个固定的 I2C 器件驱动写法套路。第一步定义设备结构体和底层 I2C 操作函数指针把平台相关性隔离在外。第二步封装寄存器读写函数所有上层操作都通过这两个函数进出。第三步按照芯片功能模块拆分成多个操作函数每个函数只做一件简单的事用“读改写”的方式保持寄存器其他位不受影响。这个套路写出来的驱动无论是接温度传感器、RTC 还是其他 I2C 设备基本都能快速套用。从那以后我每次拿到一个新的 I2C 芯片都会强制走一遍这个过程先写设备结构体再写寄存器读写层然后写功能模块层最后写自测代码。这份 gpio-aw9523.zip 的价值不在于代码本身能直接编译运行而在于它完整展示了一个 I2C GPIO 扩展驱动从寄存器映射到接口封装的思考路径照着拆一遍比看十篇 I2C 协议教程都管用。希望帮到你。本文还有配套的精品资源点击获取