资讯动态

STM32F103软件模拟I2C驱动ADS1115实现16位高精度采集

发布时间:2026/9/13 11:29:19 来源:尧图企业网站定制
简介本资源是一套基于STM32F103C8T6的软I²C驱动ADS1115高精度采集与OLED12864显示的完整嵌入式工程面向嵌入式初学者及STM32进阶开发者解决多传感器模拟信号采集、I²C协议实现与人机界面集成等典型开发痛点适用于环境监测、智能仪表等数据采集类项目实践。压缩包共145个文件含49个.h头文件定义外设寄存器与接口函数、41个.c源文件涵盖Soft I²C底层驱动、ADS1115配置与读取、OLED显存管理与字符绘制、系统时钟与GPIO初始化等核心逻辑以及.o、.axf、.hex等编译产物和uvproj工程文件整体大小2.16MB结构规范适配Keil MDK-ARM v4.7开发环境。已有464人学习下载。读者可直接编译烧录运行获得可复用的软I²C时序实现、ADS1115 16位采样校准逻辑、OLED12864图形化数据显示方案并通过源码深入理解STM32F103外设协同机制与低功耗ADC应用要点。1. 在 STM32F103 上用软件模拟 I2C2 驱动 ADS1115 实现 16 位高精度采集并在 OLED12864 上实时显示——这不是“凑合能用”而是面向工业传感场景的可复现闭环方案你手头有一块 STM32F103 最小系统板没有硬件 I2C 外设空闲I2C1 已接 EEPROMI2C2 的 SCL/SDA 引脚被复用为 SWD 调试口或 GPIO但又必须接入 ADS1115 这类高精度 16 位 ADC——此时硬改 PCB 或换主控不现实。标题里的Soft I2C2不是权宜之计而是工程中真实存在的约束解通过 GPIO 模拟 I2C 时序精准控制起始/停止条件、ACK/NACK 判定和时钟延时让 ADS1115 的 16 位有效分辨率±0.5 LSB INL不因通信抖动而劣化。本方案完整覆盖从 MDK 工程搭建Keil uVision5 v5.38、STM32F103 标准外设库 v3.5.0 配置、Soft I2C2 时序校准、ADS1115 寄存器级配置含 PGA 增益与数据速率协同设置到 OLED12864SSD1306 控制器的帧缓冲刷新策略。它不是 Demo 级闪烁数字而是每秒 860 次采样ADS1115 最高 860SPS 模式、OLED 显示延迟 40ms 的嵌入式传感终端原型——适合温湿度变送器、电池电压监测、传感器标定台等对精度与时效均有要求的场景。2. 为什么必须用 Soft I2C2 而非硬件 I2C从 STM32F103 引脚复用冲突到时序容错设计2.1 STM32F103 的 I2C 外设资源瓶颈与引脚复用现实约束STM32F103C8T6常见最小系统主控仅提供两路硬件 I2CI2C1 和 I2C2。I2C1 默认映射在 PB6SCL/PB7SDA但该组引脚在多数开发板上已被用于 SWD 调试SWDIO/SWCLK强行复用会导致无法烧录I2C2 默认映射在 PB10SCL/PB11SDA而 PB10/PB11 在标准原理图中常被配置为 USART3_TX/RX 或 TIM2_CH3/CH4若已用于串口通信或定时器捕获则硬件 I2C2 不可用。此时若强行启用硬件 I2C2需重布 PCB 或牺牲其他功能——这违背快速验证原则。Soft I2C2的本质是将任意两个 GPIO如 PA2/PA3配置为开漏输出 上拉电阻通过精确延时模拟 I2C 时序。其优势在于引脚完全自由、无外设抢占、时序可调性强——尤其适配 ADS1115 对建立/保持时间的严苛要求tSU;STA ≥ 4.7μstHD;STA ≥ 4.0μs。提示不要用 SysTick 中断做 Soft I2C 延时SysTick 中断服务程序ISR执行时间不可控且可能被更高优先级中断打断导致 SCL 高电平时间抖动超限ADS1115 返回 NACK。必须使用__nop()或us_delay()纯延时函数。2.2 Soft I2C2 的时序参数推导基于 STM32F103 72MHz 主频的纳秒级控制ADS1115 数据手册规定标准模式100kHz下关键时序tLOWSCL 低电平时间≥ 4.7μstHIGHSCL 高电平时间≥ 4.0μstSU;DAT数据建立时间≥ 250nstHD;DAT数据保持时间≥ 5μsSTM32F103 在 72MHz 主频下单条__nop()指令耗时 ≈ 13.9ns1/72MHz。经实测校准以下延时宏可满足 ADS1115 通信稳定性// 在 soft_i2c.h 中定义基于 72MHz 系统时钟 #define I2C_DELAY_US(x) do { uint32_t i (x) * 72; while(i--); } while(0) #define I2C_DELAY_NS(x) do { uint32_t i (x) / 14; while(i--); } while(0) // Soft I2C2 初始化引脚以 PA2SCL, PA3SDA 为例 #define I2C2_SCL_PIN GPIO_Pin_2 #define I2C2_SDA_PIN GPIO_Pin_3 #define I2C2_GPIO_PORT GPIOA // 输出模式开漏 上拉需外接 4.7kΩ 电阻 void Soft_I2C2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin I2C2_SCL_PIN | I2C2_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; // 开漏输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(I2C2_GPIO_PORT, GPIO_InitStructure); GPIO_SetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN | I2C2_SDA_PIN); // 初始高电平 }2.2.1 关键时序函数实现逻辑说明Soft_I2C2_Start()必须严格遵循SDA 在 SCL 高电平时下降 → SCL 保持高电平 ≥ 4.0μs → SDA 下降后 SCL 才可拉低。代码中I2C_DELAY_US(5)确保 SCL 高电平时间达标I2C_DELAY_NS(300)保证 SDA 建立时间 250ns。若省略此延时ADS1115 可能忽略 START 条件。// Soft_I2C2_Start() 函数核心片段 void Soft_I2C2_Start(void) { GPIO_SetBits(I2C2_GPIO_PORT, I2C2_SDA_PIN); // SDAH GPIO_SetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN); // SCLH I2C_DELAY_US(5); // tHIGH ≥ 4.0μs GPIO_ResetBits(I2C2_GPIO_PORT, I2C2_SDA_PIN); // SDA↓ (START) I2C_DELAY_NS(300); // tSU;DAT ≥ 250ns GPIO_ResetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN); // SCL↓ }2.2.2 ACK/NACK 判定为何必须用输入模式切换ADS1115 在接收字节后于第 9 个 SCL 高电平期间拉低 SDA 发送 ACK。Soft I2C2 必须在 SCL 高电平时读取 SDA 电平但 GPIO 不能同时输出与输入。因此需动态切换 SDA 引脚模式发送字节时为GPIO_Mode_Out_OD读取 ACK 时临时改为GPIO_Mode_IN_FLOATING并加I2C_DELAY_NS(100)等待信号稳定。若直接读取未切换模式的 GPIO 输出寄存器将永远返回 1开漏输出高电平状态导致 ACK 判定失败。// Soft_I2C2_Read_Byte() 中 ACK 判定段 uint8_t Soft_I2C2_Read_Byte(uint8_t ack) { uint8_t i, data 0; GPIO_InitTypeDef GPIO_InitStructure; // 切换 SDA 为输入模式 GPIO_InitStructure.GPIO_Pin I2C2_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(I2C2_GPIO_PORT, GPIO_InitStructure); for(i 0; i 8; i) { GPIO_SetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN); // SCL↑ I2C_DELAY_US(2); data 1; if(GPIO_ReadInputDataBit(I2C2_GPIO_PORT, I2C2_SDA_PIN)) data | 0x01; GPIO_ResetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN); // SCL↓ I2C_DELAY_US(2); } // 发送 ACK/NACK GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_Init(I2C2_GPIO_PORT, GPIO_InitStructure); if(ack) GPIO_ResetBits(I2C2_GPIO_PORT, I2C2_SDA_PIN); // SDA0 (ACK) else GPIO_SetBits(I2C2_GPIO_PORT, I2C2_SDA_PIN); // SDA1 (NACK) I2C_DELAY_US(2); GPIO_SetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN); // SCL↑ I2C_DELAY_US(5); GPIO_ResetBits(I2C2_GPIO_PORT, I2C2_SCL_PIN); // SCL↓ return data; }3. ADS1115 寄存器级配置与 16 位数据解析从单次转换到连续模式的精度保障3.1 ADS1115 的 16 位数据格式与 PGA 增益选择逻辑ADS1115 是差分/单端输入的 ΔΣ ADC其 16 位输出为二进制补码格式MSB 为符号位。关键寄存器CONFIG地址 0x01决定采样行为OSOperation Status1 单次转换完成0 转换进行中MUXMultiplexer选择输入通道0x00AIN0-AIN1, 0x01AIN0-AIN3, 0x02AIN1-AIN3, 0x03AIN2-AIN3, 0x04AIN0-GND, ...PGAProgrammable Gain Amplifier增益值直接影响满量程电压FSR和 LSB 大小MODE1连续转换0单次转换DRData Rate采样速率8SPS ~ 860SPS速率越高噪声越大PGA 设置满量程电压FSRLSB 大小μV适用场景0x00 (2/3)±6.144V187.5电池电压0~5V0x01 (1)±4.096V125.0温度传感器输出0~2.5V0x02 (2)±2.048V62.5惠斯通电桥mV 级0x03 (4)±1.024V31.25高精度应变片0x04 (8)±0.512V15.625微弱生物电信号0x05 (16)±0.256V7.8125低噪声前置放大器输出注意PGA 增益并非越高越好当输入信号幅值超过 FSR 时ADC 将饱和输出 0x7FFF 或 0x8000且高增益下输入偏置电流影响更显著。例如测量 3.3V 电源电压应选 PGA2FSR±2.048V而非 PGA16FSR±0.256V。3.2 写入 CONFIG 寄存器的完整流程与校验机制ADS1115 采用 16 位寄存器写入协议先发设备地址0x48~0x4B由 ADDR 引脚接地/接 VDD/接 SDA/接 SCL 决定再发寄存器地址0x01最后发 2 字节数据高位在前。以下函数实现带超时重试的可靠写入// ADS1115_Write_Config() 函数 uint8_t ADS1115_Write_Config(uint16_t config) { uint8_t tx_buf[3]; uint8_t dev_addr 0x48; // ADDRGND uint8_t reg_addr 0x01; // CONFIG register tx_buf[0] reg_addr; tx_buf[1] (config 8) 0xFF; // MSB tx_buf[2] config 0xFF; // LSB for(uint8_t retry 0; retry 3; retry) { if(Soft_I2C2_Start() 0) continue; // START 失败则重试 if(Soft_I2C2_Write_Byte(dev_addr 1) 0) { // 写地址WRITE bit Soft_I2C2_Stop(); continue; } if(Soft_I2C2_Write_Byte(tx_buf[0]) 0) { // 写寄存器地址 Soft_I2C2_Stop(); continue; } if(Soft_I2C2_Write_Byte(tx_buf[1]) 0) { // 写 MSB Soft_I2C2_Stop(); continue; } if(Soft_I2C2_Write_Byte(tx_buf[2]) 0) { // 写 LSB Soft_I2C2_Stop(); continue; } Soft_I2C2_Stop(); return 1; // success } return 0; // failed after 3 retries } // 示例配置为单次转换、AIN0-GND、PGA1、DR128SPS0x008C uint16_t config 0x008C; // OS1, MUX0x04, PGA0x01, MODE0, DR0x04 ADS1115_Write_Config(config);3.2.1 16 位数据读取与电压换算公式ADS1115 的CONVERSION寄存器地址 0x00存储 16 位转换结果。读取后需按 PGA 设置换算为实际电压// ADS1115_Read_Conversion() 函数 int16_t ADS1115_Read_Conversion(void) { uint8_t rx_buf[2]; uint8_t dev_addr 0x48; Soft_I2C2_Start(); Soft_I2C2_Write_Byte((dev_addr 1) | 0x00); // WRITE address Soft_I2C2_Write_Byte(0x00); // CONVERSION register Soft_I2C2_Start(); // Repeated START Soft_I2C2_Write_Byte((dev_addr 1) | 0x01); // READ address rx_buf[0] Soft_I2C2_Read_Byte(1); // ACK rx_buf[1] Soft_I2C2_Read_Byte(0); // NACK Soft_I2C2_Stop(); return (int16_t)((rx_buf[0] 8) | rx_buf[1]); } // 电压换算以 PGA1, FSR±4.096V 为例 int16_t raw_data ADS1115_Read_Conversion(); float voltage (float)raw_data * 4.096f / 32768.0f; // 32768 2^154. OLED12864 显示优化避免闪屏的双缓冲机制与 ADS1115 数据刷新策略4.1 SSD1306 驱动的 DMA 传输瓶颈与 CPU 占用率实测OLED12864128×64 像素使用 SSD1306 控制器其显存为 128×64/8 1024 字节。若每次刷新都通过 I2C 逐字节写入典型 I2C 速率为 100kHz传输 1024 字节需约 82ms1024×8bit / 100kbps远超 ADS1115 单次转换时间128SPS 时为 7.8ms。此时若在main()循环中直接调用OLED_Fill()CPU 将 100% 陷入 I2C 传输无法响应 ADC 完成中断导致采样丢点。解决方案是双缓冲 定时刷新维护两块 1024 字节显存frame_buffer[2][1024]CPU 在后台填充frame_buffer[1]DMA 或 I2C 中断服务程序在空闲时将frame_buffer[0]推送到 OLED二者通过buffer_index标志位同步。4.2 基于 SysTick 的 50Hz 刷新调度与数据截断处理为平衡显示流畅性与 CPU 负载设定 SysTick 中断每 20ms 触发一次50Hz在中断中检查 ADC 转换完成标志并更新显示缓冲区// 全局变量 volatile uint8_t buffer_index 0; volatile uint8_t adc_ready 0; int16_t latest_adc_value 0; // SysTick_Handler() void SysTick_Handler(void) { static uint32_t tick_count 0; tick_count; if(tick_count % 20 0) { // 20ms × 20 400ms即每 400ms 更新一次显示 if(adc_ready) { adc_ready 0; // 将最新 ADC 值转换为字符串并写入 frame_buffer[buffer_index] char str[12]; sprintf(str, %d, latest_adc_value); OLED_ShowString(0, 0, str, 16); // 切换缓冲区索引 buffer_index !buffer_index; // 启动 OLED 刷新任务非阻塞 OLED_Refresh_Task(); } } } // OLED_Refresh_Task() —— 在 main() 循环中调用 void OLED_Refresh_Task(void) { static uint16_t pos 0; const uint8_t *buf frame_buffer[!buffer_index]; // 刷新旧缓冲区 if(pos 1024) { // 分批写入每次 I2C 发送 16 字节避免长延时阻塞 uint8_t batch_size (1024 - pos) 16 ? 16 : (1024 - pos); OLED_Write_Buffer(buf pos, batch_size); pos batch_size; } else { pos 0; // 刷新完成 } }4.2.1 OLED 显示内容布局与单位标注技巧为提升工业场景可读性显示区域划分为三行第 1 行y0ADC 原始码值16 位有符号整数范围 -32768 ~ 32767第 2 行y16换算后电压值保留 3 位小数单位 V第 3 行y32当前 PGA 增益与采样速率如 PGA1, DR128SPS关键技巧避免浮点运算拖慢刷新。电压值换算采用定点数voltage_mV (raw_data * 125) 7;因 4.096V / 32768 125μV/LSB故raw_data × 125得微伏值右移 7 位得毫伏值。再用整数除法分离整数与小数部分int32_t mV (int32_t)latest_adc_value * 125; mV 7; // now in mV int32_t V_int mV / 1000; int32_t V_dec mV % 1000; char disp_str[16]; sprintf(disp_str, %d.%03dV, V_int, V_dec); OLED_ShowString(0, 16, disp_str, 16);5. MDK 工程配置关键项GB2312 编码兼容性、启动文件匹配与 Keil v5.38 特定设置5.1 解决 Keil MDK 中文注释乱码GB2312 转 UTF-8 的工程级操作标题中mdk工程编码gbk改为utf-8直指痛点STM32F103 标准外设库 v3.5.0 的.h/.c文件默认为 GB2312 编码而 Keil uVision5 v5.38 默认以 UTF-8 解析源文件。若不统一中文注释如// 初始化 Soft I2C2 引脚将显示为涓?鍒?Soft I2C2 鍥句笣鑴?且编译器可能误判语法错误。必须全局转换工程文件编码在 Keil 中打开 Project → Options for Target → C/C 选项卡勾选Use MicroLIB减小代码体积且 MicroLIB 的printf支持 UTF-8在Misc Controls栏添加--unicode强制 UTF-8 解析手动转换所有源文件用 Notepad 打开stm32f10x_conf.h等文件 → 编码 → 转为 UTF-8 无 BOM → 保存重启 Keil重新编译——中文注释正常显示且OLED_ShowString()中传入的中文字符串如电压可正确渲染需确保 OLED 字模为 UTF-8 编码提示若使用 HAL 库其默认为 UTF-8无需转换但本方案基于标准外设库 v3.5.0必须执行此步骤否则编译报错error: #18: expected a )因乱码字符破坏了宏定义语法。5.2 STM32F103 启动文件与链接脚本匹配验证MDK 工程中startup_stm32f10x_md.s对应 Medium Density64~128KB Flash必须与实际芯片 Flash 容量匹配。若误用startup_stm32f10x_hd.sHigh Density256KB Flash复位向量表将越界导致程序跑飞。验证方法查看芯片丝印如STM32F103C8T6中C8表示 64KB Flash→ 应选用md启动文件在Options for Target → Target选项卡中Flash大小设为64KRAM设为20K检查stm32f10x_flash.ld或 Keil 自动生成的 scatter file中LR_IROM1地址范围是否为0x08000000至0x0800FFFF64KB5.3 Keil v5.38 下的 ADS1115 时序调试技巧逻辑分析仪抓取关键波形当 Soft I2C2 通信失败ADS1115 返回 NACK最有效排错方式是用逻辑分析仪捕获 SCL/SDA 波形。重点关注三点START 条件SDA 下降沿是否发生在 SCL 高电平期间下降后 SCL 是否保持高电平 ≥ 4.0μsACK 时序第 9 个 SCL 高电平期间SDA 是否被 ADS1115 拉低至 ≤ 0.4VSCL 频率计算 SCL 周期高低时间确认是否接近 10μs100kHz若过快如 5μs需增加I2C_DELAY_US()参数若过慢如 20μs需减少延时。典型故障波形与修复现象波形特征原因修复无 STARTSDA 始终高电平Soft_I2C2_Start()中GPIO_ResetBits()未执行检查 GPIO 时钟使能、引脚定义是否匹配硬件NACK 持续SDA 在第 9 个 SCL 高电平保持高电平SDA 输入模式切换失败或 ADS1115 地址错误用万用表测 SDA 引脚上拉电阻是否为 4.7kΩ确认dev_addr计算0x48 ADDR 引脚电平数据错乱SCL 低电平时间 4.7μsI2C_DELAY_US(5)在高频下不足改用I2C_DELAY_US(6)并重新校准最终当逻辑分析仪捕获到标准 I2C 波形SCL 方波、SDA 在 SCL 低电平时变化、ACK 期间 SDA 下拉且 OLED 稳定显示32767正向满量程与-32768负向满量程交替变化时Soft I2C2 ADS1115 OLED12864 的 16 位采集闭环即宣告打通。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价