STM32F4实战用软件I2C驱动ADS1115实现4通道电压采集附完整代码在嵌入式开发中精确的模拟信号采集往往是项目成败的关键。当硬件I2C资源紧张或需要灵活配置时软件模拟I2C便成为工程师的得力工具。本文将手把手带你实现STM32F4通过软件I2C驱动ADS1115高精度ADC的全过程从引脚配置到数据解析完整代码可直接移植到你的项目中。1. 硬件准备与电路连接1.1 物料清单STM32F4系列开发板本文以STM32F407为例ADS1115模数转换模块TI 16位精度杜邦线若干可调电源或传感器信号源1.2 接线示意图ADS1115与STM32的典型连接方式如下ADS1115引脚STM32连接引脚备注VDD3.3V电源正极GNDGND电源地SCLPA6可自定义任意GPIOSDAPA5需开漏输出模式ADDRGND决定I2C地址为0x90A0-A3信号输入支持差分/单端输入提示若需要监测负电压需在信号输入端增加偏置电路。ADS1115的输入范围取决于PGA设置默认±2.048V。2. 软件I2C驱动实现2.1 GPIO初始化软件I2C的核心是精确的时序控制。首先配置GPIO为开漏输出模式// i2c_virtual.h #define I2C_DELAY 5 // 微秒级延时根据主频调整 void I2C_GPIO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_Pin; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOx, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET); }2.2 关键时序函数完整的I2C协议需要实现以下基础操作// 启动信号 void I2C_Start(void) { SDA_HIGH(); SCL_HIGH(); I2C_Delay(); SDA_LOW(); I2C_Delay(); SCL_LOW(); } // 停止信号 void I2C_Stop(void) { SDA_LOW(); SCL_HIGH(); I2C_Delay(); SDA_HIGH(); I2C_Delay(); } // 发送一个字节 uint8_t I2C_SendByte(uint8_t byte) { for(uint8_t i0; i8; i) { (byte 0x80) ? SDA_HIGH() : SDA_LOW(); byte 1; SCL_HIGH(); I2C_Delay(); SCL_LOW(); I2C_Delay(); } SDA_HIGH(); // 释放SDA线 SCL_HIGH(); uint8_t ack !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); SCL_LOW(); return ack; }3. ADS1115驱动开发3.1 寄存器配置ADS1115通过配置寄存器设置工作模式主要参数包括参数可选值说明采样速率8/16/32/64/128/250/475/860 SPS影响转换时间和噪声PGA增益±6.144V到±0.256V根据输入信号幅度选择工作模式单次/连续转换功耗与实时性的权衡比较器模式传统/窗口比较器用于阈值触发配置示例void ADS1115_ConfigDefault(void) { uint16_t config ADS1115_OS_SingleConverStart | // 单次转换 ADS1115_MUX_Channel_0 | // 通道0 ADS1115_PGA_4096 | // ±4.096V量程 ADS1115_MODE_SingleConver | // 单次模式 ADS1115_DataRate_128 | // 128SPS ADS1115_COMP_MODE_0 | // 传统比较器 ADS1115_COMP_POL_0 | // 低电平有效 ADS1115_COMP_QUE_3; // 禁用比较器 uint8_t buf[3] {ADS1115_Pointer_ConfigReg, (uint8_t)(config 8), (uint8_t)config}; I2C_WriteBytes(ADS1115_ADDRESS, buf, 3); }3.2 多通道轮询策略高效的多通道采集需要解决两个核心问题通道切换后的稳定时间典型值1ms数据读取的实时性要求推荐采用状态机实现非阻塞式采集typedef enum { CHANNEL_SWITCH, WAIT_STABILIZE, DATA_READ, DATA_PROCESS } ADS1115_State; void ADS1115_PollingHandler(void) { static ADS1115_State state CHANNEL_SWITCH; static uint8_t current_ch 0; static uint32_t last_tick 0; switch(state) { case CHANNEL_SWITCH: ADS1115_SelectChannel(current_ch); state WAIT_STABILIZE; last_tick HAL_GetTick(); break; case WAIT_STABILIZE: if(HAL_GetTick() - last_tick 2) { // 等待2ms state DATA_READ; } break; case DATA_READ: if(ADS1115_ReadData(raw_data[current_ch])) { state DATA_PROCESS; } break; case DATA_PROCESS: voltages[current_ch] (raw_data[current_ch] * 4.096f) / 32768.0f; current_ch (current_ch 1) % 4; state CHANNEL_SWITCH; break; } }4. 实战优化技巧4.1 软件滤波算法针对工业现场干扰推荐组合使用以下滤波技术// 移动平均滤波 #define FILTER_DEPTH 8 float MovingAverage_Filter(float new_val, uint8_t ch) { static float buffer[4][FILTER_DEPTH] {0}; static uint8_t index[4] {0}; float sum 0; buffer[ch][index[ch]] new_val; index[ch] (index[ch] 1) % FILTER_DEPTH; for(uint8_t i0; iFILTER_DEPTH; i) { sum buffer[ch][i]; } return sum / FILTER_DEPTH; } // 中值滤波 int16_t Median_Filter(int16_t new_val, uint8_t ch) { static int16_t window[4][3] {0}; static uint8_t pos[4] {0}; int16_t temp[3]; window[ch][pos[ch]] new_val; pos[ch] (pos[ch] 1) % 3; memcpy(temp, window[ch], sizeof(temp)); // 冒泡排序取中值 for(uint8_t i0; i2; i) { for(uint8_t ji1; j3; j) { if(temp[i] temp[j]) { int16_t swap temp[i]; temp[i] temp[j]; temp[j] swap; } } } return temp[1]; }4.2 校准与补偿高精度测量必须考虑以下补偿因素零点漂移短路输入时读取的偏移量增益误差用标准电压源校准温度漂移定期自动校准校准函数示例typedef struct { float offset[4]; float gain[4]; float temp_coeff[4]; } ADS1115_Calib; void ADS1115_Calibrate(ADS1115_Calib *calib) { // 零点校准短接输入 for(uint8_t ch0; ch4; ch) { calib-offset[ch] -ADS1115_GetVoltage(ch); } // 增益校准输入已知电压 float ref_voltage 2.500; // 2.5V参考 for(uint8_t ch0; ch4; ch) { float measured ADS1115_GetVoltage(ch); calib-gain[ch] ref_voltage / (measured calib-offset[ch]); } } float ADS1115_GetCalibratedVoltage(uint8_t ch, ADS1115_Calib *calib) { float raw ADS1115_GetVoltage(ch); return (raw calib-offset[ch]) * calib-gain[ch]; }5. 完整工程集成5.1 项目文件结构/Drivers /ADS1115 ├── ads1115.c # 驱动核心实现 ├── ads1115.h # 寄存器定义与接口 └── i2c_soft.c # 软件I2C实现 /Application ├── main.c # 主循环与初始化 └── sensor_task.c # 数据采集任务5.2 主程序示例int main(void) { HAL_Init(); SystemClock_Config(); // 初始化软件I2C I2C_Soft_Init(GPIOA, GPIO_PIN_5, GPIOA, GPIO_PIN_6); // 配置ADS1115 ADS1115_Init(); ADS1115_SetConfig(ADS1115_MUX_Channel_0 | ADS1115_PGA_4096 | ADS1115_MODE_ContinuConver | ADS1115_DataRate_128); // 创建采集任务 xTaskCreate(SensorTask, ADC, 128, NULL, 2, NULL); vTaskStartScheduler(); while(1); } void SensorTask(void *pvParameters) { ADS1115_Calib calib {0}; ADS1115_Calibrate(calib); while(1) { for(uint8_t ch0; ch4; ch) { float voltage ADS1115_GetCalibratedVoltage(ch, calib); printf(CH%d: %.3fV\r\n, ch, voltage); } vTaskDelay(pdMS_TO_TICKS(100)); } }6. 常见问题排查6.1 I2C通信失败现象始终读取0xFFFF或0x0000排查步骤用逻辑分析仪抓取I2C波形检查起始/停止信号是否完整时钟频率是否超过400kHz软件I2C建议100kHz从机地址是否正确ADDR引脚决定检查上拉电阻通常4.7kΩ确认电源电压稳定3.3V±5%6.2 数据跳变严重可能原因输入信号阻抗过高ADS1115输入阻抗约6MΩPCB布局不当引入干扰电源噪声过大解决方案// 硬件层面 // 1. 信号线增加RC滤波如1kΩ100nF // 2. 使用屏蔽线传输模拟信号 // 3. 电源端并联10μF100nF电容 // 软件层面 #define SAMPLE_TIMES 16 // 过采样倍数 float GetStableVoltage(uint8_t ch) { int32_t sum 0; for(uint8_t i0; iSAMPLE_TIMES; i) { sum ADS1115_ReadRawData(ch); vTaskDelay(1); } return (sum * 4.096f) / (32768.0f * SAMPLE_TIMES); }6.3 多设备冲突当系统中有多个I2C设备时建议为每个ADS1115分配独立GPIO模拟I2C采用硬件I2C多路复用器如TCA9548A严格遵循总线仲裁时序// 多设备切换示例 void Switch_I2C_Bus(uint8_t bus_num) { static uint8_t current_bus 0; if(bus_num ! current_bus) { I2C_Stop(); // 切换GPIO组根据实际电路设计 GPIO_TypeDef* sda_port[] {GPIOA, GPIOB, GPIOC}; GPIO_TypeDef* scl_port[] {GPIOA, GPIOB, GPIOC}; uint16_t sda_pin[] {GPIO_PIN_5, GPIO_PIN_7, GPIO_PIN_9}; uint16_t scl_pin[] {GPIO_PIN_6, GPIO_PIN_8, GPIO_PIN_10}; I2C_Soft_Init(sda_port[bus_num], sda_pin[bus_num], scl_port[bus_num], scl_pin[bus_num]); current_bus bus_num; } }在实际项目中这套驱动方案已稳定运行于工业传感器采集、电池管理系统等多类场景。特别提醒当测量微弱信号时务必注意PCB布局的模拟-数字隔离接地回路处理不当可能导致LSB级波动。
STM32F4实战:用软件I2C驱动ADS1115实现4通道电压采集(附完整代码)
发布时间:2026/6/4 4:28:51
STM32F4实战用软件I2C驱动ADS1115实现4通道电压采集附完整代码在嵌入式开发中精确的模拟信号采集往往是项目成败的关键。当硬件I2C资源紧张或需要灵活配置时软件模拟I2C便成为工程师的得力工具。本文将手把手带你实现STM32F4通过软件I2C驱动ADS1115高精度ADC的全过程从引脚配置到数据解析完整代码可直接移植到你的项目中。1. 硬件准备与电路连接1.1 物料清单STM32F4系列开发板本文以STM32F407为例ADS1115模数转换模块TI 16位精度杜邦线若干可调电源或传感器信号源1.2 接线示意图ADS1115与STM32的典型连接方式如下ADS1115引脚STM32连接引脚备注VDD3.3V电源正极GNDGND电源地SCLPA6可自定义任意GPIOSDAPA5需开漏输出模式ADDRGND决定I2C地址为0x90A0-A3信号输入支持差分/单端输入提示若需要监测负电压需在信号输入端增加偏置电路。ADS1115的输入范围取决于PGA设置默认±2.048V。2. 软件I2C驱动实现2.1 GPIO初始化软件I2C的核心是精确的时序控制。首先配置GPIO为开漏输出模式// i2c_virtual.h #define I2C_DELAY 5 // 微秒级延时根据主频调整 void I2C_GPIO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_Pin; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOx, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET); }2.2 关键时序函数完整的I2C协议需要实现以下基础操作// 启动信号 void I2C_Start(void) { SDA_HIGH(); SCL_HIGH(); I2C_Delay(); SDA_LOW(); I2C_Delay(); SCL_LOW(); } // 停止信号 void I2C_Stop(void) { SDA_LOW(); SCL_HIGH(); I2C_Delay(); SDA_HIGH(); I2C_Delay(); } // 发送一个字节 uint8_t I2C_SendByte(uint8_t byte) { for(uint8_t i0; i8; i) { (byte 0x80) ? SDA_HIGH() : SDA_LOW(); byte 1; SCL_HIGH(); I2C_Delay(); SCL_LOW(); I2C_Delay(); } SDA_HIGH(); // 释放SDA线 SCL_HIGH(); uint8_t ack !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); SCL_LOW(); return ack; }3. ADS1115驱动开发3.1 寄存器配置ADS1115通过配置寄存器设置工作模式主要参数包括参数可选值说明采样速率8/16/32/64/128/250/475/860 SPS影响转换时间和噪声PGA增益±6.144V到±0.256V根据输入信号幅度选择工作模式单次/连续转换功耗与实时性的权衡比较器模式传统/窗口比较器用于阈值触发配置示例void ADS1115_ConfigDefault(void) { uint16_t config ADS1115_OS_SingleConverStart | // 单次转换 ADS1115_MUX_Channel_0 | // 通道0 ADS1115_PGA_4096 | // ±4.096V量程 ADS1115_MODE_SingleConver | // 单次模式 ADS1115_DataRate_128 | // 128SPS ADS1115_COMP_MODE_0 | // 传统比较器 ADS1115_COMP_POL_0 | // 低电平有效 ADS1115_COMP_QUE_3; // 禁用比较器 uint8_t buf[3] {ADS1115_Pointer_ConfigReg, (uint8_t)(config 8), (uint8_t)config}; I2C_WriteBytes(ADS1115_ADDRESS, buf, 3); }3.2 多通道轮询策略高效的多通道采集需要解决两个核心问题通道切换后的稳定时间典型值1ms数据读取的实时性要求推荐采用状态机实现非阻塞式采集typedef enum { CHANNEL_SWITCH, WAIT_STABILIZE, DATA_READ, DATA_PROCESS } ADS1115_State; void ADS1115_PollingHandler(void) { static ADS1115_State state CHANNEL_SWITCH; static uint8_t current_ch 0; static uint32_t last_tick 0; switch(state) { case CHANNEL_SWITCH: ADS1115_SelectChannel(current_ch); state WAIT_STABILIZE; last_tick HAL_GetTick(); break; case WAIT_STABILIZE: if(HAL_GetTick() - last_tick 2) { // 等待2ms state DATA_READ; } break; case DATA_READ: if(ADS1115_ReadData(raw_data[current_ch])) { state DATA_PROCESS; } break; case DATA_PROCESS: voltages[current_ch] (raw_data[current_ch] * 4.096f) / 32768.0f; current_ch (current_ch 1) % 4; state CHANNEL_SWITCH; break; } }4. 实战优化技巧4.1 软件滤波算法针对工业现场干扰推荐组合使用以下滤波技术// 移动平均滤波 #define FILTER_DEPTH 8 float MovingAverage_Filter(float new_val, uint8_t ch) { static float buffer[4][FILTER_DEPTH] {0}; static uint8_t index[4] {0}; float sum 0; buffer[ch][index[ch]] new_val; index[ch] (index[ch] 1) % FILTER_DEPTH; for(uint8_t i0; iFILTER_DEPTH; i) { sum buffer[ch][i]; } return sum / FILTER_DEPTH; } // 中值滤波 int16_t Median_Filter(int16_t new_val, uint8_t ch) { static int16_t window[4][3] {0}; static uint8_t pos[4] {0}; int16_t temp[3]; window[ch][pos[ch]] new_val; pos[ch] (pos[ch] 1) % 3; memcpy(temp, window[ch], sizeof(temp)); // 冒泡排序取中值 for(uint8_t i0; i2; i) { for(uint8_t ji1; j3; j) { if(temp[i] temp[j]) { int16_t swap temp[i]; temp[i] temp[j]; temp[j] swap; } } } return temp[1]; }4.2 校准与补偿高精度测量必须考虑以下补偿因素零点漂移短路输入时读取的偏移量增益误差用标准电压源校准温度漂移定期自动校准校准函数示例typedef struct { float offset[4]; float gain[4]; float temp_coeff[4]; } ADS1115_Calib; void ADS1115_Calibrate(ADS1115_Calib *calib) { // 零点校准短接输入 for(uint8_t ch0; ch4; ch) { calib-offset[ch] -ADS1115_GetVoltage(ch); } // 增益校准输入已知电压 float ref_voltage 2.500; // 2.5V参考 for(uint8_t ch0; ch4; ch) { float measured ADS1115_GetVoltage(ch); calib-gain[ch] ref_voltage / (measured calib-offset[ch]); } } float ADS1115_GetCalibratedVoltage(uint8_t ch, ADS1115_Calib *calib) { float raw ADS1115_GetVoltage(ch); return (raw calib-offset[ch]) * calib-gain[ch]; }5. 完整工程集成5.1 项目文件结构/Drivers /ADS1115 ├── ads1115.c # 驱动核心实现 ├── ads1115.h # 寄存器定义与接口 └── i2c_soft.c # 软件I2C实现 /Application ├── main.c # 主循环与初始化 └── sensor_task.c # 数据采集任务5.2 主程序示例int main(void) { HAL_Init(); SystemClock_Config(); // 初始化软件I2C I2C_Soft_Init(GPIOA, GPIO_PIN_5, GPIOA, GPIO_PIN_6); // 配置ADS1115 ADS1115_Init(); ADS1115_SetConfig(ADS1115_MUX_Channel_0 | ADS1115_PGA_4096 | ADS1115_MODE_ContinuConver | ADS1115_DataRate_128); // 创建采集任务 xTaskCreate(SensorTask, ADC, 128, NULL, 2, NULL); vTaskStartScheduler(); while(1); } void SensorTask(void *pvParameters) { ADS1115_Calib calib {0}; ADS1115_Calibrate(calib); while(1) { for(uint8_t ch0; ch4; ch) { float voltage ADS1115_GetCalibratedVoltage(ch, calib); printf(CH%d: %.3fV\r\n, ch, voltage); } vTaskDelay(pdMS_TO_TICKS(100)); } }6. 常见问题排查6.1 I2C通信失败现象始终读取0xFFFF或0x0000排查步骤用逻辑分析仪抓取I2C波形检查起始/停止信号是否完整时钟频率是否超过400kHz软件I2C建议100kHz从机地址是否正确ADDR引脚决定检查上拉电阻通常4.7kΩ确认电源电压稳定3.3V±5%6.2 数据跳变严重可能原因输入信号阻抗过高ADS1115输入阻抗约6MΩPCB布局不当引入干扰电源噪声过大解决方案// 硬件层面 // 1. 信号线增加RC滤波如1kΩ100nF // 2. 使用屏蔽线传输模拟信号 // 3. 电源端并联10μF100nF电容 // 软件层面 #define SAMPLE_TIMES 16 // 过采样倍数 float GetStableVoltage(uint8_t ch) { int32_t sum 0; for(uint8_t i0; iSAMPLE_TIMES; i) { sum ADS1115_ReadRawData(ch); vTaskDelay(1); } return (sum * 4.096f) / (32768.0f * SAMPLE_TIMES); }6.3 多设备冲突当系统中有多个I2C设备时建议为每个ADS1115分配独立GPIO模拟I2C采用硬件I2C多路复用器如TCA9548A严格遵循总线仲裁时序// 多设备切换示例 void Switch_I2C_Bus(uint8_t bus_num) { static uint8_t current_bus 0; if(bus_num ! current_bus) { I2C_Stop(); // 切换GPIO组根据实际电路设计 GPIO_TypeDef* sda_port[] {GPIOA, GPIOB, GPIOC}; GPIO_TypeDef* scl_port[] {GPIOA, GPIOB, GPIOC}; uint16_t sda_pin[] {GPIO_PIN_5, GPIO_PIN_7, GPIO_PIN_9}; uint16_t scl_pin[] {GPIO_PIN_6, GPIO_PIN_8, GPIO_PIN_10}; I2C_Soft_Init(sda_port[bus_num], sda_pin[bus_num], scl_port[bus_num], scl_pin[bus_num]); current_bus bus_num; } }在实际项目中这套驱动方案已稳定运行于工业传感器采集、电池管理系统等多类场景。特别提醒当测量微弱信号时务必注意PCB布局的模拟-数字隔离接地回路处理不当可能导致LSB级波动。