简介:这份资源是2024年全国大学生电子设计竞赛G题「简易超声波录音屏蔽系统」的完整参赛作品,面向电子信息、通信工程、自动化、计算机等专业的在校学生与指导教师,也适合作为课程设计、毕业设计或项目立项的参考范例。压缩包共880个文件,约13.24MB,以C语言源码与头文件为主体,辅以汇编启动文件、STM32CubeMX工程配置、Keil工程文件、器件手册与原理图截图等,覆盖从底层驱动到系统集成的完整链路。目前已有3001人学习下载,说明该方案在电赛备赛群体中具有较高参考价值。读者可获取经过实测运行的完整工程,理解超声波录音屏蔽的硬件选型、信号调制与软件控制思路,并借助工程配置与目录结构快速复现实验环境;代码结构清晰,便于在此基础上修改扩展,用于二次开发或功能验证。资源仅供学习参考,请勿用于商业用途。
1. 电赛G题简易超声波录音屏蔽系统:从赛题到能跑通的完整路径
2024年电赛G题要求做一个简易超声波录音屏蔽系统,核心目标是用超声波干扰录音设备的拾音,让手机、录音笔在有效范围内录不到清晰人声。这个题在赛场上翻车的队伍不少,问题基本集中在换能器选型、驱动功率和扫频策略三件事上。它适合正在准备电赛控制类或信号类题目的同学,也适合想搞明白超声波换能器怎么驱动、怎么调参数的硬件爱好者。整篇内容围绕方案选型、源代码结构、配置方法和实测踩坑展开,读完你能自己搭出一套能用的系统,而不是只停留在仿真波形上。
这个题的本质不是“发出超声波”那么简单。录音设备的MEMS麦克风或驻极体麦克风有AGC自动增益和抗混叠滤波,单纯发一个固定频率的超声,很多手机会直接把它滤掉。真正有效的做法是让超声信号携带噪声调制,落在麦克风非线性区产生互调失真,从而在可听频段内生成干扰。所以方案里必须同时解决三件事:换能器在40kHz附近的谐振匹配、驱动级的功率和散热、以及调制信号的生成方式。下面按实际调试顺序拆开讲。
2. 超声波换能器与驱动电路:选型逻辑和最小验证电路
2.1 为什么选40kHz开放型换能器而不是密封型
市面上常见的超声波换能器分开放型和密封型两类。开放型(比如TCT40-16T/R系列)背面有开孔,谐振频率典型值40kHz,带宽约2kHz,适合空气耦合发射。密封型防水但Q值更高、带宽更窄,做扫频屏蔽时反而不好用,因为频率稍微偏一点效率就掉得厉害。我一般选开放型发射头,接收头用同型号配对,方便做回波验证。
换能器等效电路可以看成一个RLC串联谐振网络:静态电容C0并联在谐振支路上,谐振支路由动态电感L1、动态电容C1和动态电阻R1组成。在40kHz谐振点,阻抗最低,电流最大。如果你直接用单片机IO推,驱动能力不够,波形会被C0吃掉。所以必须加驱动级。
| 参数 | 典型值 | 说明 |
|---|---|---|
| 谐振频率 | 40kHz ±1kHz | 决定载波频率 |
| 静态电容C0 | 2000pF左右 | 影响驱动电流 |
| 动态电阻R1 | 200Ω左右 | 决定匹配阻抗 |
| 最大驱动电压 | 20Vpp | 超过容易损坏 |
| 指向角 | 60度左右 | 影响覆盖范围 |
2.2 用IR2110半桥驱动换能器的最小电路
驱动级常见做法是半桥或全桥。半桥够用,全桥功率更大但发热也明显。下面是一个用IR2110驱动半桥的最小配置,单片机输出两路互补PWM,死区时间设500ns左右。
// STM32 TIM1 互补PWM初始化,40kHz载波 // 系统时钟72MHz,预分频PSC=0,自动重载ARR=1800-1 // 40kHz = 72MHz / (1800 * 1) 实际约40.0kHz void PWM_Init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; TIM_BDTRInitTypeDef bdtr; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); // PA8 和 PA9 作为互补输出 gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); tim.TIM_Prescaler = 0; tim.TIM_Period = 1800 - 1; // 40kHz tim.TIM_ClockDivision = TIM_CKD_DIV1; tim.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &tim); oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_OutputNState = TIM_OutputNState_Enable; oc.TIM_Pulse = 900; // 50%占空比 oc.TIM_OCPolarity = TIM_OCPolarity_High; oc.TIM_OCNPolarity = TIM_OCNPolarity_High; oc.TIM_OCIdleState = TIM_OCIdleState_Reset; oc.TIM_OCNIdleState = TIM_OCNIdleState_Reset; TIM_OC1Init(TIM1, &oc); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 死区时间:72MHz下约500ns TIM_BDTRConfig(TIM1, &bdtr); bdtr.TIM_DeadTime = 36; bdtr.TIM_Break = TIM_Break_Disable; bdtr.TIM_LockLevel = TIM_LockLevel_OFF; TIM_BDTRConfig(TIM1, &bdtr); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }这段代码的关键参数是ARR和死区。ARR=1800对应40kHz,如果你换能器实测谐振在38.5kHz,就把ARR改成1869左右。死区太小会导致半桥直通发热,太大会让输出波形失真,500ns是折中值。IR2110的自举电容用1μF陶瓷加10μF电解并联,自举二极管选UF4007,普通1N4148恢复太慢会发热。
2.3 用示波器确认换能器两端波形
焊好驱动板后,先不接换能器,用示波器看半桥输出应该是干净方波。接上换能器后,波形会变成近似正弦,峰峰值在15V到20V之间。如果峰峰值只有几伏,说明匹配没做好,常见原因是串联匹配电感没加。在换能器一端串一个1mH到2.2mH的电感,再并联一个可调电容,调到波形最干净、电流最小即可。这一步没有捷径,必须实测。
3. 噪声调制与扫频策略:让录音设备真正录不到人声
3.1 为什么固定频率超声对手机录音无效
很多队伍一开始只发40kHz正弦波,结果手机录出来只有轻微底噪,人声依然清楚。原因是手机麦克风前端有低通滤波,40kHz直接被滤掉。但麦克风振膜和前置放大存在非线性,当超声强度足够大时,会产生互调。如果你只发单频,互调产物也是单频,落在可听区就是一个固定哨音,反而容易被后期滤掉。有效做法是用白噪声或伪随机序列对40kHz载波做幅度调制,让互调产物铺满可听频段,形成类似宽带噪声的掩蔽效果。
3.2 用STM32定时器生成噪声调制PWM
下面这段代码用TIM3产生调制信号,通过查表方式输出伪随机噪声,再和TIM1的40kHz载波做与运算。实际实现时可以用TIM1的刹车输入或者外部触发来门控,但更简单的做法是直接改TIM1的CCR值做幅度调制。
// 伪随机噪声调制,更新TIM1占空比 // 调制速率约20kHz,噪声表长度256 const uint16_t noise_table[256] = { /* 预生成的伪随机数,范围0-900 */ }; void TIM3_IRQHandler(void) { static uint8_t idx = 0; if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 更新TIM1占空比,实现幅度调制 TIM1->CCR1 = noise_table[idx]; idx++; } } void TIM3_Init(void) { TIM_TimeBaseInitTypeDef tim; NVIC_InitTypeDef nvic; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); tim.TIM_Prescaler = 0; tim.TIM_Period = 3600 - 1; // 72MHz/3600 = 20kHz调制率 tim.TIM_ClockDivision = TIM_CKD_DIV1; tim.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &tim); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); TIM_Cmd(TIM3, ENABLE); nvic.NVIC_IRQChannel = TIM3_IRQn; nvic.NVIC_IRQChannelPreemptionPriority = 1; nvic.NVIC_IRQChannelSubPriority = 1; nvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic); }调制率选20kHz是因为要覆盖人声主要频段(300Hz到3.4kHz)的互调产物。噪声表的值范围要和ARR匹配,最大不超过900,否则占空比超过50%会导致半桥上下管同时导通风险。如果你发现干扰效果不够,可以把调制率降到10kHz,让互调分量更集中在中低频,但别低于5kHz,否则超声载波本身会被听出哒哒声。
3.3 扫频还是固定频率:实测对比
固定40kHz在近距离(30cm内)效果可以,但超过1米衰减很快。扫频策略是在39kHz到41kHz之间以100Hz步进循环,每个频点停留2ms。这样做的代价是每个频点的有效功率下降,但能覆盖换能器谐振点漂移和不同录音设备的滤波差异。我实测下来,扫频在1.5米处比固定频率多衰减约6dB人声,但电路复杂度不变,只是改一下定时器重载值。建议赛场直接用扫频,别赌固定频率。
4. 源代码结构与配置方法:从工程目录到参数落盘
4.1 工程目录怎么分才不乱
一套能复现的工程,目录结构比代码本身还重要。我一般这样分:
project/ ├── Core/ │ ├── Inc/ │ │ ├── pwm.h │ │ ├── noise.h │ │ └── config.h │ └── Src/ │ ├── main.c │ ├── pwm.c │ ├── noise.c │ └── stm32f1xx_it.c ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ ├── MDK-ARM/ │ └── project.uvprojx └── config.iniconfig.h里放所有可调参数,别把魔数散落在各个.c文件里。下面是一个配置头文件的例子。
// config.h 所有可调参数集中管理 #ifndef __CONFIG_H #define __CONFIG_H #define CARRIER_FREQ_HZ 40000 // 载波频率 #define SWEEP_START_HZ 39000 // 扫频起点 #define SWEEP_END_HZ 41000 // 扫频终点 #define SWEEP_STEP_HZ 100 // 扫频步进 #define SWEEP_DWELL_MS 2 // 每频点停留时间 #define MODU_RATE_HZ 20000 // 调制率 #define DEAD_TIME_NS 500 // 死区时间 #define PWM_DUTY_MAX 900 // 最大占空比对应CCR #endif这样改参数只需要动一个文件,重新编译即可。赛场上时间紧,别把参数写死在中断里。
4.2 主循环里做什么
主循环不干重活,只做状态机和按键扫描。扫频更新放在定时器中断里,每2ms换一个频点。主循环负责检测按键,切换“固定频率/扫频/关闭”三种模式。下面是一个简化状态机。
typedef enum { MODE_OFF, MODE_FIXED, MODE_SWEEP } sys_mode_t; volatile sys_mode_t g_mode = MODE_SWEEP; volatile uint32_t g_current_freq = CARRIER_FREQ_HZ; void Sweep_Update(void) { static uint32_t freq = SWEEP_START_HZ; freq += SWEEP_STEP_HZ; if (freq > SWEEP_END_HZ) freq = SWEEP_START_HZ; g_current_freq = freq; // 根据g_current_freq重算ARR并写入TIM1 TIM1->ARR = (72000000 / freq) - 1; TIM1->CCR1 = (TIM1->ARR + 1) / 2; // 50%占空比 }注意ARR重载后CCR也要跟着变,否则占空比会漂。这段代码放在TIM3中断里每2ms调一次,实际扫频周期是(41000-39000)/100 * 2ms = 40ms,人耳听不出扫频声,但录音设备会持续受到不同频点的互调干扰。
4.3 配置方法:上电前必须确认的三件事
第一,用万用表测半桥输出对地电阻,确认没有短路。第二,示波器看PWM波形,确认死区在300ns到800ns之间。第三,先接假负载(比如100Ω电阻)跑五分钟,摸驱动芯片温度,烫手就降占空比或加散热片。这三步做完再接换能器,能避免烧管。我见过太多队伍直接上电,IR2110冒烟,换一片又冒烟,最后发现是自举电容极性焊反了。
5. 避坑与排查:赛场和实验室里最容易翻车的五个点
5.1 现象:手机录音依然清晰,波形也正常
原因:换能器指向角太窄,或者功率不够。开放型换能器指向角约60度,但实际有效干扰区域只有正前方30度锥角内。解决:把换能器对准录音设备,距离控制在1米内,或者用多个换能器阵列覆盖更大角度。功率不够就提高驱动电压到18Vpp,但别超过20Vpp。
5.2 现象:驱动芯片发热严重,甚至烧毁
原因:死区时间不够导致半桥直通,或者自举电容容量不足。解决:死区调到500ns以上,自举电容用1μF陶瓷并联10μF电解。如果还热,在IR2110的VCC脚串一个10Ω电阻限流,并加小散热片。
5.3 现象:扫频时听到哒哒声
原因:扫频步进太大或者停留时间太短,导致频率跳变被人耳捕捉。解决:步进降到50Hz,停留时间加到5ms。如果还有,在扫频切换时加一个淡入淡出,用PWM占空比从0渐变到50%再切频。
5.4 现象:换能器两端波形削顶
原因:驱动电压超过换能器最大承受值,或者匹配电感饱和。解决:降低供电电压到12V,换用更大电流的电感(比如额定电流1A的1mH工字电感)。削顶会导致超声谐波增多,反而降低干扰效率。
5.5 现象:不同手机效果差异巨大
原因:各品牌麦克风AGC响应时间和滤波截止频率不同。解决:别只测一台手机。至少准备三台不同品牌设备,把扫频范围扩到38kHz到42kHz,调制率在10kHz到25kHz之间可调。赛场评测时如果只针对一台设备,风险很大。
6. 进阶技巧:用接收换能器做闭环功率校准
最后一章说一个我实际比赛里用过的技巧:加一个接收换能器做闭环。发射换能器旁边放一个同型号接收头,接到STM32的ADC上,实时测回波幅度。因为换能器谐振点会随温度漂移,开环扫频在跑十分钟后效率会下降。闭环做法是每扫一轮,记录每个频点的接收幅度,下一轮优先在幅度高的频点多停留。这样能把有效干扰功率提升3dB到5dB。
// 简易闭环:根据接收幅度调整扫频权重 uint16_t rx_amp[21]; // 39kHz到41kHz,每100Hz一个点 void ADC_Scan(void) { for (int i = 0; i < 21; i++) { Set_Freq(39000 + i * 100); Delay_ms(1); rx_amp[i] = ADC_Read(); } // 找最大幅度频点,下一轮在该频点停留时间加倍 uint16_t max_idx = 0; for (int i = 1; i < 21; i++) if (rx_amp[i] > rx_amp[max_idx]) max_idx = i; g_peak_freq = 39000 + max_idx * 100; }这个闭环不需要复杂算法,只要在扫频序列里给g_peak_freq附近多分配时间就行。实测在连续工作20分钟后,开环方案干扰距离从1.2米降到0.8米,闭环能稳在1.1米以上。代价是增加一个接收头和几行ADC代码,性价比很高。
我自己的习惯是赛前一定用热成像仪看一眼驱动板温度分布,最热的地方往往不是芯片,而是匹配电感。电感选型别抠成本,饱和电流至少留两倍余量。另外,所有参数改完一定重新测一遍波形,别信“应该没问题”。希望帮到你。
本文还有配套的精品资源,点击获取