/** * soft_i2c.c — 软件 I2C(GPIO 位操作),支持任意引脚 * * 特性: * - 不依赖硬件 I2C 外设,两根 GPIO 即可 * - 引脚在运行时通过 SoftI2C_Config 结构体传入 * - 传 NULL 则用编译时默认宏定义的引脚 * - 推挽输出 + 方向切换,不需要外部上拉 */ #include "soft_i2c.h" /*=========================================================================== * 全局配置:保存当前使用的引脚 *===========================================================================*/ static SoftI2C_Config g_i2c = { .scl_port = SOFT_I2C_SCL_PORT, .scl_pin = SOFT_I2C_SCL_PIN, .sda_port = SOFT_I2C_SDA_PORT, .sda_pin = SOFT_I2C_SDA_PIN, }; /*=========================================================================== * 引脚操作宏(展开后用 g_i2c 中的端口和引脚) *===========================================================================*/ #define SCL_HIGH() GPIO_SetBits(g_i2c.scl_port, g_i2c.scl_pin) #define SCL_LOW() GPIO_ResetBits(g_i2c.scl_port, g_i2c.scl_pin) #define SDA_HIGH() GPIO_SetBits(g_i2c.sda_port, g_i2c.sda_pin) #define SDA_LOW() GPIO_ResetBits(g_i2c.sda_port, g_i2c.sda_pin) #define SDA_READ() GPIO_ReadInputDataBit(g_i2c.sda_port, g_i2c.sda_pin) /*=========================================================================== * 延时:~5us @ 72MHz(一个 I2C 半周期,约 100kHz) * 调大 i2c_delay_cnt 降速,调小提速 *===========================================================================*/ static uint32_t i2c_delay_cnt = 24; void SoftI2C_SetSpeed(uint32_t delay_loops) { i2c_delay_cnt = (delay_loops > 0) ? delay_loops : 24; } static void i2c_delay(void) { uint32_t i = i2c_delay_cnt; while (i--) { __NOP(); } } /*=========================================================================== * SDA 方向切换 *===========================================================================*/ static void sda_output(void) { GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); gpio.GPIO_Pin = g_i2c.sda_pin; gpio.GPIO_Speed = GPIO_Speed_10MHz; gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(g_i2c.sda_port, &gpio); } static void sda_input(void) { GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); gpio.GPIO_Pin = g_i2c.sda_pin; gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(g_i2c.sda_port, &gpio); } /*=========================================================================== * 获取 GPIO 时钟 RCC 使能 * 只处理常用的 GPIOA~GPIOE,如果有 GPIOf~G 自己加 *===========================================================================*/ static void gpio_clock_enable(GPIO_TypeDef *port) { if (port == GPIOA) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); else if (port == GPIOB) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); else if (port == GPIOC) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); else if (port == GPIOD) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); else if (port == GPIOE) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); } /*=========================================================================== * 初始化 * cfg == NULL 时用编译时默认引脚 *===========================================================================*/ void SoftI2C_Init(const SoftI2C_Config *cfg) { GPIO_InitTypeDef gpio; if (cfg) { g_i2c.scl_port = cfg->scl_port; g_i2c.scl_pin = cfg->scl_pin; g_i2c.sda_port = cfg->sda_port; g_i2c.sda_pin = cfg->sda_pin; } /* 使能两个端口的时钟 */ gpio_clock_enable(g_i2c.scl_port); if (g_i2c.scl_port != g_i2c.sda_port) { gpio_clock_enable(g_i2c.sda_port); } /* SCL: 推挽输出 */ GPIO_StructInit(&gpio); gpio.GPIO_Pin = g_i2c.scl_pin; gpio.GPIO_Speed = GPIO_Speed_10MHz; gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(g_i2c.scl_port, &gpio); /* SDA: 先设推挽输出,默认高电平 */ GPIO_StructInit(&gpio); gpio.GPIO_Pin = g_i2c.sda_pin; gpio.GPIO_Speed = GPIO_Speed_10MHz; gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(g_i2c.sda_port, &gpio); /* 总线空闲:SCL=1, SDA=1 */ SCL_HIGH(); SDA_HIGH(); i2c_delay(); } /*=========================================================================== * 时序原语 *===========================================================================*/ void SoftI2C_Start(void) { sda_output(); SDA_HIGH(); SCL_HIGH(); i2c_delay(); SDA_LOW(); /* SCL 高时 SDA 下降沿 = START */ i2c_delay(); SCL_LOW(); /* 准备传数据 */ i2c_delay(); } void SoftI2C_Stop(void) { sda_output(); SDA_LOW(); SCL_HIGH(); /* 先抬 SCL */ i2c_delay(); SDA_HIGH(); /* SCL 高时 SDA 上升沿 = STOP */ i2c_delay(); } /*=========================================================================== * 发送 1 字节,返回 ACK(0=收到ACK, 1=NACK) *===========================================================================*/ uint8_t SoftI2C_SendByte(uint8_t data) { sda_output(); for (uint8_t i = 0; i < 8; i++) { if (data & 0x80) { SDA_HIGH(); } else { SDA_LOW(); } i2c_delay(); SCL_HIGH(); /* 从机在 SCL 高时采样 */ i2c_delay(); SCL_LOW(); i2c_delay(); data <<= 1; } /* 第 9 个时钟:释放 SDA,接收从机 ACK */ sda_input(); i2c_delay(); SCL_HIGH(); i2c_delay(); uint8_t ack = SDA_READ(); SCL_LOW(); i2c_delay(); sda_output(); return ack; /* 0=ACK, 1=NACK */ } /*=========================================================================== * 读取 1 字节 * ack_bit: 0=发 ACK(还要继续读), 1=发 NACK(最后字节) *===========================================================================*/ uint8_t SoftI2C_ReadByte(uint8_t ack_bit) { uint8_t data = 0; sda_input(); for (uint8_t i = 0; i < 8; i++) { SCL_HIGH(); i2c_delay(); data <<= 1; if (SDA_READ()) { data |= 0x01; } SCL_LOW(); i2c_delay(); } /* 第 9 个时钟:主机发 ACK/NACK */ sda_output(); if (ack_bit) { SDA_HIGH(); } /* NACK */ else { SDA_LOW(); } /* ACK */ i2c_delay(); SCL_HIGH(); i2c_delay(); SCL_LOW(); i2c_delay(); sda_output(); return data; } /*=========================================================================== * 寄存器级封装 *===========================================================================*/ uint8_t SoftI2C_WriteReg(uint8_t dev_addr, uint8_t reg, uint8_t data) { SoftI2C_Start(); if (SoftI2C_SendByte(dev_addr << 1)) { SoftI2C_Stop(); return 1; } if (SoftI2C_SendByte(reg)) { SoftI2C_Stop(); return 1; } if (SoftI2C_SendByte(data)) { SoftI2C_Stop(); return 1; } SoftI2C_Stop(); return 0; } uint8_t SoftI2C_ReadReg(uint8_t dev_addr, uint8_t reg) { uint8_t data; SoftI2C_Start(); SoftI2C_SendByte(dev_addr << 1); /* 写方向 */ SoftI2C_SendByte(reg); /* 发寄存器地址 */ SoftI2C_Start(); /* Repeated Start */ SoftI2C_SendByte((dev_addr << 1) | 1); /* 读方向 */ data = SoftI2C_ReadByte(1); /* NACK,只读 1 字节 */ SoftI2C_Stop(); return data; } 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); SoftI2C_Start(); if (SoftI2C_SendByte(dev_addr << 1)) { SoftI2C_Stop(); return 1; } if (SoftI2C_SendByte(reg)) { SoftI2C_Stop(); return 1; } if (SoftI2C_SendByte(lo)) { SoftI2C_Stop(); return 1; } if (SoftI2C_SendByte(hi)) { SoftI2C_Stop(); return 1; } SoftI2C_Stop(); return 0; } uint16_t SoftI2C_ReadReg16(uint8_t dev_addr, uint8_t reg) { uint8_t lo, hi; SoftI2C_Start(); SoftI2C_SendByte(dev_addr << 1); SoftI2C_SendByte(reg); SoftI2C_Start(); /* Repeated Start */ SoftI2C_SendByte((dev_addr << 1) | 1); lo = SoftI2C_ReadByte(0); /* ACK */ hi = SoftI2C_ReadByte(1); /* NACK */ SoftI2C_Stop(); return ((uint16_t)hi << 8) | lo; } /*=========================================================================== * BQ25756 初始化序列 *===========================================================================*/ uint8_t SoftI2C_WatchdogOff(void) { return SoftI2C_WriteReg(0x6B, 0x15, 0x0D); } uint8_t SoftI2C_NtcOff(void) { return SoftI2C_WriteReg(0x6B, 0x1C, 0x56); } uint8_t SoftI2C_AdcContinuous(void) { return SoftI2C_WriteReg(0x6B, 0x2B, 0xA0); } uint8_t SoftI2C_InitCharging(uint8_t dev_addr, uint8_t ntc_off, uint32_t charge_ma, uint32_t input_ma, uint32_t input_mv) { uint8_t ret; ret = SoftI2C_WatchdogOff(); if (ret) return ret; if (ntc_off) { ret = SoftI2C_NtcOff(); if (ret) return ret; } /* 充电电流 16-bit = (charge_ma / 50) << 2 */ { uint16_t v = (uint16_t)((charge_ma / 50) << 2); ret = SoftI2C_WriteReg16(dev_addr, 0x02, v); if (ret) return ret; } /* 输入电流限 16-bit = (input_ma / 50) << 2 */ { uint16_t v = (uint16_t)((input_ma / 50) << 2); ret = SoftI2C_WriteReg16(dev_addr, 0x06, v); if (ret) return ret; } /* 输入电压 DPM 16-bit = input_mv / 20 */ { uint16_t v = (uint16_t)(input_mv / 20); ret = SoftI2C_WriteReg16(dev_addr, 0x08, v); if (ret) return ret; } ret = SoftI2C_AdcContinuous(); if (ret) return ret; return 0; } /*=========================================================================== * 预充/终止电流 *===========================================================================*/ uint8_t SoftI2C_SetPrechargeCurrent(uint8_t dev_addr, uint32_t ma) { uint16_t v = (uint16_t)((ma / 50) << 2); return SoftI2C_WriteReg16(dev_addr, 0x10, v); } uint8_t SoftI2C_SetTerminationCurrent(uint8_t dev_addr, uint32_t ma) { uint16_t v = (uint16_t)((ma / 50) << 2); return SoftI2C_WriteReg16(dev_addr, 0x12, v); }