1. 所选主题和项目介绍
1.1 项目背景
在现代工业生产中,气体流量检测是过程控制、能耗管理和安全监测的关键环节。传统的流量检测方式多采用机械式流量计(如涡轮流量计、孔板流量计等),这类仪表存在压力损失大、易磨损、精度随使用时间下降等固有缺陷,尤其在腐蚀性气体、高温气体或含有颗粒杂质的工业气体测量场景中,机械式流量计的寿命和可靠性受到严重挑战。
超声波流量检测技术凭借其非接触测量、无压损、精度高、稳定性好、可测量腐蚀性气体等优势,逐渐成为工业气体流量检测的重要发展方向。特别是在食品医药行业的洁净气体输送、精细化工的反应气体监控、半导体工艺的特殊气体配送等对卫生安全和测量精度要求极高的领域,超声波流量计已成为首选方案。
1.2 项目目标
本项目旨在设计并实现一个基于超声波差时法的工业非接触式气体流量检测系统,系统具备以下核心特性:
- 测量原理:采用超声波差时法(Transit-Time Difference Method),通过测量顺流与逆流方向超声波传播时间差来计算气体流速
- 核心器件:选用TI(德州仪器)的TDC1000作为超声波飞行时间测量芯片,配合2个超声波探头实现双向传播时间的高精度测量
- 显示交互:采用1.54寸240×240分辨率TFT LCD显示屏,通过SPI接口与主控通信,实时显示正向传播时间、反向传播时间、时间差及计算得到的流量值
- 主控平台:基于TI MSPM0G3507超低功耗微控制器,该MCU具备丰富的外设资源,包括多路SPI接口、定时器捕获单元等,能够有效适配TDC1000和TFT LCD的驱动需求
1.3 差时法测量原理
超声波差时法(Transit-Time Difference Method)是目前工业超声波流量计中应用最为广泛的测量方法。其基本原理如下:
当流体在管道中流动时,顺流方向的超声波传播速度等于声波在静止介质中的传播速度与流体速度的矢量和;逆流方向的超声波传播速度则为两者之差。通过分别测量超声波在顺流和逆流方向上的传播时间,可以得到时间差Δt,进而计算出流体速度。
设管道长度为L,流体速度为v,声波在静止介质中的传播速度为c,则:
- 顺流传播时间:t₁ = L / (c + v)
- 逆流传播时间:t₂ = L / (c - v)
- 时间差:Δt = t₂ - t₁ = 2Lv / (c² - v²)
当流体速度v远小于声速c时(通常工业气体管道中满足此条件),上述公式可简化为:
Δt ≈ 2Lv / c²
进而可推导出流速:
v ≈ (c² / 2L) × Δt
最终通过管道截面积A可计算体积流量:
Q = v × A
差时法的显著优点在于测量结果与声速c无关(因为在计算流速时声速被消除),这意味着即使测量环境中温度变化导致声速漂移,也不会影响测量精度。这对于需要在宽温度范围内保持稳定测量的工业应用具有重要意义。
2. 硬件介绍
2.1 主控芯片:MSPM0G3507

MSPM0G3507是TI MSPM0系列超低功耗微控制器的一员,该系列基于Arm Cortex-M0+内核,最高工作频率可达80MHz。MSPM0G3507具备以下特性,使其非常适合作为本项目的控制核心:
参数 | 规格 |
内核 | Arm 32-bit Cortex-M0+ |
工作频率 | 最高80MHz |
Flash存储 | 256KB |
SRAM | 32KB |
工作电压 | 1.71V ~ 3.63V |
SPI接口 | 3个(支持SPI Master/Slave模式) |
定时器 | 包含通用定时器、Timer_A、Timer_G等,支持PWM输出和输入捕获 |
工作温度 | -40°C ~ 105°C |
在本项目中,MSPM0G3507主要承担以下任务:
- 通过SPI0接口驱动TFT LCD显示屏
- 通过SPI1接口配置和读取TDC1000测量数据
- 通过TimerA模块产生PWM时钟信号驱动TDC1000
- 通过TimerG模块实现超声波回波的精确捕获计时
- 通过GPIO接口读取按键输入,实现人机交互
2.2 超声波TOF测量芯片:TDC1000

