☰
TDLAS气体检测原理到STM32实战:甲烷浓度测量与二次谐波解调
2026/10/2 1:34:27 网站建设 项目流程

做了一年多TDLAS气体检测后,我最大的感受是:TDLAS这个词听起来很高端,可一旦把原理拆开,它本质上就是“激光穿过气体、光强被吸收了一点点、再想办法把这一点点测出来”。这篇文章就把从TDLAS原理到STM32实战的完整路径写出来,包括器件选型、硬件设计、调制解调代码和踩坑记录,给正在做毕业设计或者想把手头STM32项目往气体检测方向升级的朋友,提供一份可以直接抄的参考。

我手头这套方案用的是STM32F407VET6,激光器是1653nm的DFB蝶形管,配合InGaAs光电探测器,最终做到了甲烷气体的浓度显示和超限告警。整套代码是在Keil5 + STM32CubeMX HAL库环境下跑的。下面所有代码和电路描述都是我自己验证过的,但不同厂家的激光器和探测器参数有差异,抄作业的时候一定要按自己手上的器件重新标定。

1. 为什么选TDLAS做可燃气体检测,而不是传统催化燃烧/半导体

1.1 三种主流方案的比较

传统可燃气体检测,最常见的是催化燃烧式和半导体式。催化燃烧式本质上是让可燃气体在敏感元件表面燃烧,通过温度变化推算浓度。这种方案很成熟,但有两个让人头疼的问题:一是元件容易被硫、硅等物质毒化,寿命短;二是它检测的是“能烧的东西”,对甲烷、乙醇、氢气这些气体基本无法区分。

半导体式走的是表面吸附路线,气敏电阻遇到还原性气体后阻值变化。优点是便宜、灵敏,但选择性更差,环境湿度稍微一变,零点就飘得让人怀疑人生。

TDLAS的思路完全不同。它利用气体分子只在特定波长上有吸收峰这一点,直接让激光波长对准甲烷所在的吸收线。甲烷在1653.7nm附近有一条强吸收线,二氧化碳和水蒸气在这个区域干扰很小。所以TDLAS最大的好处是选择性好、寿命长、不需要频繁标定,响应速度也快。

对比项催化燃烧式半导体式TDLAS
检测原理燃烧放热表面电阻变化分子吸收光谱
选择性差差好
寿命2~3年,易中毒中等,易受湿度干扰激光器寿命长
成本低最低高,但逐渐下降
响应速度秒级秒级毫秒级
DIY难度低低中高

如果你只是想做个烟雾报警器之类的玩一玩,半导体式足够。但如果想认真做一个能检测ppm级甲烷的仪表,TDLAS是绕不开的方向。

1.2 TDLAS的基本原理和关键公式

TDLAS的理论基础是Beer-Lambert定律。激光穿过待测气体后,透射光强I和入射光强I0的关系可以写成:

I = I0 * exp(-σ(λ) * N * L)

这里的σ(λ)是吸收截面,N是气体分子数密度,L是光程。浓度越高,光程越长,吸收越明显。对于低浓度气体,吸收很小,可以近似成:

ΔI = I0 - I ≈ I0 * σ(λ) * N * L

也就是说,吸收光强和浓度近似成正比。这就是TDLAS能够定量测量的基础。

但实际做的时候没那么简单。DFB激光器的波长不是固定的,它主要靠驱动电流和管芯温度来调谐。电流调谐系数一般在0.01到0.03nm/mA,温度调谐系数大约0.1nm/K。所以我用DAC输出一个慢速三角波叠加高频正弦波,让激光器的波长在1653.7nm附近来回扫描。对应到采样信号上,吸收峰会变成周期性出现的凹陷。

为什么要用二次谐波检测?因为直接看“凹陷”很容易被激光器本身的功率波动和光路抖动干扰。我给激光器加一个高频正弦调制,当吸收峰被调制时,探测器信号里会出现调制频率的二倍频分量。这个2f分量的峰值对浓度非常敏感,而且能滤掉很多低频噪声。这就是所谓的WMS-2f方案,TDLAS传感器里最常用的信号处理方式。

