简介:面向毕业设计、课程实训及工业管道流量测量场景,这份以TMS320F28335 DSP为核心的超声波流量计完整工程项目,涵盖了从方案论证、硬件设计到软件调试的全过程。系统基于时差法测流,采用SCOT加权广义互相关时延估计算法,借助ADS805高速采集与DMA传输实现高精度计时,并利用LCD12864完成人机交互。压缩包包含11个文件,整合了3个C源码文件(负责信号发送、LCD显示、外部接口与DMA采集)、原理图与PCB设计(包含超声波收发驱动、通道切换、显示接口等电路)、系统说明文档及论文PDF,整体大小约5.5MB,目录清晰、模块分明。目前已有393人学习,适合电子、自动化、仪器仪表等专业高年级学生和工程技术人员参考。下载后可获得可运行的嵌入式代码、可二次开发的硬件图纸,以及完整的论文写作思路,能有效缩短设计周期,是一份兼具学术价值与工程实践意义的参考范例。
1. 时差法超声波流量计的工程拆解:为什么 TMS320F28335 比 MCU 更合适
超声波流量计真正的难点不在发射那束 1MHz 超声波,而在从被噪声淹没的回波里把几十纳秒的时间差稳定地抠出来。传统 MCU 用中断方式搬数据,CPU 被频繁打断,理论上能算互相关,实际跑起来时序一塌糊涂。TMS320F28335 这类 150MHz 浮点 DSP 的价值在于:片上带有外部接口 XINTF、DMA 控制器和浮点运算单元,发射、采集、时延估计、人机交互可以在一个芯片里闭环完成。这套毕业设计资源把论文、硬件原理图、PCB 和 C 语言代码放在一起,几乎就是一个完整的工业流量计原型,适合做管道流量测量验证,也适合想从原理图一路干到算法落地的嵌入式工程师拿去改造成自己的工程。
2. 发射与接收硬件链路:换能器驱动、信号调理和原理图参数选型
拿到工程包先看框图,这套系统的信号流向很清晰:DSP 的 EPWM 模块产生激励脉冲,经发射驱动电路放大后推动超声换能器;接收通道从另一个换能器取回微弱回波,经过放大、滤波后送 ADS805 采集;切换电路负责交替接通上下游换能器,实现顺流和逆流两次测量。下面把每个模块的关键参数和选型理由拆开说。
2.1 发射驱动电路:不要用 IO 直接推压电换能器
压电陶瓷换能器呈容性负载,等效阻抗在激励频率下通常只有几十到几百欧姆,DSP 的 GPIO 驱动能力只有几毫安,直接推根本起不来。常见做法是用一对 MOSFET 组成半桥电路,由 EPWM1A/1B 输出互补 PWM 驱动,配合死区控制防止上下管直通。
激励频率要和换能器中心频率匹配,工程包里默认按 1MHz 设计。1MHz 对应水中波长约 1.5mm,对于管径几十毫米的管道,时差分辨率需求在纳秒级,这个频率是精度和衰减的折中。频率再高,接收信号衰减会明显增大。
驱动电路里要留两处保护:MOSFET 栅极串电阻限制驱动电流,避免振铃;换能器两端并联一对反向稳压管或二极管,钳位接收状态下的高压脉冲。原理图 flowmeter circuit.Sch 中这两个元件都在,实物调试时千万别省,否则反射脉冲很容易打坏接收前端。
2.2 接收信号调理:增益分配比单纯放大更重要
接收换能器输出的回波幅度大致在微伏到毫伏级,而 ADS805 的满量程输入是 2Vpp,中间需要 40~60dB 增益。一次性把增益全部放在第一级,放大器很容易饱和,因为发射脉冲的串扰和管壁反射波会先到接收端,幅度远大于主回波。
我按这套原理图实际搭过的设计方案通常分三级:第一级 LNA 用低噪声运放,比如 OPA657 或 AD8099,做 10 倍左右固定增益;第二级用可变增益放大器 VGA,增益范围 20~40dB 可调;第三级是二阶带通滤波器,中心频率对准换能器频率,带宽控制在 200kHz 以内,用来抑制低频流噪声和电源工频干扰。
增益参数要配合软件动态调整。空管和满管、清水和污水,回波幅度差异很大,固定增益很容易饱和或测不到信号。调试时先用示波器看 VGA 输出,把波形峰值调到 1.5Vpp 左右,然后交给软件根据峰值检测结果微调。
2.3 切换电路与换能器安装方式
顺流和逆流两种测量方向需要交替工作。切换电路通常用模拟开关或继电器实现,由于换能器是高压驱动,模拟开关需要考虑耐压指标,常见的是用双路 SPDT 继电器或者高耐压模拟开关 MAX333 之类的器件。
换能器安装分 Z 法和 V 法,Z 法声道直接穿过管径,信号最强;V 法声程长,时差更大,低流速下分辨率更好。这套设计默认走 V 法,两个换能器装在管道同侧。安装角度 θ 直接进入流速计算公式,原理图里标的是 45 度斜装,实际安装时我建议用夹具固定,因为手动涂抹耦合剂时角度稍微偏一点,流速换算结果就会出现明显误差。
2.4 电源与人机交互模块的布局
核心板、发射电路、接收电路、LCD 和模拟采集部分对电源的敏感度完全不同。开关电源直接给发射驱动供电没问题,但给 LNA 和 VGA 供电必须经过 LDO 滤波,否则开关噪声直接叠加在微弱回波上,后级数字滤波也难彻底消除。
LCD12864 模块功耗不大,但背光点亮时电流会周期性跳变,如果和模拟电路共地共电源,显示屏刷新瞬间会产生尖峰干扰。PCB 布局上我把 LCD 电源单独用 LC 滤波,数字地和模拟地在 DSP 核心板的星型接地点处单点汇合。这套资源里 flowmeter circuit.PCB 已经是按这个思路布的,可以对照看走线。
3. ADS805 高速采集链路:XINTF 时序配置与 DMA 乒乓缓冲的 C 代码实现
时差法要求在几十微秒的采集窗口内记录完整的超声回波,12bit、20MSPS 是这套设计的选型基准。ADS805 数据输出直接挂在 TMS320F28335 的 XINTF 外部接口上,配合 DMA 搬运,CPU 只在一次测量结束后做一次互相关计算,期间完全不参与数据搬移。
3.1 ADS805 与 XINTF 的接口方式
ADS805 是 12bit 并行输出 ADC,20MSPS 采样率下数据率是每 50ns 一个样本。如果靠 CPU 中断去读,50ns 的中断间隔远超 F28335 的中断响应能力,所以必须走 DMA。把 ADS805 的输出数据线接到 XINTF Zone6 的数据总线上,地址线选一根空闲的做片选,每次 DMA 传输就相当于执行一次外部存储器读操作。
Zone6 的基址在 0x200000 附近,访问该区域的读时序由 XTIMING6 寄存器控制。ADS805 的数据建立时间约十几纳秒,XINTF 时钟按 SYSCLKOUT 150MHz 计算,周期约 6.67ns,读激活时间至少要设 2 个周期以上。代码里把 XRDACTIVE 设为 7,留足裕量,实测 20MSPS 采样率下完全能稳定读取。
// XINTF Zone6 初始化,对接 ADS805 并行输出 void Xintf_Zone6_Init(void) { // 使能 XINTF 外设时钟 SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1; // 读时序:建立2周期、激活7周期、跟踪2周期 // 写时序不关心,ADC 不往总线写数据,但寄存器要统一配置 XintfRegs.XTIMING6.bit.XRDLEAD = 2; XintfRegs.XTIMING6.bit.XRDACTIVE = 7; XintfRegs.XTIMING6.bit.XRDTRAIL = 2; XintfRegs.XTIMING6.bit.XWRLEAD = 2; XintfRegs.XTIMING6.bit.XWRACTIVE = 7; XintfRegs.XTIMING6.bit.XWRTRAIL = 2; // 数据总线宽度16位,不使用外部READY信号 XintfRegs.XTIMING6.bit.XSIZE = 1; XintfRegs.XTIMING6.bit.XREADY = 0; }读时序参数的意义在于匹配 ADC 的数据有效窗口。XRDLEAD 是片选拉低前的等待周期,用来让地址线稳定;XRDACTIVE 是读脉冲有效时间,必须覆盖 ADC 数据输出的建立时间;XRDTRAIL 是读结束后继续保持片选的时间,防止数据总线竞争。采样率从 20MSPS 降下来用时,可以把 ACTIVE 周期适当减小以降低等效负载,但提高到 25MSPS 以上时就要考虑 ADS805 的转换时间上限了。
3.2 DMA 乒乓缓冲:让采集和计算流水起来
单次测量要采集 2048 点,每组 12bit 存成 16bit 字。DMA 一次搬运最多 65535 字,2048 点完全在限制内。关键是目的地要用双缓冲,一组数据在后台被 DMA 填充时,CPU 正在处理另一组数据,测量周期可以减半。
#define ADC_SAMPLES 2048 #define XINTF_ZONE6 0x200000 Uint16 AdcBuf0[ADC_SAMPLES]; Uint16 AdcBuf1[ADC_SAMPLES]; void DMA_Channel1_Init(void) { // 用 DMA 通道搬运 ADC 数据,从 XINTF Zone6 读入 AdcBuf0 DMARegs.DMACH1.MODE.bit.PERINTSEL = 32; // XINTF 读完成触发传输 DMARegs.DMACH1.MODE.bit.ONESHOT = 0; // 连续传输,不做一次性停止 DMARegs.DMACH1.MODE.bit.DATASIZE = 1; // 16 位数据宽度 DMARegs.DMACH1.MODE.bit.CHINTE = 1; // 传输计数到零触发中断 // 源地址固定:ADS805 始终对外表现为同一地址 DMARegs.DMACH1.SRC_BEG_ADDR = XINTF_ZONE6; DMARegs.DMACH1.SRC_ACTIVE = XINTF_ZONE6; // 目的地址:先写 AdcBuf0,中断里再切换 DMARegs.DMACH1.DST_BEG_ADDR = (Uint32)AdcBuf0; DMARegs.DMACH1.DST_ACTIVE = (Uint32)AdcBuf0; // 一次传输 2048 个 16 位样本 DMARegs.DMACH1.TRANSFER_SIZE = ADC_SAMPLES; // 使能 DMA 通道中断 DMARegs.DMAINT.bit.CH1 = 1; }PERINTSEL 置为 32 表示由 XINTF 外部接口的读操作完成事件触发 DMA 请求,这是 C2000 系列 DMA 触发源里与并行 ADC 采集最匹配的一路。源地址固定是因为 ADS805 没有地址总线,所有输出数据都出现在同一外部地址,读一次出一组 12bit 结果;目的地址递增,样本按顺序落进内存数组。
要注意 DMA 的 TRANSFER_SIZE 是 16 位寄存器,最大 65535,超过这个数要拆成多段传输。对于 20MSPS 满速采集,65535 点只够约 3.3ms 的窗口,想要更长的采样窗就得在 DMA 中断里重新装载计数器,或者用多个 DMA 通道接力。工程包里的代码只做了 2048 点,已经足够覆盖管道回波的主波段时间窗口,不必刻意拉长。
3.3 触发发射和采集的时序衔接
测量开始时,先用 EPWM 模块输出一串 1MHz 方波激励换能器,同时启动 DMA 等待数据。发射和采集之间的延迟时间要精确控制,因为 DSP 计算互相关用的是绝对时间窗,发射时刻的抖动会直接转化为时延误差。
比较省事的做法是用同一个定时器中断完成任务切换:进入中断先关掉 PWM 输出,启动回波采集 DMA,等待几十微秒后读回数据,再切到逆流方向发射。两次测量之间留出足够的消隐时间,让管道内的余波衰减干净,常见做法是留 2~3 倍声程时间。我记得工程包里的 Sendsignal_28335.c 就是用定时器中断完成的这个状态机,顺着这个思路改回波采集启动时刻,就能适应不同管径。
4. SCOT 加权广义互相关时延估计:从公式到 C 代码的完整落地
时差法的核心是从两路回波里估计到达时间差。先接收的一路和延迟到达的一路本质上是同一个波形的平移版本,只是叠加了不同的噪声和幅度衰减。在低信噪比下,直接互相关的峰值很平缓,时延值抖动大,所以这套设计采用了 SCOT 加权的广义互相关法。
4.1 为什么要做频域加权而不是直接互相关
直接互相关的频域表达式是互功率谱的逆傅里叶变换,峰值位置对应时延。但互功率谱包含噪声项,在信号频带外仍然有能量,这些能量会拉平相关峰。广义互相关法在逆变换之前对互功率谱乘以一个加权函数,相当于在频域做滤波,突出信噪比高的频点,压低噪声频点。
SCOT 加权的权函数是两路自功率谱乘积平方根的倒数:
W(f) = 1 / sqrt(Gxx(f) * Gyy(f))
Gxx 和 Gyy 分别是两路信号的自动率谱。SCOT 的优点是同时归一化了两路信号的频谱能量,对幅度不一致的回波更鲁棒。相比之下,Roth 加权只使用单路自功率谱,PHAT 加权相当于让权函数恒为 1/Gxy 的模,抗噪能力更强但对相位失真敏感。SCOT 卡在中间,工程上最省心。
4.2 带 SCOT 加权的互相关计算流程
计算分五步:加窗、FFT、互功率谱和自功率谱、频域加权、IFFT 找峰。每段回波 2048 点,FFT 长度 2048,频率分辨率约 9.7kHz。若中心频率 1MHz,信号带宽约 200kHz,频率分辨率完全够用。
// SCOT 加权广义互相关:输入是两路回波,输出是时延样点数 float Scot_DelayEstimate(float *x, float *y, int n) { // n 必须是 2 的幂,这里固定为 2048 float *rxy = (float *)calloc(n, sizeof(float)); complex_fft(x, y, n, 1); // x[], y[] 做 FFT,结果存回 x[], y[] // x[] 和 y[] 此时分别是 X(k) 和 Y(k) for (int k = 0; k < n; k++) { float gxx = cabs_sq(&x[k]); // |X(k)|^2 float gyy = cabs_sq(&y[k]); // |Y(k)|^2 float den = sqrtf(gxx * gyy) + 1e-12f; // 加小常数防除零 // Gxy(k) * W(k) = X(k) * conj(Y(k)) / sqrt(Gxx*Gyy) complex_mul_conj_div(x, y, &w[k], k, den); } complex_ifft(w, rxy, n); // 逆变换得到互相关序列 int peak = argmax_abs(rxy, n); // 找最大绝对值位置 float frac = ParabolicInterp(rxy, peak); return (float)peak - (float)n / 2.0f + frac; // 换算为有符号时延 }代码逻辑做了三层简化,但主流程和工程包里的做法一致。加窗这步没有写进函数体里,实际使用时会先对 x 和 y 做 Hann 窗处理,抑制 FFT 频谱泄漏。除零保护用 1e-12 的小常数,在低流速时 Gxx 和 Gyy 可能因为增益太低而接近零,没有这个保护函数的输出会直接溢出成 NaN。
找峰之后还有一步抛物线插值,峰值位置只能精确到采样周期。20MSPS 下采样周期是 50ns,如果只取整数值,时延分辨率就是 50ns。按水流速度 1m/s、声道长度 100mm、声速 1500m/s 计算,时延约 45ns,小于一个采样周期,不插值根本测不出来。抛物线插值用峰值点和左右两点拟合二次曲线,顶点位置作为亚采样偏移量,简单写法是:
float ParabolicInterp(float *r, int peak) { int left = peak - 1; int right = peak + 1; float a = r[left]; float b = r[peak]; float c = r[right]; float denom = (a - 2.0f * b + c); // 避免分母为零 if (fabsf(denom) < 1e-12f) return 0.0f; return 0.5f * (a - c) / denom; }抛物线插值只在相关峰附近接近抛物线形状时准确,信噪比过低时插值点的噪声会被放大,所以工程里一般还会加一条峰值尖锐度判断,即峰值和左右两点的差值比例超过某个阈值才做插值,否则按整点时延输出。
4.3 从时延到流速的换算公式
V 法安装时,换能器声程长度为 L,声道与管轴线夹角为 θ,顺流和逆流的传播时间分别是 L/(c+v·cosθ) 和 L/(c−v·cosθ),两者差值为:
Δt = 2·L·v·cosθ / (c² − v²·cos²θ)
流速远小于声速时,分母的 v² 项可以忽略,简化为 v = Δt·c²/(2·L·cosθ)。这套设计里 θ=45°,cosθ≈0.707,L 是固定的几何参数。用程序实现时,把 L 和 cosθ 作为标定常量传入,声速 c 用温度传感器修正。
注意公式用的是声速 c 的平方,c 对温度非常敏感。20℃ 和 30℃ 时水中声速差约 18m/s,平方后影响约 2.4%。因此中段代码里需要留一个温度补偿接口,在每次测量时读取温度值更新 c,否则流量误差会随季节漂移。
5. LCD12864 人机交互与整机标定:零流量校准、温度补偿和峰值确认
5.0 驱动代码的边界条件
LCD12864 并口驱动不复杂,但要注意背光功耗对模拟采集的影响。驱动函数不要放在定时器中断里频繁刷新,显示汉字点阵时需要逐字节送数据,一次完整刷新在 1MHz 主频下要消耗几十毫秒,这期间 DMA 采集会被抢占总线,造成采集数据缺口。把刷新频率限制在每秒 1 次,保证测量周期不受干扰。
// ST7920 并口写命令,RS=0, RW=0, E 上升沿锁存数据 void LCD_WriteCmd(unsigned char cmd) { LCD_RS = 0; LCD_RW = 0; LCD_DATA = cmd; LCD_E = 1; DELAY_US(3); LCD_E = 0; DELAY_US(5); } // ST7920 并口写数据,RS=1, RW=0 void LCD_WriteData(unsigned char dat) { LCD_RS = 1; LCD_RW = 0; LCD_DATA = dat; LCD_E = 1; DELAY_US(3); LCD_E = 0; DELAY_US(5); }DELAY_US 的参数是给 150MHz 主频的延时循环用的,在不同主频下需要重新标定。判断 LCD 驱动是否正常,先发一条清屏命令 0x01,再用示波器看 E 引脚是否存在周期性的高电平脉冲。这个检查动作在故障诊断时最省时间。
5.1 零流量校准:先测系统偏置再算流速
任何一路回波从发射到接收都包含电路延迟、换能器振铃和管壁反射路径,这些延迟在顺流和逆流方向并不完全对称,直接套公式会出现零点漂移。错误代码示例:不校准直接减零,然后通过加一个固定偏移量掩盖问题,换管径或温度后误差又回来了。
正确做法是每次上电后静置几秒,在流速为零时测量一次时延作为偏置存储,后续每次测量都减去这个偏置。零点值的波动范围可以顺便当作系统分辨率指标,如果零点标准差在 ±0.3 个采样点以内,说明硬件信噪比够用。
5.2 温度补偿和声速更新
声速用简化经验公式 c = 331.4 + 0.6·T 会引入约 0.2% 到 0.5% 的误差,实测精度要求不高时足够。若想提升精度,用五阶拟合公式或者查表插值。温度传感器放置位置要贴近管道外壁或水流。每次温度变化超过 0.5℃ 时重新计算一次声速,不要在每个测量周期都重复计算,因为 cosθ 和 L 会随管壁热胀冷缩轻微变化,频繁更新反而引入抖动。
5.3 互相关峰值的验证技巧
排错时最容易碰到的问题有两个:互相关峰总是出现在零延迟附近,或者峰值位置随机跳动。先看发射、接收两路 FFT 输出,如果两路主频都不在 1MHz 附近,多半是增益不足或回波饱和;如果峰值位置每次变化都在零附近,说明串扰信号掩盖了真实回波,需要在软件里把采集窗口向后延迟几十微秒,避开发射脉冲的前沿串扰。
还可以用自环验证:把同一段回波复制一份,在不同位置截取后送入互相关函数,输出时延应该等于截取位置的差值。这个测试用例在划代码时先跑通,再接入真实信号,能快速区分算法问题还是硬件问题。工程包里没有单独放这个测试函数,我建议实际调试时补上,它比任何波形分析都省时间。
本文还有配套的精品资源,点击获取