TDC1000是TI推出的一款超声波飞行时间(Time-of-Flight)测量专用芯片,专为超声波流量计、液位检测等应用设计。该芯片集成了完整的超声波发射驱动电路和精密的时间测量电路,配合外置的超声波换能器即可实现高精度的距离或流速测量。
TDC1000关键特性:
参数 | 规格 |
测量分辨率 | 最高62.5ps(使用内部振荡器) |
测量范围 | 最大4ms(标准模式)/ 16ms(扩展模式) |
超声波驱动 | 内置半桥驱动,支持1~64个脉冲可编程配置 |
模拟前端 | 内置低噪声放大器(LNA)和比较器 |
通信接口 | SPI接口(模式0/3) |
供电电压 | 2.5V ~ 5.5V |
工作温度 | -40°C ~ 125°C |
在本系统中,TDC1000工作于双向测量模式——即先进行顺流方向的测量,立即切换为逆流方向进行测量,通过两次测量的时间差值计算流体速度。这种测量方式能够有效消除环境因素(如温度、声速漂移)对测量的影响。
超声波换能器是超声波流量测量系统中的关键传感器件,负责将电信号转换为超声波以及将接收到的超声波转换为电信号。本系统采用两枚相同规格的超声波探头,分别安装在管道的上游和下游位置。
2.3 显示模块:1.54寸TFT LCD

本系统采用1.54寸TFT LCD显示屏作为人机交互界面,该屏幕具体参数如下:
参数 | 规格 |
分辨率 | 240×240像素 |
显示技术 | TFT LCD(薄膜晶体管液晶显示) |
接口类型 | SPI(4线式SPI) |
驱动IC | ST7789H |
颜色深度 | 16位RGB(65K色) |
视角 | 全视角 |
背光 | LED背光 |
工作电压 | 3.3V |
显示屏通过SPI接口与MSPM0G3507通信,采用标准4线SPI模式(MOSI、MISO、SCK、CS)。ST7789H驱动IC支持丰富的显示指令集,包括像素操作、区域设置、Gamma校正、帧率设置等,能够实现字符、数字、图形的高质量显示。
3. 方案框图和项目设计思路
3.1 系统整体架构

3.2 系统设计思路
分层架构设计:
本项目采用经典的分层架构设计思想,将整个系统按照功能划分为多个层次,从底层到高层依次为:
- 硬件层(Hardware Layer)
- 包括MSPM0G3507最小系统、TFT LCD显示模块、TDC1000测量模块、按键输入模块、LED指示模块等
- 由硬件工程师完成原理图设计和PCB布局
- 驱动层(Driver Layer)
- 包括各外设的驱动函数,如TFT驱动(TFT_154.c)、TDC1000驱动(drv_TDC1000.c)、SPI通信驱动(eva_SPI.c)、LED驱动(eva_LED.c)等
- 驱动层直接操作硬件寄存器,为上层提供抽象的硬件访问接口
- 本项目中驱动层采用软件模拟SPI和硬件SPI双模式,可根据实际需求选择
- 应用层(Application Layer)
- 包括数据采集应用(Per_App.c)和人机交互应用(Menu_App.c)
- 应用层调用驱动层接口,完成业务逻辑处理
- Per_App.c负责系统初始化和数据采集协调
- Menu_App.c负责界面显示和按键响应
- 表现层(Presentation Layer)
- 负责将测量数据以直观的方式呈现给用户
- 包括欢迎界面、流速测试界面等
- 通过TFT LCD显示实时数据
硬件架构也是通过分层连接方式进行:
4. 原理图和PCB设计分析
本次设计的主要模块是传感器模块:
原理图如下:
这里使用的除了TDC1000芯片外还有一些接口和供电芯片(ADPL40502AUJZ-3.3-R7),通过电压跟随提供了一些测试点用于测试。
PCB设计如下:
5. 软件流程与关键代码
5.1 软件流程图

