ESP32 LED_PWM模块应用
这几天在等待服务器调试,所以打算把一些周边的功能做一下。最无聊的当然是指示灯啦。在准备随手开个软件定时器的时候,居然发现ESP32有一个专门的LED PWM模块,真是神奇。当然事情没有这么简单,ESP32 LED PWM模块应该是为了专业的灯光调制特别做的,当然也可以用于产生其它特定需求的PWM波形。
1:ESP32 LEDPWM模块架构
官方文档《esp32_technical_reference_manual》中关于LED PWM模块有比较仔细的描述,这里理一下:
1.1 ESP32 LED_PWM 高速/低速通道
<1>ESP32LED PWM模块由16路通道组成,高速/低速各8路通道,对应h/l_ch0~8。
<2>高、低速通道由定时器(其实是分频器)驱动,高、低速通道各有4个定时器,对应h/l_timer0~4。
<3>高、低速通道的区别仅在于:
A时钟源:高速通道使用系统的APB_CLK或者REF_CLK;低速通道使用系统的REF_CLK或者SLOW_CLK。
B当软件修改了高速通道计数器的最大值或分频系数的话,输出信号的更新将会在下一次溢出中断之后生效。而低速通道在置位 LEDC_LSTIMERx_PARA_UP 之后,立刻更新计数器的计数范围参数和分频器的分频系数。
1.2 ESP32 LED_PWM高速/低速通道结构
ESP32 LED_PWM模块由16个高低速通道构成,通道包括通道分频器(h/l_timer)与比较输出通道(h/l_chn),结构如下:
通道分频器(h/l_timerx)包括:时钟源选择器,时钟源分频器,时钟计数器;通道分频器决定输出PWM波的频率。
A:时钟源选择器:
时钟源输入到通道定时器。高速通道使用系统的ABP_CLK或者REF_CLK作为时钟源;低速通道使用系统REF_CLK或者SLOW_CLK作为时钟源。
B:时钟源分频器:
输入到LED_PWM通道的时钟首先需要分频:分频系数LEDC_CLK_DIV_NUM_H/LSTIMERx固定为18位,分频系数公式:
公式中A是LEDC_CLK_DIV_NUM_H/LSTIMER高10位,B是其低8位;
若B不为0,则256个输出周期中有B个以(A+1)分频,(256-B)个以A分频;
B个以(A+1)分频的周期会均匀分布在256个周期内。
C:时钟计数器:
时钟源分频器输出时钟输入时钟计数器,时钟计数器计数范围由LEDC_HSTIMERx_DUTY_RES配置,当到达最大值也就是2^ LEDC_HSTIMERx_DUTY_RES – 1时,计数器产生溢出中断(LEDC_H/LS_TIMERn_OVF_INT)。时钟计数器可以被软件暂停,复位,以及读取。计数器最大可被设置为20位。
D:高速通道最终输出的PWM信号频率计算公式:
低速通道的输出频率类似,换算时钟和对应寄存器数值就可以获知。
比较输出通道(h/l_chx)接收来自通道分频器输出的脉冲并计数,根据计数值与高位/低位比较器预设值进行比较,变更PWM波引脚电平。比较输出通道决定PWM波占空比。
A:高位比较器:当比较输出通道接收来自通道分频器的脉冲数达到高位比较器设定值时,该通道连接的GPIO输出高电平,高位比较器取值(hpoint)只由LEDC_HPOINT_H/LSCHn确定
B:低位比较器:当比较输出通道脉冲计数值达到低位比较器设定值时,GPIO输出低电平。但低位比较器设定值(lpoint)由多个寄存器决定,这些寄存器共同决定PWM最终输出固定占空比或是可变占空比的波形:
决定低位比较器设定值的寄存器包括:
LEDC_DUTY_INC_H/LSCHn:占空比变化趋势,增加或减少
LEDC_DUTY_START_H/LSCHn:启动LEDC_DUTY_H/LSCHn设定的占空比
LEDC_DUTY_CYCLE_H/LSCHn:每计数CYCLE个脉冲,波形渐变一次
LEDC_DUTY_SCALE_H/LSCHn:每计数CYCLE个脉冲,LEDC_DUTY_H/LSCHn的值递增/递减SCALE
LEDC_DUTY_NUM_H/LSCHn:占空比变化的总次数,渐变完成时,会产生渐变完成中断(LEDC_DUTY_CHNG_END_LSCH/Ln_INT)
LEDC_DUTY_H/LSCHn:该寄存器高20位即是低位比较器的值(lpoint);低4位用于“抖动”lpoint的值:当低4位非0时,输出信号的脉冲宽度有LEDC_DUTY_HSCHn[3:0]/16 的概率多一个计数周期。低4位小数有利于提高输出信号占空比的精度。
综上所述,ESP32 LED_PWM模块由高/低速通道构成,每个通道由通道分频器与比较输出通道构成;每个通道都可以产生通道分频器溢出中断与占空比渐变完成中断。
下面简析ESP32 LED_PWM低速通道的通道分频器、比较输出通道配置以及如何设置输出固定占空比与可变占空比波形。
ESP32SDK关于LED_PWM模块的驱动位于:
\esp-idf-v3.0-rc1\components\driver\ledc.c
\esp-idf-v3.0-rc1\components\driver\include\ledc.h
2:通道分频器配置与PWM频率
ESP32LED_PWM通道分频器的配置接口是:
esp_err_tledc_timer_config(const ledc_timer_config_t* timer_conf)
参数类型ledc_timer_config_t:
对照通道分频器的结构,参数非常明显:
○speed_mode:选用高速/低速通道;意味着时钟源不同。
○duty_resolution:时钟计数器计数阈值设置,若duty_resolution = 10,则时钟计数器计数范围(0 - 2^10-1)
○timer_num:高低速通道均有4个定时器,该参数决定使用哪个
○freq_hz:通道分频器输出频率,也即是最终输出到GPIO的PWM波形的频率。
ledc_timer_config接口内会根据输入的参数计算并配置相应的寄存器。最终在:
ledc_timer_set(speed_mode, timer_num,div_param, duty_resolution, timer_clk_src)设置相关寄存器。
3:比较输出通道配置与PWM占空比
ESP32LED_PWM比较输出通道的配置接口是:
esp_err_tledc_channel_config(const ledc_channel_config_t* ledc_conf)
参数类型:ledc_channel_config_t:
相关参数也非常好理解,这里只关注intr_type与duty。
duty:
ledc_channel_config最终会通过ledc_set_duty将duty参数传入到ledc_duty_config:
参见1.2节对于波形输出通道配置的描述,可知函数ledc_duty_config是通过配置高位比较器与低位比较器来控制占空比,其中:
高位比较器取值相关参数:hpoint= 0;
低位比较器取值相关参数:
duty_increase = 1:占空比递增
duty_num = 1:占空比变化的次数,仅1次
duty_cycle = 1:每计数1个脉冲,占空比发生1次渐变
duty_value = duty << 4:duty_value即是lpoint的值,左移4位是因为低4位是小数部分
duty_scale = 0:计数1个(duty_cycle=1)脉冲,duty_value递增(duty_increase=1) duty_scale,而此处duty_scale = 0意味着波形占空比不变化
也就是说,通过ledc_channel_config配置比较输出通道,会得到一个占空比固定的波形。其占空比固定为duty/2^duty_resolution。
intr_type:该参数用于使能/禁止“fade”中断,也就是PWM波形占空比渐变完成时产生的中断。这个中断需要调用ledc_fade_func_install注册中断处理到ledc_fade_isr。
该功能用于控制PWM波占空比连续渐变,后面再提到。
4:LED频率闪烁实现
对于简单的LED闪烁提示功能,只需要设定闪烁频率,固定闪烁亮度(固定波形占空比)即可。下面是一个例子:
5:LED亮度渐变实现
LED若需要实现亮度渐变功能,则需要(连续)变化PWM占空比。ESP32 LED_PWM模块提供了相关的功能。
驱动内相关的API包括:
ledc_fade_func_install:注册LED_PWM“占空比渐变完成”中断处理函数ledc_fade_isr
ledc_set_fade:设置LED_PWM占空比渐变参数(不要直接用这个接口,一堆BUG)
ledc_set_fade_with_time/ledc_set_fade_with_step:按照设定的渐变时间或者步长设置LED_PWM占空比渐变参数
ledc_fade_start:启动渐变占空比的波形输出
输出占空比渐变的PWM归根结底还是要依赖设置ledc_duty_config的函数,也就是比较输出通道的高位点与低位点,以下是一个例子:
可以在示波器看到波形连续变化,LED越来越亮:
6:问题
ledc驱动下代码肯定有问题(手动笑脸),不过暂时够用,遇到复杂情况再行讨论。