ADS1118 SPI驱动详解:PGA量程切换后的等待时间与源码实现

ADS1118 SPI驱动详解:PGA量程切换后的等待时间与源码实现 简介这是一套针对TI公司16位ADC芯片ADS1118、基于51单片机编写的C语言源代码工程面向嵌入式初学者与需要高精度电压采集的开发者适用于传感器信号采集、工业控制、仪表测量等场景可直接学习或移植到实际项目。压缩包共17个文件、约41KB内包含ads1118.c、main.c等核心源码与ADS1118.H头文件以及Keil工程文件uvproj/uvopt、编译生成的hex/obj/lst/m51文件和若干备份其中.c和.h用于代码阅读与修改.uvproj/.uvopt用于打开工程.hex可烧录.lst/.m51便于分析编译细节结构完整清晰。目前已有841人学习浏览适合理解ADS1118的寄存器配置、SPI通信时序以及51单片机串口收发机制。代码注释详尽主程序完整演示了初始化芯片、读取电压值并通过串口发送到PC的流程还涉及波特率、通信参数等配置说明能帮助使用者快速上手并在理解ADC工作原理后自行调整代码实现多通道采集或数据记录功能。 做16位数据采集项目时ADS1118是一颗绕不开的芯片SPI接口、16位分辨率、内置4.096V基准、自带温度传感器价格不贵非常适合做多路模拟量采集。但真正动手写驱动时你会发现网上大量教程都是ADS1115的I2C接口寄存器配置和SPI时序完全是另一套逻辑直接套用根本跑不通。这篇文章就把ADS1118的SPI时序、命令寄存器、源代码实现一次讲清楚并且重点回答一个经常被忽略的问题重新配置量程PGA之后到底需要等待多久才能读到有效数据。1. ADS1118到底是一颗什么样的ADC1.1 ADS1118和ADS1115的本质区别很多人选型时会先搜到ADS1115再用到ADS1118结果发现两者从接口到寄存器都不是一回事。ADS1115是I2C接口而ADS1118是SPI接口频率最高可以跑到4MHz。更关键的是ADS1118内部集成了4.096V基准电压源还带一个板载温度传感器这两点是ADS1115没有的。也就是说ADS1118不需要外部基准芯片数据可以直接换算成绝对电压值省掉一路基准电路。另外要留意ADS1118的PGA和ADS1115的PGA虽然看起来都是3位配置但FSR档位并不完全一致。ADS1118常见的可用档位从±4.096V往下到±0.256V共5档个别版本手册也列了±6.144V实际工程中极少使用。这一点在写配置命令时特别容易踩坑——抄了ADS1115的寄存器值很可能配置出一个超出芯片实际能力的量程。1.2 写驱动前必须确认的四个关键参数动手写代码之前我建议先把下面四个参数在数据手册里圈出来因为它们直接决定了驱动函数怎么写。第一个是数据格式。ADS1118的转换结果是16位二进制补码最高位是符号位。计算真实电压的公式是电压值 原始码值 × FSR / 32768例如PGA配置为±4.096V时1个LSB对应4.096 / 32768 0.125mV。注意不是除以65536因为FSR是正负对称的完整量程。第二个是PGA和FSR的对应关系。ADS1118的PGA[2:0]位位于命令寄存器的bit11~bit9常用配置如下PGA[2:0]FSR分辨率1 LSB001±4.096V0.125mV010±2.048V0.0625mV011±1.024V0.03125mV100±0.512V0.015625mV101±0.256V0.0078125mV第三个是数据速率。DR[2:0]位于bit7~bit5支持从8SPS到860SPS共8档这个参数不仅决定采样快慢也决定后面要讲的“配置生效等待时间”。第四个是SPI工作模式。ADS1118要求SPI Mode 1也就是CPOL0、CPHA1这一点比寄存器配置还容易让人翻车。很多人照着ADS1115的代码写了个I2C转SPI的壳子结果读回来的数据全是0xFF问题往往就出在这里。2. 驱动源码的结构与关键设计2.1 命令寄存器的每一位到底怎么填ADS1118的SPI通信核心是16位命令寄存器。每次传输数据时首先要把这16位配置写到DIN引脚上。这16位的排列顺序是高位在前具体的位定义如下bit15 保留位写0 bit14~12 MUX[2:0]通道选择 bit11~9 PGA[2:0]增益配置 bit8 MODE1表示单次模式0表示连续模式 bit7~5 DR[2:0]数据速率 bit4 TS_MODE1表示温度传感器模式0表示ADC模式 bit3 PULL_UP_ENDOUT引脚内部上拉使能 bit2 NOP写0 bit1~0 保留位写0通道选择MUX的编码也要记清楚因为ADS1118的引脚不是纯粹的“8选1”单端输入而是支持多种差分组合。比如MUX000表示AIN0和AIN1差分输入MUX100表示AIN0对GND的单端输入。写驱动时最好把每个组合定义成宏否则直接在函数里填数字过两周自己都看不懂了。这里有一个很容易忽略的点命令寄存器的bit3是上拉使能位。如果你的DOUT引脚没有外部上拉建议把这一位置1否则在SCLK频率较高时MISO线上的信号沿可能不够陡峭导致采到错误数据。2.2 SPI通信时序一次CS低周期内发生了什么ADS1118的SPI时序和普通SPI从机不太一样。常规做法是CS拉低发送16位命令然后继续发送16个SCLK来读取转换结果。也就是说一次有效操作需要完整的32个SCLK中间CS不能拉高。具体过程是这样的CS拉低后主机向DIN发送配置命令此时芯片会根据命令内容启动一次新的转换接着主机再发送16个时钟ADS1118会把最近一次完成的转换结果从DOUT线逐位移出。第一次发送命令时DOUT上也会移出数据但那是上一次的旧数据通常直接丢弃。因此代码里最简单的通信函数就是这样static uint16_t ads1118_transfer16(uint16_t tx) { uint8_t tx_buf[2] { (uint8_t)(tx 8), (uint8_t)(tx 0xFF) }; uint8_t rx_buf[2] { 0, 0 }; HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 2, 10); return ((uint16_t)rx_buf[0] 8) | rx_buf[1]; }这里要注意字节顺序。寄存器定义是高位在前所以发送时先发高8位再发低8位。读取时同样把收到的第一字节作为高8位。如果你用的MCU是STM32的HAL库SPI配置为8位数据帧就可以不用开16位帧模式。16位帧模式在部分STM32型号上会因为字节序配置不对导致高低字节互换反而多一层麻烦。3. 可直接使用的源代码实现3.1 头文件与关键宏定义下面这套驱动代码基于STM32 HAL库编写但核心逻辑不依赖HAL移植到其他平台只需要替换底层HAL_SPI_TransmitReceive和两个CS控制函数即可。首先是头文件把命令寄存器的常用配置全部定义成宏// ads1118.h #ifndef ADS1118_H #define ADS1118_H #include stdint.h // MUX通道配置 #define ADS1118_MUX_DIFF_0_1 0x0000 #define ADS1118_MUX_DIFF_0_3 0x1000 #define ADS1118_MUX_DIFF_1_3 0x2000 #define ADS1118_MUX_DIFF_2_3 0x3000 #define ADS1118_MUX_AIN0_GND 0x4000 #define ADS1118_MUX_AIN1_GND 0x5000 #define ADS1118_MUX_AIN2_GND 0x6000 #define ADS1118_MUX_AIN3_GND 0x7000 // PGA增益配置 #define ADS1118_PGA_4_096V 0x0200 #define ADS1118_PGA_2_048V 0x0400 #define ADS1118_PGA_1_024V 0x0600 #define ADS1118_PGA_0_512V 0x0800 #define ADS1118_PGA_0_256V 0x0A00 // 工作模式 #define ADS1118_MODE_CONTINUOUS 0x0000 #define ADS1118_MODE_SINGLE 0x0100 // 数据速率 #define ADS1118_DR_8_SPS 0x0000 #define ADS1118_DR_16_SPS 0x0020 #define ADS1118_DR_32_SPS 0x0040 #define ADS1118_DR_64_SPS 0x0060 #define ADS1118_DR_128_SPS 0x0080 #define ADS1118_DR_250_SPS 0x00A0 #define ADS1118_DR_475_SPS 0x00C0 #define ADS1118_DR_860_SPS 0x00E0 // 温度传感器模式 #define ADS1118_TS_MODE_ADC 0x0000 #define ADS1118_TS_MODE_TEMP 0x0010 uint16_t ads1118_read_raw(uint16_t config); float ads1118_read_voltage(uint16_t mux, uint16_t pga, uint16_t dr); float ads1118_read_temperature(void); #endif这里我把PGA从±4.096V开始定义没有放±6.144V。ADS1118的内置基准就是4.096V±6.144V档位在实际工程中几乎用不到而且输入范围很容易超出电源轨导致芯片损坏所以直接忽略反而更安全。3.2 底层SPI收发函数与单次读取实现C文件里先实现最底层的CS控制和SPI收发然后实现一次完整的读写周期。单次模式的关键在于发送配置命令后必须等转换完成再发起第二次SPI传输读取结果。// ads1118.c #include ads1118.h #include spi.h #include gpio.h static void ads1118_cs_low(void) { HAL_GPIO_WritePin(ADS1118_CS_GPIO_Port, ADS1118_CS_Pin, GPIO_PIN_RESET); } static void ads1118_cs_high(void) { HAL_GPIO_WritePin(ADS1118_CS_GPIO_Port, ADS1118_CS_Pin, GPIO_PIN_SET); } static uint16_t ads1118_transfer16(uint16_t tx) { uint8_t tx_buf[2] { (uint8_t)(tx 8), (uint8_t)(tx 0xFF) }; uint8_t rx_buf[2] { 0, 0 }; HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 2, 10); return ((uint16_t)rx_buf[0] 8) | rx_buf[1]; } static void ads1118_wait_conversion(uint16_t dr_cfg) { uint32_t delay_ms 0; switch (dr_cfg) { case ADS1118_DR_8_SPS: delay_ms 125; break; case ADS1118_DR_16_SPS: delay_ms 63; break; case ADS1118_DR_32_SPS: delay_ms 32; break; case ADS1118_DR_64_SPS: delay_ms 16; break; case ADS1118_DR_128_SPS: delay_ms 8; break; case ADS1118_DR_250_SPS: delay_ms 4; break; case ADS1118_DR_475_SPS: delay_ms 3; break; case ADS1118_DR_860_SPS: delay_ms 2; break; default: delay_ms 10; break; } HAL_Delay(delay_ms); } uint16_t ads1118_read_raw(uint16_t config) { uint16_t result 0; ads1118_cs_low(); ads1118_transfer16(config); // 写配置启动转换 if (config ADS1118_MODE_SINGLE) { ads1118_wait_conversion(config 0x00E0); } result ads1118_transfer16(config); // 发送相同配置读回转换结果 ads1118_cs_high(); return result; }注意第二次传输时发送的仍然是同样的config而不是0。原因在前文说过任何一次16位SPI传输都会被ADS1118当作配置命令写入。如果第二次传0等于把PGA、数据速率、模式全部清掉芯片配置就乱了。传相同的config虽然会多触发一次转换但当前读取到的是上一次转换已经完成的结果不会出错这个代价可以接受。3.3 连续模式与温度传感器读取连续模式下不需要每次等待转换完成因为芯片一直在后台转换读到的始终是最近一次结果。初始化一次配置后后续读取速度可以很快适合对采样率有要求的场景。float ads1118_read_voltage(uint16_t mux, uint16_t pga, uint16_t dr) { uint16_t config mux | pga | ADS1118_MODE_SINGLE | dr | ADS1118_TS_MODE_ADC; uint16_t raw ads1118_read_raw(config); int16_t code (int16_t)raw; float fsr 4.096f; switch (pga) { case ADS1118_PGA_4_096V: fsr 4.096f; break; case ADS1118_PGA_2_048V: fsr 2.048f; break; case ADS1118_PGA_1_024V: fsr 1.024f; break; case ADS1118_PGA_0_512V: fsr 0.512f; break; case ADS1118_PGA_0_256V: fsr 0.256f; break; } return (float)code * fsr / 32768.0f; } float ads1118_read_temperature(void) { uint16_t config ADS1118_MUX_AIN0_GND | ADS1118_PGA_4_096V | ADS1118_MODE_SINGLE | ADS1118_DR_8_SPS | ADS1118_TS_MODE_TEMP; uint16_t raw ads1118_read_raw(config); int16_t temp_code (int16_t)(raw 0xFFFC); // 低2位无效清零 return (float)(temp_code 2) * 0.03125f; }温度传感器是自检驱动最方便的通道。它的数据格式是14位有符号数每LSB对应0.03125摄氏度低两位始终为0。读出来的值在常温下应该是20到40摄氏度之间如果明显偏离这个范围基本可以确定SPI时序或字节序有问题。4. 重新配置量程后到底要等多久4.1 量程切换为什么不能“立即生效”这是标题里那个问题ADS1118如果重新配置量程需要等待多少时间很多人在调试时会发现切换PGA后第一次读到的电压值还是老样子或者数值明显不对然后开始怀疑代码写错了。原因在于ADC内部的前端模拟电路需要稳定时间而且芯片的转换过程是持续进行的。写入新配置后正在进行的转换可能仍然基于旧配置新配置要到下一个转换周期才会完整生效。更稳妥的说法是修改PGA或DR之后必须丢弃最新读到的一个结果从第二次读取开始才是新量程下的有效数据。所以等待时间的下限是至少一个转换周期。数据手册里的转换周期可以近似为1/DR。4.2 一张表搞定所有数据率下的等待时间按转换周期估算各档数据率下的最短等待时间如下数据速率转换周期修改PGA后建议等待时间8 SPS125ms250ms16 SPS62.5ms125ms32 SPS31.25ms62.5ms64 SPS15.625ms31.25ms128 SPS7.8125ms15.625ms250 SPS4ms8ms475 SPS2.105ms4.2ms860 SPS1.163ms2.3ms这里“建议等待时间”按两个转换周期计算也就是保留一个周期的裕量。如果应用对时间要求极苛刻可以只等一个周期但第一次读到的数据要主动丢弃。4.3 代码里的等待实现与优化前面代码中的ads1118_wait_conversion已经按一个转换周期做了延时。如果应用层切换PGA正确的调用顺序是这样的// 切换到±2.048V量程 ads1118_read_voltage(ADS1118_MUX_AIN0_GND, ADS1118_PGA_2_048V, ADS1118_DR_128_SPS); // 丢弃第一次 float v ads1118_read_voltage(ADS1118_MUX_AIN0_GND, ADS1118_PGA_2_048V, ADS1118_DR_128_SPS); // 有效值调一次ads1118_read_voltage内部已经包含了写配置加等待所以连续调用两次第二次拿到的就是稳定结果。如果连第一次的浪费都不想接受可以把ads1118_read_raw改成只写配置不读数据然后延时两个转换周期再读但代码可读性会差一些。另一个优化点是用DOUT/DRDY脚做硬件判断。ADS1118的DOUT/DRDY在转换完成时会有电平变化可以接到MCU的外部中断引脚转换完成后再发起SPI读取。这样省掉固定延时同时能获得最大吞吐率。不过这个功能在SPI多从机总线上的表现需要仔细看手册如果嫌麻烦固定延时方案在绝大多数项目里都够用了。5. 调试实录我踩过的几个坑和排查方法5.1 SPI模式写成Mode 0读回全0xFF新手最容易犯的错误就是把SPI初始化成Mode 0CPOL0, CPHA0。这会导致主机在错误的时钟边沿采样MISO线上的数据ADS1118输出的信号在采样点还没稳定下来读回的自然是一堆0xFF。排查方法很简单先初始化SPI为Mode 1用逻辑分析仪抓SCLK、MOSI、MISO三根线确认一次CS低周期内MOSI上有16位配置数据MISO上有16位回读数据。如果没有逻辑分析仪就反复切换MUX通道观察读到的值是否跟着变化如果始终是同一个值大概率是SPI模式不对。5.2 电压值抖动严重如果你的输入信号是稳定的直流电压读出来的值却在几十个LSB范围内来回跳先别怀疑芯片。检查一下电源和参考地。ADS1118内部虽然有4.096V基准但基准电路的稳定性依然依赖AVDD和GND的干净程度。建议在AVDD和GND之间放一个1uF陶瓷电容加一个0.1uF高频电容位置尽量靠近芯片引脚。软件层面可以加分均值滤波。以128SPS采样率为例连续读16次取平均等效数据率降到8SPS噪声能显著压低。如果对速度不敏感直接把数据速率降到32SPS或16SPS是改善抖动最省事的方法。5.3 用温度传感器自检驱动每次写好驱动我做的第一件事不是接传感器而是读温度。因为温度传感器不需要外部电路只要SPI通信正常读出来的值一定是合理温度。如果温度读数在预期范围内说明SPI时序、寄存器写入、数据拼接都没问题了再接外部信号排查干扰才有意义。这个习惯帮我省了很多时间。有一次硬件上MISO和MOSI接反了我第一时间去查SPI模式查了半小时没结果。后来把MUX配置成AIN0对GND把AIN0引脚直接接地读到的值依然是乱码才反应过来是硬件接反。所以调试顺序应该是先软件自检再硬件排查。5.4 多通道采集时配置切换顺序ADS1118每次读取都要重新发送完整配置所以多通道循环采样时要注意“丢弃第一次”的规则。比如你从AIN0切换到AIN1如果立即读取拿到的可能还是AIN0的数据。切换通道后先丢一个结果再开始采集这个操作表面上看浪费时间实际能省掉后续大量数据校验工作。如果项目里有多个模拟通道建议把每个通道的配置都存成常量循环采样时复用配置值。不要每次都临时算一遍PGA和MUX的位或结果这样容易在通道切换时漏掉某个位导致量程忽大忽小。我见过有人因为宏定义里漏了一个0x1000所有AIN0单端读数都变成了差分读数折腾了一天才定位到。最后再分享一个经验拿到ADS1118后第一件事不要急着接复杂电路。先按数据手册搭一个最小系统用温度传感器自检再短接AIN0到GND和AVDD各测一次确认满偏值与0点都对得上再接入真实传感器。这套流程走下来驱动层面基本不会再有玄学问题。本文还有配套的精品资源点击获取