STM32驱动AW9523B IO扩展芯片:从寄存器映射到多设备管理实战

张开发
2026/4/7 22:58:40 15 分钟阅读

分享文章

STM32驱动AW9523B IO扩展芯片:从寄存器映射到多设备管理实战
1. 认识AW9523B你的STM32 IO口救星第一次用STM32做项目时最头疼的就是IO口不够用。记得当时为了省两个引脚硬是把按键扫描改成了矩阵式结果调试时差点崩溃。直到发现了AW9523B这颗神器——16个可编程IO扩展芯片通过I2C两根线就能控制瞬间解放了我的引脚焦虑。AW9523B最让我惊艳的是它的多功能性16个双向IO每个引脚可独立配置为输入/输出内置LED驱动输出模式下支持8~16mA恒流驱动中断功能输入状态变化时自动触发中断级联支持同一I2C总线可挂载多达4颗芯片实际项目中我用它做过这些骚操作连接16个机械按键带硬件消抖驱动128颗LED组成的点阵屏作为多路传感器状态监测接口扩展成调试用的状态指示灯阵列// 最简单的使用示例 void LED_Blink(void) { EXIO_WriteBit(EXIO_IC_1, EXIO_PORT_0, EXIO_PIN_0, Bit_SET); HAL_Delay(500); EXIO_WriteBit(EXIO_IC_1, EXIO_PORT_0, EXIO_PIN_0, Bit_RESET); }2. 寄存器映射把硬件寄存器变成内存变量刚开始看AW9523B的datasheet时我被那一堆寄存器地址搞晕了。直到发现寄存器映射这个神器——把硬件寄存器变成结构体变量操作硬件就像操作内存一样简单。2.1 寄存器结构体设计我的经验是先用typedef定义寄存器组结构体typedef struct { uint8_t InputReg[2]; // P0/P1输入寄存器 uint8_t OutputReg[2]; // P0/P1输出寄存器 uint8_t ConfigReg[2]; // P0/P1配置寄存器 uint8_t IntReg[2]; // P0/P1中断屏蔽寄存器 } AW95_REG_T;然后定义具体的寄存器地址常量#define P0_INPUT 0x00 #define P0_OUTPUT 0x02 #define P0_CONFIG 0x04 #define P0INT_MSK 0x06 // P1寄存器地址偏移量12.2 实例化寄存器映射通过结构体初始化实现地址绑定const AW95_REG_T Aw95Reg { {P0_INPUT, P1_INPUT}, {P0_OUTPUT, P1_OUTPUT}, {P0_CONFIG, P1_CONFIG}, {P0INT_MSK, P1INT_MSK} };这样要修改P1的输出寄存器时代码就变得非常直观i2c_WriteBytes(devAddr, Aw95Reg.OutputReg[1], data, 1);3. 多设备管理框架设计当项目需要连接多颗AW9523B时我踩过一个坑没有统一管理导致代码混乱。后来设计了这个框架支持动态扩展芯片数量。3.1 设备描述结构体每个芯片对应一个实例结构体typedef struct { uint8_t IcId; // 设备I2C地址 uint8_t IrqFlag; // 中断标志 uint8_t Input[2]; // 端口输入缓存 uint8_t Output[2]; // 端口输出缓存 uint8_t Config[2]; // 配置状态缓存 uint8_t Int[2]; // 中断状态缓存 } AW95_IC_T;3.2 设备管理池使用指针数组管理多个设备AW95_IC_T Aw95Ic1 { .IcId 0xB0 }; // 第一颗芯片 AW95_IC_T Aw95Ic2 { .IcId 0xB4 }; // 第二颗芯片 AW95_IC_T *Aw95IcBuf[] {Aw95Ic1, Aw95Ic2}; #define GPIO_IC_NUM (sizeof(Aw95IcBuf)/sizeof(Aw95IcBuf[0]))这样遍历所有芯片就很简单for(int i0; iGPIO_IC_NUM; i){ AW95_IC_T *pDev Aw95IcBuf[i]; // 对每个芯片进行操作 }4. 驱动核心实现详解4.1 初始化流程完整的初始化需要三步走硬件检测检查I2C通信是否正常uint8_t id; i2c_ReadBytes(0xB0, 0x10, id, 1); // 读取芯片ID if(id ! 0x23) return ERROR;模式配置设置全局控制寄存器uint8_t mode 0x10; // GPIO模式 i2c_WriteBytes(0xB0, GCR_REG, mode, 1);默认状态初始化EXIO_PinInit(EXIO_IC_1, EXIO_PORT_0, EXIO_PIN_0, EXIO_OUTPUT); EXIO_WriteBit(EXIO_IC_1, EXIO_PORT_0, EXIO_PIN_0, Bit_RESET);4.2 中断处理实战AW9523B的中断处理有几个关键点配置中断触发条件// 设置P00引脚下降沿触发 uint8_t intCfg 0x01; i2c_WriteBytes(devAddr, INT_REG, intCfg, 1);在GPIO中断服务函数中标记void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin GPIO_PIN_2){ Aw95Ic1.IrqFlag 1; } }主循环中处理void EXIO_Handle(void) { if(Aw95Ic1.IrqFlag){ Aw95Ic1.IrqFlag 0; i2c_ReadBytes(Aw95Ic1.IcId, Aw95Reg.InputReg[0], Aw95Ic1.Input, 2); // 处理输入变化... } }5. 工程化进阶技巧5.1 错误处理机制实际项目中必须考虑I2C通信失败的情况#define I2C_RETRY_MAX 3 int safe_I2C_Write(uint8_t devAddr, uint8_t reg, uint8_t *data) { int retry 0; HAL_StatusTypeDef status; do { status HAL_I2C_Mem_Write(hi2c1, devAddr, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); if(status HAL_OK) return 0; retry; HAL_Delay(5); } while(retry I2C_RETRY_MAX); return -1; }5.2 性能优化方案当需要快速操作多个引脚时建议批量写入减少I2C传输次数void EXIO_WritePort(EXIO_IC_E ic, EXIO_PORT_E port, uint8_t value) { AW95_IC_T *pDev Aw95IcBuf[ic]; pDev-Output[port] value; i2c_WriteBytes(pDev-IcId, Aw95Reg.OutputReg[port], value, 1); }缓存同步定期同步硬件状态void EXIO_SyncAll(void) { for(int i0; iGPIO_IC_NUM; i){ AW95_IC_T *pDev Aw95IcBuf[i]; i2c_ReadBytes(pDev-IcId, Aw95Reg.InputReg[0], pDev-Input, 2); } }6. 常见问题排查指南6.1 芯片无响应检查硬件连接I2C上拉电阻(4.7kΩ)电源滤波电容(0.1μF)地址引脚电平验证I2C信号// 扫描I2C总线上的设备 void I2C_Scan(void) { for(uint8_t addr0x08; addr0x78; addr){ if(HAL_I2C_IsDeviceReady(hi2c1, addr1, 3, 100) HAL_OK){ printf(Found device at 0x%02X\n, addr); } } }6.2 输出异常确认配置模式GPIO模式GCR寄存器bit41LED模式GCR寄存器bit40检查输出驱动能力默认4mA可通过CR寄存器提升到16mA// 设置P00引脚驱动能力为16mA uint8_t current 0x01; i2c_WriteBytes(devAddr, 0x08, current, 1);7. 扩展应用实例7.1 矩阵键盘扫描利用16个IO口实现4x4矩阵键盘uint8_t KEY_Scan(void) { // 设置列线为输出行线为输入 for(int col0; col4; col){ EXIO_WritePort(EXIO_IC_1, EXIO_PORT_0, ~(1col)); uint8_t rows EXIO_ReadPort(EXIO_IC_1, EXIO_PORT_1); if(rows ! 0xFF){ return (rows 4) | (1col); } } return 0; }7.2 多路PWM控制虽然AW9523B没有硬件PWM但可以用软件模拟void PWM_Thread(void) { static uint8_t duty 0; while(1){ for(int i0; i100; i){ EXIO_WriteBit(EXIO_IC_1, EXIO_PORT_0, EXIO_PIN_0, (iduty)?Bit_SET:Bit_RESET); HAL_Delay(1); } duty (duty 1) % 100; } }

更多文章