☰
DSP28335驱动AD7606串行采样:源码调试与避坑指南
2026/10/14 16:25:20 网站建设 项目流程

简介:面向TMS320F28335 DSP与AD7606高精度ADC的SPI数据采集示例工程,适合嵌入式开发、工业控制和数字信号处理方向的工程师及学生参考。代码包共91个文件,包含35个h头文件、9个c源文件、14个asm启动与延时程序、8个mk构建脚本、2个cmd链接命令以及CCS工程配置文件等,整体仅173KB,结构紧凑。已有1060人学习下载。工程基于TI CCS环境,可直接导入查看主程序、HAL硬件抽象层、DSP2833x外设库及SPI初始化代码,完整演示了DSP作为SPI主机读取AD7606六通道转换结果的流程,对理解AD7606采样时序、SPI通信协议和DSP低层驱动开发均有直接帮助。通过阅读和修改源码,可快速移植到同类数据采集系统中,减少从零搭建的调试成本。

1. DSP28335 AD7606 源码包:它到底帮你省掉了什么

拿到一套 DSP28335 AD7606 源码包,很多人第一件事就是直接编译烧录,然后对着调试窗口里稀奇古怪的采样值发愣。其实这套源码解决的事非常具体:让 28335 这颗 DSP 通过串行或并行接口,把 AD7606 这个 8 通道 16 位同步采样 ADC 的转换结果稳定读回来,并按量程换算成真正的电压值。适合做电力电子、电机控制、并网装置、多通道波形采集的开发者,也适合刚把 28335 和外部 ADC 搭在一起的学生。不管这份源码从哪来,结构上基本跳不出引脚配置、SPI/总线读写、通道换算、中断或查询这几块;下面按最常见的串行方案为主线,把接线、初始化、读数据、参数调整和避坑排查一条龙讲清楚。

2. 先把 AD7606 与 DSP28335 的信号链路理清楚,再动源码

2.1 串行还是并行:决定整份源码结构的第一个分叉

AD7606 同时提供并行和串行两套读出接口,这直接决定了源码包长什么样。打开一份源码,先搜初始化函数里的关键寄存器:如果大量出现XINTF、DB[15:0]、XA0这类字样,就是并行接法;如果看到SPI、SPICLK、SPISOMI、GPIO模拟片选,就是串行接法。

并行模式的好处是单次读操作就能拿一个通道的数据,8 个通道分 8 次读,速度上限高,适合连续采样率超过 100kSPS 的场景。代价是要占 16 根数据线,加上 CS、RD、BUSY、CONVST、RANGE 这些控制脚,连线很密,PCB 布线稍微不用心就容易串扰。串行模式正好反过来:数据线只要一根 DOUTA,时钟线一根 SCLK,再加上 CS、BUSY、CONVST,5 根线就能把 8 个通道读回来,28335 的 SPI-A 模块可以直接复用。

选择建议很直接:如果你手里这块板子是两层板、线又长,老老实实走串行;如果是自己画的高速采集板,并行配合外部接口总线更合理。我见过不少翻车案例,都是因为源码里写的是并行,板子却按串行接的,结果读回来的数据完全是噪声。所以动手改代码前,先确认接口方式和你手里的原理图一致。

2.2 控制引脚清单:CONVST、BUSY、RANGE、OS 怎么接

这一节的目的是让你拿到源码后,第一件事能对着原理图把引脚关系理清。AD7606 的控制引脚不多,但每一个都直接影响采样行为。

AD7606 信号类型建议连接作用说明
CONVST A/B输入两个引脚短接后接 28335 GPIO拉高启动一次 8 通道同步转换
BUSY输出接 28335 GPIO(输入方向)转换中为高,转换结束拉低
CS输入接 GPIO 或 SPI 片选拉低后数据开始移出
SCLK输入接 SPI-A 的 SPICLK串行读时钟
DOUTA/DOUTB输出DOUTA 接 SPISOMI串行数据输出
RESET输入接 GPIO上电后给一次高脉冲复位
RANGE输入接 GPIO 或跳线高电平 ±10V,低电平 ±5V
OS[2:0]输入接 GPIO 或固定电平过采样倍率设置
VDRIVE电源接 3.3V决定数字接口电平,必须与 28335 匹配

