简介:ADS8326驱动例程基于Keil MDK开发环境,面向STM32F103ZET6战舰开发板,是一份完整的16位高精度单通道ADC驱动程序。由于网络资源匮乏,作者依据芯片手册时序自行编写,采用软件模拟SPI协议,能规避硬件SPI引脚限制,实测外部直流电压采集误差在±0.6mV以内,亲测有效。压缩包内共209个文件,大小约5.18MB,以C源码、头文件、Keil工程配置为主,并集成LCD显示、定时器、I2C、CAN等外设模块,既可直接打开工程查看完整逻辑,也可将核心驱动提取移植到其他STM32项目。目前已有1134人浏览学习,适合嵌入式开发初学者与进阶者学习高精度ADC驱动设计、软件模拟SPI时序以及ST标准库使用。整体资源结构清晰,能够帮助读者理清从时序配置、数据采集、电压换算到界面显示的完整闭环。
1. ADS8326 驱动程序:.rar 里装的不是 Windows 驱动,而是一段比特级采样时序
拿到“ADS8326驱动程序.rar”这个压缩包,如果第一反应是把它当成 Windows 驱动去装、去排查数字签名问题,方向就偏了。ADS8326 是 TI 的一颗 16 位、最高 250kSPS 的 SAR ADC,它没有寄存器、没有 SPI 地址、没有配置字,所谓“驱动程序”,本质就是一段把 CONVST、CS、SCLK、SDO 四根引脚按数据手册时序摆动的代码。解压后通常是 C 源码或 Verilog 源码,外加简短 README。做数据采集、工业信号采样、传感器前端的人会需要它;新手用它跑通一颗串行 ADC 的完整链路,熟手则用它评估这颗芯片在 STM32F4 或 FPGA 平台上的极限采样率。
2. 先把串行时序读对:前导位、边沿极性和 17 拍读取的由来
2.1 引脚分工:CONVST、CS、SCLK、SDO 各自管什么
ADS8326 单通道输入,接口一共 4 根信号线,外加电源和参考电压。CONVST 是转换触发脚,下降沿启动一次 SAR 转换,转换结束后结果被锁存在内部移位寄存器;CS 是片选,拉低后 SDO 才从高阻态释放;SCLK 是移位时钟,负责把移位寄存器里的数据逐位移出来;SDO 是串行数据输出。
这颗芯片最特别的地方:整个通信过程没有任何“命令字”,主机不需要向芯片写任何配置。你拉低 CS、给时钟,芯片就把最近一次转换结果直接推出来。因此“写驱动”的全部工作都集中在时序上——把四根引脚的波形对齐,数据自然就对了。初次接触的人容易用惯性思维去找寄存器映射表,在这颗芯片上找不到,这是第一个认知切换。
2.2 数据手册里必须记住的 5 个时序数字
驱动能跑通,靠的是手册“Timing Requirements”表里几个关键参数。我把常用的整理成一张表,单位以 ns 为主:
| 参数 | 含义 | 典型参考值 |
|---|---|---|
| t_CONV | CONVST 下降沿到转换结果就绪 | 1.7us 典型,留余量按 2.1us 算 |
| t_CS | CS 拉低到 SCLK 开始移位的建立时间 | 约 40ns |
| t_SU | SDO 数据相对 SCLK 采样沿的建立时间 | 约 20ns |
| t_H | 采样沿之后数据保持时间 | 约 10ns |
| SCLK 频率 | 移位时钟上限 | 我习惯按 20MHz 以内用 |
这几个数字是边界,不是目标。写驱动时不需要卡着最小值跑,反而要留 2 到 3 倍裕量。CS 拉低后先延时 100ns 再给时钟,SCLK 半周期至少给到 50ns,换来的稳定性远大于省下的那几百纳秒。t_CONV 要特别注意:CONVST 下降沿之后不能立刻去读数据,SAR 转换需要时间,读太早会把上一次的旧数据甚至中间态读出来。
2.3 为什么 SPI 模式 0 会翻车:CPOL/CPHA 与下降沿移出的矛盾
ADS8326 的输出节奏是 SCLK 下降沿移出数据、上升沿锁存数据,对应标准 SPI 模式 1,也就是 CPOL=1、CPHA=1:SCLK 空闲时为高电平,数据在时钟第二个边沿被采样。很多人直接套用 SPI 模式 0(空闲低、第一个边沿采样),结果读回来的数据高位乱跳,还以为是硬件焊接问题。
原因在于极性和相位错开半拍。用 STM32F4 的 SPI 外设时,必须把 SPI 配置成 CPOL=1、CPHA=1;如果纯 GPIO 模拟,SCLK 初始电平保持高,下降沿输出数据、上升沿前把 SDO 电平读进变量即可。还有个容易漏的细节:ADS8326 的串行帧带一个前导位。我常用的是 17 拍读取——第一拍读到的值丢弃,后面 16 拍才是真正的转换结果。具体帧格式以你手上那份 datasheet 的时序图为准,如果手册版本显示没有前导位,把循环从 17 改成 16,用满量程校验法确认(第 6 章会讲)。
3. STM32F4 上的最小驱动实现:GPIO 模拟时序,先跑通再考虑 DMA
3.1 初始化代码:把四个引脚配成你想要的初始电平
我先用 GPIO 模拟时序。比 SPI 外设多了 SCLK 高低的开关控制,但逻辑直白,出问题容易定位。下面是一份基于 STM32F4 + HAL 库的初始化,假设 CONVST 接 PB0、CS 接 PB1、SCLK 接 PB2、SDO 接 PB3:
void ads8326_gpio_init(void) { GPIO_InitTypeDef gpio; __HAL_RCC_GPIOB_CLK_ENABLE(); /* CONVST、CS、SCLK 全部推挽输出 */ gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &gpio); /* SDO 为输入,下拉防止悬空抖动 */ gpio.Pin = GPIO_PIN_3; gpio.Mode = GPIO_MODE_INPUT; gpio.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(GPIOB, &gpio); /* 初始电平:CONVST 高、CS 高、SCLK 高(对应 CPOL=1) */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2, GPIO_PIN_SET); }初始电平很关键。SCLK 空闲必须保持高,对应 SPI 模式 1 的极性;CS 初始为高,保证芯片 SDO 处于高阻;CONVST 初始为高,避免上电瞬间误触发转换。GPIO 速度配 HIGH,是为了让边沿更陡、时序更接近手册要求,信号质量会直接影响高位读取的正确性。
3.2 一次采样的核心函数:拉低 CONVST、等转换、17 拍读数据
核心读取函数如下。这里用了一个简单的 for 循环延时,因为 STM32F4 的主频固定,粗略延时足够稳定:
static void delay_ns(uint32_t ns) { for (volatile uint32_t i = 0; i < (ns * 168) / 1000; i++) ; } uint16_t ads8326_read_once(void) { uint16_t raw = 0; int i; /* 1. CONVST 下降沿启动转换 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); delay_ns(50); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); /* 2. 等待转换完成,按 2.1us 最大值加余量 */ delay_ns(2500); /* 3. CS 拉低,SDO 解除高阻 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); delay_ns(100); /* 4. 第 0 拍:前导位,直接丢弃 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); delay_ns(50); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET); delay_ns(50); /* 5. 16 拍数据:下降沿芯片移出,下降沿后采样 */ for (i = 0; i < 16; i++) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); /* 下降沿 */ delay_ns(50); /* 等数据稳定 */ raw <<= 1; raw |= HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3); /* 读取当前位 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET); /* 上升沿 */ delay_ns(50); } /* 6. CS 拉高,结束本次传输 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); return raw; }逻辑上分六步,每一步对应一段物理过程。CONVST 低脉冲保持 50ns,触发转换;延时 2.5us 等 SAR 内部完成比较;CS 拉低后 SDO 才输出有效电平;前导位读掉后,16 个数据位在下降沿之后读取,原因是芯片在 SCLK 下降沿更新 SDO,更新后延时 50ns 电平稳定,此时读取最安全。参数上三个数值值得调:延时 2.5us 来自 t_CONV,别省;SCLK 半周期 50ns 对应 20MHz 时钟,对 SPI 外设够用,对 GPIO 模拟来说已经是比较紧的速度;如果发现高几位闪烁,优先把 50ns 改成 100ns。
3.3 关键参数调整:SCLK 延时、采样率上限和中断触发
这套 GPIO 实现能跑多快,取决于延时函数和主频。我按 STM32F407 168MHz 计算:单次采样耗时约等于 t_CONV 2.5us 加 17 拍乘每拍 100ns,大约 4.2us,折算采样率约 230kSPS。听起来接近标称 250kSPS,但 GPIO 模拟的延时抖动和中断打扰会让实际可靠采样率掉到 150kSPS 左右。工程上更常见的做法是降到 100kSPS 用,或者把 CONVST 触发交给定时器,读取放在中断回调里。
用定时器触发时要注意:CONVST 引脚最好由定时器输出比较通道直接控制,这样触发抖动是纳秒级,而不是软件中断的微秒级。读取仍用 GPIO 模拟没关系,只要保证读取耗时小于采样周期。100kSPS 下周期是 10us,上面函数约 4.2us 的耗时完全够。想逼近 250kSPS,就得换第 6 章的 SPI 外设 + DMA 方案。
4. FPGA 版驱动实现:用状态机把采样时序固化下来
4.1 为什么用 FPGA:连续采样率、抖动和确定性
FPGA 做 ADS8326 驱动是另一条常见路线,适合需要连续采样的场合。STM32F4 用 GPIO 模拟,读 16 位数据之间要穿插 for 循环延时,延时的长短依赖主频和编译优化等级,时序抖动免不了。FPGA 则用分频计数器精确产生 SCLK,每个边沿都在确定的时钟周期上,抖动只来自 PLL 或外部晶振,可以把 SCLK 推到 20MHz 附近,连续跑满 250kSPS。
状态机设计上,我习惯拆成四拍:IDLE 等触发;CONV 产生 CONVST 低脉冲并等待转换完成;SHIFT 产生 17 拍 SCLK 并串行采样;FINISH 输出结果。表层看步骤和 STM32 版一样,但每步的时间精确到时钟周期,没有软件分支跳变的随机延迟。和 AD7606 那种 8 通道并行接口相比,ADS8326 引脚占用少,代价是吞吐量小,适合单通道高精度采集场景。
4.2 Verilog 实现:计数器分频产生 SCLK,奇数拍采样
FPGA 代码的骨架如下。用 cnt 做 SCLK 半周期分频:cnt[0]=0 时 SCLK 拉低,cnt[0]=1 时 SCLK 拉高,每两拍形成一个完整时钟周期:
module ads8326_driver ( input wire clk, // 系统时钟,假设 50MHz input wire rst_n, input wire start, // 外部触发,高电平启动一次采样 output reg convst, // 接 ADS8326 CONVST output reg cs_n, // 接 ADS8326 CS,低有效 output reg sclk, // 接 ADS8326 SCLK input wire sdo, // 接 ADS8326 SDO output reg [15:0] result, // 16 位转换结果 output reg done // 单次采样完成标志 ); localparam IDLE = 3'd0, CONV = 3'd1, SHIFT = 3'd2, FINISH = 3'd3; reg [2:0] state; reg [7:0] cnt; // 分频与延时计数 reg [4:0] bit_cnt; // 已采样位数 reg [16:0] shift_reg; // 17 位移位寄存器,bit16 为前导位 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; convst <= 1'b1; cs_n <= 1'b1; sclk <= 1'b1; result <= 16'd0; done <= 1'b0; cnt <= 8'd0; bit_cnt <= 5'd0; shift_reg <= 17'd0; end else begin case (state) IDLE: begin done <= 1'b0; cs_n <= 1'b1; if (start) begin state <= CONV; cnt <= 8'd0; end end CONV: begin if (cnt == 8'd0) begin convst <= 1'b0; // CONVST 拉低,触发转换 end else if (cnt == 8'd4) begin convst <= 1'b1; // 低脉冲结束 end else if (cnt == 8'd105) begin cs_n <= 1'b0; // 转换完成,拉低 CS sclk <= 1'b1; state <= SHIFT; bit_cnt <= 5'd0; cnt <= 8'd0; end cnt <= cnt + 1'b1; end SHIFT: begin if (bit_cnt == 5'd17) begin cs_n <= 1'b1; // 读够 17 拍,CS 拉高 state <= FINISH; end else begin cnt <= cnt + 1'b1; sclk <= cnt[0] ? 1'b1 : 1'b0; // 偶数拍低,奇数拍高 if (cnt[0]) begin shift_reg <= {shift_reg[15:0], sdo}; bit_cnt <= bit_cnt + 1'b1; end end end FINISH: begin result <= shift_reg[15:0]; // 丢弃前导位,输出 16 位结果 done <= 1'b1; state <= IDLE; end endcase end end endmodule参数说明:50MHz 时钟下,CONVST 低脉冲保持 5 个时钟也就是 100ns,满足触发要求;转换等待 105 个时钟约 2.1us,对应 t_CONV 最大值;SCLK 在 SHIFT 状态由 cnt[0] 翻转,每两拍一个周期,SCLK 约 25MHz,离手册上限还有余量,如果目标 250kSPS,SCLK 用 12.5MHz 足够。采样点在 cnt[0]=1 的时钟沿,也就是 SCLK 拉高之后的稳定窗口内,保证读到的电平不是边沿附近的毛刺。行为仿真能跑通,综合之前建议把 SCLK 分频改成独立模块输出,并且检查 SDO 到寄存器输入之间的路径时序,这是 FPGA 驱动最常被忽略的一步。
4.3 与 STM32 版对比:平台选择的三个维度
| 对比项 | STM32F4 + GPIO 模拟 | FPGA 状态机 |
|---|---|---|
| 可靠采样率 | 100kSPS 到 150kSPS | 250kSPS 满速 |
| 时序抖动 | 受中断和编译影响,微秒级 | 由时钟分频决定,纳秒级 |
| 开发难度 | 调试方便,示波器看 GPIO 即可 | 需要仿真和时序约束 |
| 适用场景 | 低速采集、多通道切换、快速原型 | 连续采样、固定节拍、对抖动敏感 |
选择上,如果只是把 ADS8326 接到 MCU 上做低速传感器采集,STM32 版足够;如果目标是音频波形、振动分析这类需要连续等间隔采样的场景,FPGA 或带 DMA 的 SPI 外设是更稳的路线。两者代码结构是等价的——前导位丢弃、16 位组装、CS 周期控制,换平台只是把延时换成计数器。
5. ADS8326 驱动避坑笔记:移植时最容易翻车的 5 个地方
5.1 读数重复上一帧的数据
现象:连续读取时,每次拿到的值都是上一次采样结果,波形明显滞后一拍。
原因:CONVST 下降沿之后没有等待足够长的 t_CONV 就去拉低 CS 读数据。SAR 转换还没完成,移位寄存器里还是旧数据。
解决:把 CONVST 触发后到 CS 拉低之间的延时从随手写的几百纳秒增加到 2.5us 以上。时序紧张的项目可以用示波器测 CONVST 下降沿到 SDO 出现新数据的间隔,实际值通常小于手册最大值,但代码里必须按最大值加 20% 裕量。
5.2 用串口打印调试,波形上出现周期性缺口
现象:连续采样时数据大部分正常,但每隔几十个点出现一次明显跳变或缺失。
原因:printf 阻塞串口输出时,采样线程被卡住,CONVST 触发间隔从固定的 10us 拉到几百微秒,采样点不再等间隔。
解决:采样阶段把数据先存进内存缓冲,采满一轮再集中打印。STM32 上可以用 DMA + 环形缓冲,或者在 printf 前后关采样中断。排查时先看采样间隔是否恒定,用逻辑分析仪抓 CONVST 引脚的脉冲间隔,如果抖动超过 2us,问题基本在软件阻塞。
5.3 参考电压纹波大,低 2 位一直跳
现象:输入接稳定直流电压,读取值低 2 位无规律跳动,高 14 位正常。
原因:ADS8326 是 SAR 结构,参考电压直接参与逐次逼近比较,VREF 引脚上的高频纹波会落在结果低位。去耦电容离引脚太远或者容量不够,是板上常见问题。
解决:VREF 引脚旁边放 1uF 陶瓷电容并联 100nF,尽量靠近引脚;确认参考源本身低频噪声低于 1mVpp。调试时把 VREF 上的纹波用示波器 20MHz 带宽限制档看,峰峰值超过 2mV 就要处理。
5.4 读到的数据像按位取反
现象:输入接近满量程时读回来是接近 0 的值,输入接地时读回来却是满量程。
原因:SPI 极性配反,数据在错误的边沿被采样。GPIO 模拟时忘了让 SCLK 初始为高,或者在下降沿之后没有延时就读取,导致采到的是芯片尚未更新的上一拍电平。
解决:确认 CPOL=1、CPHA=1;GPIO 版初始化里把 SCLK 置高。如果数值不是严格取反而是随机跳,多半是边沿采样时机不对,把 SCLK 半周期延时加大,问题通常会消失。
5.5 输入引脚悬空,读数漂移到怀疑人生
现象:ADC 输入什么都没接,读值在大范围内缓慢漂移,和手册标称底噪完全对不上。
原因:SAR ADC 前端有采样电容,悬空输入时电容残存电荷无处泄放,加上引脚周围耦合噪声,读出来就是漂移值。这不是芯片坏了,也不是驱动时序问题。
解决:输入接一个 10k 电阻到 GND,或者直接接一个已知直流电压验证。实际应用中,传感器输出是低阻抗就直接接;高阻抗输出需要加一级运放缓冲,而不是寄希望于 ADC 引脚自身的输入阻抗。
6. 验证与进阶:用满量程回环确认驱动正确,再把采样率往上推
6.1 三步验证法:不需要高级仪器也能判断驱动是否正常
第一步:输入悬空或短接 GND,连续读 1000 次,看数值分布范围。驱动时序正确时,数值集中在 0 附近,波动范围在手册底噪指标内,ADS8326 一般在 ±2LSB 以内。第二步:把输入接到 VREF 引脚,读到的值应该接近 0xFFFF。如果读到 0x7FFF,说明帧格式差一位,把 17 拍改成 16 拍或反过来,这是最直接的帧格式校准法。第三步:用信号发生器输入 1kHz 正弦波,采样后用简单 FFT 看频谱,如果除了 1kHz 主峰没有异常谐波,驱动基本合格。没有信号发生器就用一节电池加分压电阻,量出实际电压和 ADC 读数换算后的电压对一下。
6.2 进阶:SPI 外设 + 定时器触发 + DMA,逼近 250kSPS
GPIO 模拟能跑通功能,但要把采样率推到接近标称值,得换成 SPI 外设。配置要点如下:SPI 设为模式 1(CPOL=1、CPHA=1),数据宽度 16 位,主机模式;CONVST 由定时器输出比较通道驱动,定时器频率设为 250kHz;每次定时器更新事件触发一次转换,转换完成后由外部中断拉低 CS 开读。SPI 接收用 DMA,每收到一帧 16 位数据自动搬进内存缓冲。关键配置:
hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; /* CPOL=1 */ hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; /* CPHA=1 */ hspi1.Init.DataSize = SPI_DATASIZE_16BIT; __HAL_SPI_ENABLE(&hspi1);数据宽度直接设 16 位,一次 SPI 收发正好对应 16 个数据位;前导位需要多检查一次 frame 格式。如果芯片帧格式是前导位 + 16 位,SPI 外设收发 16 位会造成数据错位,那就要把帧调整逻辑放在 DMA 中断里,或者硬件上改为 17 拍 GPIO 控制。DMA 配合双缓冲,一个缓冲在传输、另一个缓冲在处理,250kSPS 下每帧 4us,处理时间完全够。新板子到手,我的第一步永远是接 VREF 读满量程,而不是直接接传感器信号凑数。这个动作能省下后面一两个小时查时序的时间,希望帮到你。
本文还有配套的精品资源,点击获取