/** * @file soft_i2c.c * @brief 软件 GPIO 位操作 I2C — 平台无关 * * 用两个 GPIO 引脚模拟 I²C 总线时序(100kHz 标速)。 * 不依赖硬件 I²C 外设,可适配任意 MCU。 * * 内置 3 次重试机制:单次 I²C 毛刺不会导致通信失败。 * * 分层位置:最底层 — 被 bq25756_hal.c 调用。 */ #include "soft_i2c.h" /*=========================================================================== * 硬件配置 *===========================================================================*/ static GPIO_TypeDef *scl_port = NULL; static uint16_t scl_pin = 0; static GPIO_TypeDef *sda_port = NULL; static uint16_t sda_pin = 0; /* SDA 方向切换宏 — 输入带弱上拉,输出开漏 */ #define SDA_IN() do { sda_port->CRH &= ~0xF; sda_port->CRH |= 0x8; } while(0) #define SDA_OUT() do { sda_port->CRH &= ~0xF; sda_port->CRH |= 0x3; } while(0) #define SCL_HIGH() (scl_port->BSRR = scl_pin) #define SCL_LOW() (scl_port->BRR = scl_pin) #define SDA_HIGH() (sda_port->BSRR = sda_pin) #define SDA_LOW() (sda_port->BRR = sda_pin) #define SDA_READ() ((sda_port->IDR & sda_pin) ? 1 : 0) /*=========================================================================== * 时序参数 *===========================================================================*/ /** 重试次数 */ #define I2C_RETRY_COUNT 3 /** 延时循环计数 @ 72MHz ≈ 5µs(I²C 半周期 ≈ 100kHz) */ static uint32_t i2c_delay_cnt = 24; /*=========================================================================== * 内部辅助函数 *===========================================================================*/ /** * @brief I²C 半周期延时(~5µs @ 72MHz) * * 调整 i2c_delay_cnt 可调速。 */ static void i2c_delay(void) { uint32_t i = i2c_delay_cnt; while (i--) { __NOP(); } } /** * @brief 重试退避延时(~1ms @ 72MHz) * * I²C 写失败后给从机恢复时间。 */ static void i2c_retry_delay(void) { uint32_t i = 72000; while (i--) { __NOP(); } } /*=========================================================================== * 公开接口 *===========================================================================*/ /** * @brief 初始化软件 I²C 引脚 * * @param cfg 引脚配置结构体指针 * - cfg == NULL: 使用默认引脚 (PA1=SCL, PA2=SDA) * - cfg != NULL: 使用自定义引脚 * * 配置 SCL/SDA 为推挽输出,初始均为高电平。 */ void SoftI2C_Init(const SoftI2C_Config *cfg) { GPIO_InitTypeDef gpio; if (cfg == NULL) { scl_port = SOFT_I2C_SCL_PORT; scl_pin = SOFT_I2C_SCL_PIN; sda_port = SOFT_I2C_SDA_PORT; sda_pin = SOFT_I2C_SDA_PIN; } else { scl_port = cfg->scl_port; scl_pin = cfg->scl_pin; sda_port = cfg->sda_port; sda_pin = cfg->sda_pin; } /* 时钟使能 — 从端口反查 */ if (scl_port == GPIOA) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); else if (scl_port == GPIOB) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); else if (scl_port == GPIOC) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); /* SCL — 推挽输出, 初始高 */ GPIO_StructInit(&gpio); gpio.GPIO_Pin = scl_pin; gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(scl_port, &gpio); SCL_HIGH(); /* SDA — 推挽输出, 初始高 */ GPIO_StructInit(&gpio); gpio.GPIO_Pin = sda_pin; gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(sda_port, &gpio); SDA_HIGH(); } /** * @brief 调节 I²C 速度 * * @param delay_loops 延时循环次数(默认 24 @ 72MHz ≈ 100kHz) * 值越大速度越慢。 */ void SoftI2C_SetSpeed(uint32_t delay_loops) { i2c_delay_cnt = (delay_loops > 0) ? delay_loops : 24; } /** * @brief I²C 起始条件 — SDA 下降沿(SCL 高时) * * 时序:SDA_H → SDA_L(SCL 保持高)。 */ void SoftI2C_Start(void) { SDA_OUT(); SDA_HIGH(); SCL_HIGH(); i2c_delay(); SDA_LOW(); i2c_delay(); SCL_LOW(); } /** * @brief I²C 停止条件 — SDA 上升沿(SCL 高时) * * 时序:SCL_L → SCL_H → SDA_L → SDA_H。 */ void SoftI2C_Stop(void) { SDA_OUT(); SDA_LOW(); SCL_HIGH(); i2c_delay(); SDA_HIGH(); i2c_delay(); } /** * @brief 发送一个字节(MSB 先出) * * @param data 待发送的 8-bit 数据 * @return 0 = 收到 ACK, 1 = 收到 NACK(从机无响应) * * 时序:8 个数据位 + 1 个 ACK 位。 * 发完第 9 个时钟后释放 SDA,读取从机 ACK/NACK。 */ uint8_t SoftI2C_SendByte(uint8_t data) { uint8_t i; SDA_OUT(); for (i = 0; i < 8; i++) { if (data & 0x80) SDA_HIGH(); else SDA_LOW(); i2c_delay(); SCL_HIGH(); i2c_delay(); SCL_LOW(); data <<= 1; } /* 第 9 位 — 读 ACK */ SDA_IN(); i2c_delay(); SCL_HIGH(); i2c_delay(); uint8_t ack = SDA_READ(); SCL_LOW(); SDA_OUT(); return ack; } /** * @brief 读取一个字节(MSB 先入) * * @param ack_bit 0 = 主机应答(ACK),继续读下一字节 * 1 = 主机不应答(NACK),读最后字节 * @return 8-bit 读取数据 * * 时序:8 个数据位,主机在第 9 个时钟输出 ack_bit。 */ uint8_t SoftI2C_ReadByte(uint8_t ack_bit) { uint8_t i, data = 0; SDA_IN(); for (i = 0; i < 8; i++) { SCL_HIGH(); i2c_delay(); data = (uint8_t)((data << 1) | SDA_READ()); SCL_LOW(); i2c_delay(); } /* 第 9 位 — 发 ACK/NACK */ SDA_OUT(); if (ack_bit) SDA_HIGH(); else SDA_LOW(); i2c_delay(); SCL_HIGH(); i2c_delay(); SCL_LOW(); SDA_IN(); return data; } /** * @brief 写 8-bit 寄存器(3 次重试) * * @param dev_addr 7-bit I²C 从机地址 * @param reg 寄存器地址 * @param data 8-bit 数据 * @return 0 = 成功, 1 = 3 次重试均失败 * * I²C 序列:START → 地址(W) → 寄存器 → 数据 → STOP */ uint8_t SoftI2C_WriteReg(uint8_t dev_addr, uint8_t reg, uint8_t data) { uint8_t retry; for (retry = 0; retry < I2C_RETRY_COUNT; retry++) { SoftI2C_Start(); if (SoftI2C_SendByte(dev_addr << 1)) { SoftI2C_Stop(); goto retry_wr; } if (SoftI2C_SendByte(reg)) { SoftI2C_Stop(); goto retry_wr; } if (SoftI2C_SendByte(data)) { SoftI2C_Stop(); goto retry_wr; } SoftI2C_Stop(); return 0; retry_wr: i2c_retry_delay(); } return 1; } /** * @brief 读 8-bit 寄存器(3 次重试) * * @param dev_addr 7-bit I²C 从机地址 * @param reg 寄存器地址 * @return 寄存器值(失败返回 0xFF) * * I²C 序列:START → 地址(W) → 寄存器 → RESTART → 地址(R) → 数据 → NACK → STOP */ uint8_t SoftI2C_ReadReg(uint8_t dev_addr, uint8_t reg) { uint8_t data, retry; for (retry = 0; retry < I2C_RETRY_COUNT; retry++) { SoftI2C_Start(); if (SoftI2C_SendByte(dev_addr << 1)) { SoftI2C_Stop(); goto retry_rd; } if (SoftI2C_SendByte(reg)) { SoftI2C_Stop(); goto retry_rd; } SoftI2C_Start(); if (SoftI2C_SendByte((dev_addr << 1) | 1)) { SoftI2C_Stop(); goto retry_rd; } data = SoftI2C_ReadByte(1); SoftI2C_Stop(); return data; retry_rd: i2c_retry_delay(); } return 0xFF; } /** * @brief 写 16-bit 寄存器(LSB 先发,3 次重试) * * @param dev_addr 7-bit I²C 从机地址 * @param reg 寄存器地址 * @param data 16-bit 数据(发送顺序:低字节 → 高字节) * @return 0 = 成功, 1 = 3 次重试均失败 * * I²C 序列:START → 地址(W) → 寄存器 → data_lo → data_hi → STOP */ uint8_t SoftI2C_WriteReg16(uint8_t dev_addr, uint8_t reg, uint16_t data) { uint8_t lo = (uint8_t)(data & 0xFF); uint8_t hi = (uint8_t)((data >> 8) & 0xFF); uint8_t retry; for (retry = 0; retry < I2C_RETRY_COUNT; retry++) { SoftI2C_Start(); if (SoftI2C_SendByte(dev_addr << 1)) { SoftI2C_Stop(); goto retry_w16; } if (SoftI2C_SendByte(reg)) { SoftI2C_Stop(); goto retry_w16; } if (SoftI2C_SendByte(lo)) { SoftI2C_Stop(); goto retry_w16; } if (SoftI2C_SendByte(hi)) { SoftI2C_Stop(); goto retry_w16; } SoftI2C_Stop(); return 0; retry_w16: i2c_retry_delay(); } return 1; } /** * @brief 读 16-bit 寄存器(LSB 先收,3 次重试) * * @param dev_addr 7-bit I²C 从机地址 * @param reg 寄存器地址 * @return 16-bit 寄存器值(失败返回 0xFFFF) * * I²C 序列:START → 地址(W) → 寄存器 → RESTART → 地址(R) → lo(ACK) → hi(NACK) → STOP */ uint16_t SoftI2C_ReadReg16(uint8_t dev_addr, uint8_t reg) { uint8_t lo, hi, retry; for (retry = 0; retry < I2C_RETRY_COUNT; retry++) { SoftI2C_Start(); if (SoftI2C_SendByte(dev_addr << 1)) { SoftI2C_Stop(); goto retry_r16; } if (SoftI2C_SendByte(reg)) { SoftI2C_Stop(); goto retry_r16; } SoftI2C_Start(); if (SoftI2C_SendByte((dev_addr << 1) | 1)) { SoftI2C_Stop(); goto retry_r16; } lo = SoftI2C_ReadByte(0); hi = SoftI2C_ReadByte(1); SoftI2C_Stop(); return ((uint16_t)hi << 8) | lo; retry_r16: i2c_retry_delay(); } return 0xFFFF; }