接线时有几个细节值得多说一句。CONVST A 和 CONVST B 如果分开控制,可以分别启动前 4 通道和后 4 通道;但绝大多数应用是 8 通道同步采样,所以直接短接,一路 GPIO 同时触发。RANGE 别直接焊死,尽量引到 GPIO 上,因为 ±5V 和 ±10V 的换算完全是两套公式,软件能切换会省很多事。OS[2:0] 如果固定接地,过采样就永远是 1 倍,很多源码里写了过采样配置却跑不出效果,往往是这里接了固定电平。

还有一个容易被忽略的点:VDRIVE。AD7606 的数字输出电平由 VDRIVE 引脚决定,必须接和 28335 IO 电源一致的电平,通常是 3.3V。如果 VDRIVE 接了 5V,28335 的 GPIO 不一定承受得住;不接的话芯片完全不工作,BUSY 永远拉不起来。

2.3 三个时序参数:CONVST 脉宽、转换时间与 SCLK 上限

这块看手册容易看晕,我按实际调试顺序挑三个最要命的参数讲。

第一个是 CONVST 高电平脉宽。手册要求至少有几十纳秒,28335 的 GPIO 翻转完全够用。但实际工程里我不会去抠这个极限值,一般直接拉高延时 1~2 微秒再拉低,留足裕量。反正一次转换周期至少 4 微秒,不差这 2 微秒。

第二个是转换时间。AD7606 不开启过采样时,一次转换典型耗时 4 微秒左右。开启过采样后转换时间会变长,过采样倍数越大越长。所以源码里等转换完成的逻辑,我强烈建议用 BUSY 引脚轮询,而不是固定延时。固定延时写 5 微秒,过采样开到 64 倍时就不够用了,读回来的数据全是上一轮的旧值。

第三个是 SCLK 上限。串行读数据时,每个通道 16 位,8 个通道就是 128 个 SCLK。SCLK 太快,线上稍微有点寄生电容就时序出错;太慢,读取时间会拖长,采样率上不去。工程上我一般把 SCLK 控制在 2~5MHz,对应的 SPIBRR 值后面会细说。虽然手册给的上限更高,但实际板子上跑满速很容易在长走线下踩坑。

这三个参数是串行读时序的骨架,后面改源码时所有延时和采样率计算都围着它们转。

3. 在板上跑通最小采集流程:源码导入、引脚初始化与 8 通道读取

3.1 源码包一般长什么样:哪些文件要改,哪些不要动

一份典型的 28335 工程源码包,结构上分三层。第一层是芯片厂商提供的公共库,包括器件头文件、全局变量定义、系统初始化、PIE 中断向量表初始化这些,文件名通常以DSP2833x_开头。第二层是外设驱动,可能包含 SPI、定时器、SCI 串口的初始化封装。第三层才是核心业务代码,一般是一个AD7606.c / AD7606.h加一个main.c。

你真正要改的,集中在第三层,以及第二层里 AD7606 相关的 SPI 配置。第一层公共库原则上不要动,尤其是DSP2833x_GlobalVariableDefs.c这类文件,它负责给所有外设寄存器分配地址,很多编译报错都是因为它没被加进工程导致的。

改源码前先做一件事:把AD7606.h里引脚宏定义和你的板子原理图对一遍。源码默认用 GPIO0 做片选、GPIO1 做 CONVST,你的板子未必一样。引脚不对,后面所有逻辑都是空中楼阁。这个坑很常见,有相当一部分“源码跑不通”其实只是引脚映射没改。

3.2 引脚与 SPI 初始化:先把 GPIO 方向和时钟配对

下面这段代码是按 28335 的寄存器风格写的初始化函数,引脚分配我用的是最常见的一组。实际的 GPIO 号以你板子原理图为准,但结构与这个一致。