打个比方,就像收音机调台:慢速旋转频率找台,同时手指轻微抖动,听信号强弱变化来判断是否对准了电台。二次谐波就是这个“信号强弱变化”被量化后的结果。

2. 系统总体设计与器件选型

2.1 光路结构:透射式还是反射式

自己做TDLAS,首先要决定光路。最常见的有两种:

透射式最简单,激光器出光后直接穿透气体,打到对面的探测器上。优点是光路好调,适合第一版验证原理。缺点是在同样浓度下,光程短,吸收信号弱。

反射式是通过反射镜让激光多次穿过气体,等效光程可以做到几十米甚至上百米,灵敏度大幅提升。但镜片对准非常痛苦,我调试Herriott池的时候,花了一整天才勉强把光斑对上探测器。

我的建议是:第一版老老实实做透射式,光程做到20cm到50cm,先把信号链跑通,再考虑反射池。

光路类型优点缺点适用场景
透射式结构简单,易对准光程短,灵敏度有限DIY验证、教学演示
反射式光程长,灵敏度高光路调试困难高端仪表、环境监测
开放光路无需气室,可大范围检测长光程下受环境湍流影响天然气泄漏监测

2.2 核心器件清单和选型逻辑

我做这套系统用的器件清单如下:

  • DFB蝶形激光器,中心波长1653.7nm,带TEC和热敏电阻,输出功率大概5mW。
  • InGaAs光电探测器,工作波长900nm到1700nm,光敏面1mm。
  • 跨阻放大运放,我用的是AD8628和OPA2376,偏置电流小,噪声低。
  • STM32F407VET6,带2路12位DAC,内置ADC和多个定时器。
  • 激光器恒流驱动电路,由运放、MOS管和精密采样电阻组成。
  • TEC温控回路,负责把激光器管芯温度稳定在25℃。

选器件的时候有几个坑要提前避开。激光器一定选带TEC的蝶形封装,别图便宜买同轴封装,否则波长随温度漂到你怀疑人生。光电探测器要选InGaAs材料的,普通硅光电二极管对1653nm基本没有响应。

运放的选择也很关键。跨阻放大器的输入端是光电二极管的微弱电流,运放输入偏置电流比这个电流还大,那信号就完全被淹没在运放自身噪声里了。所以优先选低偏置电流、低噪声的运放。

2.3 为什么MCU选STM32F407而不是专用硬件

TDLAS检测设备在工业产品里经常用FPGA或DSP做数字锁相放大,成本高,开发周期也长。做DIY和毕业设计,STM32F407完全够用。

F407的主频是168MHz,带2路DAC、多路ADC、DMA控制器,还有一堆定时器,可以非常方便地把DAC波形输出和ADC采集同步起来。更重要的是HAL库资料多,出问题容易查。F103也能跑基础扫描,但它只有1路DAC,做双路波形输出会有点吃力,所以我最终选了F407。

3. 硬件电路与STM32接口设计

3.1 激光驱动电路:DAC怎么控制激光电流

DFB激光器的工作电流一般在20mA到80mA之间,STM32的DAC输出能力远不够直接驱动,所以中间必须加一级压控恒流源。基本结构是:DAC输出电压经过电压跟随器,送到恒流源的参考端,由运放控制MOS管调节流过激光器的电流。

电路的核心关系很简单:

I_LD = V_CTRL / R_sense

R_sense是源极采样电阻。我用的R_sense是33Ω,DAC输出限幅在2.6V以内,对应电流约78mA,低于激光器80mA的绝对最大值。代码里生成波形时,会把归一化数值限制在0.17到0.98之间,就是为了防止DAC电压超限。

