1. ADS1115与STM32的完美组合
在嵌入式系统开发中,高精度模拟信号采集是个常见需求。无论是监测电池电压、采集传感器信号,还是进行精密测量,ADS1115这款16位ADC芯片都是性价比极高的选择。它最高支持860SPS的采样率,内置可编程增益放大器(PGA),输入电压范围从±256mV到±6.144V可调,特别适合与STM32系列MCU搭配使用。
我最近在一个智能农业项目中就用到了这个组合,需要同时监测土壤湿度、光照强度和环境温度三个模拟信号。ADS1115的多通道特性正好满足需求,而STM32CubeMX+HAL库的开发方式让整个驱动开发过程变得异常简单。实测下来,这个方案稳定性很好,连续工作72小时数据采集零丢失。
相比STM32内置的12位ADC,ADS1115的16位分辨率能提供更高的测量精度。举个例子,当使用±4.096V量程时,ADS1115的最小可检测电压变化约为125μV,而STM32内置ADC在3.3V参考电压下最小变化约为800μV。对于需要检测微小信号变化的场景,这种精度提升非常关键。
2. CubeMX的I2C配置详解
2.1 硬件连接检查
在开始CubeMX配置前,先确保硬件连接正确。ADS1115的I2C接口通常连接到STM32的任意一组I2C引脚上,注意上拉电阻必不可少。我在第一次使用时忘了加上拉电阻,结果通信一直失败,后来用示波器检查才发现SDA和SCL线无法正常拉高。
基本连接如下:
- VDD接3.3V
- GND接地
- SDA接STM32的I2C_SDA引脚
- SCL接STM32的I2C_SCL引脚
- ADDR引脚根据需要的I2C地址决定接法(接地为0x48)
2.2 CubeMX图形化配置
打开CubeMX新建工程,选择你的STM32型号。在Pinout界面找到I2C外设并启用:
- 选择I2C模式为"I2C"
- 时钟速度设置为400kHz(ADS1115最高支持400kHz)
- 启用I2C中断(可选,用于事件驱动方式)
在Configuration标签页的I2C参数设置中,有几个关键点需要注意:
- Timing参数建议使用自动计算值
- 地址模式选择7位
- 不要启用时钟延展(Clock Stretching)
配置完成后生成代码,CubeMX会自动初始化I2C外设,我们只需要专注于ADS1115的驱动开发。
3. HAL库驱动开发实战
3.1 寄存器定义与初始化
首先创建ads1115.h头文件,定义所有必要的寄存器地址和配置位。这部分看似繁琐,但良好的定义能让后续开发事半功倍。我在项目中是这样组织的:
#define ADS1115_ADDRESS 0x48 // ADDR接地时的地址 // 配置寄存器位定义 #define ADS1115_OS_SINGLE_CONVERSION 0x8000 #define ADS1115_MUX_CHANNEL_0 0x4000 #define ADS1115_PGA_4_096V 0x0200 #define ADS1115_MODE_SINGLE 0x0100 #define ADS1115_DR_128SPS 0x0080初始化函数需要配置ADS1115的工作模式。这里分享一个实用技巧:使用结构体保存所有配置参数,方便后续修改:
typedef struct { uint16_t mode; // 单次/连续转换 uint16_t data_rate; // 采样率 uint16_t pga; // 增益设置 } ADS1115_Config; void ADS1115_Init(I2C_HandleTypeDef *hi2c, ADS1115_Config *config) { uint8_t buf[3]; buf[0] = 0x01; // 指向配置寄存器 buf[1] = (config->mode | config->data_rate | config->pga) >> 8; buf[2] = (config->mode | config->data_rate | config->pga) & 0xFF; HAL_I2C_Master_Transmit(hi2c, ADS1115_ADDRESS, buf, 3, HAL_MAX_DELAY); }3.2 单通道数据采集实现
单次转换模式适合不频繁采样的场景,能有效降低功耗。实现步骤分为三步:
- 写入配置寄存器启动转换
- 等待转换完成(可通过轮询或中断)
- 读取转换结果寄存器
这里有个坑要注意:写入配置后需要适当延时,等待转换完成。我最初没加延时,经常读到前一次的结果。修正后的代码:
int16_t ADS1115_ReadChannel(I2C_HandleTypeDef *hi2c, uint8_t channel) { // 配置并启动转换 uint8_t config[3] = {0x01, (ADS1115_OS_SINGLE_CONVERSION | channel | ADS1115_PGA_4_096V) >> 8, (ADS1115_MODE_SINGLE | ADS1115_DR_128SPS) & 0xFF}; HAL_I2C_Master_Transmit(hi2c, ADS1115_ADDRESS, config, 3, 100); // 等待转换完成 HAL_Delay(10); // 根据采样率调整 // 读取结果 uint8_t reg[1] = {0x00}; // 指向转换寄存器 HAL_I2C_Master_Transmit(hi2c, ADS1115_ADDRESS, reg, 1, 100); uint8_t data[2]; HAL_I2C_Master_Receive(hi2c, ADS1115_ADDRESS, data, 2, 100); return (int16_t)((data[0] << 8) | data[1]); }3.3 多通道轮询技巧
对于需要连续监测多个信号的场景,轮询各通道是个实用方案。关键点在于通道切换后要留足够稳定时间。我的做法是使用定时器中断定期切换通道:
void ADS1115_ScanNextChannel(I2C_HandleTypeDef *hi2c) { static uint8_t current_channel = 0; // 配置下一通道 uint8_t config[3] = {0x01, (ADS1115_OS_SINGLE_CONVERSION | (0x4000 << current_channel) | ADS1115_PGA_4_096V) >> 8, (ADS1115_MODE_SINGLE | ADS1115_DR_128SPS) & 0xFF}; HAL_I2C_Master_Transmit(hi2c, ADS1115_ADDRESS, config, 3, 100); // 更新通道号 current_channel = (current_channel + 1) % 4; }在定时器中断中调用这个函数,主循环中读取数据,可以实现高效的多通道采集。我在项目中设置250ms切换一次通道,系统响应非常流畅。
4. 数据处理与优化技巧
4.1 原始数据转电压计算
ADS1115返回的是16位有符号整数,需要根据PGA设置转换为实际电压。这个转换公式很多人容易搞错,特别是正负电压的处理:
float ADS1115_ConvertToVoltage(int16_t raw, uint16_t pga) { float lsb_size; switch(pga) { case ADS1115_PGA_6_144V: lsb_size = 0.1875f; break; case ADS1115_PGA_4_096V: lsb_size = 0.125f; break; // 其他量程... default: lsb_size = 0.125f; } return raw * lsb_size / 1000.0f; // 转换为伏特 }4.2 软件滤波实现
工业环境中信号常伴有噪声,软件滤波能有效提高数据质量。我常用的是一阶滞后滤波,实现简单效果不错:
#define FILTER_GAIN 0.1f float filtered_value = 0.0f; void ADS1115_ApplyFilter(float new_value) { filtered_value = filtered_value * (1.0f - FILTER_GAIN) + new_value * FILTER_GAIN; }对于快速变化的信号,可以适当增大FILTER_GAIN值;对于缓慢变化的信号(如温度),使用较小的值能更好抑制噪声。
4.3 低功耗优化
电池供电的设备需要特别注意功耗。ADS1115在单次转换模式下的功耗可以低至0.5μA,配合STM32的低功耗模式,可以大幅延长电池寿命。我的实现方案:
- 配置ADS1115为单次转换模式
- 配置STM32进入STOP模式
- 通过外部中断唤醒(如ADS1115的ALERT引脚)
- 唤醒后读取数据,处理完成后再次进入STOP模式
实测下来,这种方案可使系统平均电流降至50μA以下,对于纽扣电池供电的设备可以工作数月之久。