ESP32 - Micropython ESP-IDF 双线教程 脉宽调制(PWM)
-
- [PWM 的基本原理](#PWM 的基本原理)
- [PWM 的应用](#PWM 的应用)
- [PWM 的优点](#PWM 的优点)
- [PWM 的实现方式](#PWM 的实现方式)
- [ESP32-micropython 中的 PWM 功能](#ESP32-micropython 中的 PWM 功能)
-
- [使用 micropython 控制 PWM 的代码示例](#使用 micropython 控制 PWM 的代码示例)
- 代码介绍
- [ESP32-IDF 中的 PWM 功能](#ESP32-IDF 中的 PWM 功能)
脉宽调制(PWM,Pulse Width Modulation)是一种模拟控制技术,通过数字手段来产生模拟效果。它基于一种思路:通过对一系列脉冲的宽度进行调制,从而等效地获得所需要的波形(含形状和幅值)。在电子电路中,PWM 波形通常用于控制模拟电路,因为它具有比传统模拟方法更高的分辨率和更简单的电路结构。
PWM 的基本原理
PWM 的基本原理是在一个固定的周期(或称为"载波周期")内,改变脉冲信号的高电平时间(或称为"占空比")来模拟不同的模拟信号。占空比是指在一个周期内,高电平时间(脉冲宽度)与整个周期时间的比值。例如,如果占空比为 50%,则在一个周期内,高电平时间等于低电平时间。
PWM 的应用
PWM 在许多领域都有广泛的应用,包括但不限于:
-
LED 亮度控制:通过改变 PWM 的占空比,可以控制 LED 的平均电流,从而控制其亮度。这种方法比传统的模拟电压控制更为精确和高效。
-
电机速度控制:PWM 可以用于控制直流电机或步进电机的速度。通过改变 PWM 的占空比,可以控制电机的平均输入电压,从而控制其转速。
-
音频放大:PWM 可以用于音频放大器的功率控制。与传统的线性放大器相比,PWM 放大器具有更高的效率和更低的失真。
-
电源管理:PWM 可以用于电源管理中的电压调节和电流控制。例如,在计算机电源的 DC-DC 转换器中,PWM 用于控制输出电压。
-
通信和信号处理:在某些通信和信号处理系统中,PWM 可以用于编码和解码信息。
PWM 的优点
-
分辨率高:PWM 的分辨率仅受限于载波频率和脉冲宽度的精度。通过提高载波频率和使用高精度的脉冲宽度控制,可以实现非常高的分辨率。
-
效率高:由于 PWM 是一种数字控制方法,因此它可以利用数字电路的高效性。与传统的模拟控制方法相比,PWM 控制通常具有更高的效率。
-
灵活性强:PWM 可以很容易地通过改变占空比来模拟不同的模拟信号。这使得 PWM 在许多应用中都非常灵活和方便。
-
抗干扰能力强:由于 PWM 是一种数字信号,因此它具有较强的抗干扰能力。即使在存在噪声和干扰的情况下,PWM 信号也能保持较好的稳定性和可靠性。
PWM 的实现方式
PWM 的实现方式有很多种,包括软件 PWM 和硬件 PWM。软件 PWM 是通过编程来产生 PWM 信号的方法,它通常使用定时器中断来周期性地改变脉冲的宽度。硬件 PWM 是通过专门的硬件电路来产生 PWM 信号的方法,它通常具有更高的精度和更低的噪声。在 ESP32 这样的微控制器中,通常提供了硬件 PWM 支持,使得用户可以方便地实现 PWM 控制。
ESP32-micropython 中的 PWM 功能
在 ESP32-micropython 中,可以使用 machine
模块中的 PWM
类来创建和操作 PWM 信号。PWM 对象可以配置为不同的频率和占空比,以产生所需的输出信号。
使用 micropython 控制 PWM 的代码示例
以下是一个简单的示例,展示了如何使用 ESP32-micropython 和 GPIO 来控制一个 LED 的亮度,模拟呼吸效果。我们将使用一个按钮(连接到另一个 GPIO)来改变呼吸速度。
python
import machine
import utime
# 配置 PWM 引脚和频率
led_pin = machine.Pin(2, machine.Pin.OUT) # 假设 LED 连接到 GPIO 2
pwm = machine.PWM(led_pin)
pwm.freq(1000) # 设置 PWM 频率为 1kHz
# 配置按钮引脚
button_pin = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP) # 假设按钮连接到 GPIO 0,并启用上拉电阻
# 呼吸效果函数
def breathe(brightness_max, speed):
brightness = 0
increment = brightness_max / 10 # 分为 10 步增加/减少亮度
while True:
for i in range(brightness_max, 0, -increment):
pwm.duty_u16(int(i * 65535 / brightness_max)) # 设置 PWM 占空比
utime.sleep_ms(speed) # 等待一段时间以控制呼吸速度
for i in range(0, brightness_max, increment):
pwm.duty_u16(int(i * 65535 / brightness_max))
utime.sleep_ms(speed)
# 初始呼吸速度
speed = 50 # 毫秒
try:
while True:
if not button_pin.value(): # 检测到按钮按下
# 等待按钮释放
while not button_pin.value():
pass
# 改变呼吸速度
speed = speed * 2 if speed < 200 else 50 # 如果速度小于 200ms,则加倍;否则重置为 50ms
print("Changed breath speed to:", speed, "ms")
breathe(255, speed) # 调用呼吸效果函数
except KeyboardInterrupt:
pwm.deinit() # 清理 PWM 对象
machine.reset() # 重启设备
代码介绍
- 导入必要的模块 :我们导入了
machine
模块,用于访问 ESP32 的硬件功能,以及utime
模块,用于精确的时间控制。 - 配置 PWM 和按钮引脚:我们设置了 LED 和按钮连接的 GPIO 引脚,并初始化了 PWM 对象,设置了其频率。
- 定义呼吸效果函数:这个函数通过改变 PWM 的占空比来模拟呼吸效果。它使用两个嵌套的 for 循环来逐渐增加和减少亮度。
- 主循环:在主循环中,我们不断调用呼吸效果函数。当检测到按钮按下时,我们改变呼吸速度。注意,我们使用了简单的去抖动逻辑来确保只检测一次按钮按下。
- 异常处理 :我们使用
try-except
块来处理可能的 KeyboardInterrupt 异常(例如,用户按下了复位按钮)。在异常处理程序中,我们清理了 PWM 对象并重启了设备。
ESP32-IDF 中的 PWM 功能
ESP32的PWM库函数主要用于配置和控制PWM(脉宽调制)信号。这些函数通常是在ESP-IDF(Espressif IoT Development Framework)中提供的,以下是对ESP32 PWM库函数的一些讲解:
1. 初始化配置函数
- ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits)
- 功能:设置PWM通道的频率和分辨率。
- 参数 :
channel
:PWM通道号,范围从0到15。freq
:PWM频率,最大频率由公式80000000 / 2^bit_num
给出,其中bit_num
是分辨率位数。resolution_bits
:PWM分辨率位数,支持1到16位。分辨率和频率成反比。
2. 引脚绑定函数
- ledcAttachPin(uint8_t pin, uint8_t channel)
- 功能:将GPIO引脚绑定到指定的PWM通道。
- 参数 :
pin
:要绑定的GPIO引脚号。channel
:PWM通道号,与ledcSetup
函数中设置的通道对应。
3. 占空比设置函数
- ledcWrite(uint8_t channel, uint32_t duty)
- 功能:设置指定PWM通道的占空比。
- 参数 :
channel
:PWM通道号。duty
:占空比值,与PWM分辨率有关。
4. 读取函数
- ledcRead(uint8_t channel)
- 功能:读取指定PWM通道的当前占空比值。
- 参数 :
channel
,PWM通道号。
5. 更改频率函数
- ledcChangeFrequency(uint8_t chan, uint32_t freq, uint8_t bit_num)
- 功能:更改PWM通道的频率和分辨率。
- 参数 :
chan
:PWM通道号。freq
:新的PWM频率。bit_num
:新的PWM分辨率位数。
6. 其他功能函数
- ledcWriteTone(通道,频率) 和 ledcWriteNote(channel, note, oc) (注意:这些函数可能在某些库版本中不存在或名称略有不同)
- 功能:这些函数允许开发者以特定的频率或音符播放PWM信号,通常用于音频应用。
归纳
- 初始化 :使用
ledcSetup
函数设置PWM通道的频率和分辨率。 - 引脚绑定 :使用
ledcAttachPin
函数将GPIO引脚绑定到PWM通道。 - 占空比控制 :使用
ledcWrite
函数设置PWM通道的占空比。 - 读取 :使用
ledcRead
函数读取PWM通道的当前占空比。 - 更改频率 :使用
ledcChangeFrequency
函数更改PWM通道的频率和分辨率(如果需要)。 - 其他功能 :使用其他函数(如
ledcWriteTone
和ledcWriteNote
)实现特定应用需求。
请注意,以上函数和参数是基于ESP-IDF库的一般描述,实际使用时可能需要根据具体的库版本和开发环境进行调整。建议查阅ESP-IDF的官方文档以获取最准确和最新的信息。
1. 初始化PWM
首先,需要初始化PWM模块,并配置PWM通道的参数,如频率、占空比等。
2. 配置GPIO引脚
需要配置用于PWM输出的GPIO引脚,以及用于按钮输入的GPIO引脚。
3. 编写呼吸效果函数
这个函数将循环改变PWM的占空比,以模拟呼吸效果。
4. 编写主循环
在主循环中,检测按钮的输入,并根据需要改变呼吸速度。
示例代码
c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/pwm.h"
#include "driver/gpio.h"
#define LED_PWM_CHANNEL 0 // 假设使用PWM通道0
#define LED_GPIO_NUM 2 // 假设LED连接到GPIO 2
#define BUTTON_GPIO_NUM 0 // 假设按钮连接到GPIO 0
#define PWM_HZ 1000// PWM频率设置为1kHz
#define BREATHE_MAX 1023// 占空比最大值(10位PWM)
static void breathe_led(uint16_t max_brightness, uint32_t speed_ms);
void app_main(void)
{
// 初始化PWM
pwm_config_t pwm_config = {
.freq_hz = PWM_HZ,
.duty_mode = PWM_DUTY_MODE_MS,
.intr_mode = PWM_INTR_DISABLE,
.output_select_low = PWM_OUTPUT_LOW_HIGH,
.clk_sel = PWM_SEL_APB_CLK,
};
pwm_init(LED_PWM_CHANNEL, &pwm_config, 1, NULL);
pwm_set_pin(LED_PWM_CHANNEL, LED_GPIO_NUM);
// 初始化GPIO(按钮)
gpio_pad_select_gpio(BUTTON_GPIO_NUM);
gpio_set_direction(BUTTON_GPIO_NUM, GPIO_MODE_INPUT);
gpio_set_pull_mode(BUTTON_GPIO_NUM, GPIO_PULLUP_ONLY);
// 初始呼吸速度
uint32_t speed_ms = 50;
// 呼吸效果主循环
while (1) {
if (gpio_get_level(BUTTON_GPIO_NUM) == 0) { // 检测到按钮按下
// 等待按钮释放(简单去抖动)
vTaskDelay(pdMS_TO_TICKS(20));
if (gpio_get_level(BUTTON_GPIO_NUM) == 0) {
// 改变呼吸速度
speed_ms = (speed_ms < 200) ? speed_ms * 2 : 50;
printf("Changed breath speed to: %d ms\n", speed_ms);
// 稍微等待以确保按钮完全释放
vTaskDelay(pdMS_TO_TICKS(20));
}
}
breathe_led(BREATHE_MAX, speed_ms);
}
}
static void breathe_led(uint16_t max_brightness, uint32_t speed_ms)
{
uint16_t brightness = 0;
uint16_t increment = max_brightness / 10; // 分为10步增加/减少亮度
while (1) {
for (brightness = 0; brightness <= max_brightness; brightness += increment) {
pwm_set_duty(LED_PWM_CHANNEL, 0, brightness);
vTaskDelay(pdMS_TO_TICKS(speed_ms));
}
for (brightness = max_brightness; brightness > 0; brightness -= increment) {
pwm_set_duty(LED_PWM_CHANNEL, 0, brightness);
vTaskDelay(pdMS_TO_TICKS(speed_ms));
}
}
}