
简介面向嵌入式开发者的DSP28335与AD7606采样程序源码包聚焦多通道同步采样场景适用于电力监测、工业数据采集等对实时性和精度要求较高的项目。整个工程围绕完整采样链路提供精简代码从硬件引脚映射与外设初始化开始实现SPI通信模块的初始化与数据读取函数并利用BUSY信号编写中断服务程序以确保采样触发实时性主程序框架理顺了系统初始化与转换启动流程结构清晰、易于移植。压缩包共3个文件包含1个inscode工程代码文件、1个HTML说明页面和1个gitignore版本管理配置整体仅9KB轻量实用目前已有92人学习。此外资源还给出了关键参数配置说明、调试技巧和性能优化方案并针对典型调试问题提供了解决思路能够协助开发者快速搭建稳定的DSP28335AD7606采样系统。1. DSP28335与AD7606采样程序从12位到16位外挂采样的正确打开方式做电力电子调试的人多半会遇到一个尴尬DSP28335片内ADC是12位采样保持通道虽然多但实际精度和抗干扰能力在大电压大电流现场经常不够用——同一段母线电压用内部ADC采出来和用精密万用表差出几十个LSB是常有的事。与其在片内ADC上反复滤波和校准不如外挂一块16位、8通道同步采样的AD7606。这套采样程序正是为这个组合准备的DSP28335做主控AD7606做模拟前端并行读取、中断触发、DMA搬运都有现成代码可以抄。适合做逆变器、电机驱动、电能质量监测这类需要同时抓多路波形的人也适合刚把28335从点灯进阶到真实采集的新手。2. 接口选型与初始化XINTF总线映射与上电时序的细节账2.1 三种数据接口怎么选并行、串行、字节模式的真实代价AD7606提供三种读取接口16位并行、8位字节并行和串行SPI。很多人一上来就选SPI因为引脚少、接线简单。低速采集场景下SPI确实够用但放到DSP28335上要想清楚串行模式下8通道全部读出需要至少8次SPI事务每次16位还要加上帧同步和各通道切换的空闲时间很难跑满200kSPS。而DSP28335自带16位外部总线走并行几乎不占额外资源反而是最顺手的一种接法。字节模式是8位单片机时代的产物通常是为了省GPIO才用的。DSP28335的外部数据总线本来就是16位把16位数据拆成两次8位读等于多一次握手、多一倍总线占用。所以这套程序直接用16位并行模式这是最简单也最能逼近芯片指标的一种接法。实际画PCB时并行模式的代价是占掉16根数据线和五六根控制线。如果你的板子CPU资源够、GPIO紧张可以把数据线直接连到DSP的XD0~XD15上控制线单独引GPIO这样读取时用XINTF地址访问配置时用GPIO控制两不耽误。2.2 引脚连接与XINTF映射把AD7606映射成一个外部内存地址接线是这套方案的核心。AD7606的数据线DB0~DB15直接接到DSP28335的XD0~XD15CS接XZCS6nRD接XRD这样AD7606就出现在DSP的Zone6地址空间里。完整接线对照如下AD7606引脚DSP28335引脚电平方向说明DB0~DB15XD0~XD15双向16位并行数据总线CSXZCS6n输出Zone6片选选通一次读周期RDXRD输出外部读信号下降沿锁存数据RESETGPIO某脚输出上电复位必须给出低→高脉冲CONVSTGPIO某脚输出上升沿启动一次同步转换BUSYGPIO某脚输入转换期间为高结束后拉低FRSTDATAGPIO某脚输入第一通道数据有效时拉高RANGEGPIO某脚输出选择±5V还是±10V量程OS0~OS2GPIO三脚输出设置过采样倍率把CS接在XZCS6n上之后程序里读写地址0x100000就会自动产生完整读时序RD的下降沿、建立时间和跟踪时间全由XINTF控制器管理不需要再用GPIO模拟波形。这里有个容易想糊涂的点AD7606不是按地址选通道的。数据手册的并行读时序是“每给一个RD下降沿芯片内部把当前通道的数据放到总线上指针自动切到下一通道连续给8个RD脉冲就把8个通道读完了”。所以在DSP端8次读操作访问的是同一个地址而不是8个不同的地址。那些把XA0~XA2接过去想“一次选一个通道”的人往往会发现通道数据完全对不上。// 读取一个通道的原始数据 uint16_t adc_read_raw(void) { // volatile告诉编译器每次访问都是真实的外部总线事务 return *(volatile uint16_t *)0x100000; }这里的关键在volatile关键字。如果不加编译器在开启优化后可能把同一地址的多次读取合并成一次最终读到的只有第一通道数据。CCS工程里-O2优化档位最容易踩这个坑。2.3 初始化时序复位、量程与过采样配置代码AD7606最容易被忽略的是初始化顺序。数据手册要求上电后必须给RESET一个低脉冲等芯片内部状态机稳定后再开始配置。实际项目里我会把低脉冲宽度放宽到10us宁可慢一点也不让它带着不确定状态进入首次转换。复位之后量程和过采样这两个静态配置也要在此时一次性设好。RANGE拉高选±5V拉低选±10VOS0~OS2的组合决定过采样倍率。首次调试建议把过采样先关掉拿到的是一档未经数字滤波的原始数据方便对照示波器找问题。#define ADC7606_BASE 0x100000 // Zone6基地址 void AD7606_Reset(void) { GPIO_SetPin(AD7606_RESET, 0); // RESET拉低 DELAY_US(10); // 满足最小脉冲宽度 GPIO_SetPin(AD7606_RESET, 1); // 释放复位 DELAY_US(20); // 等待内部逻辑稳定 } void AD7606_Config(int os0, int os1, int os2, int range) { // 过采样组合0001倍0012倍0104倍0118倍10016倍 GPIO_SetPin(AD7606_OS0, os0); GPIO_SetPin(AD7606_OS1, os1); GPIO_SetPin(AD7606_OS2, os2); // 量程1代表±5V0代表±10V GPIO_SetPin(AD7606_RANGE, range); // 等待配置稳定 DELAY_US(5); }除了这些XINTF本身的时序也要一起配好。XINTF总线时钟一般设150MHz一个周期约6.7ns。AD7606的RD低电平脉宽要求最少约20ns但总线负载一重20ns很容易变成临界值我把读激活时间设成8个周期约53ns留出两倍以上余量读取稳定性会明显改善。// XINTF Zone6时序配置实例 void Init_XINTF_Zone6(void) { // 读时序建立2周期激活8周期跟踪2周期 XintfRegs.XTIMING6.bit.XRDLEAD 2; XintfRegs.XTIMING6.bit.XRDACTIVE 8; XintfRegs.XTIMING6.bit.XRDTRAIL 2; // 写时序如果不用XINTF写功能可以保持和读一致 XintfRegs.XTIMING6.bit.XWRLEAD 2; XintfRegs.XTIMING6.bit.XWRACTIVE 8; XintfRegs.XTIMING6.bit.XWRTRAIL 2; // 数据总线宽度1代表16位 XintfRegs.XTIMING6.bit.XSIZE 1; }初始化顺序有个经验先配XINTF时序再做AD7606复位和配置最后才允许主循环里调用转换函数。如果反过来第一次启动转换时XINTF还没来得及稳定BUSY下降沿后读回来的数据很可能是一堆乱码。3. 读取与缓存从查询等待到DMA双缓冲的完整链路3.1 BUSY下降沿与外部读时序软件查询和中断两种读取方式AD7606启动转换的入口很简单给CONVST一个上升沿8个通道同时开始采样芯片内部自行完成采样保持和ADC转换整个过程中BUSY引脚保持高电平。转换完成后BUSY拉低这时才能去读取数据。最简单的读取方式是查询BUSYuint16_t g_adc_buf[8]; // 全局缓冲区 void AD7606_ReadAll(void) { // 启动一次同步转换 GPIO_SetPin(AD7606_CONVST, 0); DELAY_US(1); GPIO_SetPin(AD7606_CONVST, 1); DELAY_US(1); GPIO_SetPin(AD7606_CONVST, 0); // 等待BUSY下降沿转换完成 while (GPIO_GetPin(AD7606_BUSY) 1); // 连续读取8个通道源地址固定 for (int i 0; i 8; i) { g_adc_buf[i] *(volatile uint16_t *)ADC7606_BASE; } }这段代码的耗时大头在最后的8次读取每次XINTF读周期按前面的配置约53ns8次总耗时不超过0.5us。真正拖后腿的是while(BUSY)这一句它在转换期间一直占着CPU。如果主循环里还有SCI通信、PWM占空比更新、上位机协议解析等任务查询方式会挤掉这些任务的时间片。更合适的做法是把启动和等待拆开需要采样时先发CONVST然后去做别的事等BUSY下降沿触发外部中断在中断里读取数据。这样等待转换的时间CPU是空闲的可用于处理其他实时任务。interrupt void XINT1_ISR(void) { // 把8通道数据从总线搬到全局数组 for (int i 0; i 8; i) { g_adc_buf[i] *(volatile uint16_t *)ADC7606_BASE; } // 置位新数据标志主循环轮询这个标志 g_adc_ready 1; // 清除中断标志允许下一次触发 XintRegs.XINT1CR.bit.INTFLG 1; }用中断方式有一个前提BUSY引脚必须接到带外部中断功能的GPIO上通常是XINT1~XINT7对应的那几个复用脚。画原理图时就要把这个对应关系定好而不是上电之后靠改程序解决。如果板子已经做错了中断方案只能退化成查询方案采样率上限会打折扣。3.2 数据拼接与通道映射8个通道与FRSTDATA的正确用法每次读回来的16位原始码要明确它的格式。AD7606输出的是双极性二进制补码正满量程对应0x7FFF负满量程对应0x8000中间0对应0V。不是在16位里只取低12位或低8位所有16位都有效读出来是什么就存什么。读取8通道时每触发一次RD总线数据自动切到下一通道顺序固定为V1~V8。因此缓冲区数组的索引天然对应通道号不需要额外查表映射。FRSTDATA这个引脚我一般会接上但不是每次都读它。它只在两个场景里有价值一是读取过程中被高优先级中断打断重新调度后你不知道当前总线上的通道号二是芯片内部状态出问题指针漂了需要靠FRSTDATA重新对齐。如果每次转换后一口气读满8次、中间没有外部打断FRSTDATA不参与逻辑也完全没问题。把原始码转成实际电压的代码float adc_to_voltage(uint16_t raw, int range_5v) { // ±5V量程时1LSB约152.6uV±10V量程时1LSB约305.2uV float lsb_mv range_5v ? 0.1526f : 0.3052f; // 二进制补码转为有符号数 int32_t code (raw 0x8000) ? (int32_t)raw - 0x10000 : (int32_t)raw; return code * lsb_mv; // 返回电压值单位mV }这段转换逻辑里最容易出错的是LSB数值。±5V量程对应10V全跨度除以65536得152.6uV左右±10V量程对应20V除以65536得305.2uV左右。如果你把量程搞错采样出来的电压会差一倍而且往往不容易发现——低频正弦波形状完全一样只有幅值不对。3.3 双缓冲与DMA搬运采样任务里不要让CPU一直忙在开关电源或电机控制场景中数据更新周期只有几十微秒如果每个周期都靠CPU中断去读8个通道系统压力很大。更稳的做法是用DMA把数据从XINTF地址直接搬到内存缓冲区CPU只在缓冲区切换时介入一次。DMA方案的核心配置是把源地址固定指到0x100000目标地址指向RAM缓冲区每次触发搬运8个字。触发源选外部中断也就是BUSY下降沿经过XINT映射后触发DMA。// DMA通道0配置示例 void ADC_DMA_Init(void) { // 源地址固定指向XINTF Zone6 DMA-SRC_BEG_ADDR ADC7606_BASE; DMA-SRC_ADDR ADC7606_BASE; // 目标地址指向缓冲区A搬运后地址递增 DMA-DST_BEG_ADDR (Uint32)g_adc_buf_a; DMA-DST_ADDR (Uint32)g_adc_buf_a; // 每次触发搬运8个字源地址不变目标地址步进 DMA-BURST_SIZE 8; DMA-SRC_BURST_STEP 0; DMA-DST_BURST_STEP 1; // 触发源XINT1事件具体位域写法按CCS头文件配置 // DMA传输完成后触发中断切换缓冲区指针 }DMA搬运结束后会发出传输完成中断在中断里只需要把缓冲区指针从A切到B同时置一个标志位。A/B两个缓冲区交替使用主循环处理A数据时DMA在往B填新数据不会因为读取和计算抢同一个数组而丢帧。有一个限制要提前知道DSP28335的DMA触发源不能直接接GPIO必须通过外部中断XINT或某个片上外设事件中转。所以电路设计时就要把BUSY引到XINT映射的引脚上否则只能退而求其次用软件触发DMA效率和查询方式相差不大。4. 采样程序常见问题排查四条实测踩坑记录4.1 现象一采样值末几位不停跳变波形毛刺很多代码逻辑看起来没问题接线也核对了但采集回来的正弦波总有毛刺把波形放大看低几位每次差好几个码。原因AD7606的AVCC和DVCC共用一个电源模拟电源上混入了数字开关噪声另外XINTF读周期太短数据总线翻转时采到了中间电平。解决把AVCC和DVCC分开供电模拟电源回路加磁珠和100nF退耦电容同时把读激活时间从默认几个周期加长到8个周期。实测把这两个改完后毛刺能降低一个数量级。4.2 现象二通道数据错位V3显示的是V1的信号程序连续读8次但波形显示某个通道的数据对应到了另一个通道的输入信号上而且错位规律固定。原因读取过程被高优先级中断打断打断点恰好落在两次读操作之间。中断返回来继续读时AD7606内部通道指针已经往前走了几个通道读取序列整体错开。解决读取代码段里临时关闭可屏蔽中断或把读取放进一个不可打断的临界区unsigned short old_state int_disable(); for (int i 0; i 8; i) { g_adc_buf[i] *(volatile uint16_t *)ADC7606_BASE; } int_restore(old_state);如果是DMA方案不存在这个问题因为DMA传输不依赖CPU中断响应。4.3 现象三上电后偶尔读不到数据RESET一下又好了这个故障最磨人——板子正常上电程序能启动但偶尔读回来的数据全是0或全是0xFFFF复位一次就恢复。原因上电时序里AD7606和DSP28335的复位节奏没对齐。DSP启动速度快AD7606还没完成内部上电复位DSP就已经开始配置引脚、发CONVST导致AD7606进入不确定状态。解决在DSP主程序入口处加一段固定延时等AD7606完全上电后再执行复位和配置。延时不用太讲究100ms足够只消耗启动阶段一次。void main(void) { // 系统时钟、GPIO初始化... DELAY_MS(100); // 等待AD7606完成上电复位 Init_XINTF_Zone6(); AD7606_Reset(); AD7606_Config(0,0,0,1); // 过采样关闭量程±5V // 进入主循环... }4.4 现象四采样率测出来只有4kSPS离200kSPS差太远用定时器固定频率触发采样逻辑上没问题但示波器上看到实际采样周期比设定值大了几十倍。原因定时器中断里塞了太多东西中断处理函数本身跑不完就重复进入或者过采样倍率设得太高转换时间本身就不支持这个采样率。比如64倍过采样下单片AD7606的实际最高采样率只有几千SPS想跑200kSPS物理上不成立。解决先检查过采样配置再统计中断服务程序最长执行时间。用一个IO翻转来测进入中断置高、出中断置低示波器看高电平宽度。如果中断负载超过20%就要考虑改DMA方式或者把数据处理挪到主循环。5. 采样率调优与验证把程序跑到极限并确认数据可信5.1 时间开销到底花在哪从XINTF读周期倒推采样率上限要判断一段采样程序能不能逼近200kSPS先算一笔时间账。单个XINTF读周期包含建立、激活、跟踪三段总线时钟150MHz时配置成8周期激活加2周期建立、2周期跟踪一次读约12个时钟周期折合80ns左右。8个通道连续读约640ns加上AD7606的转换时间约4us理论周期约4.7us对应采样率约210kSPS。但实际运行时还有中断响应时间、软件判断和缓冲区切换开销所以查询方式能稳定跑到的采样率大约180~190kSPS。如果需求只有50kSPS还能接受如果非要顶满200kSPS查询方式就比较勉强DMA是必选项。如果发现采样率怎么都上不去先别急着优化读取逻辑看一下过采样倍率是否设高了。过采样会让芯片内部累积多次采样再输出一个码实际吞吐率成倍下降。参考对应关系如下过采样档位过采样倍率参考采样率上限8通道0001x约200kSPS0012x约100kSPS0104x约50kSPS0118x约25kSPS10016x约12.5kSPS10132x约6kSPS11064x约3kSPS这个表是折算参考值不同温度、不同基准源下会有浮动但量级很明确过采样倍率每翻一倍采样率上限就大约砍一半。调试初期为了跑速度先把这档设在000等波形确认没问题了再根据噪声需求逐步加过采样。5.2 测量中断负载用示波器直接看IO翻转宽度想知道采样程序占了多少CPU时间不需要跑性能分析器。在读取函数入口把某脚拉高、出口拉低示波器看高电平宽度就是这段代码的实际执行时间。interrupt void XINT1_ISR(void) { GPIO_SetPin(DEBUG_IO, 1); // 波形起点 for (int i 0; i 8; i) { g_adc_buf[i] *(volatile uint16_t *)ADC7606_BASE; } g_adc_ready 1; GPIO_SetPin(DEBUG_IO, 0); // 波形终点 XintRegs.XINT1CR.bit.INTFLG 1; }如果示波器抓出的高电平宽度是2us而采样率目标是100kSPS也就是周期10us那么中断本身占CPU约20%主循环还剩80%。这个比例算健康。如果高电平宽度超过4us建议马上改DMA方案把读取耗时从微秒级压到接近零。5.3 波形还原验证50Hz正弦输入下的三个动作采样程序改完以后我不会直接上真实电压信号而是先接一个信号发生器给V1通道加50Hz、峰值为量程80%左右的正弦波然后按三步验证。第一步用串口把原始码送出来检查最大值和最小值是否大致对称。±5V量程下正峰应在0x7FFF附近负峰应在0x8000附近。第二步把采集数据导入上位机画波形看是否平滑正弦、频率和相位是否连续。第三步把两个通道接同一个信号源验证同步性两通道波形应完全重合相位差不大于一个采样周期。// 把原始码转成电压数组方便上位机画图 void adc_formatted_output(uint16_t *raw_buf, float *voltage_buf, int len) { for (int i 0; i len; i) { // 假设量程为±5V调用转换函数 voltage_buf[i] adc_to_voltage(raw_buf[i], 1); } }这三个动作做完基本可以确认采样率和精度两个维度没有明显问题。剩下就是上真实信号测试那部分和模拟前端设计有关不属于程序排查范围。6. 顺手把调试链路补全串口一帧数据同步波形不是难事采集程序跑通后大部分人会被一个问题卡住怎么把数据弄到电脑上看波形我习惯的做法是走SCI串口波特率9216008通道原始码打包成16字节一帧加帧头帧尾和校验。这样既方便上位机解析又不会因为浮点格式转换拖慢采样。// 串口发送一帧采样数据 void adc_send_frame(void) { uint8_t frame[18]; frame[0] 0xAA; // 帧头 frame[1] 0x55; // 帧头同步字节流 // 8通道原始码低字节在前 for (int i 0; i 8; i) { frame[2 i * 2] (uint8_t)(g_adc_buf[i] 0xFF); frame[3 i * 2 1] (uint8_t)(g_adc_buf[i] 8); } // 累加和校验只保留低8位 uint8_t sum 0; for (int i 0; i 18; i) { sum frame[i]; } frame[17] sum; for (int i 0; i 18; i) { scia_send_byte(frame[i]); } }上位机解析时先找0xAA 0x55的帧头再从数据段拼回原始码。这套帧格式很朴素但胜在解析容易、出错能发现。波特率921600按10bit/byte折算每秒约能传92000字节18字节一帧约等于每秒5000帧对应采样率大约能做到60~80kSPS。如果要跑更高采样率要么把帧里塞更多采样点要么换USB虚拟串口。这个串口调试链路我到现在还在用。后来养成的习惯是每改一次采样参数先跑一遍50Hz正弦、用串口抓一段波形、量一下频率和幅值偏差确认没问题再放到真实环境里去接电压电流。这个动作看起来不起眼却能省掉很多藏在时序毛刺里的玄学问题。希望这次的程序拆解和踩坑记录能帮到你尤其是刚把AD7606挂上28335总线、还在为数据和波形打架的那部分人。本文还有配套的精品资源点击获取