Article

从“能编译”到“能控住”:STM32F103 风扇控制器的 Keil 移植与实机调试

将基于 Astra 风格 OLED UI 的风扇控制器移植到 STM32F103C8T6 与 Keil MDK,并记录 PWM 极性、测速接线、10k 上拉和小比例进度条等实机调试结论。

默认发布于·更新于·原文
#STM32F103#Keil#OLED#风扇控制

这次 Fan Speed 工程的目标并不复杂:用 STM32F103C8T6 驱动一块 128x64 SSD1306 OLED,通过旋转编码器和按键调节风扇占空比,同时采集测速信号并在屏幕上显示转速。

真正花时间的部分,不是把几个外设初始化出来,而是把原有工程变成一个可以直接在 Keil 中使用的项目,并让代码最终服从真实硬件,而不是服从常见接线的想象。

硬件与功能H2#

核心硬件包括:

  • STM32F103C8T6 最小系统板
  • SSD1306 128x64 OLED,IIC 接口
  • 旋转编码器与两个按键
  • NMB-MAT 2415KL-04W-B86 四线风扇
  • PB8 输出 PWM 控制信号
  • PA6 采集风扇测速信号
  • PWM 控制线上使用 10k 电阻上拉到 3.3V

界面保留了 Astra 风格的过渡动画和小屏交互,同时加入占空比、进度条和 RPM 显示。

从 CMake 工程迁移到 KeilH2#

原工程以 CMake 组织,但实际开发环境更适合 Keil MDK,因此我在 MDK-ARM 目录下补齐了完整工程:

  • FanController.uvprojx:Keil 工程配置
  • STM32F103C8T6.sct:ARMCC 5 分散加载文件
  • startup_stm32f103c8t6.s:启动文件
  • platform_interrupts.c:必要的中断入口

验证工具链为 Keil MDK 5.33 和 ARM Compiler 5.06u7。最终工程能够完成全量编译和链接,并生成 FanController.hexFanController.bin

迁移中最容易被忽略的是 SysTick。工程即使能够链接,如果 SysTick_Handler 没有调用 HAL_IncTick(),HAL 的延时和超时逻辑仍会停住:

c
void SysTick_Handler(void)
{
    HAL_IncTick();
}

另一个问题来自 U8G2:当前源码中缺少 SSD1306 全缓冲 IIC 初始化函数 u8g2_Setup_ssd1306_i2c_128x64_noname_f。补齐对应 setup 后,显示部分才完成最终链接。

这次移植采用了比较稳妥的验证顺序:先编译业务代码,再分批编译 U8G2,最后做完整链接。这样可以把启动文件、库函数和业务代码的问题分开定位。

接线不能只看颜色H2#

最初按照常见四线风扇的颜色约定判断 PWM 和测速线,结果与实机现象不一致。最终根据台架测试确认,这一只 2415KL-04W-B86 的实际接法是:

MCU 引脚风扇线功能
PB8 / TIM4_CH3黄色线PWM 控制
PA6 / TIM3_CH1蓝色线转速脉冲输入

这里的结论只针对本次使用的风扇和实测接线,不应直接套用到其他四线风扇。颜色可以作为线索,但最终应由型号资料、示波器波形和实际响应共同确认。

PWM 控制线使用开漏思路时,曾尝试省掉外部上拉,但该风扇的控制变得不可靠。最终保留 10k 电阻上拉到 3.3V。减少外围器件当然有吸引力,不过已经被实机证明必要的器件,不值得为了更简洁强行删除。

PWM 极性与线性调节H2#

实测后将 PWM 极性确定为非反相:

c
#define FAN_PWM_ACTIVE_LOW 0U

这样界面中的百分比越高,输出占空比越高,风扇转速也随之上升。编码器每次调整 5%,在 0% 100% 之间形成清晰的离散档位,比过细的步进更适合这块小屏和旋钮的交互。

界面设置值直接映射到 PWM,占空比不再经过额外的非线性变换。这样做也让调试更直观:显示 40% 时,就能明确知道定时器正在输出约 40% 的命令。

测速信号与快速校验H2#

PA6 使用 TIM3_CH1 采集测速脉冲。代码默认风扇每转输出 2 个脉冲,因此转速和输入频率的关系为:

text
RPM = 频率(Hz) x 60 / 2

例如 4000 RPM 对应的测速频率大约是 133 Hz。这个换算很适合现场快速判断:当 OLED 显示值、示波器频率和实际风声互相矛盾时,可以先用它检查测速链路是否合理。

为了区分真实转速和输入噪声,程序还保留了原始周期、滤波后周期以及被拒绝脉冲数等调试变量。相比只盯着最终 RPM,这些中间量更容易判断问题究竟来自 PWM 命令、接线、上拉还是测速干扰。

128x64 界面的边界问题H2#

小屏界面最容易在极端值上出错。进度条在 2% 等很小数值时,圆角矩形的最小宽度会导致横向溢出;RPM 文本靠近右边缘时,也可能留下看起来像竖线的残影。

最终的处理方式是:

  • 进度条宽度很小时改用普通实心矩形,不强行绘制圆角
  • 对填充宽度做边界保护
  • RPM 文本按实际字符宽度右对齐
  • 优先检查 0%、2%、5% 和 100% 等边界画面

这类问题在中间占空比下往往完全看不出来,因此 OLED 界面验证不能只停留在 50% 这样的正常画面

最终结果H2#

最终工程完成了以下内容:

  • 可直接使用的 Keil MDK 工程
  • SSD1306 IIC 全缓冲显示
  • 旋转编码器 5% 档位调节
  • PB8 非反相 PWM 输出
  • PA6 输入捕获测速
  • 面向噪声排查的测速调试变量
  • 适配极小百分比和右边缘文本的 OLED 界面

编译后的固件 ROM 16.77KBRAM 2.50KB,适合 STM32F103C8T6 的资源规模。

这次调试最重要的经验,是让软件模型持续接受硬件反馈:先建立一个能编译、能链接的工程基线,再用真实波形和现象逐项修正接线、极性、滤波和界面。对于嵌入式项目,常见做法只能提供起点,实机结果才是最后的约束。

风扇控制器实机调试画面
风扇控制器实机调试画面

界面源代码参考:AstraThreshold/oled-ui-astra

Copyright & License
© 2026 Lee
从“能编译”到“能控住”:STM32F103 风扇控制器的 Keil 移植与实机调试
CC知识共享许可
BY署名:必须保留原作者署名
NC非商业:禁止用于商业目的
SA相同方式共享:以同协议发布
许可协议:署名-非商业性使用-相同方式共享