MicroPython pyboard 教程精讲使用 PWM 实现 LED 渐亮与呼吸灯效果【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython本教程源自 docs/pyboard/tutorial/fading_led.rst是 MicroPython pyboard 官方入门教程系列中关于脉宽调制PWM输出的核心实战指南。你将学会如何用一条数字引脚输出可变的平均电平从而平滑地控制 LED 亮度实现渐亮与呼吸灯两种经典效果同时文章会结合仓库源码带你理解 PWM 在 STM32 定时器层级的真实实现原理为后续用 PWM 驱动舵机、调光、发声等应用打下基础。为什么需要 PWM数字引脚也能输出模拟亮度LED 最简单的玩法是用数字引脚拉高/拉低实现亮与灭的开关控制。但数字引脚只有 0 和 1 两个状态无法表达半亮。解决思路是Pulse-Width Modulation脉宽调制让引脚在一个固定周期内一部分时间输出高电平、一部分时间输出低电平。只要切换频率足够高人眼感知到的就是介于全亮与全灭之间的平均亮度——高电平占比占空比越高LED 越亮。这正是本教程的出发点在只开关 LED的基础上通过控制占空比实现任意亮度从而做出渐亮动画。硬件清单与电路搭建本教程需要的元件非常少标准5 mm 或 3 mm 直插 LED一颗100 Ω 限流电阻一颗防止流过 LED 的电流过大若干导线面包板可选但能让接线更整洁、更易复现。接线方式本教程使用X1引脚将电阻的一端接到X1电阻的另一端接到 LED 的阳极anode——即较长的那根引脚将 LED 的阴极cathode接到板子的GND地。下面这张面包板接线图来自仓库 docs/pyboard/tutorial/img/fading_leds_breadboard_fritzing.png直观展示了完整的连接拓扑提示接线方向很关键——LED 是单向导电器件接反了不会发光。认准长脚为阳极这一经验法则即可。认识 X1 引脚为什么它能输出 PWM不是所有引脚都适合做 PWM 输出。在 STM32 上PWM 信号由硬件定时器的通道产生因此必须选择引脚 → 定时器通道这一映射关系成立的组合。根据 docs/pyboard/quickref.rst 的说明X1连接到定时器 5 的通道 1TIM5 CH1。在仓库中可以找到对应证据ports/stm32/boards/PYBV11/pins.csv 第 1 行X1,PA0即 pyboard v1.1 的X1对应 STM32 的PA0引脚ports/stm32/boards/stm32f405_af.csv 第 3 行PA0引脚的复用功能Alternate Function表中同时包含TIM2_CH1/TIM2_ETR与TIM5_CH1。所以PA0是一个一孔多能的引脚——quickref 中 PWM 示例用的是Timer(2)而本教程选择Timer(5)两者都是X1合法的硬件映射。这正体现了定时器与引脚的可选复用特性同一引脚可以绑定到不同定时器的对应通道。核心代码用 Timer TimerChannel 输出 PWM明确了硬件映射后代码分两步走先创建定时器对象再为指定通道创建TimerChannel对象并配置 PWM 模式。from pyb import Timer from time import sleep # timer 5 以 100 Hz 的频率工作 tim Timer(5, freq100) tchannel tim.channel(1, Timer.PWM, pinpyb.Pin.board.X1, pulse_width0)注意原文档此处写作tim pyb.Timer(5, freq100)但脚本开头已通过from pyb import Timer导入pyb这个名字并未被导入实际运行时应写为Timer(5, freq100)或把导入改为import pyb。以下代码均按可运行版本给出。代码要点Timer(5, freq100)将定时器 5 配置为100 Hz即每个 PWM 周期为1 / 100 0.01 s10 mstim.channel(1, Timer.PWM, pinpyb.Pin.board.X1, pulse_width0)取通道 1模式为Timer.PWM输出引脚绑定X1初始脉宽为 0LED 初始全灭。脉宽pulse_width是什么在 PWM 模式下LED 的亮度由脉宽决定——即每个周期内 LED 处于点亮状态的时间长度。定时器频率为 100 Hz 时每个周期 10 ms脉宽就是这 10 ms 内高电平占用的时长。从源码看脉宽的单位是定时器时钟的 tick而不是纳秒/微秒。相关证据在 ports/stm32/timer.c第 16471658 行注释明确说明capture、compare、pulse_width是同一个底层函数的不同别名在 PWM 模式下应当使用pulse_width这个语义名第 16601661 行注释给出换算关系边沿对齐edge aligned模式下pulse_width period 1时占空比为 100%中心对齐模式下pulse_width period时占空比为 100%。也就是说占空比 pulse_width / (period 1)。以 pyboard 的定时器时钟APB 总线时钟其计算逻辑见 ports/stm32/timer.c 第 354429 行的注释可以推断100 Hz 对应约 84 万个 tick 的周期因此教程中的min_width 20000对应约 2.4% 的占空比暗光max_width 200000对应约 23.8% 的占空比较亮但远未到全亮为渐亮动画留足了动态范围。实现渐亮动画Fading Effect掌握了脉宽与亮度的关系渐亮动画就顺理成章从最小脉宽开始每个循环把脉宽增加一个固定步长超过最大值后回到最小值周而复始# 最大和最小脉宽分别对应最大和最小亮度 max_width 200000 min_width 20000 # 每一步脉宽的改变量 wstep 1500 cur_width min_width while True: tchannel.pulse_width(cur_width) # 决定多久改变一次脉宽相当于动画的帧率 sleep(0.01) cur_width wstep if cur_width max_width: cur_width min_width运行后即可看到 LED 从暗到亮、然后瞬间跳回最暗、再次渐亮的循环效果。其中tchannel.pulse_width(cur_width)是每次循环的核心调用它直接写定时器通道的比较寄存器对应 ports/stm32/timer.c 第 16621672 行的pyb_timer_channel_capture_compare实现最终调用__HAL_TIM_SET_COMPAREsleep(0.01)相当于动画的帧间隔配合 1500 的步长从 20000 走到 200000 大约需要 120 帧、约 1.2 秒形成肉眼舒适平滑的渐变。进阶呼吸灯效果Breathing Effect渐亮后瞬间熄灭是循环动画而渐亮 → 渐暗 → 渐亮的往复效果更像呼吸。实现方法只需一个技巧在到达亮度上限时反转wstep的符号到达下限时再次反转。while True: tchannel.pulse_width(cur_width) sleep(0.01) cur_width wstep if cur_width max_width: cur_width max_width wstep * -1 elif cur_width min_width: cur_width min_width wstep * -1与前一版代码相比只多了两处边界处理超过max_width时把脉宽钳制在max_width并让wstep取反亮度开始下降低于min_width时把脉宽钳制在min_width并再次取反亮度重新上升。如此往复LED 亮度呈现正弦般平滑的呼吸节奏。为什么亮得快、灭得慢韦伯定律与指数补偿细心的读者运行呼吸灯代码后会注意到LED 似乎从暗到亮的变化更快从亮到暗的变化更慢。这不是代码 bug而是人眼的感知特性造成的错觉。人眼对亮度的感知不是线性的而是对数的——这被称为韦伯定律Webers Law相关背景资料见原文档引用的 telescope-optics.net 亮度响应讨论。也就是说人眼对暗处的微小变化敏感对亮处的同等幅度变化不敏感而我们的代码让脉宽等步长线性增减实际亮度增量恒定映射到人眼感知后就显得不均匀了。原文档给出的思考题是对数函数的反函数是什么答案是指数函数y e^x。要做感知均匀的渐变应当让脉宽按指数曲线变化这样人眼感知到的亮度增量才是近似均匀的。参考解法from pyb import Timer from time import sleep import math tim Timer(5, freq100) tchannel tim.channel(1, Timer.PWM, pinpyb.Pin.board.X1, pulse_width0) max_width 200000 min_width 20000 steps 100 ratio max_width / min_width # 指数曲线的动态范围 while True: # 先上升指数插值 for i in range(steps): cur int(min_width * (ratio ** (i / steps))) tchannel.pulse_width(cur) sleep(0.01) # 再下降反向指数插值 for i in range(steps - 1, -1, -1): cur int(min_width * (ratio ** (i / steps))) tchannel.pulse_width(cur) sleep(0.01)用指数曲线驱动脉宽后呼吸节奏在感知上会更加均匀自然。这是一个非常典型的硬件线性输出 vs 感官非线性感知的工程折中问题也是本教程最有价值的引申思考。备选方案DAC 与 PWM 的取舍原文档的附注Addendum还提到另一种实现途径数字模拟转换器DAC。pyboard 的X5引脚支持 DAC在 docs/pyboard/quickref.rst 中有对应示例from pyb import Pin, DAC dac DAC(Pin(X5)) dac.write(120) # 输出范围 0~255两种方案各有利弊DAC 方案直接输出连续的模拟电压代码上更直观但 DAC 输出的是电压LED 的电流取决于电路且 DAC 通道数量有限PWM 方案本教程采用每次都以相同的电流驱动 LED只是点亮的时间长短不同见原文档 Addendum 的说明。由于 LED 的亮度与驱动电流之间未必呈线性关系PWM 这种恒定电流 可变时长的方式反而能带来更精确、更一致的亮度控制效果。在实际工程中PWM 也是驱动 LED、舵机、蜂鸣器等最常见的手段通用性更强。源码视角PWM 的完整配置面与底层机制最后把教程里的 API 放到 ports/stm32/timer.c 的源码语境中可以看清它的完整能力面1.Timer.channel()的关键字参数timer.c 第 11641213 行参数含义mode必填Timer.PWM即本教程使用的脉宽调制模式pin输出引脚不传则保持None传入时会为该引脚配置对应定时器通道的复用功能引脚与定时器不匹配会报错pulse_width初始脉宽tick 单位整数pulse_width_percent初始脉宽占空比百分比0100支持浮点如 25 表示 25% 占空比pulse_width_us初始脉宽微秒pulse_width_ns初始脉宽纳秒callback通道回调函数2. 脉宽的多种设置方式timer.c 第 16751725 行除了教程使用的pulse_width()TimerChannel还提供pulse_width_percent()、pulse_width_us()、pulse_width_ns()均可不传参读取、传参设置。其中pulse_width_percent的实现compute_percent_from_pwm_value/compute_pwm_value_from_percent会把 0100 的百分比换算成对应的比较寄存器值适合需要直接按占空比调光的场景。比如 quickref 中的写法p Pin(X1) # X1 同时具备 TIM2 CH1 的复用功能 tim Timer(2, freq1000) ch tim.channel(1, Timer.PWM, pinp) ch.pulse_width_percent(50) # 直接设为 50% 占空比3. 定时器频率如何落地timer.c 第 449498 行Timer(5, freq100)的 100 Hz 并非直接写入硬件而是由compute_prescaler_period_from_freq()根据定时器输入时钟计算出一组预分频系数prescaler与计数周期period再配置到硬件定时器寄存器。这也是为什么脉冲宽度的最大值天然受period约束——脉宽超过period 1后占空比将超过 100%实际效果等同恒高电平所以教程用 200000 作为安全上限而不是更大值。总结本教程用最少的硬件一颗 LED、一个电阻、几根线演示了 MicroPython 最具代表性的硬件控制能力之一——PWM 脉宽调制输出。回顾要点选对引脚PWM 必须使用与定时器通道存在硬件映射的引脚X1PA0的复用功能同时支持TIM2_CH1与TIM5_CH1两个对象Timer决定频率/周期与TimerChannel决定脉宽/占空比脉宽本质是定时器 tick 数占空比 pulse_width / (period 1)三种动画线性渐亮、往返呼吸灯、基于指数曲线的感知均匀渐变两种输出方式PWM恒定电流 可变时长与 DAC可变电压本场景 PWM 更优。继续深入可阅读同目录下的 pyboard 快速参考、pyb.Timer 模块文档 以及定时器完整实现的 ports/stm32/timer.c。掌握了 PWM 之后下一个自然的进阶方向是用相同思路驱动舵机角度或生成 PWM 音频信号。【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
