STM32U575项目IO口不够用手把手教你用PCA9535拓展板实现独立按键控制附完整代码在嵌入式开发中IO资源紧张是常见问题。最近在开发一个智能家居控制面板时STM32U575的GPIO很快就被各种传感器和显示屏占满而产品经理还在不断要求增加功能按键。这时I2C接口的IO扩展芯片PCA9535就成了救命稻草。1. 硬件准备与环境搭建1.1 硬件连接PCA9535是一款通过I2C总线扩展16个GPIO的芯片工作电压1.65V-5.5V兼容3.3V和5V系统。典型连接方式如下STM32U575 --I2C-- PCA9535 -- 按键矩阵/LED阵列具体接线时需要注意SCL/SDA需要上拉电阻通常4.7kΩ地址引脚A0-A2的接法决定了器件地址中断引脚可选项可用于按键唤醒1.2 开发环境配置使用STM32CubeMX进行基础配置启用I2C外设标准模式100kHz或快速模式400kHz配置GPIO为上拉输入用于按键或推挽输出用于LED生成基础工程代码关键配置参数示例参数推荐值说明I2C速度400kHz平衡速度和稳定性上拉电阻4.7kΩ确保信号质量去抖时间20ms机械按键典型值2. PCA9535驱动开发2.1 寄存器详解PCA9535有8个核心寄存器分为两组// 寄存器定义 typedef enum { REG_INPUT_0 0x00, // P0输入状态 REG_INPUT_1 0x01, // P1输入状态 REG_OUTPUT_0 0x02, // P0输出状态 REG_OUTPUT_1 0x03, // P1输出状态 REG_POLARITY_0 0x04, // P0极性反转 REG_POLARITY_1 0x05, // P1极性反转 REG_CONFIG_0 0x06, // P0方向配置 REG_CONFIG_1 0x07 // P1方向配置 } PCA9535_Register;2.2 基础读写函数首先实现基础的寄存器读写函数// 写寄存器 HAL_StatusTypeDef PCA9535_WriteReg(uint8_t reg, uint8_t data) { uint8_t buf[2] {reg, data}; return HAL_I2C_Master_Transmit(hi2c1, PCA9535_ADDR, buf, 2, HAL_MAX_DELAY); } // 读寄存器 HAL_StatusTypeDef PCA9535_ReadReg(uint8_t reg, uint8_t *data) { HAL_StatusTypeDef ret HAL_I2C_Master_Transmit(hi2c1, PCA9535_ADDR, reg, 1, HAL_MAX_DELAY); if(ret ! HAL_OK) return ret; return HAL_I2C_Master_Receive(hi2c1, PCA9535_ADDR, data, 1, HAL_MAX_DELAY); }注意实际项目中应该添加重试机制和超时处理3. 独立按键实现方案3.1 硬件设计推荐两种按键连接方式直接连接每个按键独立连接到PCA9535的一个GPIO另一端接地优点软件简单缺点占用IO多矩阵扫描4x4矩阵只需8个IO优点节省IO缺点需要扫描逻辑3.2 按键驱动实现实现带消抖的按键检测// 按键状态结构体 typedef struct { uint8_t current; // 当前状态 uint8_t last; // 上次状态 uint8_t changed; // 状态变化标志 uint32_t last_time; // 上次变化时间 } KeyState; // 初始化所有按键状态 KeyState keys[16]; // 按键扫描函数 void Key_Scan(void) { static uint8_t port0, port1; PCA9535_ReadReg(REG_INPUT_0, port0); PCA9535_ReadReg(REG_INPUT_1, port1); uint32_t now HAL_GetTick(); for(int i0; i8; i) { // P0端口按键 uint8_t state !(port0 (1i)); // 低电平有效 if(keys[i].current ! state) { if(now - keys[i].last_time 20) { // 20ms消抖 keys[i].last keys[i].current; keys[i].current state; keys[i].changed 1; keys[i].last_time now; } } else { keys[i].changed 0; } // P1端口按键同上 // ... } }3.3 使用示例在主循环中调用while(1) { Key_Scan(); // 检测按键0按下 if(keys[0].changed keys[0].current) { printf(Key0 pressed\n); // 执行相应功能 } HAL_Delay(10); }4. 高级功能与优化4.1 中断模式配置PCA9535支持中断输出可以降低CPU负载配置寄存器使能中断连接INT引脚到STM32的外部中断引脚在中断服务程序中读取按键状态配置代码示例// 使能P0所有引脚的中断 PCA9535_WriteReg(REG_CONFIG_0, 0xFF); // 全部设为输入 PCA9535_WriteReg(REG_POLARITY_0, 0xFF); // 极性反转低电平触发4.2 功耗优化对于电池供电设备在不使用时关闭I2C总线利用中断唤醒代替轮询适当降低I2C时钟频率4.3 多设备扩展单个I2C总线可以挂载多个PCA9535通过不同的地址区分A2A1A0地址0000x200010x21............1110x275. 完整代码示例以下是整合后的模块化代码pca9535.h#ifndef __PCA9535_H #define __PCA9535_H #include stm32u5xx_hal.h #define PCA9535_ADDR 0x20 // 默认地址 typedef enum { KEY_EVENT_NONE 0, KEY_EVENT_PRESS, KEY_EVENT_RELEASE, KEY_EVENT_LONG_PRESS } KeyEvent; void PCA9535_Init(I2C_HandleTypeDef *hi2c); uint8_t PCA9535_ReadGPIO(uint8_t port); void PCA9535_WriteGPIO(uint8_t port, uint8_t value); KeyEvent Key_GetEvent(uint8_t key_num); void Key_UpdateAll(void); #endifpca9535.c#include pca9535.h #include string.h static I2C_HandleTypeDef *hi2c_pca; // 初始化函数 void PCA9535_Init(I2C_HandleTypeDef *hi2c) { hi2c_pca hi2c; // 配置所有端口为输入带内部上拉 uint8_t config[] {0xFF, 0xFF}; HAL_I2C_Mem_Write(hi2c_pca, PCA9535_ADDR1, 0x06, 1, config, 2, 100); } // 读取GPIO状态 uint8_t PCA9535_ReadGPIO(uint8_t port) { uint8_t reg port ? 0x01 : 0x00; uint8_t value; HAL_I2C_Mem_Read(hi2c_pca, PCA9535_ADDR1, reg, 1, value, 1, 100); return value; } // 其他函数实现...在实际项目中这套方案成功将原本需要16个GPIO的按键矩阵缩减到只需2个I2C引脚释放出的GPIO可以用于其他重要功能。调试时发现适当降低I2C速度能显著提高稳定性特别是在长线缆连接时。
STM32U575项目IO口不够用?手把手教你用PCA9535拓展板实现独立按键控制(附完整代码)
发布时间:2026/5/30 4:03:54
STM32U575项目IO口不够用手把手教你用PCA9535拓展板实现独立按键控制附完整代码在嵌入式开发中IO资源紧张是常见问题。最近在开发一个智能家居控制面板时STM32U575的GPIO很快就被各种传感器和显示屏占满而产品经理还在不断要求增加功能按键。这时I2C接口的IO扩展芯片PCA9535就成了救命稻草。1. 硬件准备与环境搭建1.1 硬件连接PCA9535是一款通过I2C总线扩展16个GPIO的芯片工作电压1.65V-5.5V兼容3.3V和5V系统。典型连接方式如下STM32U575 --I2C-- PCA9535 -- 按键矩阵/LED阵列具体接线时需要注意SCL/SDA需要上拉电阻通常4.7kΩ地址引脚A0-A2的接法决定了器件地址中断引脚可选项可用于按键唤醒1.2 开发环境配置使用STM32CubeMX进行基础配置启用I2C外设标准模式100kHz或快速模式400kHz配置GPIO为上拉输入用于按键或推挽输出用于LED生成基础工程代码关键配置参数示例参数推荐值说明I2C速度400kHz平衡速度和稳定性上拉电阻4.7kΩ确保信号质量去抖时间20ms机械按键典型值2. PCA9535驱动开发2.1 寄存器详解PCA9535有8个核心寄存器分为两组// 寄存器定义 typedef enum { REG_INPUT_0 0x00, // P0输入状态 REG_INPUT_1 0x01, // P1输入状态 REG_OUTPUT_0 0x02, // P0输出状态 REG_OUTPUT_1 0x03, // P1输出状态 REG_POLARITY_0 0x04, // P0极性反转 REG_POLARITY_1 0x05, // P1极性反转 REG_CONFIG_0 0x06, // P0方向配置 REG_CONFIG_1 0x07 // P1方向配置 } PCA9535_Register;2.2 基础读写函数首先实现基础的寄存器读写函数// 写寄存器 HAL_StatusTypeDef PCA9535_WriteReg(uint8_t reg, uint8_t data) { uint8_t buf[2] {reg, data}; return HAL_I2C_Master_Transmit(hi2c1, PCA9535_ADDR, buf, 2, HAL_MAX_DELAY); } // 读寄存器 HAL_StatusTypeDef PCA9535_ReadReg(uint8_t reg, uint8_t *data) { HAL_StatusTypeDef ret HAL_I2C_Master_Transmit(hi2c1, PCA9535_ADDR, reg, 1, HAL_MAX_DELAY); if(ret ! HAL_OK) return ret; return HAL_I2C_Master_Receive(hi2c1, PCA9535_ADDR, data, 1, HAL_MAX_DELAY); }注意实际项目中应该添加重试机制和超时处理3. 独立按键实现方案3.1 硬件设计推荐两种按键连接方式直接连接每个按键独立连接到PCA9535的一个GPIO另一端接地优点软件简单缺点占用IO多矩阵扫描4x4矩阵只需8个IO优点节省IO缺点需要扫描逻辑3.2 按键驱动实现实现带消抖的按键检测// 按键状态结构体 typedef struct { uint8_t current; // 当前状态 uint8_t last; // 上次状态 uint8_t changed; // 状态变化标志 uint32_t last_time; // 上次变化时间 } KeyState; // 初始化所有按键状态 KeyState keys[16]; // 按键扫描函数 void Key_Scan(void) { static uint8_t port0, port1; PCA9535_ReadReg(REG_INPUT_0, port0); PCA9535_ReadReg(REG_INPUT_1, port1); uint32_t now HAL_GetTick(); for(int i0; i8; i) { // P0端口按键 uint8_t state !(port0 (1i)); // 低电平有效 if(keys[i].current ! state) { if(now - keys[i].last_time 20) { // 20ms消抖 keys[i].last keys[i].current; keys[i].current state; keys[i].changed 1; keys[i].last_time now; } } else { keys[i].changed 0; } // P1端口按键同上 // ... } }3.3 使用示例在主循环中调用while(1) { Key_Scan(); // 检测按键0按下 if(keys[0].changed keys[0].current) { printf(Key0 pressed\n); // 执行相应功能 } HAL_Delay(10); }4. 高级功能与优化4.1 中断模式配置PCA9535支持中断输出可以降低CPU负载配置寄存器使能中断连接INT引脚到STM32的外部中断引脚在中断服务程序中读取按键状态配置代码示例// 使能P0所有引脚的中断 PCA9535_WriteReg(REG_CONFIG_0, 0xFF); // 全部设为输入 PCA9535_WriteReg(REG_POLARITY_0, 0xFF); // 极性反转低电平触发4.2 功耗优化对于电池供电设备在不使用时关闭I2C总线利用中断唤醒代替轮询适当降低I2C时钟频率4.3 多设备扩展单个I2C总线可以挂载多个PCA9535通过不同的地址区分A2A1A0地址0000x200010x21............1110x275. 完整代码示例以下是整合后的模块化代码pca9535.h#ifndef __PCA9535_H #define __PCA9535_H #include stm32u5xx_hal.h #define PCA9535_ADDR 0x20 // 默认地址 typedef enum { KEY_EVENT_NONE 0, KEY_EVENT_PRESS, KEY_EVENT_RELEASE, KEY_EVENT_LONG_PRESS } KeyEvent; void PCA9535_Init(I2C_HandleTypeDef *hi2c); uint8_t PCA9535_ReadGPIO(uint8_t port); void PCA9535_WriteGPIO(uint8_t port, uint8_t value); KeyEvent Key_GetEvent(uint8_t key_num); void Key_UpdateAll(void); #endifpca9535.c#include pca9535.h #include string.h static I2C_HandleTypeDef *hi2c_pca; // 初始化函数 void PCA9535_Init(I2C_HandleTypeDef *hi2c) { hi2c_pca hi2c; // 配置所有端口为输入带内部上拉 uint8_t config[] {0xFF, 0xFF}; HAL_I2C_Mem_Write(hi2c_pca, PCA9535_ADDR1, 0x06, 1, config, 2, 100); } // 读取GPIO状态 uint8_t PCA9535_ReadGPIO(uint8_t port) { uint8_t reg port ? 0x01 : 0x00; uint8_t value; HAL_I2C_Mem_Read(hi2c_pca, PCA9535_ADDR1, reg, 1, value, 1, 100); return value; } // 其他函数实现...在实际项目中这套方案成功将原本需要16个GPIO的按键矩阵缩减到只需2个I2C引脚释放出的GPIO可以用于其他重要功能。调试时发现适当降低I2C速度能显著提高稳定性特别是在长线缆连接时。