TEC温控是另一条独立链路。激光器模块内的热敏电阻接STM32 ADC,TEC通过一个H桥用PWM驱动,PID输出控制PWM占空比。TEC控温如果省略,短时间测试还能凑合,一旦环境温度变化,激光器波长就会偏移,吸收峰会直接消失。

3.2 光电检测与放大链路

光电探测器接收透过气体的激光后,产生微弱光电流,一般只有微安到纳安级别。这个电流需要经过跨阻放大器转换成电压,再进行放大和滤波,最后进STM32 ADC。

跨阻放大器的反馈电阻我用了1MΩ,输出灵敏度可以到1V/mA级别。反馈电容必须加,一般几皮法到几十皮法,否则高频段容易自激振荡。后面再接一级10倍放大和低通滤波,把带宽限制在2MHz以内,防止高频噪声混叠。

PD放大电路是整个系统里最容易出问题的地方。我踩过的坑是排线太长,导致输出波形叠了一堆50Hz工频干扰。后来把PD板子直接贴在主板上,用屏蔽线走模拟信号,噪声才压下去。

3.3 STM32引脚分配与GPIO配置

我用的引脚分配如下:

功能引脚说明
DAC输出PA4输出扫描+调制波形,控制激光驱动
ADC输入PB0采集PD放大后的信号
TIM6内部触发DAC与ADC同步
USART2 TXPA2向上位机发送浓度数据
USART2 RXPA3接收指令
告警输出PC13驱动蜂鸣器
TEC PWMPA8控制TEC驱动器

GPIO配置时有个容易被忽略的细节:DAC和ADC引脚要配成模拟模式,别开上下拉,否则外部电阻会影响采样精度。如果用CubeMX生成,直接在Pinout里把PA4和PB0拖成DAC和ADC即可。

4. 核心代码:从HAL库到扫描与解调

4.1 生成激光扫描与调制波形

代码的第一步是生成一张wavetable,里面同时含有慢速三角扫描和高速正弦调制。我用的数组长度是2048,DAC更新频率是100kHz,所以一张表循环完是20.48ms,约49Hz的扫描频率。正弦调制周期在表里循环了32次,也就是说调制频率约为1.6kHz。

#define SCAN_POINTS 2048 #define SIN_CYCLES 32 uint16_t wavetable[SCAN_POINTS]; uint16_t adc_buf[SCAN_POINTS]; volatile uint8_t adc_done = 0; void WaveTable_Init(void) { for (int i = 0; i < SCAN_POINTS; i++) { float phase = (float)i / SCAN_POINTS; float tri = 1.0f - 2.0f * fabsf(2.0f * phase - 1.0f); float mod = 0.08f * sinf(2.0f * M_PI * SIN_CYCLES * phase); float value = 0.25f + 0.65f * tri + mod; if (value > 1.0f) value = 1.0f; if (value < 0.0f) value = 0.0f; wavetable[i] = (uint16_t)(value * 4095.0f); } }

生成这张表的时候,要把DAC输出范围留出余量。0.25f + 0.65f * tri + mod的含义是:在0.25到0.9的基线的基础上,叠加快扫描三角波,再叠加上0.08幅度的正弦调制。这样既保证激光器不会过流,又让波长扫过吸收峰。

在CubeMX里,TIM6配置成定时器触发,设置好分频和自动重装值,让更新频率为100kHz。比如定时器时钟84MHz,PSC=4,ARR=167,更新频率就是84MHz / 5 / 168 = 100kHz。然后把DAC和ADC的外部触发源都选成TIM6的TRGO事件,这样DAC搬运下一个波形点和ADC采集当前点会严格同步。

4.2 ADC采集与二次谐波解调

启动DAC的DMA输出,同时启动ADC的DMA采集:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DAC_Init(); MX_TIM6_Init(); MX_ADC1_Init(); MX_USART2_UART_Init(); WaveTable_Init(); HAL_TIM_Base_Start(&htim6); HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)wavetable, SCAN_POINTS, DAC_ALIGN_12B_R); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, SCAN_POINTS); while (1) { if (adc_done) { adc_done = 0; float v2f = 0.0f; LockInDetect(adc_buf, SCAN_POINTS, &v2f); float conc = Concentration(v2f); SendResult(conc, v2f); SetAlarm(conc); } } }

