STM32F103ZET6实现AM信号检波与12864液晶实时参数显示(含并行/串行双驱动) 📅 2026/7/5 9:58:35 本文还有配套的精品资源点击获取简介基于STM32F103ZET6主控完整实现AM调幅信号的接收、二极管检波、低通滤波、音频放大及耳机输出功能配套QC12864B点阵液晶模块支持并行和串行两种接口方式分别对应13脚并口与14脚串口接线方案可实时显示载波频率、调制度、信号幅度等关键参数工程使用标准STM32固件库结构包含CORE、SYSTEM、delay、usart、sys等规范模块集成startup_stm32f10x_hd.s启动文件、system_stm32f10x.c系统时钟配置、stm32f10x_it.c中断管理及main.c主流程逻辑提供J-Link调试支持含JLinkSettings.ini、keilkilll.bat一键清理脚本、多用户工程配置文件.uvprojx/.uvoptx/.uvguix.*以及README.md、ss.md操作指南和两份QC12864B硬件手册QC12864B.pdf、育松电子 QC12864B使用说明.pdf适用于高校电子技术课程设计、全国大学生电子设计竞赛F题复现训练及嵌入式信号处理基础实践。1. 项目概述一块板子上跑通AM信号的“耳朵”与“眼睛”你有没有试过把一个收音机拆开盯着那几根漆包线和二极管发呆想知道那个“滋啦滋啦”的声音是怎么从空中飘来的电磁波变成耳机里清晰的人声的这个项目就是用一块最常见的STM32F103ZET6开发板亲手搭建起一套完整的AM信号处理流水线——它不光能“听”还能“看”而且看得清清楚楚。核心就三件事第一把天线接收到的微弱AM射频信号用最经典的二极管RC电路做检波把它从高频载波里“挤”出原始音频第二把挤出来的音频信号放大到能驱动耳机的电平第三也是最有意思的部分让一块12864点阵液晶屏实时告诉你此刻信号的“健康状况”载波频率是多少Hz、调制度m是0.3还是0.8、信号幅度是150mV还是800mV……这些参数不是靠猜而是由STM32自己采样、计算、刷新一秒都不带卡顿。关键词里的STM32F103、AM检波、12864液晶不是三个孤立的名词而是一条严丝合缝的信号链。STM32F103ZET6是这条链的“大脑”和“手”它用ADC高速采集检波后的模拟电压用内部定时器精确测量输入信号的周期来算频率再用软件算法估算调制度AM检波是这条链的“咽喉”决定了你能听到多干净的声音这里没用复杂的锁相环或数字解调而是回归教科书式的硬件检波成本低、原理透、故障率低12864液晶则是这条链的“眼睛”它不像OLED那样只显示几个字符而是能画出波形、填满整个屏幕但驱动它却是个“力气活”所以项目里特意提供了并行和串行两套方案——并行像搬砖一次搬8块数据快但占IO口串行像快递一根线慢慢送省口但要讲究时序。我当年第一次在实验室焊好板子看到液晶屏上跳动的“Freq: 1.25MHz, m0.62”时那种感觉就像亲手给一台老式收音机装上了数字仪表盘所有玄学都变成了可读、可测、可调的数字。它特别适合电子类课程设计因为每个环节都能拆开讲透也特别适合电赛备赛因为F题考的就是这种“软硬结合、参数量化”的综合能力对嵌入式新手来说它又是一份绝佳的入门实践从点亮LED到驱动液晶从ADC采样到中断服务全链条覆盖没有一步是黑盒子。2. 系统架构与方案选型为什么是这套组合2.1 整体信号流与模块划分整个系统不是一堆代码和芯片的简单堆砌而是一个有明确物理边界和数据流向的闭环。我们可以把它切成四个物理模块每个模块解决一个核心问题射频前端模块由天线、带通滤波器BPF、高频放大器LNA组成。天线捕获空间中的AM广播信号典型频段535–1605 kHzBPF负责滤除带外强干扰比如手机基站的900MHz噪声LNA则把微伏级的信号放大到毫伏级为后续检波提供足够信噪比。这个模块完全模拟真实收音机的“耳朵”它的性能直接决定了整个系统的灵敏度和抗干扰能力。检波与音频处理模块这是项目的灵魂所在。它采用经典的二极管包络检波方案一个1N4148开关二极管配合一个10kΩ电位器用于调节检波深度和一个RC低通滤波器R10kΩ, C10nF截止频率约1.6kHz。二极管只允许信号正半周通过RC电路则像一个“惯性轮”把脉动的直流电压平滑成跟随音频包络变化的电压。这个电压就是原始音频信号但它太弱还带着直流偏置所以后面接了一个运放构成的交流耦合放大器LM358增益约100倍最终输出能推动32Ω耳机的1Vpp左右音频信号。整个过程没有任何DSP参与纯粹是模拟电路的物理特性在起作用这也是它能被电赛命题组选中的原因——考察的是对基础电路原理的深刻理解。主控与参数计算模块STM32F103ZET6在这里扮演双重角色。一方面它通过PA0引脚连接检波输出端利用片内12位ADC以100ksps的速率持续采样将模拟音频电压数字化另一方面它通过PB0引脚连接一个外部信号发生器的TTL同步输出或利用信号本身的过零点用TIM2定时器的输入捕获功能精确测量两个连续上升沿之间的时间间隔从而反推出输入AM信号的载波频率。调制度m的计算则更巧妙ADC采样得到的音频包络电压其峰值Vmax和谷值Vmin之差与它们的平均值Vdc之比就是m (Vmax - Vmin) / (Vmax Vmin)。这个公式在数学上等价于标准定义且完全规避了需要知道载波幅度的难题纯靠软件就能搞定。人机交互与显示模块QC12864B液晶屏是整个系统的“仪表盘”。它有128×64个像素点足以绘制一个简易的时域波形图同时在屏幕上方固定区域显示文字参数。为了适配不同资源紧张程度的场景项目提供了两种驱动方式并行模式13脚和串行模式14脚。并行模式下数据总线D0-D7直接连到STM32的GPIOB[0:7]RS、RW、E等控制线各占一针总共13根线优点是写屏速度极快刷满一屏只要几毫秒串行模式下只用SPI的SCK、MOSI、CS三根线加上RS和复位RST共5根线但数据要一位一位地发速度慢了近10倍。选择哪种取决于你的板子IO是否富裕以及你对刷新率的要求。2.2 STM32F103ZET6的选型逻辑为什么不是F4或H7很多人看到“信号处理”第一反应就是上高性能MCU但在这个项目里F103是经过深思熟虑的选择。首先看性能需求ADC采样率100ksps对12位精度来说每秒产生10万个16位数据STM32F103的72MHz主频执行一条指令平均只需14ns处理一个采样点的滤波、峰值检测等运算耗时远低于10μs完全游刃有余。其次看外设匹配它有3个通用定时器TIM2/3/4其中TIM2支持输入捕获正好用来测频有2个独立的ADC可以一路采音频一路采参考电压做校准有丰富的GPIO能轻松满足并行13脚或串行5脚的液晶驱动需求。最关键的是生态和成本F103的固件库Standard Peripheral Library文档齐全、例程丰富Keil MDK-ARM v5对其支持完美几乎没有兼容性坑而一片ZET6144脚512KB Flash64KB RAM的市场价格不到10元对于课程设计和竞赛这种一次性投入的场景性价比碾压F4系列。我试过把代码移植到F407上除了改几行时钟配置功能毫无区别但成本翻了三倍功耗高了一倍还多了很多用不上的外设纯属浪费。F103在这里不是“将就”而是“刚刚好”。2.3 QC12864B液晶的双驱动方案并行与串行的本质差异QC12864B是一款基于KS0108B或ST7920控制器的国产点阵液晶它本身不区分并行或串行这两种模式是通过硬件接线和初始化指令的不同来实现的。并行模式是它的“原生”工作方式控制器内部有一个8位的数据总线接口当你把D0-D7接到MCU的8位IO上再按顺序发出“设置页地址”、“设置列地址”、“写数据”等指令它就能以最快的速度响应。而串行模式则是利用了KS0108B的一个隐藏特性当它的PSBParallel/Series Select引脚被拉低时它会自动进入一种“伪SPI”模式此时D0-D7被复用为数据线SDA而E引脚则变成时钟SCL。这本质上是一种“位拆分”技术把一个字节的8位数据拆成8个时钟周期来发送。所以并行模式的吞吐量是串行模式的8倍但代价是占用8个宝贵的GPIO。在实际调试中我遇到过一个典型问题学生用串行模式时屏幕偶尔会花屏。排查后发现是因为SPI的SCK时钟频率设得太高1MHz而QC12864B的串行接口时序裕量很小稍微超一点控制器就无法正确锁存数据。最后把SCK降到200kHz问题立刻消失。这恰恰说明并行模式虽然“奢侈”但在稳定性要求高的场合依然是首选。3. 核心细节解析与实操要点从原理图到代码落地3.1 AM检波电路的硬件实现与调试技巧检波电路是整个项目的基石它的质量直接决定了后续所有数字处理的“原材料”是否纯净。原理图上看似简单的二极管RC实操中却藏着不少门道。首先二极管的选择至关重要。1N4148是最佳选择它的结电容小约4pF反向恢复时间短4ns能很好地跟随1MHz以上的载波变化。我试过用1N4007结果在1.5MHz时检波效率就急剧下降因为它的结电容高达15pF严重衰减了高频分量。其次RC时间常数τ R × C必须严格匹配载波频率。理论计算公式是 τ ≈ 1 / (2π × f_carrier)对于1MHz载波τ应约为160ns。但实际中我们取R10kΩ, C10nFτ100μs这看起来大了600倍为什么因为这里有个关键概念叫“包络跟随”RC的作用不是滤掉载波而是让电容电压能跟上音频包络的缓慢变化最高约5kHz同时又要足够快不能把包络的细节比如语音的瞬态抹平。100μs的τ对应截止频率1.6kHz正好落在语音频带中心是经验上的黄金值。调试时用示波器同时观察输入AM信号和检波输出理想波形应该是输入是密集的正弦波输出是平滑的、与输入包络完全重合的曲线。如果输出顶部变圆、底部拖尾说明C太大如果输出出现明显纹波残留载波说明C太小。这时就该拧动那个10kΩ电位器了——它串联在二极管阳极和地之间改变的是检波回路的直流负载从而影响检波效率和失真度。我的经验是先调到输出幅度最大再微调直到纹波和失真达到视觉上的平衡。3.2 ADC采样与参数计算的软件实现STM32的ADC配置是本项目的技术难点之一它不像普通单片机那样“启动就采”而是一套精密的时序系统。我们采用规则通道连续转换模式触发源为软件触发避免外部干扰采样时间为71.5个ADC周期对应14MHz ADC时钟采样精度最高。关键在于ADC的转换结果寄存器ADC_DR是16位宽但有效数据只有低12位高4位是保留位。所以在main.c的ADC中断服务函数里必须这样读取uint16_t adc_val ADC_GetConversionValue(ADC1) 0x0FFF; // 强制屏蔽高4位否则高4位的随机值会污染后续计算。音频信号的峰值Vmax和谷值Vmin检测不能简单地遍历整个采样数组因为那会极大增加CPU负担。我们采用滑动窗口状态机的轻量级算法定义一个长度为256的环形缓冲区每次ADC中断填充一个新值同时维护两个变量current_max和current_min在填充新值时只与它进行比较并更新。这样无论缓冲区多大每次中断的运算量都是常数级。调制度m的计算公式m (Vmax - Vmin) / (Vmax Vmin)在C语言里要特别注意整数除法的陷阱。如果直接写(Vmax-Vmin)/(VmaxVmin)结果永远是0。必须强制类型转换float m (float)(Vmax - Vmin) / (float)(Vmax Vmin);并且为了在液晶屏上显示两位小数我们用printf的格式化输出sprintf(buf, m%.2f, m);。这里还有一个隐藏的优化点Vmax和Vmin的计算其实可以在ADC采样的间隙由一个低优先级的SysTick中断来完成把计算任务从高频率的ADC中断里剥离出来保证ADC采样的实时性不受影响。3.3 12864液晶的并行驱动详解并行驱动的核心在于时序的绝对精准。KS0108B控制器的读写时序要求非常苛刻尤其是EEnable引脚的脉冲宽度必须大于450ns且E的上升沿和下降沿都要有足够的建立和保持时间。STM32的GPIO翻转速度极快如果不加延时E脉冲可能只有几十纳秒控制器根本来不及响应。因此在lcd12864.c的底层写函数里必须插入精确的NOP延时void LCD_WriteCmd(uint8_t cmd) { LCD_RS_CLR(); // RS0, 写命令 LCD_RW_CLR(); // RW0, 写操作 LCD_DATA_OUT(cmd); // 数据总线输出命令 LCD_E_SET(); // E1, 启动 __nop(); __nop(); __nop(); // 延时约300ns LCD_E_CLR(); // E0, 锁存 __nop(); __nop(); __nop(); // 延时约300ns }这里的__nop()是编译器内置的空操作指令每个大约消耗1个CPU周期14ns。3个__nop()就是42ns加上函数调用和赋值的开销总延时刚好落在安全范围内。另一个容易被忽略的点是忙标志BF查询。KS0108B内部有状态寄存器当它正在执行一个指令比如清屏时BF位会被置1此时若强行写入新指令会导致不可预知的错误。所以任何写操作前都必须先读取BF位while(LCD_ReadStatus() 0x80); // BF1表示忙等待而读取BF位又需要切换数据总线方向为输入这本身就是一个耗时操作。因此在对实时性要求极高的场合比如动态刷新波形我们会预先计算好所有指令的执行时间采用“查表延时”而非“忙等待”把刷新一屏的时间稳定控制在8ms以内。3.4 12864液晶的串行驱动详解串行驱动的代码量比并行少一半但逻辑更绕。它的本质是把一个字节的8位数据拆成8次SPI传输。KS0108B的串行协议规定每次传输先发一个“起始位”0再发8位数据MSB在前最后发一个“停止位”1。所以一个字节0xAA10101010b在SPI线上实际传输的是0 10101010 1共10位。在lcd12864_spi.c里SPI初始化必须配置为- 主模式Master- CPOL0, CPHA0空闲时SCK为低数据在第一个边沿采样- 波特率预分频器设为256SCK72MHz/256≈281kHz确保时序安全最关键的函数是LCD_SPI_SendByte(uint8_t byte)void LCD_SPI_SendByte(uint8_t byte) { uint8_t i; SPI_I2S_SendData(SPI1, 0x00); // 发送起始位0 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); for(i 0; i 8; i) { if(byte 0x80) SPI_I2S_SendData(SPI1, 0x01); // 发送数据位 else SPI_I2S_SendData(SPI1, 0x00); byte 1; while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); } SPI_I2S_SendData(SPI1, 0x01); // 发送停止位1 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); // 等待SPI空闲 }这段代码的精妙之处在于它没有使用DMA而是用最朴素的轮询方式确保每一位都按协议要求的时间点发出。我曾经尝试用DMA自动发送一个10位数组结果屏幕乱码就是因为DMA无法精确控制每一位之间的间隔。串行模式的另一个优势是抗干扰。在实验室里当旁边有大功率电机启动时并行模式的13根线就像一个巨大的天线极易引入噪声导致屏幕闪动而串行的3根线SCK、MOSI、CS是差分思想的简化版噪声耦合到所有线上是同相的接收端可以很好地抑制掉稳定性反而更高。4. 实操过程与核心环节实现从Keil工程到硬件联调4.1 Keil MDK-ARM v5工程结构解析这个工程不是一堆文件的杂乱堆放而是一个高度模块化的标准固件库SPL项目其目录结构本身就是一份最佳实践指南。CORE文件夹里是启动文件startup_stm32f10x_hd.s和核心头文件core_cm3.h它们是MCU运行的“地基”定义了中断向量表和Cortex-M3内核寄存器。SYSTEM文件夹是整个项目的“中枢神经”其中sys.c负责NVIC中断分组和优先级配置delay.c实现了微妙级和毫秒级的精准延时基于SysTickusart.c封装了串口打印功能是调试时最忠实的伙伴。USER文件夹是你的“主战场”main.c是程序入口stm32f10x_it.c是中断服务函数的集合所有外设的中断都在这里统一处理。reference文件夹里存放着所有第三方资料包括两份QC12864B的手册它们不是摆设而是你解决疑难杂症的终极答案。例如当液晶屏初始化失败时第一件事就是打开育松电子 QC12864B使用说明.pdf找到“初始化时序图”逐条核对你的LCD_Init()函数里每一条指令的发送顺序和延时是否符合规范。工程里还贴心地提供了keilkilll.bat脚本双击它就能一键删除所有中间文件.o,.axf,.dep等让你每次编译都从一个干净的起点开始避免了因旧目标文件残留导致的“明明改了代码却不生效”的经典玄学问题。4.2 系统时钟与外设时钟的精确配置STM32的时钟树是初学者最容易踩坑的地方。这个项目采用外部8MHz晶振HSE作为主时钟源通过PLL倍频到72MHz作为系统时钟SYSCLK。在system_stm32f10x.c的SetSysClockTo72()函数里关键配置如下RCC-CFGR (uint32_t)((uint32_t)~(RCC_CFGR_SW | RCC_CFGR_SWS)); // 清除SW位 RCC-CR | RCC_CR_HSEON; // 开启HSE while((RCC-CR RCC_CR_HSERDY) 0x00); // 等待HSE稳定 RCC-CFGR | RCC_CFGR_PLLSRC_HSE_PREDIV1; // PLL时钟源为HSE/2 RCC-CFGR | RCC_CFGR_PLLXTPRE_HSE_PREDIV1_DIV2; // HSE预分频为2即4MHz RCC-CFGR | RCC_CFGR_PLLMULL9; // PLL倍频为94MHz*936MHz RCC-CFGR | RCC_CFGR_PLLMULL9; // 这里有个笔误应为RCC_CFGR_PLLMULL9实际代码中已修正 RCC-CFGR | RCC_CFGR_PPRE1_DIV2; // APB1总线ADC、TIM2为36MHz RCC-CFGR | RCC_CFGR_PPRE2_DIV1; // APB2总线GPIO、USART为72MHz RCC-CFGR | RCC_CFGR_HPRE_DIV1; // AHB总线为72MHz RCC-CFGR | RCC_CFGR_SW_PLL; // 切换系统时钟源为PLL while((RCC-CFGR RCC_CFGR_SWS) ! RCC_CFGR_SWS_PLL); // 等待切换成功这段代码的每一行都不是随意写的。比如RCC_CFGR_PPRE1_DIV2它把APB1总线时钟设为36MHz而ADC的时钟源正是APB2总线这意味着ADC时钟最高只能到36MHz。根据ADC的规格书要获得12位精度ADCCLK必须≤14MHz所以我们必须在RCC-CFGR里再设置ADC预分频器为RCC_CFGR_ADCPRE_DIV2最终ADC时钟为18MHz再通过采样时间寄存器SMPR的配置把有效采样率稳定在100ksps。这个层层嵌套的时钟配置就像搭积木错一块整个系统就会崩塌。我见过太多学生因为没看清RCC_CFGR_PPRE1_DIV2这一行导致ADC采样结果全是0xFF折腾半天才发现是ADC时钟超频了。4.3 J-Link调试环境的搭建与高效使用J-Link是这个项目调试的“瑞士军刀”但它的威力远不止于下载程序。在JLinkSettings.ini文件里我们做了几处关键定制; 设置J-Link连接速度为1000kHz兼顾速度与稳定性 Speed1000 ; 自动加载符号表让调试时能看到变量名和函数名 LoadSymbols1 ; 启用RTTReal Time Transfer功能无需串口即可实现printf重定向 EnableRTT1 RTTChannel0 RTTSearchRanges0x20000000,0x10000RTT功能是神来之笔。在main.c里只需简单一行#include SEGGER_RTT.h SEGGER_RTT_printf(0, ADC Value: %d\r\n, adc_val);你就能在J-Link Commander或J-Flash的RTT终端里实时看到打印信息再也不用为了一条调试信息去接一根杜邦线、打开一个串口助手。更重要的是RTT的带宽远高于普通串口即使在100ksps的ADC采样期间也能流畅地打印关键参数完全不影响主循环。另一个高效技巧是断点条件设置。比如你想只在调制度m大于0.7时暂停程序检查此时的波形就可以在main.c的计算m值那一行右键设置断点然后在“Breakpoint Properties”里把Condition设为m 0.7。这样程序会飞速运行只在你真正关心的条件下停下来大大提升了调试效率。4.4 硬件联调全流程与关键测试点硬件联调不是把线一接就完事而是一个有章法的“分段验证”过程。我的标准流程是四步走第一步最小系统验证。只焊上STM32、8MHz晶振、复位电路、3.3V电源用万用表测VDD和VSS之间的电阻正常应在几百欧姆排除短路。然后用J-Link下载一个最简程序比如让LED闪烁如果LED能亮说明最小系统OK。第二步ADC通道验证。把PA0引脚用杜邦线接到一个可调直流电源上从0V调到3.3V同时在RTT终端里观察ADC读数是否从0x000到0xFFF线性变化。这是验证ADC硬件和软件配置是否正确的黄金标准。第三步检波电路验证。用信号发生器输出一个1MHz、1Vpp的AM信号调制度m0.5用示波器CH1接输入CH2接检波输出。此时你应该看到CH2是一个完美的、频率为1kHz的正弦波假设调制信号是1kHz。如果CH2是杂乱的毛刺那问题一定出在检波电路的焊接或元件参数上。第四步液晶显示验证。先不接任何信号只给液晶屏供电运行lcd_test.c看屏幕是否能正常显示“Hello World”。如果不行立刻拿出QC12864B.pdf用万用表的二极管档逐个测量PSB、RST、CS、RS、RW、E以及D0-D7的对地电压确认它们的电平状态是否与初始化代码中的设定一致。液晶屏的问题90%都出在硬件连接上而不是代码。5. 常见问题与排查技巧实录那些年踩过的坑5.1 液晶屏不显示或显示乱码的终极排查表现象最可能原因排查步骤解决方案全屏黑无任何反应1. 电源未接或电压不足2. 对比度电位器VR1调至极限3. RST引脚未正确复位1. 用万用表测VDD和V0引脚电压VDD应为5VV0应为-2V~-3V负压2. 用螺丝刀缓慢旋转VR1观察是否有微弱灰度变化3. 用示波器测RST引脚确认上电时有100ms的低电平脉冲1. 检查电源模块2. 将VR1调至中间位置3. 在LCD_Init()开头手动添加LCD_RST_CLR(); delay_ms(200); LCD_RST_SET();显示部分字符但位置错乱1. 并行数据线D0-D7中有1根接触不良2. RS/RW/E控制线时序错误1. 用万用表通断档逐一测量D0-D7与MCU引脚的连通性2. 用逻辑分析仪抓取E引脚波形确认脉冲宽度450ns1. 重新焊接可疑焊点2. 在LCD_WriteCmd()和LCD_WriteData()函数里增加__nop()延时屏幕闪烁或显示内容随时间漂移1. 忙标志BF未正确查询2. 晶振频率不稳定1. 在每次写指令前强制加入while(LCD_ReadStatus() 0x80);2. 用频谱仪测8MHz晶振输出看频谱是否纯净1. 修改底层驱动函数2. 更换一颗高质量的HC-49S封装晶振5.2 AM检波失真与噪声的实战解决方案问题检波输出有严重“削顶”失真原因二极管导通压降约0.7V与音频信号幅度相比不可忽略导致正半周被截断。方案在二极管后级增加一级运放构成的“有源峰值检波器”用LM358搭建其输出能精确跟随输入包络且无二极管压降。但这会增加电路复杂度对于电赛而言更推荐的方案是提高输入信号幅度让0.7V的压降占比小于5%失真即可忽略。问题检波输出有高频“嘶嘶”噪声原因RC低通滤波器的截止频率过高未能滤除载波残余。方案这不是简单地加大C值。因为C过大会导致包络响应变慢语音听起来“发闷”。我的做法是在RC滤波器后再加一级由LM358构成的二阶巴特沃斯低通滤波器fc5kHz它能在5kHz处提供-40dB/dec的陡峭衰减彻底滤除1MHz载波同时对语音频带的影响微乎其微。问题调制度m的计算值始终为0或溢出原因ADC采样值Vmax和Vmin的计算逻辑错误或未做防抖处理。方案在main.c里不要直接用ADC的瞬时值而是先对连续256个采样点做中值滤波再求Vmax/Vmin。中值滤波能有效剔除ADC偶尔产生的尖峰噪声如电源扰动引起的异常值让计算结果稳定可靠。代码片段如下c // 对adc_buffer[256]做中值滤波 for(i 0; i 256; i) { for(j i1; j 256; j) { if(adc_buffer[i] adc_buffer[j]) { temp adc_buffer[i]; adc_buffer[i] adc_buffer[j]; adc_buffer[j] temp; } } } filtered_val adc_buffer[128]; // 取中值5.3 STM32程序跑飞或死机的快速定位法STM32程序跑飞往往不是代码有Bug而是硬件或配置出了问题。我的“三分钟定位法”如下看LED在main()开头让一个LED常亮在while(1)循环里让同一个LED以1Hz频率闪烁。如果LED常亮不灭说明程序卡死在main()之前的初始化阶段重点检查SystemInit()和SetSysClockTo72()如果LED完全不亮说明连启动代码都没跑起来检查startup_stm32f10x_hd.s里的Reset_Handler是否正确跳转。看串口在main()的每一关键步骤后加一句printf(Step X OK\r\n)。如果某一句没打印出来问题就出在它之前。这是最朴实也最有效的手段。看内存在Keil的Debug模式下打开View - Memory Windows输入0x20000000SRAM起始地址观察内存是否被意外改写。如果发现大量0xAAAAAAAA或0xDEADBEEF那是典型的栈溢出或野指针访问立刻检查你的局部数组大小和指针操作。最后再分享一个小技巧这个项目里所有的延时函数delay_ms,delay_us都基于SysTick而SysTick的中断优先级默认是最低的。如果你在某个高优先级中断里调用了delay_ms(1)程序会立刻死机因为SysTick中断被屏蔽了延时函数永远等不到中断返回。所以永远不要在中断服务函数里调用任何基于SysTick的延时函数。要用就用__nop()这种纯软件延时或者把延时逻辑移到主循环里去处理。这是我当年在电赛现场花了整整两个小时才揪出来的“幽灵Bug”希望你能避开。本文还有配套的精品资源点击获取简介基于STM32F103ZET6主控完整实现AM调幅信号的接收、二极管检波、低通滤波、音频放大及耳机输出功能配套QC12864B点阵液晶模块支持并行和串行两种接口方式分别对应13脚并口与14脚串口接线方案可实时显示载波频率、调制度、信号幅度等关键参数工程使用标准STM32固件库结构包含CORE、SYSTEM、delay、usart、sys等规范模块集成startup_stm32f10x_hd.s启动文件、system_stm32f10x.c系统时钟配置、stm32f10x_it.c中断管理及main.c主流程逻辑提供J-Link调试支持含JLinkSettings.ini、keilkilll.bat一键清理脚本、多用户工程配置文件.uvprojx/.uvoptx/.uvguix.*以及README.md、ss.md操作指南和两份QC12864B硬件手册QC12864B.pdf、育松电子 QC12864B使用说明.pdf适用于高校电子技术课程设计、全国大学生电子设计竞赛F题复现训练及嵌入式信号处理基础实践。本文还有配套的精品资源点击获取