1 ESP32上的PWM资源
ESP32本身提供了两种PWM输出,分别是
1.1 LED PWM 控制器 (LEDC)
ESP32上提供最高16路 LEDC的PWM输出,其中8路为高速PWM,8路为低速PWM,而高低速通道都分别含有4个对应的高/低速时钟。
该PWM输出主要针对LED的驱动(但实际应用中不限制)。这个针对是因为其预定义了类似逐渐增加占空比等操作,可以在不占用处理器资源的情况下,实现对亮度(幅值)和颜色(频率)的调控。
1.2 电机控制脉宽调制器 (MCPWM)
ESP32提供6个该PWM输出,含有两个MCPWM外设,每个外设可以提供3个PWM通道。 该PWM个人理解是包含更多高级的功能,但对于PWM波形的控制则相对没有LEDC的精细。 结构如下图所示,可以看到除了分频器、计时器、和操作器外,还包含了错误检测,专用捕获模块(例如捕获电机转速等)
2 ESP32-Arduino使用PWM资源
首先,上述的两个资源并不都在ESP32-Arduino上提供支持,仅LEDC提供支持(查询支持的外设)。 因此我们下文主要集中在LEDC中。但参考上一篇blogESP32+Arduino+VS code开发环境搭建+BLINK,若确实需要MCPWM,可以考虑在程序中调用ESP-IDF的语法去使用,这应该是可能的。
2.1 LEDC的频率与解析度
与Arduino Uno板的8位固定PWM解析度不同,ESP32的PWM解析度是动态的,与设定相关。 解析度:即输出PWM波输出中,对时间的离散化精度,对应的结果,若是等频率,改变占空比以改变等效幅值的应用中,会得到更精细的等效电压输出。例如,对于UNO,8位的解析度,则最小的变化电压是1/256*3.3V=0.01289V,而若是10位的解析度,则为0.0032V。 对于ESP32的LEDC输出,其最高解析度可以根据下式计算并向下取整,具体原因可参照下一节的解析。
[
l
o
g
2
(
80
M
H
z
P
W
M
输
出
频
率
)
]
f
l
o
o
r
[log_2(\frac{80MHz}{PWM输出频率})]_{floor}
[log2?(PWM输出频率80MHz?)]floor?
2.2 LEDC的结构
其中一路高速时钟+高速通道的结构如下所示,下面参照这一路对PWM波形产生的过程进行解释。
- 时钟输入
在高速分频器中,可以选择REF_TICK或APB_CLK,REF_TICK是APB_CLK经过分频后再得到的,因此小于APB_CLK。 默认情况下(即不对CPU的时钟进行修改),APB_CLK=80MHz。
一般来说,CPU在低功耗时,会采用RTC8M_CLK时钟工作,LEDC的低速通道可以采用REF_TICK或SLOW_CLOCK进行工作,而SLOW_CLOCK时钟可以选择采用RTC8M_CLK工作。 即当ESP32进入低功耗时,所有的外设都会应APB_CLK关闭而不能工作,而低速LEDC则可以正常工作,可以实现待机亮灯的操作。
- 信号频率配置
时钟信号会依次经过分频器和计数器,产生供后续比较用的信号。 计数器的周期直接决定了最后PWM的周期,即,如下图所示,从0到溢出值的过程对应了PWM波形的一个周期。 而频率的计算则如下式所示:
f
P
W
M
=
f
c
l
o
c
k
分
频
系
数
?
溢
出
值
f_{PWM}=\frac{f_{clock}}{分频系数*溢出值}
fPWM?=分频系数?溢出值fclock?? 分频系数由LEDC_CLK_DIV_NUM_HSTIMERx决定 溢出为
2
L
E
D
C
_
H
S
T
I
M
E
R
x
_
D
U
T
Y
_
R
E
S
2^{LEDC\_HSTIMERx\_DUTY\_RES}
2LEDC_HSTIMERx_DUTY_RES - PWM波形的产生
以下述图为例,上部分为经过分频器产生的计数信号,而下面则为实际输出的PWM波形。通过比较计数信号与hpoint,lpoint的值,决定了输出高电平的区间。 在这里对上述解析度的计算进行解释。从上图我们可以看出,在一个周期内,即从0到溢出值的过程,溢出值的大小直接决定了区间内脉冲的最小时间宽度。例如溢出值为256,则,最小的时间精度为1/256*一个周期时间。 因此,为了得到最高的解析度,首先肯定不希望对时钟进行分频,因为:
溢
出
值
=
时
钟
频
率
输
出
频
率
溢出值=\frac{时钟频率}{输出频率}
溢出值=输出频率时钟频率? 即若80MHz的时钟,为了输出40MHz的信号,则计数器只能在0,1之间变化,此时占空比只能为50%,解析度为1。而若输出20MHz的信号,则计数器可以在0,1,2,3间变化,则可以输出25%,50%,75%的信号,解析度为2。 这样,便可以计算出在当前时钟下可以实现的最大解析度了,默认情况下可以用上面的log式子进行计算(假设了时钟为80Hz)
并且,支持如下所示的渐变占空比的设定,并且增大减小的方向,与每次增大减小的步长都可以进行调整。 相关寄存器: LEDC_DUTY_INC_HSCHn:决定方向; LEDC_DUTY_SCALE_HSCHn:决定每次渐变的步长; LEDC_DUTY_NUM_HSCHn:决定渐变的步数;
2.3 LEDC的使用
输出一个5kHz,60%工作周期(40%占空比)的PWM讯号 基本逻辑:配置PWM通道–>指定输出引脚–>输出
double ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits);
void ledcWrite(uint8_t chan, uint32_t duty);
uint32_t ledcRead(uint8_t chan);读取设定duty
void ledcAttachPin(uint8_t pin, uint8_t chan);
void ledcDetachPin(uint8_t pin);
uint32_t brightness=0;
void setup(){
ledcSetup(0, 5000, 10);
ledcAttachPin(25, 0);
}
void loop(){
ledcWrite(0, brightness);
printf("%u\n", brightness);
if (!(++brightness % 1024)){
brightness=0;
}
}
调控PWM讯号的频率-让扬声器发出固定音量,不同音调的声音
ESP32不支持Arduino的tone()函数
double ledcWriteTone(uint8_t chan, double freq);
double ledcWriteNote(uint8_t chan, note_t note, uint8_t octave);
参考音阶、音名
void setup(){
ledcSetup(0, 20000, 10);
ledcAttachPin(25, 0);
}
void loop(){
ledcWriteTone(0, 262);
ledcWriteNote(0, Note_C, 4);
ledcWriteTone(0, 0);
}
附一个无源蜂鸣器驱动的程序
typedef struct data{
uint32_t pitch;
uint16_t interval;
} note;
note tones[] = {
{659, 1000},
{440, 1000}
};
byte toneSize = sizeof(tones) / sizeof(note);
void alarmSnd(){
static uint8_t i = 0;
ledcWriteTone(0, tones[i].pitch);
delay(tones[i].interval);
if (++i % toneSize==0){
i = 0;
}
}
void setup(){
ledcSetup(0, 20000, BITS);
ledcAttachPin(BUZZER_PIN, 0);
}
void loop(){
alarmSnd();
}
参考资料 《ESP32 超图解》-赵英杰 《ESP32 技术规格书》-乐鑫官方
|