ADC采集完成后进入回调函数,把标志位置1:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { adc_done = 1; } }

二次谐波解调函数的核心是正交锁相放大。按照2倍调制频率提取参考信号,分别与ADC数据做相关运算,然后求幅度:

void LockInDetect(uint16_t *buf, uint16_t len, float *out2f) { float X = 0.0f, Y = 0.0f, mean = 0.0f; for (int i = 0; i < len; i++) mean += buf[i]; mean /= len; for (int i = 0; i < len; i++) { float p = (float)i / len; float angle = 2.0f * M_PI * 2.0f * SIN_CYCLES * p; float val = (float)buf[i] - mean; X += val * cosf(angle); Y += val * sinf(angle); } X = 2.0f * X / len; Y = 2.0f * Y / len; *out2f = sqrtf(X * X + Y * Y); }

为什么要在2f参考分量上做相关?因为信号里真正由吸收峰产生的谐波成分是调制频率的两倍,而激光器本身的光强变化主要在一倍频和低频段。乘上2f参考再积分之后,低频噪声会被平均掉很多。这就是锁相放大的威力。

这段代码在F407上跑没问题,但cosf和sinf在中断或高频循环里反复调用会占用时间。实际工程里我会提前算好一张cos和sin的查找表,把三角函数换成查表,速度能快好几倍。

4.3 浓度标定和温压补偿

解调出来的2f信号,和气体浓度之间并不是严格线性关系,但在低浓度段可以近似线性。强烈建议用标准气做多点标定,而不是直接套理论公式。

我做的标定数据大概是这样的:

甲烷浓度(vol%)2f信号幅度
00.012
0.50.087
1.00.158
2.00.305
5.00.710

用线性拟合后,浓度换算函数代码如下:

float Concentration(float v2f) { float conc_percent = 0.0068f * v2f - 0.0012f; if (conc_percent < 0.0f) conc_percent = 0.0f; return conc_percent; }

这套系数只针对我手上这套光路。换了激光器、探测器或者光程,必须重新标定。对精度要求高的场合,还要做温度和压力修正。气体吸收的谱线强度随温度变化,分子密度随压力和温度变化,所以通用补偿公式可以简化成:

conc_true = conc_raw * (P_ref / P) * ((T + 273.15) / (T_ref + 273.15))

这里P_ref和T_ref是标定时的基准压力和温度。如果只是做演示,这一步可以省略,但它直接影响户外环境下的长期稳定性。

4.4 数据输出与告警逻辑

最后把浓度和2f幅度通过串口发出去,方便上位机观察:

void SendResult(float conc, float v2f) { char msg[64]; int n = snprintf(msg, sizeof(msg), "CH4:%.2f%% V2f:%.4f\r\n", conc, v2f); HAL_UART_Transmit(&huart2, (uint8_t *)msg, n, 100); }

告警逻辑很简单,浓度超过1%就拉低蜂鸣器引脚:

void SetAlarm(float conc) { if (conc >= 1.0f) HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); else HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); }

这里注意PC13在大多数开发板上默认状态是高电平驱动LED,要想直接驱动继电器或蜂鸣器,建议加一级三极管或光耦,不要用引脚直接带大负载。

5. 常见问题与排查技巧实录

5.1 吸收峰找不到怎么办

这个问题我遇到太多次了。新手最常犯的错是直接按激光器手册上的中心波长去设电流,结果发现PD信号毫无变化。

