## 用PWM控制LED亮度
### 1. RP2040的PWM
脉宽调制(PWM)是一种数字信号提供平滑变化的平均电压的方案。这是通过一定宽度的正脉冲以一定的间隔实现的。高消耗时间的比例称为占空比。这可以用来近似模拟输出,或控制开关模式电力电子。
RP2040 PWM模块有8个相同的切片。每个薄片可以驱动两个PWM输出信号,或测量一个输入信号的频率或占空比。这提供了总共多达16个可控PWM输出。所有30个GPIO引脚都可以由PWM模块驱动。
{{ :rp2040_pwm.png |}}
单个PWM片。一个16位计数器从0计数到一些编程值,然后wrap到零,或计数回来,取决于PWM模式。A和B输出过渡高和低基于当前计数值和预编程的A和B阈值。计数器根据若干事件前进:它可以是自由运行的,或者由B引脚上输入信号的电平或边缘门控。分数分频器降低了整体计数率,以更好地控制输出频率。
A single PWM slice. A 16-bit counter counts from 0 up to some programmed value, and then wraps to zero, or counts back down again, depending on PWM mode. The A and B outputs transition high and low based on the current count value and the preprogrammed A and B thresholds. The counter advances based on a number of events: it may be free- running, or gated by level or edge of an input signal on the B pin. A fractional divider slows the overall count rate for finer control of output frequency.
每个PWM片配置如下:
* 16位计数器
* 8.4分数时钟分频器
* 两个独立的输出通道,占空比从0%到100%包含
* 用于频率测量的边缘敏感输入模式
* 用于占空比测量的电平敏感输入模式
* 可配置计数器warp值
* Wrap和水平寄存器是双缓冲,而PWM运行时,可以改变中断请求和DMA请求计数器Wrap以便没有竞争
* 相位可以在运行时精确地提前或延迟(增加一个计数)
可以通过单个全局控件寄存器同时启用或禁用片。然后这些薄片完美地同步运行,这样就可以通过多个薄片的输出来切换更复杂的电源电路。
{{ :rp2040_pwm_mach.png |}}
{{ :rp2040_pwm_reg.png |}}
### 2. 参考示例
使用电位器的直流电压来控制LED的亮度,LED的亮度由PWM进行控制,这个任务在MicroPython中非常简单。
# Raspberry Pi Pico LED PWM Test
# led-pwm.py
# POT - Pico GPIO 28 ADC2
# RED LED - Pico GPIO 25
# EETree Info&Tech 2021
# https://www.eetree.cn
import machine
import utime
led_red = machine.PWM(machine.Pin(25))
potentiometer = machine.ADC(28)
led_red.freq(1000)
while True:
led_red.duty_u16(potentiometer.read_u16())
在这个脚本中需要注意的一个关键项目是我们定义 "led_red "的方式。我们将其定义为 "PWM",而不是输出。电位器的定义与上一个脚本中的方式完全相同。
现在我们已经给输出赋予了 "PWM "的属性,它继承了许多其他参数。其中之一是PWM频率,我们将其设置为1000Hz。
在true循环中,我们不断地从电位器中获取无符号的16位值,并将其传递给LEDs占空比,也方便地指定为无符号的16位整数。
这就说明了两者保持相同的编号方案的价值,不需要将模拟值,真的是0到4095,映射到占空比,真的是0到100。
运行程序,你应该可以顺利地控制红色LED段的亮度。
### 2. 卓晴老师的例子
控制板载LED的波形是PWM运行。
from machine import Pin, PWM
import time
pwm = PWM(Pin(25))
pwm.freq(1000)
duty = 0
direction = 1
for _ in range(16*255):
duty += direction
if duty > 255:
duty = 255
direction = -1
elif duty < 0:
duty = 0
direction = 1
pwm.duty_u16(duty*duty)
time.sleep(0.001)
PWM是软件PWM,它可以设置在任意管脚上。初步测试过Pin0, 15, 16等等。都具有相类似波形。
### 3. 呼吸灯的例子
from machine import Pin, PWM,Timer
LED = PWM(Pin(25))
n = 0
def breathing(t):
global n
LED.duty_u16(abs(32000- n*1000))
n = (n + 1) % 64
T0 = Timer(-1)
T0.init(period=20, mode=Timer.PERIODIC, callback=breathing)
### 3. Maker.io上的文章
[[https://www.digikey.com/en/maker/projects/raspberry-pi-pico-and-rp2040-micropython-part-1-blink/58b3c31ac93649849b58824caa00529c|使用MicroPython点灯]]
### 4. 使用DMA来传递PWM控制字
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// Fade an LED between low and high brightness. An interrupt handler updates
// the PWM slice's output level each time the counter wraps.
#include "pico/stdlib.h"
#include
#include "pico/time.h"
#include "hardware/irq.h"
#include "hardware/pwm.h"
#ifdef PICO_DEFAULT_LED_PIN
void on_pwm_wrap() {
static int fade = 0;
static bool going_up = true;
// Clear the interrupt flag that brought us here
pwm_clear_irq(pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN));
if (going_up) {
++fade;
if (fade > 255) {
fade = 255;
going_up = false;
}
} else {
--fade;
if (fade < 0) {
fade = 0;
going_up = true;
}
}
// Square the fade value to make the LED's brightness appear more linear
// Note this range matches with the wrap value
pwm_set_gpio_level(PICO_DEFAULT_LED_PIN, fade * fade);
}
#endif
int main() {
#ifndef PICO_DEFAULT_LED_PIN
#warning pwm/led_fade example requires a board with a regular LED
#else
// Tell the LED pin that the PWM is in charge of its value.
gpio_set_function(PICO_DEFAULT_LED_PIN, GPIO_FUNC_PWM);
// Figure out which slice we just connected to the LED pin
uint slice_num = pwm_gpio_to_slice_num(PICO_DEFAULT_LED_PIN);
// Mask our slice's IRQ output into the PWM block's single interrupt line,
// and register our interrupt handler
pwm_clear_irq(slice_num);
pwm_set_irq_enabled(slice_num, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
irq_set_enabled(PWM_IRQ_WRAP, true);
// Get some sensible defaults for the slice configuration. By default, the
// counter is allowed to wrap over its maximum range (0 to 2**16-1)
pwm_config config = pwm_get_default_config();
// Set divider, reduces counter clock to sysclock/this value
pwm_config_set_clkdiv(&config, 4.f);
// Load the configuration into our PWM slice, and set it running.
pwm_init(slice_num, &config, true);
// Everything after this point happens in the PWM interrupt handler, so we
// can twiddle our thumbs
while (1)
tight_loop_contents();
#endif
}
## 返回目录
[[pico_micropython|MicroPython编程PICO训练板]]