ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DSP28335 ADC与DMA协同:采样触发到数据搬运的完整配置指南

DSP28335 ADC与DMA协同:采样触发到数据搬运的完整配置指南 简介这份示例资料围绕TI DSP28335的ADC与DMA协同应用面向嵌入式开发、数字信号处理及实时数据采集场景。压缩包内含1个C源文件Example_2833xAdcToDMA.c文件总量仅1个、大小3KB虽精简却完整提供了ADC初始化、DMA通道配置、转换完成中断触发及DMA中断处理例程等关键代码框架适合需要快速掌握28335片上外设联调的开发者直接参考。目前已有272人学习资源虽小却覆盖了从模拟通道采样到内存搬运的完整链路。通过研读该示例可理解如何配置12位ADC的采样率、通道与触发源如何设置DMA源地址、目标地址及传输长度并借助中断处理实现连续数据流的无CPU干预搬移对音频采集、工业控制、信号分析等需高效处理大量模拟量的应用具有直接借鉴价值。1. 用 DMA 接住 ADCDSP28335 上最容易写错却最该用的外设组合排查一块 DSP28335 板的 ADC 采样抖动时最常见的结论不是基准源坏了也不是 PCB 走线太差而是 CPU 自己把采样节奏拖垮了。中断服务里从 ADCRESULT0 到 ADCRESULT7 逐个读取、滤波、再搬进应用缓冲这段代码让采样周期被拉到毫秒级而 ADC 本身在 25MHz 转换时钟下跑完 8 个通道只需要几微秒。把 DMA 插在 ADC 和内存之间是 28335 上性价比最高的改动转换完成信号直接触发 DMA 通道硬件把一组结果寄存器搬到内存CPU 只在整帧搬完后进一次中断。标题里的 AdcToDMA 例程解决的就是这个分工问题。这篇文章按官方例程的思路把 ADC 侧触发、DMA 侧突发传输和地址回绕拆开讲重点放在参数怎么设、数据不对时先查哪里。适合正在调 2833x 数据采集的工程师从 STM32 转过来的人也能借对照理解 TI 的外设互连逻辑。2. ADC 采样链路先理清SOC 触发、采样窗口与结果寄存器DMA 只是搬运工搬多快、搬什么完全由 ADC 侧的生产节奏决定。先把 ADC 这条流水线的时间关系算清楚后面所有 DMA 参数才不会拍脑袋。2.1 一次转换要多久ADCCLKPS 与 ACQ_PS 决定采样周期28335 的 ADC 时钟由 HSPCLK 分频而来关系是 ADCLK HSPCLK / (ADCCLKPS 1)如果 ADCTRL1 的 CPS 位置 1还会再除一次 2。数据手册要求 ADCLK 最高 25MHz所以在 150MHz 主频、HSPCLK 为 75MHz 的常见配置里ADCCLKPS 取 2得到正好 25MHz。采样窗口宽度由 ACQ_PS 决定采样保持阶段持续 (ACQ_PS 1) 个 ADCLK之后固定用 13 个 ADCLK 完成逐次逼近转换。单个通道从 SOC 到结果可写总共需要 (ACQ_PS 1 13) 个 ADCLK这个值直接决定后面 DMA 触发的极限频率。AdcRegs.ADCTRL3.bit.ADCCLKPS 2; // ADCLK 75MHz / (21) 25MHz AdcRegs.ADCTRL1.bit.ACQ_PS 7; // 采样保持窗口 8 个 ADCLKACQ_PS 是最容易被当成延时越大越稳而乱调的参数。它影响的不仅是转换总时间还包括输入源阻抗的匹配信号源内阻高、前端 RC 滤波电容大时采样窗口太短会导致采到的电压还没稳定就已经进入保持阶段读数表现为随触发频率变化的偏差看起来像漂移。常用的取值区间可以按下面这张表估算ADCLK 按 25MHz 算每个周期 40ns。ACQ_PS采样保持时间单通道总时间含 13 个转换周期01 ADCLK14 × 40ns 560ns34 ADCLK17 × 40ns 680ns78 ADCLK21 × 40ns 840ns1516 ADCLK29 × 40ns 1.16µs如果做 16 通道级联连续转换ACQ_PS 取 7 时一帧需要 16 × 840ns ≈ 13.4µs对应最高触发频率约 74kHz。触发源给得比这个快ADC 就会丢弃触发事件DMA 侧表现为整帧停滞或重复上一帧而不是采样变慢这么直观。这是我在多块板上反复踩过的坑先算采样周期上限再定触发频率最后才写 DMA 参数。2.2 16 个 ADCRESULT 寄存器为什么天然适合 DMA28335 的 ADC 结果寄存器组 ADCRESULT0 到 ADCRESULT15 是 16 个连续地址的 16 位只读寄存器转换结果是左对齐存放的也就是 12 位有效数据占据高 12 位低 4 位补零满量程读出来是 0xFFF0 而不是 0x0FFF。这一点决定了 DMA 搬完数据后CPU 侧必须做一次右移 4 位的换算。这批寄存器连续、只读、按转换顺序自动更新的特性恰好是 DMA 最理想的搬运对象源地址每搬一个字递增 1搬完一帧后地址复位回 ADCRESULT0 等待下一帧。和带读指针的 FIFO 类外设不同ADCRESULT 没有读一次少一个的状态DMA 和 CPU 同时读也不会破坏转换结果。这意味着排错时可以直接对比 DMA 缓冲区和 ADCRESULT 寄存器组数据不一致基本可以断定是 DMA 配置问题而不是寄存器被谁动过。需要留意的是 ADCINT 信号的产生时刻。ADCINT1 在序列转换全部结束后产生此时 16 个结果寄存器都已锁存完毕DMA 可以安全地连续读取。不要把 ADCINT 当作第一个通道转换完成的信号来用那样 DMA 读到的一半是上一帧的残留值。2.3 触发源怎么选软件、ePWM 与 XINT2 在 DMA 场景下的差别28335 的 ADC 支持三种典型启动方式写 ADCTRL2 的 SOC_SEQ1 位做软件触发、ePWM1 到 ePWM4 的 SOCA/SOCB 事件触发、XINT2_ADCSOC 外部引脚触发。SOC 决定转换何时开始DMA 只跟在 ADCINT 后面两者频率一旦不匹配就丢帧所以触发源选择本质上是采样节奏设计。软件触发适合自检和单次调试写一次跑一帧DMA 同步搬一帧逻辑最简单但不适合定频采样。外部引脚触发适合和外部事件同步的场景但容易受引脚抖动干扰。做电机控制、电源控制这类定频采集标配是 ePWM 触发PWM 周期计数器在指定计数值产生 SOCA 事件ADC 转换完一帧后产生 ADCINT1DMA 通道把这一帧结果搬进内存。整条链路里 CPU 只在 DMA 中断里短进短出采样时刻严格绑定在 PWM 周期上抖动只取决于 TBCLK 本身而不是中断响应延迟。2.4 CONT_RUN 与连续请求DMA continuous requests 在 28335 上的表现ADCTRL1 的 CONT_RUN 位置 1 后序列指针在完成 MAX_CONV1 1 个转换后自动回到起点重新跑不需要再次写 SOC。这种ADC 自己连续出帧的模式配上一帧一次 ADCINT1就是你理解的 DMA continuous requestsDMA 被持续请求每帧搬一次搬完地址回绕周而复始。但连续模式有个容易漏掉的细节DMA 的触发标志 PERINTFR 是电平性质的ADCINT1 来了之后如果没及时写 PERCTL 的 PERINTCLR 位清掉下一次触发来了会因标志仍为 1 而被忽略表现就是每隔几帧丢一帧。很多人把丢帧问题定位成DMA 速度不够实际是标志没清。正确习惯是在 DMA 中断入口第一行写 PERINTCLR 1而不是处理完数据再清如果同时开了 ADC 到 CPU 的中断和 DMA 触发同一个事件会进两个处理路径读到的帧数天然对不上。3. DMA 六通道的配置骨架PERCTL、ADDRCTL 与 BURST/TRANSFER/WRAPF28335 的 DMA 模块有 6 个独立通道每个通道的寄存器结构完全一样。学会一个通道的配置其余五个只是改触发源和地址。难的不是寄存器数量而是理解 burst、transfer、wrap 三层嵌套关系。3.1 每个通道由 8 组寄存器描述每个 DMA 通道的核心寄存器可以按职责分成四组地址组SRCBEG/SRCADDR、DSTBEG/DSTADDR、计数与步长组SRCBURST/DSTBURST、TRANSFER、回绕组SRCWRAP/DSTWRAP、控制组PERCTL、ADDRCTL。SRCBEG 和 SRCADDR 的区别要一开始就建立BEG 是每帧开始时的初值地址ADDR 是当前正在使用的地址一个 burst 结束后地址会按规则更新到 ADDR只有在一帧 transfer 启动时才可能回到 BEG。所以初始化时两块都要写只改 BEG 不改 ADDR第一次触发就会从旧地址开始搬。ADDRCTL 里的 SRCADDRINIT/DSTADDRINIT 决定每个 transfer 开始时是否把当前地址重载为 BEG 值。ADC→内存的场景里源地址必须 INIT1否则第二帧会从 ADCRESULT8 而不是 ADCRESULT0 开始搬。另外六个方向位 SRCDIR/DSTDIR 决定递增还是递减SRCADDRMOD/DSTADDRMOD 决定每搬一个字后是否更新地址从结果寄存器这类连续地址搬数据方向递增、地址更新即可但搬 FIFO 类外设时反而要把 MOD 置 1 禁用更新保持地址锁在同一个寄存器上。3.2 触发源映射PERINTSEL 怎么选 ADCINT1每个 DMA 通道的 PERCTL 寄存器里PERINTSEL[3:0] 选择触发源PERINTE 使能触发PERINTCLR 清触发标志PERINTFRC 是软件强制触发位。28335 上不是所有外设都能触发 DMA能用的就这么几路常用映射如下。PERINTSEL触发源典型用途0软件触发自检、单步调试1ADCINT1ADC 结果搬运最常用2ADCINT2双 ADC 序列独立搬运3CPU Timer 1定时搬一块固定数据4CPU Timer 0周期性内存拷贝5–11XINT1–XINT7外部事件触发搬运12–13SCI-A TX/RX串口 DMA 收发14–15McBSP-A TX/RX音频数据流搬运ADC 场景下优先用 ADCINT1 触发 DMA 通道 1把 ADCINT2 留给其他用途。选触发器时有个原则触发源的事件频率要和 DMA 的搬运能力匹配DMA 一个 burst 搬 16 个字只需要几十个系统时钟瓶颈永远在 ADC 转换周期上所以真正要算的是 2.1 节那张表。3.3 BURST、TRANSFER、WRAP 把一次搬运切成三层DMA 的搬运模型是三层嵌套一次 transfer 由 N 次 burst 组成一次 burst 由 SIZE 1 次单字搬运组成写一次 PERINTFRC 或来一个外设触发只启动一个 burst。对应关系是外部触发来一次 → 搬完一个 burst → transfer 计数减一 → 计数到 0 时整次 transfer 完成产生 DMA 中断前提是 CHINTMODE 配成 transfer 完成中断。SRCBURST.SIZE 15 表示一个 burst 搬 16 个字配合结果寄存器组就是一帧 16 通道一次搬完。TRANSFER.COUNT K 表示总共要搬 K 1 个 burst典型用法是单通道连续采 N 个点做软件滤波SIZE 0COUNT N - 1目的地址递增一帧 DMA 中断拿回 N 个点。WRAP 则是地址回绕每经过指定数量的 burst把源或目的地址加一个 STEP 偏移通常用来在两个不连续地址段之间交替采样或者实现环形缓冲。对 ADC→内存这种线性搬运WRAP 一般置 0地址复位交给 ADDRCTL 的 INIT 位即可。3.4 参数速查ADC→DMA 常用组合实际项目里 ADC→DMA 的组合就几种背熟下面这张表可以省掉大半查手册时间。注意表中 SIZE 和 COUNT 都要按实际值减 1填写。应用场景SRCBURST.SIZETRANSFER.COUNTDST 地址行为ADDRCTL.INIT8 通道单帧采集70目的地址递增源和目的都置 116 通道连续采集150每帧从 DSTBEG 重载源和目的都置 1单通道连续采 64 点做滤波063目的地址递增不重载源置 1目的置 0乒乓双缓冲ISR 切地址150中断里改 DSTBEG/DSTADDR源置 1目的置 14. 从 ADC 结果寄存器到内存一份可抄的 ADC→DMA 初始化代码原理说完给一份可以直接往工程里放的初始化代码。寄存器访问方式按 TI 标准头文件的位域写法不同版本的库封装略有差异但寄存器名和位名是通用的。4.1 最小例程8 通道单帧搬运先做最简版本8 路模拟输入软件触发一帧ADCINT1 让 DMA 通道 1 把 8 个结果搬进数组。这个版本跑通了后续加连续模式、加 PWM 触发都是在这个骨架上改。#define ADC_BUF_LEN 8 Uint16 adcData[ADC_BUF_LEN]; // DMA 直接写入CPU 只读 void InitAdcForDma(void) { EALLOW; AdcRegs.ADCTRL1.bit.RESET 1; // 复位 ADC随后同步释放 AdcRegs.ADCTRL1.bit.ACQ_PS 7; // 采样窗口 8 ADCLK AdcRegs.ADCTRL1.bit.CPS 0; // 不再 2 分频 AdcRegs.ADCTRL1.bit.CONT_RUN 0; // 先做单次模式 AdcRegs.ADCTRL1.bit.SEQ_CASC 1; // 级联成 16 通道序列 AdcRegs.ADCTRL3.bit.ADCCLKPS 2; // ADCLK 25MHz AdcRegs.ADCTRL3.bit.SMODE_SEL 0; // 顺序采样 AdcRegs.ADCMAXCONV.bit.MAX_CONV1 7; // 8 次转换 AdcRegs.ADCCHSELSEQ1.bit.CONV00 0; // 通道 0ADCINA0 AdcRegs.ADCCHSELSEQ1.bit.CONV01 1; // 通道 1ADCINA1 AdcRegs.ADCCHSELSEQ1.bit.CONV02 2; AdcRegs.ADCCHSELSEQ1.bit.CONV03 3; AdcRegs.ADCCHSELSEQ1.bit.CONV04 8; // 跳到 ADCINB0 AdcRegs.ADCCHSELSEQ1.bit.CONV05 9; AdcRegs.ADCCHSELSEQ1.bit.CONV06 10; AdcRegs.ADCCHSELSEQ1.bit.CONV07 11; EDIS; }段代码里 CONV04 8 对应 ADCINB0这是顺序采样模式下通道号的定义0 到 7 是 A 组8 到 15 是 B 组。ADCCHSELSEQ1 到 ADCCHSELSEQ4 四个寄存器按顺序排满 16 个转换槽级联模式下只用 MAX_CONV1 这一个计数位8 次转换就填 MAX_CONV1 7。ADCTRL2 的 INT_ENA_SEQ1 目前保持默认关断因为 DMA 要独占 ADCINT1不让 CPU 在同一事件上再插一脚。DMA 侧初始化是重头八个寄存器组各司其职void InitDmaForAdc(void) { EALLOW; DMACh1.PERCTL.bit.PERINTSEL 0x1; // ADCINT1 触发本通道 DMACh1.PERCTL.bit.PERINTE 1; // 使能外设触发 DMACh1.PERCTL.bit.PERINTCLR 1; // 清掉可能的残留触发标志 DMACh1.ADDRCTL.bit.SRCADDRINIT 1; // 每帧从 SRCBEG 重载源地址 DMACh1.ADDRCTL.bit.DSTADDRINIT 1; // 每帧从 DSTBEG 重载目的地址 DMACh1.ADDRCTL.bit.SRCDIR 0; // 源地址递增 DMACh1.ADDRCTL.bit.DSTDIR 0; // 目的地址递增 DMACh1.ADDRCTL.bit.SRCADDRMOD 0; // 每搬一字更新一次源地址 DMACh1.ADDRCTL.bit.DSTADDRMOD 0; // 每搬一字更新一次目的地址 DMACh1.SRCBURST.SIZE 7; // 一个 burst 搬 8 个字 DMACh1.SRCBURST.ADDR 1; // 源地址步进 116 位字单位 DMACh1.DSTBURST.SIZE 7; DMACh1.DSTBURST.ADDR 1; DMACh1.SRCBEG (Uint32)AdcRegs.ADCRESULT0; // 源初值 DMACh1.SRCADDR (Uint32)AdcRegs.ADCRESULT0; // 源当前值 DMACh1.DSTBEG (Uint32)adcData; // 目的初值 DMACh1.DSTADDR (Uint32)adcData; // 目的当前值 DMACh1.TRANSFER.COUNT 0; // 1 个 burst 即一次 transfer DMACh1.TRANSFER.MODE.bit.CHINTE 1; // 使能 DMA 中断 DMACh1.TRANSFER.MODE.bit.CHINTMODE 0; // transfer 完成才中断 DMACh1.MODE.bit.CHEN 1; // 使能 DMA 通道 1 EDIS; }TRANSFER.COUNT 0 意味着每次 ADCINT1 到来DMA 搬 8 个字就结束一次 transfer并触发一次 DMA 中断。这里的关键是 CHINTMODE 0它保证中断只在整批 burst 完成后产生而不是每搬 8 个字就进一次中断如果你把它配反了中断频率会放大 8 倍CPU 又被拖回老路上。源地址和目的地址的 BEG/ADDR 必须同时初始化只写 BEG 的话第一次触发时 SRCADDR 仍是上电默认值。4.2 16 通道连续采集的配置差异从 8 通道单帧扩到 16 通道连续采集改动只有四个点MAX_CONV1 改为 15ADCCHSELSEQ1 到 ADCCHSELSEQ4 填满 16 个槽CONT_RUN 置 1然后删除软件触发的 SOC_SEQ1 写法改成每次由 ePWM 或外部事件提供 SOC。DMA 侧把 SRCBURST.SIZE 和 DSTBURST.SIZE 从 7 改成 15其余不动。AdcRegs.ADCMAXCONV.bit.MAX_CONV1 15; // 16 次转换 AdcRegs.ADCTRL1.bit.CONT_RUN 1; // 序列自动循环 DMACh1.SRCBURST.SIZE 15; // 一次搬完 16 个字 DMACh1.DSTBURST.SIZE 15;连续模式下的一个隐蔽行为序列循环重跑时如果 ADC 转换还没结束就有新的 SOC 到来这个 SOC 会被忽略对应的那一帧 DMA 不会触发。所以连续模式下触发频率必须低于 2.1 节算出的上限而不是只要 DMA 搬得过来就行。工程上我会给上限留 20% 余量比如 16 通道 ACQ_PS7 时理论最高 74kHz实际触发频率不超过 60kHz。4.3 ePWM 定时触发 ADCINT 触发 DMA 的一条链定频采样的标准接法是 ePWM 产生 SOCAADC 转换完产生 ADCINT1ADCINT1 触发 DMA。ePWM 侧只需要配触发事件不需要让 PWM 输出引脚真出波形省掉 AQCTLA 也不影响 SOCA 的产生。void InitEPwm1AsAdcTrigger(void) { EALLOW; EPwm1Regs.TBPRD 14999; // 10kHz 触发频率 EPwm1Regs.TBCTL.bit.CTRMODE 0; // 向上计数 EPwm1Regs.TBCTL.bit.CLKDIV 0; // TBCLK 不分频 EPwm1Regs.TBCTL.bit.HSPCLKDIV 0; EPwm1Regs.CMPA.half.CMPA 7500; // 周期中点触发 EPwm1Regs.ETSEL.bit.SOCAEN 1; // 使能 SOCA 输出 EPwm1Regs.ETSEL.bit.SOCASEL 3; // 上计数到 CMPA 时触发 EPwm1Regs.ETPS.bit.SOCAPRD 1; // 每周期触发一次 EDIS; }SOCASEL 3 表示在上计数到 CMPA 时产生触发脉冲。TBPRD 14999 配合 150MHz 的 TBCLK 得到 10kHz 采样率这个频率对 16 通道、ACQ_PS 7 约 13.4µs 的一帧转换时间留了足够余量。触发时刻放在周期中点而不是零点是为了避开 PWM 占空比更新和电流采样通常发生的零点附近减少数字侧噪声耦合到模拟采样的概率。4.4 DMA 中断里只做三件事DMA 中断服务程序里只应该做清标志、切缓冲、置软件标志这三件事任何滤波、换算、协议打包都放到主循环。中断里做算术是 28335 数据采集性能变差的第二大原因第一大原因就是没有 DMA 还在逐个读结果寄存器。interrupt void DMACh1_ISR(void) { DMACh1.PERCTL.bit.PERINTCLR 1; // 第一行就清触发标志 PieCtrlRegs.PIEACK.all 0x0020; // 应答 PIE Group6 frameReady 1; // 主循环看到这个标志再处理 }PIEACK 写 0x0020 是对应 Group6 的应答位DMA 通道 1 的中断向量挂在 PIE 的 Group6 上这个值不能写错否则中断不会再次响应。IER 使能位和 PIEIER6 的对应位也要在初始化里配好DMA 中断使能链是外设级 PERINTE、PIE 级 PIEIER6、CPU 级 IER 三层少一层中断都进不来。5. 数据出来了但不对劲对齐、错位、漂移的排错顺序DMA 搬完数据只是第一步数据对不对是另一回事。按我的经验ADC→DMA 的绝大多数问题都集中在三处对齐方式、触发标志、以及根本没坏的漂移。排错顺序固定成下面这样能省一半时间。5.1 左对齐还是右对齐先看低 4 位是否恒为 028335 的结果寄存器是左对齐的12 位有效数据占高 12 位低 4 位是 0。拿到 DMA 缓冲的第一个动作先看原始数值的低 4 位如果所有通道的低 4 位恒为 0说明对齐没错如果低 4 位有值说明数据被当成右对齐处理过或者换算公式写错了。Uint16 raw adcData[i]; // 左对齐原始值 Uint32 mv (raw 4) * 3300UL / 4096UL; // 换算成 mV如果忘了右移 4 位直接乘比例满量程会被算成 65535 对应的电压误差高达 6%。反过来如果把 0x04D91V 输入当成 0x04D9 直接乘得到的是完全错误的电压值。这类问题的典型现象是所有通道读数整体偏大且不规则和硬件增益误差很像但校准怎么调都压不回来。5.2 错位与丢帧PERINTCLR、RST_SEQ1 与复位顺序DMA 数据错位的症状很典型通道顺序整体平移、每隔几帧丢一帧、或者第一帧是对的但后续帧通道顺序错乱。先查 PERINTCLR 有没有在中断入口清掉再查 ADC 序列是否被意外复位最后查通道映射表。AdcRegs.ADCTRL2.bit.RST_SEQ1 1; // 序列指针复位到 CONV00 AdcRegs.ADCTRL2.bit.SOC_SEQ1 1; // 重新启动一帧 DMACh1.PERCTL.bit.PERINTCLR 1; // 同步清 DMA 触发标志这三个操作的顺序有讲究先复位序列再触发清 DMA 标志放在最后。顺序反了可能出现序列已经启动、DMA 却把旧标志当新触发搬走一帧半新半旧的数据。下面这张表是这类问题的定位对照按行从上往下查。现象大概率原因检查顺序通道顺序整体平移CONVxx 映射或 DMA 起点错误对照 ADCCHSELSEQx 与 adcData 下标每隔固定帧数丢一帧PERINTCLR 清晚了或没清中断第一行加清标志观察 PERINTFR 位第一帧对后续帧错位CONT_RUN 下地址未重载检查 SRCADDRINIT/DSTADDRINIT 是否为 1数据像隔帧重复ADCINT1 和 ADCINT2 同时被使用关掉一个触发源确认 PERINTSEL 没有冲突5.3 漂移先查基准与采样窗口再怀疑 DMAADC 数值随温度缓慢漂移或者同一电压在不同时段读出差值这类问题九成不在 DMA。28335 的 ADC 基准来自内部 bandgapADCREFP、ADCREFM、ADCRESEXT 三个引脚的外部器件接错或布局离开关节点太近漂移量会远超芯片手册标称值。排错时先把这三个引脚的退耦电容和走线确认一遍再用 0V 和接近满量程的参考电压做两点校准算 offset 和 gain 补偿。另一个被误判为漂移的场景是采样窗口长度。信号源内阻大、前端滤波电容取大了ACQ_PS 太短会导致采到的是未稳定的电压读数随触发频率变化看起来就是漂移。验证方法很简单把 ACQ_PS 从 7 改到 15如果读数变化超过几个 LSB问题在采样电路不在 DMA。时钟抖动的影响此时也会显形——ePWM 触发时刻的抖动会直接叠成电压噪声这是 PCB 布局问题DMA 帮不了忙。5.4 对照记忆STM32/GD32 上的 ADC DMA 紊乱在 28335 上对应什么很多从 STM32 转过来的工程师习惯用老经验排错。STM32 上常见的 ADC DMA 数据紊乱原因比如缓冲区未按 4 字节对齐、DMA 传输宽度 16/32 位不匹配在 28335 上都不存在——它是 16 位字宽的机器天然对齐。但 GD32E230 上讨论很多的ADC DMA 数据紊乱现象对应到 28335 就是两个问题一是触发标志没清导致丢帧二是多通道映射和 DMA 搬运顺序不一致。还有人在 STM32 上用 DMA 加空闲中断做串口不定长接收28335 的 DMA 没有串口空闲检测串口收发要么固定长度 DMA要么走中断这个差异经常让刚上手的人走弯路。6. 让 DMA 采样更稳的两个习惯乒乓缓冲与传输完整性校验最后一层优化通常不是调寄存器而是改数据流结构。把单缓冲改成乒乓缓冲再把完整性校验做成可观测的计数这套组合能覆盖绝大多数采样异常。6.1 用 DSTBEG 切换实现双缓冲单缓冲的问题在于DMA 正在往 adcData 里写第二帧时主循环可能还在读上一帧读到一半被覆盖。乒乓缓冲把缓冲区拆成两块DMA 写 A 时 CPU 读 BDMA 写 B 时 CPU 读 A互相不碰。Uint16 adcBufA[16]; Uint16 adcBufB[16]; Uint16 *volatile curBuf adcBufA; // DMA 当前写入的缓冲 Uint16 *volatile doneBuf; // 主循环可安全处理的缓冲 interrupt void DMACh1_ISR(void) { DMACh1.PERCTL.bit.PERINTCLR 1; PieCtrlRegs.PIEACK.all 0x0020; if (curBuf adcBufA) { doneBuf adcBufA; // A 已写完交给主循环 curBuf adcBufB; // 下一帧写 B } else { doneBuf adcBufB; curBuf adcBufA; } DMACh1.DSTBEG (Uint32)curBuf; DMACh1.DSTADDR (Uint32)curBuf; }切换 DSTBEG 和 DSTADDR 必须在 DMA 空闲时进行中断入口正是两个 transfer 之间的安全窗口。要注意 DSTADDRINIT 保持为 1否则下一帧会从旧地址继续写。主循环里用 doneBuf 处理数据处理完不需要还给 DMA指针已经被中断换走了。6.2 校验 DMA 是否真的搬完双缓冲解决了覆盖问题但没法回答这一帧是不是完整、是不是按时到的。校验手段不复杂在 ePWM 的周期中断里放一个计数器 pwmCnt在 DMA 中断里放一个计数器 dmaCnt长时间运行后两者差值必须恒定。差值持续增长说明有触发事件没转换成 DMA 搬运现场指向 PERINTCLR差值忽大忽小说明触发源本身抖动检查 ePWM 配置。更细的校验是检查 DMA 通道状态。28335 的 DMA 通道没有专门的完成标志寄存器但可以通过 SRCCNT 和 DSTCNT 观察剩余计数空闲时两者应回到初始装载值。把这个检查放在主循环的慢速任务里每秒查一次比在中断里加断点靠谱得多。调试期间还可以把一个 GPIO 在 DMA 中断里翻转示波器上看翻转频率是否等于触发频率一旦看到周期翻倍或丢失问题立刻现形。这套计数与 GPIO 组合是验证 ADC→DMA 链路最直接的做法的比任何仿真器断点都真实。本文还有配套的精品资源点击获取
返回列表