
简介LED风扇设计与实现资料是一套面向电子爱好者、嵌入式开发者和物联网初学者的完整项目资源围绕旋转字幕显示、NFC近场通信模块及电路原理图展开解决从硬件设计到交互控制的完整学习路径问题。压缩包共14个文件、约6.86MB内含7张BMP位图素材、2个DLL动态库、2个DOC操作说明、1个TTC字体、1个HTM网页说明和1个LED上位机程序其中DOC文档提供中英双语操作说明HTM网页说明可快速指引搭建流程。原理图与接线图详细描绘了微控制器、LED驱动和NFC模块的电路连接源码部分涉及C/C或MicroPython控制逻辑配合LED.exe可导入车标、文字等自定义图像实现旋转字幕显示效果并支持通过NFC配置显示内容。目前已有676人学习浏览适合动手实践、项目复现及物联网交互装置设计入门有助于提升嵌入式显示与无线通信的综合技能。1. LED风扇不是风扇是环形光栅扫描仪LED风扇的视觉效果来自视觉暂留——一排LED绕轴转一圈每个角度位置只亮一瞬你的眼睛把这些光点拼成整幅画面。标题里的「原理图」解决三个问题电流怎么进旋转板、霍尔怎么捕捉机械零点、一串灯珠归谁管「源码」解决另外两个转速变了角度分频怎么跟着变、后半圈图像怎样镜像才不反。这个项目适合想低成本做环形POV显示、或者准备在STM32上做「角度同步行扫描」的开发者。难点比想象中靠前焊灯不是重点霍尔脉冲与图片第一行对齐才是。下面以STM32F103C8T6为控制核心给出一套能直接改的图纸和固件思路不依赖厂家的显示模组。2. LED风扇原理图怎么画先定供电、磁极采样和LED驱动再接MCU2.1 整板分区旋转LED板和静止底座各自放什么画原理图前先分清楚哪些器件会跟着扇叶转哪些必须静止。常见做法是让电池、主控、LED、霍尔全部放在旋转盘上静止底座只放磁钢、充电触点和总开关。这样电气上自洽不需要滑环也避免碳刷磨损带来的接触噪声。静止底座上的N52磁钢就是机械零点。旋转盘每经过一次霍尔输出一个脉冲MCU把这个脉冲当作一帧画面的起始位置。旋转部分内部用电池直供不受线缆约束原理图也简单得多一张旋转板原理图加一个结构件图纸不用画跨转动界面的复杂连接。2.1.1 为什么把MCU放在旋转盘而不是底座把MCU放在静止侧需要把图像数据实时传上旋转盘无线或者滑环都会引入额外延迟和误码。放在旋转盘上霍尔信号在同一块PCB上直接进MCU信号路径短时序抖动小。底座上的磁钢位置可以在安装时微调也可以用源码里的零点偏移做软校准这个放到第5章。2.2 电源链路电池随转盘转LED直接吃电池电压LED不经过LDO直接从3.7V锂电池经限流电阻取电。MCU和霍尔才用稳压后的3.3V。这样做的原因是LED瞬时电流大经过LDO会压降发热而低边MOS开关本来就适合从VBAT直接驱动灯珠。电源树如下VBAT经过SS14防反接二极管后分两路。一路直接进LED限流电阻另一路进XC6206P332MR稳压到3.3V。电池和LED之间加100uF电解电容靠近MOS漏极3.3V输出加10uF陶瓷电容靠近MCU的VDD脚。上电瞬间电机启动电流大电压跌落会导致MCU重启所以储能电容不能省。限流电阻先按下式计算不要凭手感焊python3 -c for vf in [2.0,2.2,3.0,3.2]: print(f{vf:.1f}V - {int((3.7-vf-0.2)/0.015)} Ohm)参数说明3.7是电池标称电压0.2是AO3400 MOSFET的导通压降0.015是目标电流15mA。红黄灯珠压降约2.0-2.2V算出来约86欧姆白色蓝色灯珠约3.0-3.2V算出来约18欧姆。实际取E24系列电阻红黄用91欧姆白蓝用18欧姆。注意电池满电4.2V时电流会变大LED亮度变化不明显寿命也没问题。2.2.1 电机驱动为什么不用H桥电路很多资料里习惯性画H桥驱动电机但LED风扇里的电机只需要单方向转动半桥或者低压侧MOS就够。H桥电路原理图放在这种场景属于过度设计还会多出两路PWM和死区处理。旋转盘上空间紧张我一般用空心杯电机或玩具直流电机配一个AO3400做低压侧开关MCU输出一个固定占空比的PWM控制转速。2.3 磁极采样A3144霍尔的上拉、滤波和输入捕获接法霍尔传感器选A3144开集输出供电3.3V输出端接4.7k上拉电阻到3.3V再进MCU的PA0。PA0复用到TIM2_CH1输入捕获上升沿触发。输出端对地加1nF电容滤除高频干扰但不要太大会拖慢上升沿。原理图里这个电容要靠近MCU引脚而不是靠近霍尔。在STM32CubeMX里配置TIM2输入捕获sConfig.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfig.ICSelection TIM_ICSELECTION_DIRECTTI; sConfig.ICPrescaler TIM_ICPSC_DIV1; sConfig.ICFilter 0x0F; // 内部数字滤波约700ns毛刺 HAL_TIM_IC_ConfigChannel(htim2, sConfig, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1);逻辑说明ICFilter开启内部数字滤波后比外部RC更可控且不额外增加上升沿时间。霍尔脉冲每圈只有一个所以这里不需要捕获两个沿只捕获上升沿即可。若发现磁钢距离太远导致漏脉冲把ICFilter降为0x04并把霍尔和磁钢间距控制在2-4mm。2.4 LED驱动低压侧MOS开关阵列比595更省事LED排成一条直线径向安装在扇叶上常见24颗。我把它们按8颗一组分成三组每组共阳接VBAT每颗灯的阴极串限流电阻后接到一颗AO3400的漏极栅极由MCU的PB0-PB7并行控制。三个组共用8条GPIO但分组后每组需要单独使能这里和原理图分段扫描对应。功能块参考器件关键连接电池防反接SS14阴极朝向VBAT阳极接电池正极3.3V稳压XC6206P332MR输入接SS14后输出3.3VC10uF磁极检测A3144OUT上拉4.7k到3.3V接PA0/TIM2_CH1LED灯串8颗LED91/18ΩVBAT-R-LED-MOS漏极低压侧开关AO3400栅极接PBx源极接GND漏极接LED阴极GPIO不能直接灌45mA所以不能把LED阴极直接接PA7这类引脚。AO3400的开启电压只有0.65V3.3V逻辑电平可以直接驱动不需要三极管放大。原理图上注意栅极加一个100k下拉电阻防止MCU上电瞬间引脚悬空导致LED全亮。2.5 画完原理图后的检查清单网络标签、地回路和封装我习惯用KiCad画完后跑一遍ERC重点看有没有未连接引脚、电源输出是否冲突。如果你用AD20导出原理图PDF只有部分区域通常是原理图页面尺寸设置不对在Document Options里把页面大小改成A3并重新放置网络标签。三个高频踩坑项一是霍尔上拉电阻接到VBAT而不是3.3V。电池电压跌落后霍尔输出高电平幅度跟着跌可能进入MCU逻辑阈值的不确定区造成额外脉冲。二是MOS栅极漏画下拉电阻。MCU复位期间GPIO浮空栅极电压悬在阈值附近MOS管半导通LED出现不规则微亮而且MOS发热。三是地线走线。LED返回的大电流不要穿过XC6206的地脚和霍尔的地用星形单点接地否则霍尔脉冲会叠加几十毫伏的噪声转速高时容易出现双脉冲。3. 把源码拆成三件事角度同步、行数据映射、亮度调制3.1 先定分辨率和帧格式360段对STM32F103很温和一圈显示多少列直接决定图案圆润度。取360每个角度片段对应1度肉眼看起来已经很圆。再往上加到512每段时长变短中断频率升高对固件的实时性要求更严。我用360做默认值LED_COUNT取24正好和原理图里8×3组对应。分辨率SEGMENTS转速rpm段中断频率Hz每段时长us36012007200138.936018001080092.651212001024097.751218001536065.1图像数据放在Flash里格式是二维数组#define SEGMENTS 360 #define LED_COUNT 24 static const uint8_t frame[SEGMENTS][LED_COUNT] { // 每一行对应一个角度片段每个字节对应一颗LED // 值为1代表这颗LED在该角度亮 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* ... 后续359行 ... */ };逻辑说明数组第一维是角度第二维是LED序号。360×248640字节STM32F103C8T6的64KB Flash能放好几张图。第二维的bit0对应原理图上靠近轴心的一颗灯装配PCB时定好方向否则整个图案会左右颠倒。3.2 霍尔测速和动态段定时器角度不是时间霍尔脉冲每圈一次两个脉冲之间的时间dt就是一圈的周期。一圈分成SEGMENTS段每段时长就是dt/SEGMENTS。但转速会波动不能把这个值算一次就用到底要在每次霍尔中断里更新。用TIM2输入捕获测量dt用TIM3做段定时器每次霍尔到来时重装ARR以实现动态分频static uint32_t last_ccr 0; static uint32_t angle_arr 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance ! TIM2) return; uint32_t now __HAL_TIM_GET_COMPARE(htim2, TIM_CHANNEL_1); uint32_t dt (now - last_ccr) 0xFFFFFFFFu; // 防回绕 last_ccr now; if (dt 2) return; // 滤掉毛刺脉冲 angle_arr dt / SEGMENTS; if (angle_arr 0) angle_arr 1; __HAL_TIM_SET_AUTORELOAD(htim3, angle_arr - 1); }参数说明dt是TIM2计数器的差值单位是计数器时钟周期。如果TIM2用72MHz主频预分频72计数频率1MHzdt就是微秒数。dt 2的判断是防止霍尔抖动产生两个极短的连续中断。ARR设置为angle_arr - 1是因为定时器从0数到ARR共angle_arr个计数点。这里有个隐藏坑dt / SEGMENTS的余数被直接丢掉。转速稳定时看不出来转速轻微波动时每个角度片段的长度误差会积累图案出现周期性横移。余数处理放到第5章。3.3 行数据查表和半圈镜像别让字反着走图像数组的行号是角度LED在旋转时前180度从第0行查到第179行后180度如果继续按顺序查表图案会左右镜像。因为旋转方向不变人在正面看到的是扇叶前侧后半个圆周LED背对观察者但视觉暂留会把光点翻到前面来合成。处理方式是在后180度把行号做一次反转static volatile uint16_t angle_index 0; void TIM3_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(htim3, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(htim3, TIM_FLAG_UPDATE); angle_index; if (angle_index SEGMENTS) angle_index 0; uint16_t row; if (angle_index SEGMENTS / 2) row angle_index; else row SEGMENTS - 1 - angle_index; led_write_row(frame[row]); __HAL_TIM_SET_COMPARE(htim3, pwm_level[row]); } if (__HAL_TIM_GET_FLAG(htim3, TIM_FLAG_CC1)) { __HAL_TIM_CLEAR_FLAG(htim3, TIM_FLAG_CC1); led_all_off(); } }逻辑说明上半圈行号和角度索引一致下半圈用SEGMENTS - 1 - angle_index反转这样图像的后半段按相反顺序重放视觉上补齐为一个完整画面。led_write_row把一行的24位写到PB口对应的MOS栅极。pwm_level[row]是这一行的亮度值等下在3.4节说明。3.4 用PWM做灰度和亮度而不是只开和关段定时器中断里写LED后立刻把同一路定时器的比较寄存器设为pwm_level[row]。定时器继续计数到达比较值时触发CC1中断把所有LED关掉。这样每个角度片段的点亮时长从固定时长变成可调实现灰度。亮度值越大点亮时间越长占空比越高。整张图的pwm_level[]可以统一用同一个值做整体亮度也可以每行不同做渐晕补偿。旋转时外圈线速度比内圈高同一个LED在外圈扫过的角度范围相同但弧长长亮度本来一致但由于每颗LED视角不同外圈视觉上稍暗可以把外圈行的pwm_level调高5%-10%。4. 合板调试转速、亮度、残影和串口日志怎么配合4.1 不接LED先看霍尔波形和角度计数把扇叶放在台架上不装LED先用串口看dt和angle_arr。STM32F103C8T6的USART1默认映射到PA9/PA10重定向printfint _write(int fd, char *p, int sz) { for (int i 0; i sz; i) { while (!(USART1-ISR USART_ISR_TXE)); USART1-TDR (uint8_t)p[i]; } return sz; }参数说明这段代码把标准库的printf输出指向USART1调试串口波特率设115200。上电后用手拨动扇叶串口应该能每秒看到几十到几百组dt数据。如果dt值忽大忽小先检查霍尔和磁钢距离再用示波器看霍尔输出是否只有一个干净的上升沿。如果看到dt偶尔是正常值的两倍说明丢了一个霍尔脉冲。不是霍尔坏大概率是转速瞬间波动导致磁钢掠过霍尔时速度太快脉冲宽度小于霍尔响应时间。A3144的响应频率虽然高但开集输出经过1nF电容后沿变慢把电容去掉或者把ICFilter改小。4.2 段频率自动重算直接从公式看参数怎么设段频率由两个参数决定转速和SEGMENTS。转速1800rpm等于每秒30圈乘SEGMENTS 360就是每秒10800个段中断。每个段中断里要完成查表、写GPIO、设置比较值全部开销必须小于92.6us。如果开了串口打印和HAL_Delay这类阻塞代码时间就不够。调试期可以把SEGMENTS临时改成90段时长变成原来的4倍方便在中断里加断点。稳定后再改回360不要一开始就上高分辨率。4.3 残影的四个来源每个都能在图里看出来现象主要原因处理方向图案后面拖一条亮尾LED关断时间过长换Trf更小的LED或将低边MOS换成开关型驱动画面左右轻微错位霍尔零点偏移增加zero_offset软校准整圈闪动转速波动太大对angle_arr做一阶低通滤波高速时亮度下降每段点亮时间太短提高pwm_level或降低SEGMENTS到240残影还有一个常被忽略的来源行切换瞬间上一行LED还没完全关断下一行已经开始亮。这个时间很短但旋转速度快时会被视觉系统抓住。可以在led_write_row之前加两三条空指令或者把CC1中断里的关灯操作提前几微秒。4.4 串口日志里该看什么转速、ARR、丢帧计数打印过多会拖慢系统我一般只在调试期每秒打印一次printf(rpm%u arr%u idx%u miss%u\r\n, (unsigned int)(60000000u / dt), (unsigned int)angle_arr, (unsigned int)angle_index, (unsigned int)miss_count);rpm的计算公式是60乘以计数频率再除以dt。如果dt单位是微秒那么60000000/dt就是每分钟转速。miss_count是每次霍尔中断里检查上次中断是否超时如果角度索引和理论位置差超过一个段就加1。这个值在稳定转动时应该一直是0出现非零值说明中断阻塞或者霍尔丢脉冲。5. 让显示更稳的三个参数余数累加、零点偏移和慢动作验证5.1 余数累加吃掉角度分频的量化误差第3章的dt / SEGMENTS会丢弃余数。假设dt是1000个计数SEGMENTS是360每段平均应是2.777个计数实际取2每圈累计少了280个计数段图案边界会缓慢漂移。转速变化越频繁越明显。在霍尔中断里加一个累加器static uint32_t phase_acc 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance ! TIM2) return; uint32_t now __HAL_TIM_GET_COMPARE(htim2, TIM_CHANNEL_1); uint32_t dt (now - last_ccr) 0xFFFFFFFFu; last_ccr now; if (dt 2) return; uint32_t q dt / SEGMENTS; phase_acc dt % SEGMENTS; if (phase_acc SEGMENTS) { phase_acc - SEGMENTS; q; } if (q 0) q 1; __HAL_TIM_SET_AUTORELOAD(htim3, q - 1); }逻辑说明phase_acc把每次除不尽的余数攒起来攒够一个SEGMENTS就给当前段补一个计数。这样长时间统计下角度定时器的平均周期接近真实转速图案不再慢慢横移。这个技巧对7200Hz以上的段中断效果尤其明显。5.2 零点偏移软校准不动磁钢也能修垂直安装时磁钢位置和LED第一颗灯很难严格对准图案会整体倾斜。给固件加一个按键每按一次zero_offset加1图像的行号先用偏移量修正uint16_t row (angle_index zero_offset) % SEGMENTS; if (row SEGMENTS / 2) row SEGMENTS - 1 - row;把zero_offset存在STM32的备份寄存器或者片内Flash里每次上电读取。校准方法是显示一张带十字线的图边转边按键直到十字线的竖线正好经过扇叶转轴的垂直方向。5.3 用慢动作拍摄验证图案是否稳定肉眼看不出的抖动手机慢动作视频一拍就现原形。用240fps录制扇叶正面逐帧看LED亮条的位置。如果亮条在屏幕上水平滑动是5.1的量化误差没处理好如果亮条宽度不均匀是pwm_level逐行设置有问题如果每一圈起始位置都在变霍尔脉冲本身就不稳。验证图案时先在ROM里放一张全亮的图确认所有LED都能点亮再放一张只有中间一行的图检查这一行是不是出现在以转轴为中心的同一半径上。通过这两步基本就能把「能转的LED风扇」调成「显示稳定的环形POV屏」。本文还有配套的精品资源点击获取