基于STM32 DSP库的2FSK信号调制解调实战:从FIR滤波到包络检波

张开发
2026/4/8 10:04:30 15 分钟阅读

分享文章

基于STM32 DSP库的2FSK信号调制解调实战:从FIR滤波到包络检波
1. 2FSK通信基础与STM32实现优势2FSK二进制频移键控是物联网设备中最常用的调制方式之一它的核心思想非常简单用两个不同频率的正弦波分别表示数字0和1。比如我们可以用500Hz表示01000Hz表示1。这种调制方式在无线门磁、智能水表等低功耗场景特别常见我去年做的农业传感器项目就采用了这种方案。相比其他调制方式2FSK有三个明显优势抗干扰强频率变化对幅度衰减不敏感特别适合存在噪声的无线环境实现简单不需要复杂的相位同步电路用普通MCU就能实现功耗低适合电池供电的IoT设备STM32的DSP库为我们提供了现成的FIR滤波器函数arm_fir_f32配合DAC和ADC外设完全可以在单芯片上实现完整的2FSK收发系统。这里有个实际对比数据使用专用调制芯片的方案BOM成本要$1.2而STM32方案仅需$0.5这对量产设备来说非常关键。2. 硬件配置与信号生成2.1 时钟与定时器配置先说说我是怎么踩坑的第一次调试时发现输出的频率总是不对后来发现是时钟树配置有问题。STM32F4系列默认使用内部16MHz晶振需要通过PLL倍频到168MHz。这里有个关键点TIM2的时钟源要选择APB1总线其最大频率是84MHz。具体配置步骤在CubeMX中启用TIM2选择内部时钟源配置PSC为1-1即不分频ARR初始值设为700-1这个值后面会动态修改开启TIM2的更新中断提示一定要勾选auto-reload preload否则修改ARR值时会导致定时器卡死这个问题我调试了整整一个下午才发现。2.2 DAC信号生成DAC配合DMA可以自动输出正弦波不需要CPU干预。这里我预先生成了一个120点的正弦波表uint16_t sinx[120]; for(int i0; i120; i){ sinx[i] (sin(i*2*3.1415926/120)1.2)*1700; }这个公式的1.2偏移量是为了让波形始终在DAC的有效输出范围内0-3.3V。实际测试中发现如果不加这个偏移负半周会被截断导致波形失真。启动DAC输出的代码很有讲究HAL_DAC_Start_DMA(hdac, DAC_CHANNEL_2, (uint32_t*)sinx, 120, DAC_ALIGN_12B_R);注意最后一个参数一定要用DAC_ALIGN_12B_R表示12位右对齐这个细节手册上说得不太清楚。3. 动态频率调制实现3.1 数据帧结构设计我采用了经典的包头数据包尾结构uint8_t head[4] {1,0,1,0}; // 同步头 uint8_t tail[4] {1,0,1,0}; // 结束标志 uint8_t send[12]; // 完整帧每个比特持续6ms这样设计有两个考虑保证足够的频偏500Hz和1000Hz差异明显适应STM32的处理能力在TIM7的中断回调函数中切换频率void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ if(htim htim7){ signal send[num1]; if(num1 12) num1 0; } if(htim htim2){ TIM2-ARR (signal 1) ? 700-1 : 1400-1; } }这里有个重要技巧修改ARR值要在定时器中断里操作如果在主循环中修改会导致频率切换不同步。4. 信号采集与FIR滤波4.1 ADC配置要点ADC采样率设置为10kHz这个值需要满足奈奎斯特采样定理大于最高频率2倍。TIM3的配置参数PSC 84-1ARR 100-1 计算公式84MHz/(84*100)10kHz启动ADC采集要用DMA模式HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buff, 2048);我选择2048点是因为覆盖完整数据帧12bit×6ms72ms10kHz采样需要720点满足DSP库对2^N长度的要求留出处理余量4.2 FIR滤波器设计用MATLAB的fdatool设计了一个46阶低通滤波器截止频率设在750Hz。这里分享我的设计参数Fs 10kHzFpass 500HzFstop 1kHzAstop 50dB导出系数后初始化滤波器arm_fir_instance_f32 *S; S (arm_fir_instance_f32*)malloc(sizeof(arm_fir_instance_f32)); arm_fir_init_f32(S, BL, TB, pState, blockSize);滤波处理时要注意数据类型转换for(uint16_t i0; i2048; i){ fir_inputbuf[i] (float)adc_buff[i]*3.3/4096; } arm_fir_f32(S, fir_inputbuf, fir_outputbuf, blockSize);实测发现这个滤波器能有效滤除1000Hz成分保留500Hz信号将2FSK转为2ASK信号。5. 包络检波与数据恢复5.1 信号整形处理滤波后的信号还需要两步处理反转负半周if(fir_outputbuf[i]1.6) fir_outputbuf[i]3.2-fir_outputbuf[i];消除直流分量fir_outputbuf[i] - 1.6;这一步相当于硬件电路中的全波整流我在示波器上观察过处理后的信号已经接近理想的包络波形。5.2 模拟包络检波用软件模拟RC检波电路的效果#define V_MAX 21 // 对应500Hz信号的20个采样点 for(uint16_t i0; i2048; i){ if(fir_outputbuf[i] 0.8){ v V_MAX; // 充电 }else{ if(v0) v--; // 放电 } fir_outputbuf[i] (v V_MAX/4) ? 1 : 0; }这个算法巧妙地用数字方式模拟了电容的充放电过程。调试时发现V_MAX/4这个阈值最稳定能有效避免噪声误触发。5.3 比特流解码最后一步是将波形转换为二进制数据for(uint16_t i0; i2048; i){ if(fir_outputbuf[i] 1){ high_time; if(!flag){ low_count low_time / 60 ((low_time%60 30)?1:0); for(int j0; jlow_count; j) recieve[num] 1; low_time 0; } flag 1; }else{ low_time; if(flag){ high_count high_time / 60 ((high_time%60 30)?1:0); for(int j0; jhigh_count; j) recieve[num] 0; high_time 0; } flag 0; } }这里用60个采样点对应6ms的比特持续时间采用四舍五入的方式处理时间计数实测误码率可以控制在1%以下。6. 工程优化与调试技巧6.1 性能优化方案在实际项目中我发现两个优化点降低采样率对于500-1000Hz的信号其实5kHz采样率就足够了能减轻CPU负担使用查表法将sin函数计算结果预先存储减少实时计算量修改后的DAC初始化const uint16_t sinx[60] {2048,2145,...,2048}; // 预计算60点波形 HAL_DAC_Start_DMA(hdac, DAC_CHANNEL_2, (uint32_t*)sinx, 60, DAC_ALIGN_12B_R);6.2 常见问题排查频率不准检查时钟树配置确保TIM2的时钟源正确波形失真确认DAC输出范围避免削顶滤波效果差检查MATLAB导出的滤波器系数是否正确加载数据错位调整包络检波的阈值和时间常数调试时建议先用VOFA这类工具观察各阶段波形我通常按这个顺序检查原始2FSK信号ADC采样后的波形FIR滤波结果包络检波输出7. 扩展应用与进阶改进这个基础框架可以扩展出很多实用功能添加CRC校验在数据帧中加入校验字段提高可靠性多设备组网通过不同频率组合实现简单频分复用自适应速率根据信号质量动态调整传输速率一个实用的CRC8校验实现uint8_t crc8(uint8_t *data, uint8_t len){ uint8_t crc 0xFF; while(len--){ crc ^ *data; for(uint8_t i0; i8; i) crc (crc 0x80) ? (crc1)^0x31 : (crc1); } return crc; }在最近的一个仓库环境监测项目中我基于这个框架实现了10米距离的可靠数据传输整套系统功耗仅1.8mA用纽扣电池就能工作半年以上。

更多文章