#define AD7606_CS 0 // GPIO0 #define AD7606_CONVST 1 // GPIO1 #define AD7606_BUSY 2 // GPIO2 #define AD7606_RESET 3 // GPIO3 #define AD7606_RANGE 4 // GPIO4 #define AD7606_OS0 5 // GPIO5 #define AD7606_OS1 6 // GPIO6 #define AD7606_OS2 7 // GPIO7 void AD7606_GPIO_Init(void) { EALLOW; // GPIO0~GPIO7 全部先按普通 IO 处理,方向单独配置 GpioCtrlRegs.GPAMUX1.all &= 0xFF00; // 输出:CS、CONVST、RESET、RANGE、OS0~OS2 // 输入:BUSY GpioCtrlRegs.GPADIR.all |= 0x00EF; GpioCtrlRegs.GPADIR.bit.GPIO2 = 0; // 初始电平:CS 高(空闲)、CONVST 低、RESET 低、RANGE 高(±10V) GpioDataRegs.GPACLEAR.all |= 0x00FF; GpioDataRegs.GPASET.bit.GPIO0 = 1; GpioDataRegs.GPASET.bit.GPIO4 = 1; EDIS; }

这段代码的逻辑是先把低 8 位引脚全部设为普通 GPIO,再单独把 BUSY 配成输入,其余配成输出。初始电平的设置很关键:CS 空闲必须为高,否则 AD7606 以为你要读数据;RESET 先保持低,后面单独给复位脉冲;RANGE 默认拉高选 ±10V,避免上电瞬间输入信号超量程。

接着是 SPI 模块初始化。28335 的 SPI-A 支持 16 位字符长度,正好一个 SPI 字对应 AD7606 一个通道:

