STM32F103C8T6智能风扇实战:从课设到工程落地
发布时间:2026/9/4 10:15:49来源:尧图网络
简介本资源是一份面向嵌入式初学者的STM32实践项目源码包适用于高校电子类专业低年级学生或单片机入门学习者聚焦基础外设驱动与闭环控制逻辑实现。压缩包共325个文件含75个.o目标文件、73个.crf编译中间文件、42个.h头文件及37个.c源文件涵盖STM32F10x标准外设库核心模块如tim、rcc、usart、adc、i2c等并包含完整Keil工程.uvprojx、可执行镜像.axf/.hex及链接脚本.sct总大小14.36MB。已有359人下载学习项目虽未采用PID算法但清晰呈现了温度反馈→PWM调节→风速响应的完整控制链路配套说明文档详述Human/Auto双模式逻辑、DS18B20读取流程、L298N驱动时序及按键状态机设计代码结构规范、注释充分便于理解外设配置、中断处理与状态切换机制。1. 项目概述从一块蓝色小板子开始的智能风扇实战你手头刚拆开一个压缩包名字叫“课程设计一个基于STM32F103C8T6的智能风扇项目源码项目说明.zip”——这几乎就是国内高校电子类、自动化、测控专业大三学生期末课设的“标准封面”。它不像商业产品那样包装精美但里面藏着一套完整闭环环境温度采集→算法判断→PWM调速→人机交互→状态反馈。核心芯片是那块被戏称为“蓝 pill”的STM32F103C8T664KB Flash、20KB RAM、72MHz主频在今天看参数平平无奇却是无数工程师的“第一块MCU”。它不靠性能碾压而靠极简架构、成熟生态和极低门槛让初学者能真正“摸到寄存器”而不是被困在抽象层里打转。这个项目不是炫技而是教你怎么把芯片引脚、ADC通道、定时器、GPIO、串口这些名词变成风扇转速随室温自动升降的真实物理动作。它解决的不是“能不能做”而是“怎么做才不翻车”——比如为什么用TIM1而不是TIM2做PWM为什么DS18B20比DHT11更适合教学演示为什么串口打印必须加换行符才能被串口助手识别这些细节恰恰是课堂PPT里不会写、但焊完板子通电失败时最要命的问题。适合两类人一是正为课设 deadline 熬夜的学生需要可直接烧录、带注释、有调试线索的源码二是想从51单片机过渡到ARM Cortex-M的开发者它用最精简的外设组合讲清楚中断嵌套、DMA搬运、库函数与寄存器操作的边界在哪里。2. 整体架构与设计逻辑为什么选这套组合而不是更“高级”的方案2.1 芯片选型STM32F103C8T6不是妥协而是精准匹配很多人看到“C8T6”第一反应是“太老了”但把它放进这个项目里恰恰是经过反复权衡的理性选择。我们来算一笔账智能风扇的核心任务是周期性采样温度→比较阈值→调节PWM占空比→刷新OLED显示→响应按键。整个流程对实时性要求不高毫秒级响应足够对计算量要求极低只是几个浮点数比较和比例运算对存储空间要求有限代码字模不到32KB。C8T6的资源刚好卡在这个甜点上Flash容量64KB。Keil MDK编译后带OLED驱动、DS18B20底层、PID简易算法、串口printf重定向的完整工程实际占用约28KB。留出近一倍余量方便后续加功能比如加个红外遥控解码或调试时开更多日志。RAM容量20KB。全局变量堆栈OLED显存128×64点阵需1KB ADC DMA缓冲区双通道各128点共256字节总占用不到3KB。余量充足避免野指针踩内存导致莫名死机。外设资源它自带2个12位ADC满足温度光敏电阻双采样、3个通用定时器TIM2做系统滴答、TIM3做PWM输出、TIM4做按键消抖、1个高级控制定时器TIM1支持互补PWM虽本项目未用但为后续加BLDC电机预留接口、2路USARTUART1接PC调试、UART2可扩展WiFi模块、I2C备用接BH1750光感、SPI备用接SD卡记录数据。没有冗余也没有缺失。对比其他选项用STM32F407性能过剩启动文件复杂HAL库臃肿初学者容易迷失在配置宏里用ESP32Wi-Fi和蓝牙是亮点但温度控制不需要联网反而增加功耗和调试复杂度用Arduino Nano开发快但隐藏了底层细节学生交报告时说不清“中断向量表在哪”“SysTick怎么配置”。提示C8T6的“最小系统板”之所以流行关键在于它把所有必要信号BOOT0/1、SWDIO/SWCLK、NRST、3.3V/GND都引出了且默认焊接了8MHz晶振保证系统时钟精度省去了新手自己搭电路的第一道坎。你拿到手插上ST-Link5分钟就能点亮LED——这是建立信心的关键。2.2 功能模块划分五个物理动作对应五段核心逻辑整个系统不是“一个大while循环”而是按职责清晰切分为五个独立模块每个模块有明确输入输出和状态机环境感知模块负责读取DS18B20温度传感器单总线协议和可选的光敏电阻ADC采样。它不关心风扇怎么转只管每500ms提供一个float型温度值℃和一个uint16_t型光照强度0-4095。决策控制模块接收感知模块数据执行“温度-转速映射算法”。本项目采用分段线性查表法非PID因为课设要求简单可靠。例如20℃以下停转20-25℃低速30%占空比25-30℃中速60%30℃以上高速100%。算法代码不足20行但注释写了半页——告诉学生“这里为什么用if-else而不是switch”。执行驱动模块将决策模块输出的占空比0-100转换为TIM3_CH2的PWM波形。关键点在于使用APB1总线时钟72MHz预分频器设为71得到1MHz计数频率自动重装载值设为999得到1kHz PWM频率这样占空比数值CCR寄存器值直观易懂。人机交互模块包含OLEDSSD1306I2C接口显示当前温度、设定模式、转速档位以及3个独立按键K1模式切换、K2加档、K3减档。所有按键处理都带硬件消抖10ms定时器扫描和软件防抖连续3次读取相同值才确认避免误触发。调试通信模块通过USART1PA9/PA10以115200bps速率向PC发送JSON格式日志如{temp:26.5,speed:60,mode:auto}。这不仅是调试手段更是教会学生“如何让机器开口说话”。这五个模块通过全局结构体system_status_t共享数据而非全局变量乱传既保证耦合度低又避免RTOS带来的学习负担。源码里每个.c文件开头都有模块功能注释比如fan_control.c第一行就写着“本文件实现风扇转速的PWM生成与占空比更新不涉及温度采集或按键处理”。2.3 开发环境与工具链Keil MDK STM32标准外设库的务实选择项目说明文档里明确要求使用Keil MDK v5.25或更高搭配STM32F10x Standard Peripheral Library v3.5.0。这个组合看似“过时”实则是教学场景下的最优解标准外设库SPL函数命名直白如GPIO_Init()、ADC_RegularChannelConfig()、TIM_SetCompare2()参数列表清晰学生能一眼看出“这个函数在配置什么”。对比HAL库的HAL_ADC_ConfigChannel()后者需要先填一堆结构体初学者容易在hadc.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4;这种语句上卡住。Keil MDK调试界面直观寄存器视图、内存视图、外设寄存器视图一键切换。当学生发现风扇不转可以立刻在TIM3-CCER寄存器里看CC2E位是否置1比在命令行里敲arm-none-eabi-gdb高效十倍。ST-Link V2成本低于20元支持SWD下载和实时调试。项目说明里特别强调“禁止使用JTAG接口”因为C8T6的JTAG引脚JTDO/TMS等与普通GPIO复用一旦配置错误会导致无法下载。而SWD只需SWDIO和SWCLK两根线引脚固定容错率高。注意项目源码中system_stm32f10x.c文件里的SystemInit()函数已将HSE外部8MHz晶振作为系统时钟源并通过PLL倍频至72MHz。这是关键一步——如果学生误删了这行RCC-CFGR | (uint32_t)RCC_CFGR_PLLMULL9;系统会退回到内部8MHz RC振荡器导致所有定时器时间全乱风扇狂转或停转。源码注释里用加粗标出“此处修改将影响所有基于SysTick和TIM的延时请勿随意改动”。3. 核心细节解析与实操要点从原理图到PCB那些文档里没写的坑3.1 电源与复位电路不起眼但决定第一次上电成败C8T6最小系统板的电源设计常被忽略但它直接关系到ADC采样精度和DS18B20通信稳定性。项目原理图stm32f103c8t6_sch.pdf中电源部分包含三个关键设计LDO稳压芯片AMS1117-3.3V输入电压范围4.75-12V输出3.3V/1A。但它的输入电容10μF钽电容和输出电容22μF电解电容必须严格按手册布局。我曾遇到一个案例学生用100nF陶瓷电容替代输出电容结果上电后OLED闪屏原因是LDO瞬态响应差3.3V纹波达200mV导致SSD1306 I2C通信失败。正确做法是输出端并联一个100nF陶瓷电容滤高频22μF电解电容滤低频。复位电路RC时间常数R10kΩC100nFτ1ms。这个值确保上电时NRST引脚保持低电平足够长10ms让芯片完成内部初始化。如果换成10nF电容τ0.1ms可能复位不彻底导致程序跑飞。去耦电容布局每个VDD/VSS引脚旁必须放置0.1μF陶瓷电容且走线长度2mm。原理图里标了10处但PCB布线时学生常把电容放在板边用长走线连接到芯片引脚——这会让高频噪声无法被有效滤除ADC采样值跳变±2℃。实测对比规范布局下DS18B20读数稳定在25.12℃乱布局下在24.8~25.6℃间抖动。实操心得焊接前用万用表二极管档测量VDD与GND间电阻。正常应为几百欧姆内部上拉/下拉电阻。如果接近0Ω说明PCB短路如果无穷大说明电源网络未连通。这个简单测试能避免80%的“烧板”事故。3.2 DS18B20单总线通信时序严苛但可预测DS18B20是本项目温度采集的核心它采用单总线1-Wire协议仅需一根数据线DQ加一个4.7kΩ上拉电阻即可工作。但它的时序要求极其严格初始化脉冲必须是主机拉低≥480μs然后释放等待从机应答脉冲60~240μs。很多学生用普通GPIO模拟时序结果读数全为85℃DS18B20复位值。源码中ds18b20.c采用精确延时状态机方案使用SysTick定时器做1μs级延时SysTick_Config(SystemCoreClock/1000000)而非for(i0;i10;i)这种不可靠循环初始化阶段分三步拉低480μs → 释放并延时60μs → 读取DQ电平应为0读取ROM指令0x33和读取温度指令0x44后必须等待750ms转换时间否则读出的是旧数据。关键代码片段// 拉低DQ线 GPIO_ResetBits(GPIOA, GPIO_Pin_0); delay_us(480); // 精确480μs // 释放DQ线上拉电阻拉高 GPIO_SetBits(GPIOA, GPIO_Pin_0); delay_us(60); // 等待从机应答窗口 // 读取DQ电平 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) Bit_RESET) { // 应答成功继续后续操作 }常见问题DS18B20在-10℃以下读数不准。这是因为其内部温度传感器在低温区线性度下降。项目说明文档里明确建议“若需宽温区应用请改用TMP36或LM75本项目默认工作温度范围0~50℃”。3.3 OLED显示驱动SSD1306的I2C通信陷阱OLED屏选用0.96寸SSD1306128×64通过I2C总线PB6/PB7连接。表面看只需调用I2C_WriteByte()发送命令但实际有三个隐形雷区I2C地址冲突SSD1306默认地址是0x78写/0x79读但有些山寨屏地址是0x3C/0x3D。源码中oled.c第23行定义#define SSD1306_I2C_ADDR 0x3C并注明“若屏幕不亮请尝试改为0x78”。这个细节救了无数学生。初始化时序要求SSD1306上电后需等待100ms才能发初始化命令否则寄存器配置无效。源码OLED_Init()函数开头强制delay_ms(100)这个延时不能省略。显存刷新效率全屏刷新128×641024字节耗时约12ms。如果每帧都全刷OLED会闪烁。源码采用“局部刷新”策略温度值只更新右上角8×16区域16字节模式图标只更新左下角16×16区域32字节其余部分复用上帧数据。这样单帧刷新时间降至2ms以内肉眼完全无感。实操技巧OLED屏背面有四个焊盘VCC/GND/SCL/SDA但部分廉价屏的VCC焊盘与GND焊盘间距仅1.27mm焊接时极易短路。建议用刀片刮开焊盘间绿油再用细锡丝点焊比直接拖焊成功率高得多。3.4 PWM风扇驱动MOSFET选型与续流保护风扇驱动电路采用N沟道MOSFETIRF3205作为开关而非三极管。原因很实在IRF3205导通电阻Rds(on)仅0.008Ω驱动12V/0.3A风扇时自身功耗仅0.008×0.3²≈0.0007W几乎不发热而用S8050三极管饱和压降Vce(sat)≈0.2V功耗0.2×0.30.06W长时间运行MOSFET壳温30℃三极管壳温60℃。但MOSFET有个致命弱点关断瞬间风扇线圈产生的反向电动势L·di/dt会击穿MOSFET。源码配套的原理图里必须在风扇两端并联一个1N4007续流二极管阴极接VCC阳极接MOSFET漏极。这个二极管在MOSFET关断时为线圈电流提供回路将反向电压钳位在0.7V左右。我见过太多学生省略这个二极管结果烧毁3块C8T6板子——因为反向高压通过MOSFET体二极管倒灌进芯片损坏GPIO或电源管理单元。PWM频率设定为1kHz这是权衡结果频率太低如100Hz人耳能听到风扇“嗡嗡”声频率太高如20kHzMOSFET开关损耗增大且C8T6的TIM3在72MHz主频下1kHz PWM需设置ARR71999CCRx计算稍复杂1kHz人耳不敏感开关损耗低寄存器配置直观ARR999PSC71。4. 实操过程与核心环节实现从新建工程到烧录运行的全流程拆解4.1 Keil工程搭建五步建立可编译框架新建工程不是点击“New Project”就完事必须按顺序完成五个关键步骤否则后续编译必报错创建工程目录结构在Project/下建CMSIS/放启动文件、FWLIB/放标准外设库、USER/放main.c和模块文件、OUTPUT/放编译输出。这个结构让团队协作时路径清晰避免“找不到startup_stm32f10x_md.s”的尴尬。添加启动文件从FWLIB/Tools/startup_stm32f10x_md.s复制到CMSIS/。注意文件名中的md代表Medium Density64KB Flash不能错用hdHigh Density版本否则中断向量表偏移错误。配置Target选项在Keil的“Options for Target”→“Target”页设置Xtal(MHz): 8 外部晶振频率ARM Compiler: Use default compiler versionCode Generation: 将Optimization设为Level 3-O3但勾选One ELF Section per Function避免链接时函数被优化掉。添加头文件路径在“C/C”页的Include Paths中添加..\CMSIS ..\FWLIB\inc ..\USER缺少..\FWLIB\inc会导致#include stm32f10x.h报错。添加源文件分组在Project窗口右键→“Manage Project Items”创建GroupsCMSIS放startup文件、FWLIB放所有.c文件、USER放main.c和模块文件。这样编译时依赖关系清晰修改fwlib文件不会误触发user文件重编译。实操心得每次新建工程后先编译一次空的main.c只含while(1);。如果编译通过且无警告说明环境配置正确如果报错一定是上述五步中某步遗漏。这个“空工程验证法”能节省3小时调试时间。4.2 主函数逻辑main()里的状态机骨架main.c不是简单罗列初始化函数而是构建了一个清晰的状态机循环int main(void) { SystemInit(); // 系统时钟初始化72MHz RCC_Configuration(); // 外设时钟使能GPIO/ADC/TIM/I2C等 NVIC_Configuration(); // 中断优先级分组组22位抢占2位响应 GPIO_Configuration(); // 所有GPIO初始化输入/输出/上拉/下拉 ADC_Configuration(); // ADC1初始化通道0温度通道1光敏 TIM_Configuration(); // TIM2SysTick、TIM3PWM、TIM4按键 I2C_Configuration(); // I2C1初始化OLED USART_Configuration(); // USART1初始化调试串口 OLED_Init(); // OLED硬件初始化 while(1) { // 1. 采集环境数据阻塞式500ms周期 ds18b20_read_temp(temp_value); adc_read_light(light_value); // 2. 决策控制纯逻辑无延时 fan_speed_set control_algorithm(temp_value, light_value); // 3. 执行驱动更新PWM占空比 TIM_SetCompare2(TIM3, fan_speed_set * 10); // 0-100映射到0-1000 // 4. 人机交互刷新OLED扫描按键 oled_refresh_display(temp_value, fan_speed_set, current_mode); key_scan_process(); // 5. 调试输出JSON格式1s一次 if(sys_tick_1s_flag) { printf({\temp\:%.2f,\speed\:%d,\mode\:\%s\}\r\n, temp_value, fan_speed_set, mode_str[current_mode]); sys_tick_1s_flag 0; } delay_ms(500); // 主循环周期 } }这个结构的关键在于所有耗时操作ADC采样、DS18B20读取都在主循环中同步执行而非放在中断里。理由很实际DS18B20单总线时序要求微秒级精度如果放在SysTick中断里中断服务程序ISR执行时间波动会导致时序偏差而主循环中用delay_us()可控性更强。学生容易犯的错误是“为了‘高大上’把所有东西塞进中断”结果调试时发现温度读数忽高忽低。4.3 ADC多通道采样DMA搬运与软件触发的平衡本项目需同时采集温度DS18B20已提供数字值无需ADC和光敏电阻模拟量因此ADC配置聚焦于单通道PA0的稳定采样。但源码预留了多通道扩展接口adc.c中ADC_Configuration()函数已配置ADC1规则组为通道0PA0并启用DMA// 启用ADC1时钟 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1, ENABLE); // 配置ADC结构体 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; // 单通道不扫描 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; // 单次转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; // 软件触发 ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55_5Cycles); // 启用DMA ADC_DMACmd(ADC1, ENABLE); // 启用ADC ADC_Cmd(ADC1, ENABLE);关键点在于ADC_ContinuousConvMode DISABLE——这意味着每次采样都需要手动触发ADC_SoftwareStartConvCmd(ADC1, ENABLE)。这样做的好处是主循环中adc_read_light()函数可以精确控制采样时机避免与DS18B20通信冲突两者都占用CPU时间。如果设为连续模式ADC会自主运行但学生很难理解“为什么光敏值一直在变而温度没变”。DMA缓冲区设为uint16_t adc_dma_buffer[1]每次转换后DMA自动将结果存入此地址。adc_read_light()函数中ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换结束 *value ADC_GetConversionValue(ADC1); // 读取结果实操技巧ADC采样前务必对PA0引脚做“浮空输入”配置GPIO_Mode_AIN而非GPIO_Mode_IN_FLOATING。后者是通用输入模式内部上拉/下拉电阻未禁用会影响模拟信号精度。源码gpio.c中GPIO_Configuration()函数第87行明确写了GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN;。4.4 串口调试与printf重定向让MCU“开口说话”Keil默认不支持printf()必须重定向到USART1。这个过程有三个易错点重定义fputc()函数在usart.c中添加int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t) ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); return ch; }关键是等待USART_FLAG_TCTransmission Complete而非USART_FLAG_TXETransmit Data Register Empty。前者表示字节已移出移位寄存器后者只表示数据已写入发送寄存器此时立即返回可能导致后续字符丢失。开启微库MicroLIB在Keil的“Target”页勾选Use MicroLIB。否则printf()会链接到标准C库导致Flash溢出标准库printf代码量4KB。波特率计算误差115200bps在72MHz下USARTDIV 72000000 / (16 × 115200) ≈ 39.0625。整数部分39小数部分0.0625对应DIV_Fraction 0x010.0625 1/16。源码usart.c中USART_InitStruct.USART_BaudRate 115200;由库函数自动计算分频值但学生手动计算时常用错公式。实操心得串口调试时如果PC端收不到任何字符先用示波器测PA9引脚是否有方波。如果有说明MCU在发问题在PC端驱动/波特率如果没有说明USART_Cmd(USART1, ENABLE)没执行或RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_USART1, ENABLE)被注释掉了。5. 常见问题与排查技巧实录那些让课设挂科的“幽灵Bug”5.1 问题速查表按现象归类直击根源现象可能原因快速验证方法解决方案风扇完全不转1. MOSFET栅极未加驱动电阻2. PWM通道未使能CC2E03. 风扇供电未接入用万用表测MOSFET栅极电压应为3.3V跳变测TIM3-CCER寄存器CC2E位在PA7TIM3_CH2与MOSFET栅极间加10kΩ电阻检查TIM_CCxCmd(TIM3, TIM_Channel_2, TIM_CCx_Enable);是否执行OLED全黑或花屏1. I2C地址错误2. SCL/SDA上拉电阻缺失4.7kΩ3. 初始化时序不足用逻辑分析仪抓I2C波形看是否有ACK响应修改SSD1306_I2C_ADDR为0x78在PB6/PB7各加一个4.7kΩ电阻到3.3VDS18B20读数恒为85℃1. DQ线上拉电阻过大10kΩ2. 初始化时序超时3. 单总线设备地址未匹配用示波器测DQ线波形看应答脉冲宽度换4.7kΩ上拉电阻检查delay_us(480)是否准确用SysTick校准串口打印乱码1. 波特率设置错误2. 晶振频率配置不匹配3. PC端串口助手未设为“无校验、1停止位”用示波器测PA9波形计算实际波特率在system_stm32f10x.c中确认HSE_VALUE为8000000串口助手选对参数按键失灵或连击1. 消抖延时不足2. 按键硬件未接下拉电阻3. 扫描频率过高用逻辑分析仪看按键IO电平变化将KEY_SCAN_INTERVAL从10ms改为20ms确保按键一端接地另一端经10kΩ上拉到3.3V5.2 独家避坑技巧来自实验室的血泪经验技巧1ST-Link下载失败90%是BOOT引脚惹的祸C8T6有两个启动模式BOOT00时从主闪存启动正常运行BOOT01时从系统存储器启动ISP模式。很多学生下载失败是因为忘记把BOOT0拨到0。更隐蔽的问题是某些山寨ST-Link V2的SWDIO线虚焊表现为“Device ID读取失败”。解决方案用杜邦线直接短接ST-Link的SWDIO/SWCLK到C8T6对应引脚绕过排针接触不良。技巧2ADC采样值跳变别急着怪芯片当PA0接光敏电阻时读数在100~300间跳变学生第一反应是“ADC坏了”。实测发现是光敏电阻引脚靠近USB接口USB2.0的480Mbps数据线辐射干扰了模拟信号。解决方案将光敏电阻移到PCB远离USB和SWD接口的一侧并在PA0走线旁铺地铜皮。技巧3OLED显示延迟不是代码慢是I2C速率低默认I2C速率为50kHz刷新一帧需12ms。将I2C_InitStructure.I2C_ClockSpeed 400000;400kHz后时间降至1.5ms。但要注意400kHz要求SCL/SDA上拉电阻≤2.2kΩ否则上升沿过缓I2C通信失败。技巧4Keil编译报“undefined reference to__use_no_semihosting”这是标准外设库与Keil半主机模式冲突。解决方案在main.c顶部添加#pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; void _sys_exit(int x) { x x; } int fputc(int ch, FILE *f) { /* 如前所述 */ }技巧5风扇转速与占空比不符查TIM时钟树明明设置TIM_SetCompare2(TIM3, 500)但风扇转速不对。用示波器测PA7波形发现PWM频率是500Hz而非1kHz。原因是TIM3挂载在APB1总线而APB1预分频器被设为2RCC_CFGR.PPRE10b100导致TIM3时钟为72MHz/236MHz。修正在RCC_Configuration()中添加RCC_PCLK1Config(RCC_HCLK_Div2);确保APB1时钟为36MHzTIM3时钟为36MHz。最后分享一个小技巧课设答辩前把main.c里所有printf()替换成OLED_ShowString()在OLED上显示实时日志。这样答辩时老师用手机拍视频就能看到“温度25.3℃→转速60%→模式AUTO”的完整闭环比对着电脑看串口窗口更有说服力。这个细节能让评分老师眼前一亮。本文还有配套的精品资源点击获取
网站建设高端定制企业官网