首先判断DAC输出的扫描范围是否覆盖了吸收峰。1653nm附近的电流调谐范围可以先用2%甲烷标准气,慢慢手动增加DAC电压,同时看PD信号的谷底。如果没有标准气,可以用保鲜袋套住短光程气室,通入从打火机气罐收集的丁烷?不对,这里要测甲烷,所以最好还是买一瓶高纯甲烷或标准气。

如果曲线有凹陷但很浅,可能是激光器波长和吸收峰位置没对准,也可能是温控没稳住。先检查TEC温度是不是稳定在25℃,然后用波长计确认出光波长。

5.2 2f信号噪声大、底噪飘

2f信号最常见的问题不是信号没出来,而是底噪太高。我遇到过一个案例,2f波形像毛刺山一样,最后发现是开关电源的地线噪声串进了PD放大电路。

排查顺序是这样的:先用示波器看PD输出,正常应该是一条干净的扫描基线;如果上面有高频毛刺,优先怀疑电源和地线。STM32的ADC输入引脚不要和功率地混在一起,模拟地单独走线,最后单点接地。

其次检查DAC和ADC是否真的同步。如果不同步,锁相解调的参考相位会漂,2f信号会忽大忽小。最简单的验证方法是用一个已知峰值的模拟信号代替PD信号,看解调结果是否稳定。

5.3 温控振荡导致波长乱飘

TEC温控PID参数没调好时,输出会出现等幅振荡,激光器波长也跟着来回跳。表现是2f信号一会强一会弱,甚至完全无法锁定。

解决办法是先把PID参数调保守一点,让温控曲线只出现小的过冲,再用标准气验证吸收峰位置是否稳定。TEC的H桥驱动频率建议在20kHz以上,避免低频时电流噪声耦合进激光器供电。

5.4 串口乱码和工程编译问题

串口输出乱码,九成是系统时钟和CubeMX配置不一致。F407外部晶振是8MHz还是25MHz,必须在CubeMX里选对,否则波特率全部错位。Keil5打开工程前,记得先在Pack Installer里装好STM32F4系列的芯片包,不然Device列表里找不到F407。

故障现象可能原因排查方法
无吸收峰波长没对准扫全量程找谷底
2f噪声大地线串扰、光路抖动示波器查PD输出
信号漂移TEC温控振荡调PID,确认温度反馈
串口乱码时钟配置错误检查外部晶振频率
Dataloss/ADC缓冲错位DMA配置不对查DMA buffer和传输方向

6. 调试心得与后续扩展

6.1 先标定,再谈算法

我一开始犯的错是上来就调算法,不停地改锁相放大的解调方式,结果2f信号还是不对。后来发现根本不是算法问题,是激光器电流和波长没有标定,吸收峰压根没被扫到。

所以我想强调一句:TDLAS项目的坑,百分之八十在光学链路和电流标定上,只有百分之二十在代码里。先把激光驱动、温控、PD放大这条模拟链路做稳,再写解调算法就顺理成章了。

6.2 后面还能怎么升级

这套基础版做完后,想继续深入可以从几个方向扩展。

一是换成双激光器方案,同时测甲烷和水的吸收线,用水蒸气谱线做辅助标定,抗干扰能力会强很多。二是把透射式光路升级成多次反射吸收池,光程从20cm变成几十米,检测下限能大幅降低。三是在F407上加入Modbus RTU或RS485输出,方便接PLC和上位机。如果性能还不够,再把锁相放大移植到FPGA上,但那已经是更复杂的项目了。

另外提一句安全方面的经验:激光器出光口调试时戴好护目镜,不要直视激光;测试可燃气体时务必在通风良好的环境下进行,浓度不要超过仪表的量程上限。无论你是做毕业设计还是准备量产,安全习惯都要从第一版就养成。

最后再分享一个小技巧:调试TDLAS时别急着上高深的谐波算法,先用最简单的DAC三角波扫描,把PD信号送到示波器上,亲眼看到甲烷吸收峰那个“凹陷”,再把调制和解调加进去。这个顺序能帮你省下一大半排查时间。

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

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

立即咨询