
MicroPython 在 ESP8266 上的 PWM 脉冲宽度调制实战指南LED 呼吸灯与舵机控制【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython导读本文围绕官方教程 docs/esp8266/tutorial/pwm.rst 展开系统讲解在 ESP8266 平台上使用 MicroPython 的machine.PWM在数字引脚上产生模拟输出的完整方法包括 PWM 的物理原理、ESP8266 的硬件约束支持引脚与频率范围、PWM 对象的创建与配置、占空比取值规则以及两个经典实战——LED 呼吸灯渐变与舵机角度控制。阅读本文后你将掌握 ESP8266 上 PWM 的全部基础 API 用法并能从源码层面理解其单频率共享、软件定时器驱动的实现机制。一、PWM 是什么用数字引脚模拟模拟输出脉冲宽度调制Pulse Width ModulationPWM是一种在数字引脚上获得人工模拟输出的技术。数字引脚只能输出高3.3V或低0V两种电平但通过极快速地反复切换高低电平并控制高电平所占的时间比例就可以让外部设备感知到介于 0 与 3.3V 之间的平均电压。PWM 有两个核心参数频率frequency引脚每秒完成低→高→低完整切换周期的次数单位为 Hz占空比duty cycle一个周期内引脚保持高电平的时间占整个周期低电平时间 高电平时间的比例。占空比最大时引脚全程为高最小时全程为低。在实际应用中占空比越高LED 越亮、电机转速越快、舵机偏转角度越大。二、ESP8266 的 PWM 硬件约束哪些引脚可用、频率范围是多少官方教程明确给出了 ESP8266 平台的两个关键限制这两个限制直接来源于底层实现理解它们能避免开发中的常见坑支持 PWM 的引脚固定为 0、2、4、5、12、13、14 和 15共 8 个所有 PWM 通道必须使用同一个频率且该频率必须在1Hz ~ 1kHz之间。从源码看这两条限制并非文档杜撰而是写死在 ports/esp8266/esppwm.c 中的#define PWM_CHANNEL 8 // 最多 8 个通道 #define PWM_DEPTH 1023 // 占空比分辨率 0~1023 #define PWM_FREQ_MAX 1000 // 最大频率 1kHz // 可用的 PWM 引脚编号表 static const uint8_t pin_num[PWM_CHANNEL] {0, 2, 4, 5, 12, 13, 14, 15};同时频率下限在pwm_set_freq中被钳制为 1Hzvoid pwm_set_freq(uint16 freq, uint8 channel) { if (freq PWM_FREQ_MAX) { pwm.freq PWM_FREQ_MAX; } else if (freq 1) { pwm.freq 1; } else { pwm.freq freq; } pwm.period PWM_1S / pwm.freq; }为什么所有通道必须同频率从源码结构看ESP8266 的 PWM 是纯软件实现底层只使用一个 FRC1 硬件定时器预分频 DIVDED_BY_16、边沿中断模式中断服务函数pwm_tim1_intr_handler按各通道占空比换算出的时间差依次翻转对应 GPIOpwm_start()中先用插入排序pwm_insert_sort对所有通道的高电平时间排序再计算相邻时间差。因为只有一个定时器轮流驱动所有通道所以全局只能有一个频率而频率受限于定时器精度最高只能到 1kHz。需要留意官方另一处速查文档 docs/esp8266/quickref.rst 的表述是除 Pin(16) 外所有引脚都可用。两者并不矛盾——以教程 pwm.rst 为准明确保证支持的是 0、2、4、5、12、13、14、15 这 8 个引脚其余引脚即便创建成功也无法保证 PWM 输出正常。若传入不支持的引脚底层pwm_add会返回 -1并在 ports/esp8266/machine_pwm.c 中抛出ValueError: PWM not supported on pin %d。三、PWM 对象的基本用法3.1 创建引脚对象与 PWM 对象使用 PWM 前必须先创建引脚Pin对象再基于它创建 PWM 对象 import machine p12 machine.Pin(12) # 第一步创建引脚对象 pwm12 machine.PWM(p12) # 第二步基于引脚创建 PWM 对象也可以一步到位在 PWM 构造函数中直接通过关键字参数设置频率和占空比下文的呼吸灯与舵机示例都会用到这种写法。3.2 设置频率与占空比创建 PWM 对象后通过freq()与duty()方法即可设置参数 pwm12.freq(500) # 设置频率为 500Hz pwm12.duty(512) # 设置占空比为 512约 50%占空比的取值范围是 0 ~ 10230 表示全程低电平完全关闭1023 表示全程高电平完全打开512 恰好是 50% 占空比。超出该范围的数值会被**自动裁剪clip**到边界值——这一行为同样可以在 ports/esp8266/esppwm.c 的pwm_set_duty中看到if (duty 1) { pwm.duty[channel] 0; } else if (duty PWM_DEPTH) { pwm.duty[channel] PWM_DEPTH; } else { pwm.duty[channel] duty; }3.3 查看当前配置直接打印 PWM 对象MicroPython 会显示其当前配置 pwm12 PWM(12, freq500, duty512)freq()和duty()也可以不带参数调用来读取当前值即 getter/setter 二合一参见 extmod/machine_pwm.c 中machine_pwm_freq的参数个数判断逻辑。在 ports/esp8266/machine_pwm.c 的mp_machine_pwm_print中可以看到打印格式的实现——只有 PWM 处于激活状态self-active为真时才会输出freq和duty。3.4 关闭 PWMdeinit()引脚会一直保持 PWM 模式直到你显式释放它 pwm12.deinit()调用deinit()后底层执行pwm_delete(channel)将该通道从 8 通道表中移除并重新启动 PWM 定时器见 machine_pwm.c该引脚随即恢复为普通 GPIO 功能。3.5 补充duty_u16 与 duty_ns 两种占空比设置方式底层实现 machine_pwm.c 还提供了另外两种占空比设置方法与duty()的 0~1023 刻度不同duty_u16(value)按 0~65535 的 16 位刻度设置内部换算公式为duty * 1024 / 65535即 65535 满刻度对应 1023duty_ns(value)直接以纳秒为单位指定每个周期内的高电平持续时间内部换算公式为duty_ns * freq / 9765621024 级分辨率下每级约 976.56ns。这两种方式便于与其它使用不同刻度约定的代码如 CircuitPython、Arduino 库对接。四、实战一用 PWM 实现 LED 呼吸灯教程给出了一个经典实战让 LED 亮度按正弦曲线平滑呼吸。假设开发板例如 ESP-12 模块上已有 LED 连接到GPIO 2。4.1 创建 LED 的 PWM 对象注意这里直接在构造函数中设置了频率 led machine.PWM(machine.Pin(2), freq1000)这就是在 PWM 构造函数中设置参数的用法PWM(pin, freq1000)等价于先创建再调用freq(1000)。4.2 引入时间与数学模块呼吸效果需要定时和三角函数 import time, math4.3 编写脉冲函数 def pulse(l, t): ... for i in range(20): ... l.duty(int(math.sin(i / 10 * math.pi) * 500 500)) ... time.sleep_ms(t)该函数的核心逻辑是将math.sin()在 0~π 区间内采样 20 次i / 10 * math.pi正弦值域为 [-1, 1]乘以 500 再加 500 后映射到[0, 1000]区间正好落在占空比 0~1023 的合法范围内形成一次从暗到亮再到暗的完整渐变。time.sleep_ms(t)控制每次渐变之间的停留毫秒数t越小渐变越快。试运行一次 pulse(led, 50)要获得持续的呼吸效果可以连续多次调用 for i in range(10): ... pulse(led, 20)如果效果不理想或想提前终止随时可以用Ctrl-C中断正在运行的代码。五、实战二用 PWM 控制舵机角度模型舵机hobby servo同样通过 PWM 驱动是 PWM 最典型的应用之一。舵机对 PWM 参数有固定要求频率必须为 50Hz占空比在约 40 ~ 115 之间其中 **77 为居中90°**位置。接线方式舵机的电源线与地线接到开发板的 3.3V/5V 电源与 GND信号线接到 GPIO 12其它 PWM 引脚同样可用。然后 servo machine.PWM(machine.Pin(12), freq50) servo.duty(40) # 转到一端极限角度 servo.duty(115) # 转到另一端极限角度 servo.duty(77) # 回到中间位置这里的占空比数值含义可以这样换算50Hz 的周期是 20ms20000µs占空比 40 对应约 40/1023 × 20000µs ≈ 782µs 的高电平脉宽115 对应约 2248µs——这正是常见舵机 0.5ms~2.5ms 脉宽范围的近似值因此 duty 40~115 恰好对应舵机的左右极限角度。需要精确角度时可按目标脉宽 µs ÷ (20000/1023)估算对应的 duty 值。六、使用要点与源码级注意事项汇总默认频率 500Hz首次创建 PWM 对象时底层pwm_init()会调用pwm_set_freq(500, 0)将全局默认频率设为 500Hzesppwm.c此后所有通道共用该频率。新建通道占空比默认为 0pwm_add在注册通道时把 duty 清零因此刚创建的 PWM 对象默认输出低电平不会凭空产生波形。频率是全局共享的任意一个 PWM 对象的freq()调用都会改变所有通道的频率pwm_get_freq(0)/pwm_set_freq(freq, 0)均以通道 0 作为全局频率句柄所以不要期望不同引脚上同时输出不同频率的 PWM。不支持引脚的报错对 8 个受支持引脚之外的引脚调用machine.PWM会抛出ValueError: PWM not supported on pin N建议在代码中先用try/except兜底或做引脚合法性检查。占空比自动裁剪duty()传入的负数会变为 0大于 1023 的值会变为 1023无需手动 clamp。中断随时可用REPL 中运行的循环如呼吸灯随时可用 Ctrl-C 打断PWM 输出不会影响 REPL 的可用性。如需更简洁的 API 速查可参考 docs/esp8266/quickref.rst 的 PWM 小节若想验证 PWM 输出的频率与占空比仓库中还提供了基于machine.time_pulse_us的硬件测试 tests/extmod_hardware/machine_pwm.py可作为自行搭建验证电路的参考。通用的machine.PWM类框架freq、duty_u16、duty_ns、deinit等方法的参数处理定义在 extmod/machine_pwm.c而 ESP8266 特有的引脚绑定逻辑全部集中在 ports/esp8266/machine_pwm.c 与 ports/esp8266/esppwm.c感兴趣的读者可以顺着这三份源码深入理解单定时器驱动多通道软件 PWM的完整实现。输出文章【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考