5.2 主函数流程
// empty_mspm0g3507.c - 主函数
int main(void)
{
// 1. 系统初始化 - 由SDK自动生成,配置时钟、功耗等
SYSCFG_DL_init();
// 2. 中断使能
NVIC_EnableIRQ(GPIO_KEY_GPIOA_INT_IRQN); // 按键GPIO_A中断
NVIC_EnableIRQ(GPIO_KEY_GPIOB_INT_IRQN); // 按键GPIO_B中断
NVIC_EnableIRQ(TDC_Start_CAPTURE_INST_INT_IRQN); // TDC启动捕获中断
NVIC_EnableIRQ(TDC_Stop_CAPTURE_INST_INT_IRQN); // TDC停止捕获中断
NVIC_EnableIRQ(TIMER_10ms_INST_INT_IRQN); // 10ms定时器中断
DL_TimerG_startCounter(TIMER_10ms_INST);
// 3. 应用层初始化
App_Init(); // 初始化LED、TFT、TDC1000等
// 4. 主循环 - 轮询方式协调各任务
while (1) {
App_Collect(); // 数据采集任务
App_KEY_Judge(); // 按键处理任务
App_FaceShow(); // 界面显示任务
}
}
5.3 系统初始化流程
// Per_App.c - 应用层初始化
void App_Init(void)
{
EVA_LED_init(); // LED指示灯初始化
TFT_init(); // TFT LCD引脚初始化
Drv_tft_init(); // TFT LCD驱动初始化(发送初始化序列)
TDC_IO_Init(); // TDC1000 IO初始化
TDC_Init(); // TDC1000寄存器配置初始化
Menu.Request = Holle_Interface; // 默认显示欢迎界面
}
5.4 TDC1000初始化配置
// drv_TDC1000.c - TDC1000初始化
void TDC_Init(void)
{
TDC_TRIG_Disable;
TDC_RST_Enable;
Delay_ms(100);
TDC_RST_Disable;
Delay_ms(10);
// 寄存器配置
TDC_Write(0, 0x84); // 配置寄存器0:测量模式等
TDC_Write(1, 0x41); // 配置寄存器1:接收器设置
TDC_Write(2, 0x21); // 配置寄存器2:发射/接收通道配置
TDC_Write(3, 0x0E); // 配置寄存器3:阈值设置
TDC_Write(4, 0x03); // 配置寄存器4:时序配置
TDC_Write(5, 0xE4); // 配置寄存器5:时钟配置
TDC_Write(6, 0x1E); // 配置寄存器6:中断使能
TDC_Write(7, 0x00); // 配置寄存器7:备用
TDC_Write(8, 0x63); // 配置寄存器8:时序微调
TDC_Write(9, 0x00); // 配置寄存器9:备用
}
5.5 差时法测量核心流程
// drv_TDC1000.c - TDC采样(状态机实现)
void TDC_Sample(void)
{
if(TDC.start_flag == 1) // 判断是否启动测量
{
if(TDC.Sample_state == 0) // 状态0:开始顺流测量
{
TDC.Sample_state = 1;
TDC_UltraDriveForward(); // 设置为顺流方向
TDC_StartSample(); // 启动测量
}
else if(TDC.Sample_state == 1 && TDC.Echo_flag == 1) // 顺流回波已捕获
{
TDC.Forward_cnt = value_cap; // 保存顺流传播时间
TDC.Echo_flag = 0;
TDC.Sample_state = 2;
TDCstopCaptureIT_End;
TDC_UltraDriveReverse(); // 切换为逆流方向
TDC_StartSample(); // 启动逆流测量
}
else if(TDC.Sample_state == 2 && TDC.Echo_flag == 1) // 逆流回波已捕获
{
TDC.Reverse_cnt = value_cap; // 保存逆流传播时间
TDC.Echo_flag = 0;
TDC.Sample_state = 0;
TDC.start_flag = 0;
TDC_collect.Capflag = 0;
TDC_StopSample();
// 保存到滤波缓冲区
TdcFilter.Forward_BUF[TDC.Sample_cnt] = TDC.Forward_cnt;
TdcFilter.Reverse_BUF[TDC.Sample_cnt] = TDC.Reverse_cnt;
TdcFilter.diff_BUF[TDC.Sample_cnt] = abs(TDC.Forward_cnt - TDC.Reverse_cnt);
TDC.Sample_cnt++;
// 完成10次采样后进行滤波计算
if(TDC.Sample_cnt >= 10)
{
TDC.Sample_cnt = 0;
// 中位值平均滤波
TDC.ForwardAve_cnt = Filter_MedianAve(TdcFilter.Forward_BUF, 10, 3);
TDC.ReverseAve_cnt = Filter_MedianAve(TdcFilter.Reverse_BUF, 10, 3);
TDC.diffAve_cnt = Filter_MedianAve(TdcFilter.diff_BUF, 10, 3);
TDC.FlowData = (float)TDC.diffAve_cnt;
// 根据时间差范围采用不同拟合公式计算流量
if(TDC.diffAve_cnt <= 55)
{
TDC.FlowData = ((float)TDC.diffAve_cnt - 24) * 0.07645;
if(TDC.FlowData < 0)
TDC.FlowData = 0;
}
else
{
TDC.FlowData = 0;
TDC.FlowData += 0.000000009851 * TDC.diffAve_cnt * TDC.diffAve_cnt * TDC.diffAve_cnt;
TDC.FlowData += -0.00001465 * TDC.diffAve_cnt * TDC.diffAve_cnt;
TDC.FlowData += 0.07783 * TDC.diffAve_cnt;
TDC.FlowData += -1.702;
}
TDC.updata_flag = 1; // 置位更新标志,触发界面刷新
}
}
}
}
5.6 滤波算法
// drv_TDC1000.c - 中位值平均滤波算法
// 该算法融合了中位值滤波和平均值滤波的优点,能够有效滤除脉冲干扰
uint32_t Filter_MedianAve(uint32_t *data, uint8_t number, uint8_t edge)
{
uint32_t value_buf[20], temp, sum = 0;
uint8_t cnt, i, j, cnt_edge;
// 1. 复制数据并计算总和
for(cnt = 0; cnt < number; cnt++)
{
value_buf[cnt] = data[cnt];
sum += value_buf[cnt];
}
// 2. 冒泡排序
for(j = 0; j < (number-1); j++)
{
for(i = 0; i < (number-j-1); i++)
{
if(value_buf[i] > value_buf[i+1])
{
temp = value_buf[i];
value_buf[i] = value_buf[i+1];
value_buf[i+1] = temp;
}
}
}
// 3. 去除最大值和最小值(边缘若干个)
for(cnt_edge = 0; cnt_edge < edge; cnt_edge++)
{
sum -= value_buf[cnt_edge];
sum -= value_buf[number - cnt_edge - 1];
}
// 4. 返回中间值的平均值
return (sum / (number - 2 * edge));
}
5.7 中断服务程序
// 10ms定时器中断 - 周期性触发TDC测量
void TIMER_10ms_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_10ms_INST)) {
case DL_TIMER_IIDX_ZERO:
if(TDC_collect.Capflag == 0 && Menu.ing == FlowTest_Interface)
{
TDC_collect.CapCnt++;
if(TDC_collect.CapCnt >= 5) // 50ms触发一次测量
{
TDC_collect.CapCnt = 0;
TDC_collect.Capflag = 1;
TDC.start_flag = 1;
TDC.Echo_flag = 0;
TDC.Sample_state = 0;
}
}
break;
default:
break;
}
}
// TDC启动捕获中断 - 记录超声波发射时刻
void TDC_Start_CAPTURE_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TDC_Start_CAPTURE_INST)) {
case DL_TIMERG_IIDX_CC0_DN:
DL_TimerA_startCounter(TDC_Stop_CAPTURE_INST); // 启动停止计时器
break;
default:
break;
}
}
// TDC停止捕获中断 - 记录超声波接收时刻
void TDC_Stop_CAPTURE_INST_IRQHandler(void)
{
switch (DL_TimerA_getPendingInterrupt(TDC_Stop_CAPTURE_INST)) {
case DL_TIMERA_IIDX_CC3_DN:
// 计算时间差(计时终值 - 捕获值),即为飞行时间
value_cap = TDC_Stop_CAPTURE_INST_LOAD_VALUE -
DL_Timer_getCaptureCompareValue(TDC_Stop_CAPTURE_INST, DL_TIMER_CC_3_INDEX);
TDC.Echo_flag = 1; // 置位回波捕获标志
break;
default:
break;
}
}
// GPIO按键中断 - 处理按键输入
void GROUP1_IRQHandler(void)
{
// 根据不同按键设置不同标志位
switch (DL_GPIO_getPendingInterrupt(GPIO_KEY_S2_PORT)) {
case GPIO_KEY_S2_IIDX:
KeyState.Less = 1;
break;
case GPIO_KEY_SW1_IIDX:
KeyState.Add = 1;
break;
// ... 其他按键处理
}
}
5.8 显示界面流程
// Menu_App.c - 流量显示界面
void FlowTestInterface(uint8_t Close_Or_Open)
{
// 界面元素布局
Draw_ContChar16(88, 2, (uint8_t *)"FlowTest", BarFont, BarInterface, Mod); // 标题
Draw_ContChar16(X, Y, (uint8_t *)"Forward_cnt : ", Interface, Basic, Mod); // 正向时间
Draw_ContChar16(X, Y+20, (uint8_t *)"Reverse_cnt : ", Interface, Basic, Mod); // 反向时间
Draw_ContChar16(X, Y+40, (uint8_t *)"Diff_cnt : ", Interface, Basic, Mod); // 时间差
Draw_ContChar16(X, Y+60, (uint8_t *)"Flow(lpm) :", Interface, Basic, Mod); // 流量显示
}
// 流量数据更新
void FlowUpData(void)
{
// 将测量值转换为ASCII码并显示
DecHEX_To_ASCII(TDC.ForwardAve_cnt, Ascii, 6, 0);
Draw_ContChar16(X+100, Y, Ascii, Para, Basic, Mod); // 显示正向时间
DecHEX_To_ASCII(TDC.ReverseAve_cnt, Ascii, 6, 0);
Draw_ContChar16(X+100, Y+20, Ascii, Para, Basic, Mod); // 显示反向时间
DecHEX_To_ASCII(TDC.diffAve_cnt, Ascii, 6, 0);
Draw_ContChar16(X+100, Y+40, Ascii, Para, Basic, Mod); // 显示时间差
DecHEX_To_ASCII(TDC.FlowData*100, Ascii, 6, 2);
Draw_ContChar48(X+28, Y+80, Ascii, Para, Basic, Mod); // 显示流量(大字符)
}
6. 硬件功能展示
显示功能:
- 欢迎界面(Welcome Interface):系统启动时显示,展示开发平台信息
- 流量测试界面(FlowTest Interface):实时显示以下参数
- Forward_cnt:顺流方向传播时间计数值
- Reverse_cnt:逆流方向传播时间计数值
- Diff_cnt:时间差计数值
- Flow(lpm):计算得到的体积流量(单位:升/分钟)
7. 调试中遇到的难题与解决方法
超声波回波信号捕获困难
问题描述:
超声波信号在气体介质中传播时衰减较大,尤其在管道距离较长或气体成分复杂的情况下,回波信号幅度很小,容易被噪声淹没,导致TDC1000无法正确检测到回波时刻。
原因分析:
- 超声波在气体中的传播衰减与频率成正比,频率越高衰减越大
- 管道内壁反射、湍流等因素产生杂散干扰
- TDC1000比较器阈值设置不当
解决方法:
- 调整探头中心频率:在满足测量精度要求的前提下,选择较低频率(2MHz)的探头,减小传播衰减
- 优化TDC1000寄存器配置:调整REG1(接收器设置)和REG3(阈值设置)参数,提高接收灵敏度
- 增加发射脉冲数量:在REG4中增加发射脉冲个数,增强回波信号强度
- 信号处理:在硬件和软件层面增加滤波电路和滤波算法,提高信噪比
8. 心得体会与建议
本次项目从需求分析、方案设计、硬件选型、软件编写到调试优化,完成了工业超声波气体流量检测系统的完整开发流程。通过这一过程,对嵌入式系统的开发方法论有了更深入的理解,也积累了宝贵的实战经验。
超声波流量检测是一个具有广泛应用前景的技术方向。本系统虽然实现了基本功能,但距离真正满足工业现场严格要求还有一定距离。期望在后续工作中能够不断优化,使系统达到更高的测量精度和可靠性。