void AD7606_SPI_Init(void) { // 先关闭 SPI,再改配置 SpiaRegs.SPICCR.bit.SPIEN = 0; // SPICHAR=0xF,字符长度 16 位 SpiaRegs.SPICCR.bit.SPICHAR = 0xF; // 时钟空闲为低 SpiaRegs.SPICCR.bit.POLARITY = 0; // 主机模式 SpiaRegs.SPICTL.bit.MASTER = 1; // 上升沿采样数据 SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // 使能发送 SpiaRegs.SPICTL.bit.TALK = 1; // 查询方式,不开中断 SpiaRegs.SPICTL.bit.SPIINTENA = 0; // LSPCLK=37.5MHz,SPIBRR=15 时 SCLK 约 2.34MHz SpiaRegs.SPIBRR = 15; // 重新使能 SPI SpiaRegs.SPICCR.bit.SPIEN = 1; }

这里要特别说明 CLK_PHASE 和 POLARITY 的配合。AD7606 串行数据在 SCLK 下降沿更新,上升沿稳定,所以主机要在上升沿采样,对应时钟空闲为低、相位为 0。这两个位配反了,读出来的数据要么全 0,要么全是 0xFFFF,而且很难通过改软件救回来。

SPIBRR=15 是把 SCLK 压到 2.34MHz,比极限值保守很多。第一次调板子不建议追求高速,先把数据读对,再慢慢把分频系数降下来。

3.3 启动转换并读取 8 个通道:串行读的核心函数

初始化配好后,核心读函数就是下面的逻辑:拉高 CONVST 启动转换,等待 BUSY 拉低,再通过 SPI 连续读 8 个 16 位字。只要按这个顺序,8 个通道的数据天然对位。

void AD7606_Read8Ch(Uint16 *adc_raw) { Uint16 ch; // 启动一次同步转换:CONVST 拉高,保持 2us 后拉低 GpioDataRegs.GPASET.bit.GPIO1 = 1; DELAY_US(2); GpioDataRegs.GPACLEAR.bit.GPIO1 = 1; // 等待 BUSY 从高变低,表示转换完成 while (GpioDataRegs.GPADAT.bit.GPIO2 == 1) { } // CS 拉低,开始串行读出 GpioDataRegs.GPACLEAR.bit.GPIO0 = 1; // 连续读 8 个通道,每次收发 16 位 for (ch = 0; ch < 8; ch++) { SpiaRegs.SPITXBUF = 0x0000; // 写任意值,产生 16 个 SCLK while (SpiaRegs.SPISTS.bit.INT_FLAG == 0) { } adc_raw[ch] = SpiaRegs.SPIRXBUF; } // 读完拉高 CS GpioDataRegs.GPASET.bit.GPIO0 = 1; }

adc_raw是长度为 8 的Uint16数组,函数执行完,adc_raw[0]到adc_raw[7]依次对应 V1~V8 的原始值。

这里有两个容易翻车的点。第一个是 CONVST 拉高延时不能写成DELAY_US(0.1)这种极小值,有些延时库在极小参数下可能直接跳过;2 微秒是安全值。第二个是 CS 的拉低和拉高时机必须严格包住读数据的 8 次 SPI 传输。如果中途 CS 被别的代码拉高,后续通道的数据就错位了。

另外,等待 BUSY 用的是查询方式。如果你在主循环里同时要跑其他任务,查询会卡住主流程,后面第 6 章会讲怎么改成中断触发。

3.4 在调试器里验证采样数据:三个检查点

代码烧进去后先别急着看波形,在调试器里检查三个地方。

第一,确认数组里的值不是全 0 或全 0xFFFF。全 0 通常是 SCLK 没过来,查 SPICLK 引脚复用和 SPIBRR 配置;全 0xFFFF 通常电压超过量程或 RANGE 设错。

第二,给某个通道输入一个已知电压,比如用精密电源给 V1 通道加 2.5V。RANGE 设为 ±5V 时,原始码应该接近 2.5/5×32768=16384。如果这个通道的读数在另一个通道上出现,就是 CS 时序或读取顺序问题。

第三,看相邻两次采样的值是否连续。用手去碰一下输入端子,数据应该平滑变化而不是乱跳。这一步能顺带排除接线接触不良的问题。

这三个检查点都过了,才能说最小采集流程跑通了。很多同行习惯跳过验证直接做上层算法,最后数据不对再回头查驱动,反而更浪费时间。

4. 采样率、过采样与量程:三个必调参数怎么配合

4.1 采样率怎么算:SPI 位时钟、128 个 SCLK 与转换时间

串行方式下,一次完整的数据读取要移出 128 个 SCLK(8 通道 × 16 位)。SCLK 频率直接决定读取耗时,也决定系统能跑的最高采样率。

28335 的 SPI 时钟由 LSPCLK 分频得到,公式是 SCLK = LSPCLK / (SPIBRR + 1)。主频 150MHz 时 LSPCLK 默认是 37.5MHz。我常用的几个分频点如下:

SPIBRRSCLK 频率读 8 通道耗时加上转换时间后的可持续采样率
152.34MHz54.7us约 17kSPS
311.17MHz109us约 9kSPS
630.586MHz218us约 4.5kSPS

注意这里还没算 BUSY 等待的时间。无过采样时转换时间约 4us,对整体影响不大;一旦开了过采样,转换时间可能是几十微秒甚至上百微秒,可持续采样率会进一步下降。

这个计算的意义在于:如果你的应用目标是 50kSPS 连续采样,串行方式在 2.34MHz SCLK 下根本跑不到,需要提高 SCLK,或改用并行接口。很多源码里写“采样率可达 200kSPS”指的是芯片上限,而不是这套代码在你的板子上的实际读数上限。这个差距一定要心里有数。

4.2 OS[2:0] 过采样:降噪还是保速率,一个查表函数搞定

AD7606 的过采样不是简单的软件均值,芯片内部会对同一通道连续采多次再做数字滤波,效果比在 28335 里自己累加要好,也不会占用 CPU。OS[2:0] 的编码不是线性的:

目标倍数OS[2:0] 编码说明
1(关闭)000默认,转换最快
2001轻微滤波
4010常用折中
8011噪声明显下降
16100低速高精度场景
32101转换时间显著变长
64110输出率极低

代码里我习惯用查表函数来设置,而不是在 main 里散落一堆 GPIO 操作:

void AD7606_SetOS(Uint16 times) { Uint16 code = 0; switch (times) { case 1: code = 0x00; break; case 2: code = 0x01; break; case 4: code = 0x02; break; case 8: code = 0x03; break; case 16: code = 0x04; break; case 32: code = 0x05; break; case 64: code = 0x06; break; default: code = 0x00; break; } // 先清 OS0~OS2,再按编码置位 GpioDataRegs.GPACLEAR.all |= 0x00E0; GpioDataRegs.GPASET.all |= (code << 5); }

调用时传 1、2、4、8、16、32、64 其中一个值就行。比如AD7606_SetOS(4),芯片内部对每个通道做 4 次采样再输出结果。

参数选择要结合应用场景。采样率要求不高、但希望波形干净的场景,比如电网电压监测,开 4~8 倍比较合适。如果你要做的是暂态录波,波形本身变化快,过采样反而会把毛刺磨平,而且转换时间拉长会导致采样间隔抖动,这种情况建议关掉过采样,用后续的数字滤波算法自己处理。

4.3 RANGE 量程切换与电压换算:5V 和 10V 千万别算错

AD7606 的 RANGE 引脚电平决定模拟输入量程:高电平对应 ±10V,低电平对应 ±5V。这个选择会影响满量程对应的原始码,换算公式完全不同。

// RANGE=0(低电平),量程 ±5V float ad7606_to_voltage_5V(Uint16 raw) { // 16 位有符号码,最高位为符号位 int16_t sraw = (int16_t)raw; return (float)sraw * 5.0f / 32768.0f; } // RANGE=1(高电平),量程 ±10V float ad7606_to_voltage_10V(Uint16 raw) { int16_t sraw = (int16_t)raw; return (float)sraw * 10.0f / 32768.0f; }

注意raw要先强转成int16_t再参与计算,否则大于 32767 的码值会变成很大的正数,负电压全部算错。这也是这类源码里最常见的计算错误之一。

满量程减半或翻倍的问题,十有八九出在 RANGE 硬件电平和软件换算不匹配。比如 RANGE 接了高电平,实际量程 ±10V,输入 5V 时原始码约 16384,代码却按 ±5V 公式换算,得到 2.5V,看起来就是“满量程只剩一半”。反过来,RANGE 接低电平、代码按 ±10V 算,5V 输入会被算成 10V。处理办法很简单:初始化时把 RANGE 电平固定下来,然后在换算代码里加一个条件宏,一处改动全局生效。

5. 避坑排查:这套源码最常见的 5 个翻车点

5.1 BUSY 一直为低,触发不了转换

现象是 CONVST 引脚在调试器里能看到电平翻转,但 BUSY 始终是低电平,while等待直接通过,读回来的数据全是 0。

原因是 AD7606 根本没进入转换流程。最常见的是 RESET 引脚没给过复位脉冲,芯片上电后状态机没初始化;其次是 VDRIVE 没接电,数字核心不工作;还有一种是 CONVST A/B 只拉了其中一个,另一个悬空,芯片没有收到有效触发。

解决:在 GPIO 初始化完成后,明确给 RESET 一个高脉冲再拉低。用示波器量 VDRIVE 引脚确认有 3.3V。把 CONVST A 和 B 在 PCB 上短接,或者驱动代码里同时拉高两个引脚。改完后再测,BUSY 应该能正常拉高 4 微秒以上。

5.2 通道数据错位:读出来的第三个其实是第一个

现象是给 V1 通道加信号,数组里adc_raw[2]跟着变,adc_raw[0]反而没反应。

原因是 CS 拉低后,SPI 实际移出的位数和预期不一致。比如第一次 SPI 传输前没有等接收标志就清标志,导致第一个 16 位其实只读了半个字;或者某次传输被中断打断,数据右移错位。另一个常见原因是代码里读取次数写成了 9 次或 7 次,通道边界对不上。

解决:把读函数里的循环次数固定为 8,并且每次传输后必须读SPIRXBUF清标志再进下一轮。读取期间用DINT暂时关闭全局中断,避免 PIE 中断插进来破坏 SCLK 连续性。改完用 3.4 节的方法验证通道对应关系。

5.3 满量程只剩一半:RANGE 与字节序的双簧

现象是输入一个已知正电压,读出来的电压值始终是实际值的一半或者两倍,但线性度很好,波形形状也正确。

原因一般有两个。一个是 RANGE 硬件电平和软件换算公式不匹配,这在上一节已经说过。另一个是串行模式下 DB4 引脚的字节顺序配置和读出来的数据对不上,导致 16 位数据的高低位顺序颠倒,数值被截断或放大。

解决:先用万用表量 RANGE 引脚电平,确认是高还是低,再检查换算公式选的是哪一套。字节顺序问题可以在调试器里看原始码的高 8 位和低 8 位分布,如果信号变化时低字节一直在跳、高字节变化不明显,多半是字节序反了,调整 DB4 引脚的接法或对应配置位。

5.4 高速采样时数据乱跳:SCLK 太快,标志位没等稳

现象是采样率调高后,数据开始不规则跳变,降低采样率又恢复正常。看起来像算法问题,其实是时序裕量不够。

原因有两层。第一层是 SCLK 太快,线上过冲和振铃导致时钟边沿抖动,数据采样点不稳定。第二层是代码里while (SPISTS.bit.INT_FLAG == 0)的等待逻辑,如果 SCLK 高于一定频率,标志位还没来得及置位就进入下一轮,会读到重复或旧数据。

解决:先把 SPIBRR 从较小的值改成 15,确认 SCLK 2.34MHz 时数据稳定,再逐步降低分频系数。同时检查 SPI FIFO 有没有被意外使能,FIFO 模式下INT_FLAG的行为和普通模式不同,容易混。

5.5 一编译就报错:头文件不全,位域名对不上

现象是工程导入后编译,大量报错集中在GpioCtrlRegs、SpiaRegs未定义,或者SPICHAR、INT_FLAG这类位域名找不到。

原因是工程漏了DSP2833x_GlobalVariableDefs.c和对应的DSP2833x_Device.h。28335 的寄存器头文件里定义的位域名,在不同开发环境版本之间可能有细微差异。另外,有人把 2803x 的例程直接改成 28335 用,这两款芯片的外设寄存器名和中断向量表不一样,编译必然报错。

解决:从同一个版本的公共库文件里把所有DSP2833x_开头的文件完整拖进工程,不要混用版本。编译报错先看第一个错误,通常是缺头文件,补全后后续报错往往自动消失。位域名对不上的,在头文件里搜对应字段,按现有版本的名字替换,别硬背引脚名。

6. 从查询改成中断与 FIFO:把采样率跑满的进阶改造

查询方式最大的问题是 CPU 空转。一次读 8 通道在 2.34MHz SCLK 下要占掉约 55 微秒,主循环其他任务全部被卡住。要跑更高采样率,第一步是把 BUSY 下降沿变成硬件中断。

改造思路是这样的:BUSY 引脚接到 28335 的 GPIO 中断输入上,配置下降沿触发。中断服务程序里执行上一章那个读函数,把结果写入一个环形缓冲区,主循环只管消费数据。代码结构大致如下:

interrupt void AD7606_ISR(void) { Uint16 raw[8]; AD7606_Read8Ch(raw); // 写入环形缓冲,更新写指针 ring_buf_write(raw); // 清除 GPIO 中断标志 PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; }

中断方式下要注意一个问题:读函数本身依然占用 50 多微秒,这段时间里其他中断会被延后。如果系统里还有通信中断,可能出现丢帧。此时建议把 SPI 的 FIFO 打开,让数据移出过程尽量硬件化。28335 的 SPI 带 16 级收发 FIFO,可以一次写入 8 个任意数据触发连续 SCLK,再批量读取结果:

void AD7606_SPI_FIFO_Init(void) { // 使能发送和接收 FIFO,收发都按 8 级触发 SpiaRegs.SPIFFTX.all = 0xE040; SpiaRegs.SPIFFRX.all = 0x2048; }

使能 FIFO 后,读流程变成先连写 8 次SPITXBUF,再等接收 FIFO 满 8 个数据,一次性搬走。CPU 参与的次数从 8 次降到 1 次,中断占用时间大幅缩短。

验证改造效果有个很实用的技巧:让 CPU 定时器产生一个 10kHz 的周期中断,每次中断翻转一个空闲 GPIO,用示波器看这个方波是否均匀。如果方波周期抖动明显,说明中断被读数据流程阻塞,FIFO 改造还没到位。这套验证方法能直观看到采样流程的真实时序,比单纯盯采样值更靠谱。

我调试这套源码时最大的体会是:先信时序,再信代码。现在拿到新板子,第一件事不是编译源码,而是用示波器量 CONVST、BUSY、SCLK 三个波形,确认芯片在正常工作,再谈改代码。很多翻车问题其实根源在硬件连接和时序配置,软件只是把问题暴露出来而已。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询