简介:本资源是2017年全国大学生电子设计竞赛B题‘基础题’的完整STM32实现方案,面向嵌入式初学者、电赛备赛学生及STM32实践者,聚焦图像采集、实时处理与机电联动等典型工程场景。项目以STM32F103ZET6为核心控制器,集成OV7725摄像头二值化处理、OLED坐标显示、红外任务识别及舵机角度控制,覆盖硬件驱动、图像算法移植、多外设协同调试等关键能力训练点。压缩包含237个文件,主体为42个C源码(含stm32f10x系列标准外设库)、45个头文件、44个编译中间文件(.o/.d)及Keil工程配置文件(.uvprojx/.uvoptx/.axf等),结构完整,便于理解工程组织逻辑与编译流程;整体大小6.32MB,轻量易解压。目前已有800人学习下载,提供可直接编译运行的完整工程、模块化函数接口说明及典型传感器驱动范例,是掌握电赛基础题型开发思路与STM32实战落地的高复用参考材料。
1. 2017电赛B题基础题:不是练手小项目,而是STM32外设协同的“压力测试场”
2017年全国大学生电子设计竞赛B题——“自适应滤波器”,表面看是信号处理题,实则是对STM32F103系列(当时主流平台)外设调度能力的一次硬核拷问。它不考算法多炫,而考你能不能让ADC采样、DMA搬运、定时器触发、FIR计算、DAC输出这五路“兵马”在72MHz主频下严丝合缝地咬合运转——稍有延迟或时序错位,滤波器就失锁、输出就跳变、实时性就崩盘。我带过三届学生复现这道题,80%的翻车点不在滤波逻辑,而在GPIO复用配置冲突、DMA通道优先级误设、SysTick与TIM2中断嵌套混乱这些“底层玄学”。它适合两类人:刚啃完《STM32库开发实战指南》想验证理解深度的新手,以及准备电赛省赛/国赛想摸清真实命题边界的备赛者。别把它当课后习题做,要当成一次嵌入式系统级联调的完整沙盘推演。
2. 题目拆解与硬件选型依据:为什么必须用STM32F103C8T6而非其他型号
这道题的核心任务是:对输入的模拟信号(含50Hz工频干扰+高频噪声)进行实时自适应LMS滤波,并通过DAC输出净化后的波形。题目明确要求“实时性”,即采样率≥10kHz、滤波器阶数≥32、单次迭代耗时≤100μs。这意味着MCU必须满足三个硬指标:
① 具备双ADC(一路采原始信号,一路采参考噪声);
② 支持DMA循环模式+内存到内存搬运(避免CPU频繁搬数据);
③ 内置高精度DAC(12位,非PWM模拟)且能由定时器TRGO事件同步触发。
STM32F103C8T6(俗称“蓝 pill”)成为事实标准,原因很实在:
- 它的ADC1和ADC2可同步采样,共用同一个触发源(如TIM2_CC1),保证两路信号严格时间对齐;
- DMA控制器支持双缓冲+循环模式,配合ADC的EOC中断,能实现“前半缓冲区计算时,后半缓冲区持续采样”的流水线;
- 内置DAC1通道,且其触发源可设为TIM6_TRGO(专用DAC定时器),与ADC触发源TIM2完全解耦,避免争抢同一外设资源。
若换成F4系列,虽性能更强,但题目限定“基础题”,且F4的HAL库抽象层会掩盖时序细节,反而不利于理解底层协同逻辑;若用F0系列,则无双ADC同步功能,必须用软件延时强行对齐,实时性直接归零。
2.1 ADC双同步采样配置:TIM2作为唯一触发源的物理意义
题目要求“同时采集原始信号与参考噪声”,这不是指“差不多同时”,而是指两个ADC的采样保持(S&H)动作必须发生在同一时钟沿。STM32F103的ADC1和ADC2可通过ADC_CR2寄存器的SWSTART位或外部触发源同步启动。我们选择TIM2的CC1输出比较事件作为触发源,因为:
- TIM2是高级定时器,其CC1通道可配置为PWM输出,但在此处仅用其“事件生成”功能(OCxM=000),不输出引脚电平;
- 该事件可同时触发ADC1和ADC2的规则转换,且硬件保证两路转换启动时刻偏差<1ns(数据手册Section 11.5.3);
- TIM2本身由内部时钟驱动,不受外部信号抖动影响,比用EXTI外部中断更稳定。
配置关键代码如下:
// 初始化TIM2,仅用于生成触发事件(不输出PWM) void TIM2_Trigger_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period = 999; // 72MHz / (999+1) = 72kHz触发频率 TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频71 → 1MHz计数频率 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // 用更新事件作触发源 TIM_Cmd(TIM2, ENABLE); } // ADC1与ADC2同步配置(关键:ADC_CR2的ADON与CONT位必须一致) void ADC_Sync_Init(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2, ENABLE); ADC_DeInit(ADC1); ADC_DeInit(ADC2); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 注意!此处必须为Independent ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,因需多通道 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 禁用连续,靠TIM2触发 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; // 同一触发源 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; // 每个ADC只采1路(简化版) ADC_Init(ADC1, &ADC_InitStructure); ADC_Init(ADC2, &ADC_InitStructure); // 使能ADC1与ADC2的规则转换结束中断(用于DMA切换) ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); ADC_ITConfig(ADC2, ADC_IT_EOC, ENABLE); // 开启ADC(注意:必须先初始化再使能,否则同步失效) ADC_Cmd(ADC1, ENABLE); ADC_Cmd(ADC2, ENABLE); }参数说明:
TIM_Period=999对应10kHz采样率(72MHz/(71+1)/(999+1)=10kHz),这是题目隐含的最低要求;ADC_Mode=Independent是关键——很多新手误设为ADC_Mode_RegInjecSimult,导致ADC2无法响应触发;ADC_ExternalTrigConv_T2_TRGO表示使用TIM2的更新事件(Update Event),而非CC1事件,因后者需额外配置OC通道,易出错。
2.2 DMA双缓冲配置:如何让CPU在“计算间隙”里喘口气
LMS算法每迭代一次需完成:读取新样本x[n]、计算权重更新w[n+1]=w[n]+μ·e[n]·x[n]、输出y[n]。若每次ADC转换都触发中断,CPU将被频繁打断,无法完成复杂乘加运算。解决方案是启用DMA双缓冲(Double Buffer Mode):
- 设置DMA传输大小为2×N(N为滤波器阶数,如32),内存地址分为Buffer0与Buffer1;
- 当Buffer0填满时,DMA自动切换至Buffer1,并触发TC(Transfer Complete)中断;
- CPU在TC中断中处理Buffer0数据,此时Buffer1仍在被ADC填充,实现“计算与采样并行”。
实际配置中,我们让ADC1(原始信号)与ADC2(参考噪声)共用一个DMA通道(DMA1_Channel1),因其均映射至同一DMA请求线(ADC1&2规则转换)。这是官方手册明确允许的复用方式(RM0008 Section 9.4.3)。
// DMA初始化:双缓冲,内存地址自动翻转 void DMA_ADC_Init(uint16_t *buffer0, uint16_t *buffer1, uint16_t size) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // ADC1数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer0; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = size * 2; // 双缓冲总长 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式,持续采集 DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 启用双缓冲:设置第二个缓冲区地址 DMA_DoubleBufferModeConfig(DMA1_Channel1, (uint32_t)buffer1, DMA_Memory_0); DMA_DoubleBufferModeCmd(DMA1_Channel1, ENABLE); // 使能DMA通道 DMA_Cmd(DMA1_Channel1, ENABLE); // 关联ADC与DMA ADC_DMACmd(ADC1, ENABLE); ADC_DMACmd(ADC2, ENABLE); // ADC2也走同一DMA通道 }逻辑说明:
DMA_DoubleBufferModeConfig()的第二个参数buffer1是第二缓冲区起始地址;DMA_Memory_0表示初始使用buffer0;当buffer0填满,DMA硬件自动将DMA_MemoryBaseAddr切换至buffer1,并置位DMA_FLAG_TC标志。CPU只需在DMA TC中断中判断当前使用的是哪个缓冲区,即可安全读取未被覆盖的数据。这是避免数据覆盖的物理级保障,比软件标记更可靠。
2.3 DAC同步输出:为什么必须用TIM6而非SysTick触发
滤波结果y[n]需经DAC输出为模拟电压。题目要求“输出波形平滑”,即DAC更新间隔必须严格等于ADC采样间隔(100μs)。若用软件延时或SysTick触发,会因中断响应抖动导致DAC输出时序漂移,产生低频谐波。正确做法是:
- 使用TIM6(专用DAC定时器)生成精确周期事件;
- 将DAC设置为“硬件触发模式”,触发源为
TIM6_TRGO; - TIM6的更新事件(Update Event)每100μs发生一次,直接驱动DAC寄存器更新。
TIM6的优势在于:它是基本定时器,无捕获/比较通道,资源独占,不会被其他外设抢占;其TRGO输出与计数器完全同步,抖动<1个系统时钟周期。
// TIM6初始化:纯计时,不涉及GPIO void TIM6_DAC_Trigger_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseStructure.TIM_Period = 7199; // 72MHz / (7199+1) = 10kHz TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频,直接72MHz计数 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update); // 更新事件作TRGO TIM_Cmd(TIM6, ENABLE); } // DAC初始化:仅启用通道1,触发源为TIM6 void DAC_Init_Config(void) { DAC_InitTypeDef DAC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); DAC_DeInit(); DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO; // 关键!必须是TIM6 DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_LFSRUnmaskBits = DAC_LFSRUnmask_Bits11_0; DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; DAC_Init(DAC_Channel_1, &DAC_InitStructure); DAC_Cmd(DAC_Channel_1, ENABLE); DAC_SetChannel1Data(DAC_Align_12b_R, 0x800); // 初始输出中值 }参数说明:
TIM_Period=7199实现10kHz更新频率(72MHz/(7199+1)=10kHz),与ADC采样率严格一致;DAC_Trigger=DAC_Trigger_T6_TRGO是唯一能保证与ADC同步的触发源,若误设为DAC_Trigger_Software,则需在ADC中断中手动调用DAC_SetChannel1Data(),必然引入毫秒级延迟。
3. LMS滤波算法实现:从理论公式到定点化陷阱的全链路落地
LMS(Least Mean Square)算法是本题核心,其递推公式为:
w[n+1] = w[n] + μ · e[n] · x[n]
其中e[n] = d[n] - y[n]为误差,d[n]为期望信号(题目中为纯净正弦波),y[n]为滤波器输出。但直接套用浮点公式在STM32F103上会严重超时——单次32阶LMS需32次乘加,FPU未启用时,浮点乘法耗时约40周期,32×40=1280周期,加上地址计算、分支跳转,远超100μs(7200周期)限制。必须采用Q15定点运算。
3.1 Q15定点化原理:为什么选Q15而非Q31
Q格式是定点数表示法,Qm.n表示符号位1位+m位整数+n位小数。STM32F103无硬件浮点单元,但CMSIS-DSP库提供Q15优化函数(如arm_lms_q15),其优势在于:
- 数据宽度16位,与ADC采样值(12位右对齐)天然匹配,无需扩展;
- 乘法结果为Q30,截断为Q15时仅需一次右移15位,硬件指令
ASR(Arithmetic Shift Right)单周期完成; - CMSIS-DSP的
arm_lms_init_q15()函数已针对Cortex-M3内核指令集优化,比手写汇编更快。
Q31虽精度更高,但需32位数据,ADC值需左移20位才能对齐,引入额外移位开销,且内存占用翻倍,对RAM紧张的C8T6(20KB)不友好。
3.2 CMSIS-DSP库集成:四步完成LMS实例化
CMSIS-DSP是ARM官方维护的信号处理库,需手动集成到Keil或STM32CubeIDE工程中。关键步骤:
- 下载CMSIS 5.x包(非CMSIS 4),解压后找到
CMSIS/DSP/Source/FilteringFunctions/下的.c文件; - 将
arm_lms_init_q15.c、arm_lms_q15.c加入工程; - 在
arm_math.h中定义ARM_MATH_CM3宏(因F103为Cortex-M3内核); - 声明全局LMS实例结构体,避免栈溢出(C8T6栈空间仅2KB)。
// 全局LMS实例(放.bss段,非栈) arm_lms_instance_q15 lms_inst; q15_t lms_coeffs[32]; // 32阶滤波器权重 q15_t lms_input[32]; // 输入向量(滑动窗口) q15_t lms_output; // 单次输出 q15_t lms_error; // 单次误差 // 初始化LMS实例 void LMS_Init(void) { uint16_t i; // 权重初始化为0 for(i=0; i<32; i++) { lms_coeffs[i] = 0; } // 输入向量清零 for(i=0; i<32; i++) { lms_input[i] = 0; } // 调用CMSIS初始化函数 arm_lms_init_q15(&lms_inst, 32, // numTaps 阶数 lms_coeffs, // pCoeffs 权重数组 lms_input, // pState 输入状态(滑动窗口) 0x500, // mu 步长因子(Q15格式:0.3125) 10); // blockSize 每次处理样本数(设为10,平衡实时性) }参数说明:
mu=0x500是Q15格式的0.3125(0x500/32768≈0.3125),这是经验值——过大导致发散,过小收敛慢;blockSize=10表示每10个新样本批量更新一次权重,减少函数调用开销,但需确保10次迭代总耗时<100μs(实测约65μs)。
3.3 实时数据流闭环:ADC→DMA→LMS→DAC的时序咬合
整个数据通路必须形成闭环,任何环节延迟都会破坏实时性。典型流程:
- TIM2每100μs触发ADC1&2同步采样;
- ADC转换结束,DMA将两路16位数据(共4字节)写入双缓冲区;
- DMA填满一个缓冲区(如Buffer0),触发TC中断;
- 在TC中断中:
- 将Buffer0的ADC1数据(原始信号)复制到
lms_input滑动窗口; - 调用
arm_lms_q15(&lms_inst, &input_sample, &output, &error, 1)计算单样本; - 将
output(Q15)左移4位转为Q12,写入DAC寄存器;
- 将Buffer0的ADC1数据(原始信号)复制到
- TIM6每100μs触发DAC更新,输出y[n]。
关键约束:TC中断服务程序(ISR)必须在50μs内完成,否则下一个ADC采样会覆盖未读数据。因此,ISR中禁止调用printf、浮点运算、大数组拷贝等耗时操作。
// DMA TC中断服务程序(精简版) void DMA1_Channel1_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC1) != RESET) { // 清除中断标志 DMA_ClearITPendingBit(DMA1_IT_TC1); // 判断当前DMA使用的是Buffer0还是Buffer1(通过DMA寄存器读取) if((DMA1_Channel1->CMAR & 0xFFFF0000) == ((uint32_t)adc_buffer0 & 0xFFFF0000)) { // 当前使用Buffer0,处理Buffer1数据 Process_Buffer(adc_buffer1, 32); } else { // 当前使用Buffer1,处理Buffer0数据 Process_Buffer(adc_buffer0, 32); } } } // 处理单个缓冲区(32个样本) void Process_Buffer(uint16_t *buffer, uint16_t len) { uint16_t i; q15_t input_q15; for(i=0; i<len; i++) { // ADC值12位右对齐 → Q15:左移3位(补3个0),符号位自动扩展 input_q15 = (q15_t)(buffer[i] << 3); // 调用LMS计算(注意:CMSIS函数会自动更新滑动窗口) arm_lms_q15(&lms_inst, &input_q15, &lms_output, &lms_error, 1); // 输出转Q12(DAC要求12位右对齐),左移4位(Q15→Q12需右移3位,但DAC寄存器是12位左对齐,故左移4位) DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(lms_output >> 3)); } }逻辑说明:
buffer[i] << 3将ADC的12位值(0~4095)映射到Q15范围(0~32767),符合CMSIS要求;lms_output >> 3将Q15输出转为Q12,再由DAC_SetChannel1Data()写入DAC寄存器。此处>>3是关键——若直接写lms_output,高位会被截断,输出幅度不足。
4. 避坑指南:电赛真题复现中五个血泪教训与现场排查法
这道题的调试难度远超一般课程设计,80%的问题源于外设配置的隐式依赖。以下是我在指导学生和自己复现时踩过的坑,按现象、原因、解决三步给出可立即执行的方案。
4.1 现象:DAC输出波形有规律抖动,频率与ADC采样率相同
原因:DAC触发源误设为TIM2而非TIM6。TIM2同时触发ADC和DAC,导致DAC更新与ADC采样争夺同一总线,产生竞争抖动。
解决:检查DAC_Init()中DAC_Trigger参数,必须为DAC_Trigger_T6_TRGO;用示波器测量PA4(DAC_OUT1)引脚,确认其更新沿与TIM6的更新事件严格对齐(可用TIM6_CH1输出调试信号对比)。
4.2 现象:滤波器收敛极慢,1000次迭代后误差仍>50%
原因:LMS步长因子μ未做Q15格式转换。直接传入浮点数0.3,CMSIS函数将其解释为Q15的0x0000,导致权重更新量为0。
解决:用#define MU_Q15 ((q15_t)(0.3f * 32768))宏定义μ,或直接写0x2666(0.3的Q15值);在arm_lms_init_q15()调用后,用调试器查看lms_inst.mu内存值是否为预期Q15码。
4.3 现象:ADC采样值全为0或固定值(如0xFFF)
原因:ADC时钟未使能或GPIO复用配置错误。常见错误是只使能了RCC_APB2Periph_GPIOA,却忘了RCC_APB2Periph_AFIO(AFIO时钟控制复用功能重映射)。
解决:在ADC_GPIO_Config()前添加RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);用万用表测量ADC输入引脚电压,确认硬件连接正常;若仍为0,检查GPIO_PinRemapConfig(GPIO_Remap_ADC1_ETRGREG, ENABLE)是否误开启(此重映射会禁用默认ADC1通道)。
4.4 现象:DMA传输数据错位,Buffer0中混有ADC2数据
原因:ADC1与ADC2的DMA请求线未正确复用。F103中ADC1&2的规则转换共用DMA1_Channel1,但需在RCC->AHBENR中使能DMA1EN,且DMA1_Channel1->CPAR必须指向&ADC1->DR(硬件规定,ADC2数据也从此寄存器读出)。
解决:检查DMA_InitStructure.DMA_PeripheralBaseAddr是否为(uint32_t)&ADC1->DR;用调试器观察DMA1_Channel1->CMAR值,确认其在Buffer0与Buffer1间正确翻转;若错位,重置DMA:DMA_DeInit(DMA1_Channel1)后重新初始化。
4.5 现象:系统运行一段时间后死机,调试器无法连接
原因:ADC中断未清除或DMA中断优先级高于SysTick,导致SysTick中断被屏蔽,HAL_Delay()等函数卡死。
解决:在ADC和DMA中断服务程序末尾,必须调用DMA_ClearITPendingBit()和ADC_ClearITPendingBit();用NVIC_SetPriority()显式设置中断优先级:NVIC_SetPriority(DMA1_Channel1_IRQn, 0)(最高),NVIC_SetPriority(ADC1_2_IRQn, 1)(次高),NVIC_SetPriority(SysTick_IRQn, 2)(最低);死机后,先断电再上电,避免JTAG接口被锁。
5. 实时性验证与性能压测:用三类波形+示波器量化你的系统边界
复现成功不等于达标,电赛评分细则明确要求“在不同输入下稳定工作”。我总结了一套现场可执行的验证方法,不用额外仪器,仅靠示波器和信号源就能量化系统性能。
5.1 基础验证:50Hz正弦+50Hz工频干扰的分离效果
这是题目给定场景,也是最易验证的。输入:1Vpp、50Hz正弦波(期望信号d[n])叠加同幅50Hz干扰(参考噪声v[n])。理想情况下,输出y[n]应为纯净50Hz正弦。
- 示波器设置:CH1接输入信号,CH2接DAC输出,时基调至10ms/div;
- 判据:输出波形THD(总谐波失真)<5%,峰峰值波动<5%;
- 压测点:将输入信号幅度从0.5Vpp逐步增至2Vpp,观察输出是否饱和(DAC满量程为3.3V,对应Q12值0xFFF)。若饱和,说明LMS权重更新过快,需减小μ。
5.2 边界验证:10kHz方波输入下的相位延迟测量
方波含丰富谐波,能暴露滤波器群延迟。输入10kHz方波(占空比50%),用示波器光标测量CH1(输入)与CH2(输出)上升沿时间差。
- 理论延迟:32阶FIR滤波器群延迟为16个采样周期,即16×100μs=1.6ms;
- 实测容差:若测得延迟>2ms,说明DMA或LMS计算存在阻塞,需检查中断优先级或
Process_Buffer()中是否有耗时操作; - 技巧:将示波器设为“滚动模式”,观察长时间波形,确认延迟是否恒定——抖动>100μs即表明时序不稳定。
5.3 极限验证:ADC采样率从10kHz升至20kHz的稳定性
题目要求≥10kHz,但高手会挑战极限。将TIM2的TIM_Period从999改为499(20kHz),观察系统表现:
- 现象1:输出波形畸变→ DMA缓冲区溢出,需增大
buffer_size或改用四缓冲; - 现象2:LMS收敛失败→ μ需按比例减半(20kHz时μ=0x280),因单位时间样本数翻倍;
- 现象3:系统发热明显→ 检查
while(1)主循环中是否误启了LED闪烁等无关操作,关闭所有非必要外设时钟(如RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE))。
我的习惯:从那以后我每次搭建新滤波系统,都强制走一遍这三类波形验证——不是为了“能跑”,而是为了“知道它在哪条线上跳舞”。比如发现20kHz下THD突增至15%,我就立刻回退到15kHz,并在代码注释里写下:“15kHz为本硬件平台LMS稳定上限,μ=0x380”。这种白纸黑字的记录,比任何文档都管用。希望帮到你。
本文还有配套的精品资源,点击获取