ESP32 单片机学习笔记 - 03 - MCPWM脉冲输出/PCNT脉冲计数
Posted L建豪 忄YH
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP32 单片机学习笔记 - 03 - MCPWM脉冲输出/PCNT脉冲计数相关的知识,希望对你有一定的参考价值。
ESP32 单片机学习笔记 - 03 - MCPWM脉冲输出/PCNT脉冲计数
前言,继续上一篇的内容。因为上一篇刚好实验了iic和spi,形成一对。接下来讲pwm另起一篇。
一、电机PWM输出 - MCPWM
官方例程: esp-idf/examples/peripherals/mcpwm/mcpwm_basic_config/,官方有4个例程,我先看了这个,这个例程对
mcpwm
模块的所有功能的配置过程和使用方法 都有举例。开始就结合这个例程和编程指南一起看。
官方指南: MCPWM,虽然这次也是全英文的,不过感觉搭配例程来看还是很清晰的。
数据手册: ESP32 技术参考手册 (PDF),第十六章的:电机控制脉宽调制器 (PWM)。
- 首先,结合下图“ MCPWM 外设概览”介绍一下要用到的术语名词:(手册写的很简洁:每个 MCPWM 外设都有一个时钟分频器(预分频器),三个 PWM 定时器,三个 PWM 操作器和一个捕获模块。)
- 首先,整个模块和
APB BUS
总线挂钩,时钟信号由CLK_106M
输入到clock prescaler
时钟预分频器。 MCPWM
有3个TIMER x
定时器模块(0-2),可以外部输入3个SYNC x
同步信号(0-2),可以给3个OPERATOR x
操作器模块(0-2)使用,使其生成2路PWM x A/B
脉冲信号。- 还有
FAULT DETECT
故障检测 模块,可以外部输入3个FAULT x
触发(0-2)引脚,来触发故障。同理还有CAPTURE
捕获 模块,可外部输入CAP x
信号(0-2),模块可捕获其脉冲持续时间。 - 最后,大部分功能都可以链接
INTERRUPTS
中断 使用。
吐槽一下命名:
TIMER x
PWM定时器模块 和OPERATOR x
PWM操作器模块。具体功能在技术手册有详细介绍。两者功能组合配置pwm的功能。
另一个好奇就是,我没看到引脚定义里有专门标着pwm的引脚,加上例程中的宏定义命名。这个pwm输出引脚莫非是可以链接到任意引脚的?好方便啊。
1) 引脚初始化 gpio_init
- 第一步,要先初始化所有引脚,可选择使用单独设置函数,一个个设置,也可以调用结构体一次性初始化所有引脚。其中,外部输入信号的引脚(
CAP
、SYNC
、FAULT
)还需要调用函数使能。 - ESP32中好像有2个
MCPWM
单元,也就是每个单元都有上图中全部的东西。又因为“通过配置,任一 PWM 操作器可以使用任一 PWM 定时器的定时参考。”(技术手册写的),不过,“为了简化API,每个Timer被API自动关联,以驱动同一个索引的Operator,例如Timer 0与Operator 0关联。”(编程指南写的)。所以调用esp-idf的api时,不需要再把定时器和操作器放开选择,api已经一一对应选择上了。我们只需要选择哪个MCPWM
单元,和哪个MCPWM
定时器模块就好了。
static void mcpwm_example_gpio_initialize(void)
{
printf("initializing mcpwm gpio...\\n");
#if MCPWM_GPIO_INIT // 选择使用哪个函数来初始化gpio信号
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT); // 该函数初始化MCPWM的每个gpio信号。
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT); // 这个函数每次初始化一个gpio。
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_PWM1A_OUT); // mcpwm_num:设置MCPWM单元(0-1)
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1B, GPIO_PWM1B_OUT); // io_signal:设置MCPWM信号,每个MCPWM单元有6个输出(MCPWMXA, MCPWMXB)和9个输入(SYNC_X, FAULT_X, CAP_X)其中X是timer_num(0-2)
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM2A, GPIO_PWM2A_OUT); // gpio_num:设置为MCPWM配置gpio,如果你想使用gpio16, gpio_num = 16
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM2B, GPIO_PWM2B_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, GPIO_CAP0_IN); // 总结: mcpwm_gpio_init 函数可用来初始化当个引脚的pwm,简易快速的指定 MCPWM单元(0/1) 和 输出模块(A/B),还有引脚。
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_1, GPIO_CAP1_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_2, GPIO_CAP2_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_SYNC_0, GPIO_SYNC0_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_SYNC_1, GPIO_SYNC1_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_SYNC_2, GPIO_SYNC2_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_FAULT_0, GPIO_FAULT0_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_FAULT_1, GPIO_FAULT1_IN);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_FAULT_2, GPIO_FAULT2_IN);
#else
mcpwm_pin_config_t pin_config = {
.mcpwm0a_out_num = GPIO_PWM0A_OUT, // 使用结构体快速一次性指定多个引脚,然后使用初始化函数一次完成。
.mcpwm0b_out_num = GPIO_PWM0B_OUT,
.mcpwm1a_out_num = GPIO_PWM1A_OUT, // 惊奇的发现,如果跳转过去看的话,其实 mcpwm_set_pin() 函数里就是调用了一堆 单独设置函数 ……
.mcpwm1b_out_num = GPIO_PWM1B_OUT,
.mcpwm2a_out_num = GPIO_PWM2A_OUT,
.mcpwm2b_out_num = GPIO_PWM2B_OUT,
.mcpwm_sync0_in_num = GPIO_SYNC0_IN,
.mcpwm_sync1_in_num = GPIO_SYNC1_IN,
.mcpwm_sync2_in_num = GPIO_SYNC2_IN,
.mcpwm_fault0_in_num = GPIO_FAULT0_IN,
.mcpwm_fault1_in_num = GPIO_FAULT1_IN, // 如果有不使用的引脚,要配置 -1 。(或者直接不写也可以,如果是负值会被忽略,如果不填应该就是指 IO0 了???)
.mcpwm_fault2_in_num = GPIO_FAULT2_IN, // 毕竟 mcpwm_set_pin 源函数是很简单粗暴的调用了 mcpwm_gpio_init 来初始化
.mcpwm_cap0_in_num = GPIO_CAP0_IN,
.mcpwm_cap1_in_num = GPIO_CAP1_IN,
.mcpwm_cap2_in_num = GPIO_CAP2_IN
};
mcpwm_set_pin(MCPWM_UNIT_0, &pin_config); // 设置MCPWM单元(0 - 1) MCPWM销结构
#endif
// 使能下拉CAP0信号
gpio_pulldown_en(GPIO_CAP0_IN); //Enable pull down on CAP0 signal
gpio_pulldown_en(GPIO_CAP1_IN); //Enable pull down on CAP1 signal
gpio_pulldown_en(GPIO_CAP2_IN); //Enable pull down on CAP2 signal
gpio_pulldown_en(GPIO_SYNC0_IN); //Enable pull down on SYNC0 signal
gpio_pulldown_en(GPIO_SYNC1_IN); //Enable pull down on SYNC1 signal
gpio_pulldown_en(GPIO_SYNC2_IN); //Enable pull down on SYNC2 signal
gpio_pulldown_en(GPIO_FAULT0_IN); //Enable pull down on FAULT0 signal
gpio_pulldown_en(GPIO_FAULT1_IN); //Enable pull down on FAULT1 signal
gpio_pulldown_en(GPIO_FAULT2_IN); //Enable pull down on FAULT2 signal
}
2) 配置模块 config
mcpwm
配置,最基本的pwm输出(这一部分的参数配置也有一条条单独的配置函数可以调用,就不一一列举了,大概套路就是set
+name
结构体变量名。下面的其他配置的类似)
// mcpwm_config_t 结构中 设置定时器频率和初始占空比。
mcpwm_config_t pwm_config;
pwm_config.frequency = 1000; //frequency = 1000Hz 1000赫兹频率(看样子,每个pwm定时器可以设置不同的频率,而其下的2个通道是要同频率的)
pwm_config.cmpr_a = 60.0; //duty cycle of PWMxA = 60.0% PWMxA占空比= 60.0%
pwm_config.cmpr_b = 50.0; //duty cycle of PWMxb = 50.0% 可以同时设置2个通道的占空比
pwm_config.counter_mode = MCPWM_UP_COUNTER; // 设置MCPWM计数器类型,
//对于不对称MCPWM 或
//对于对称式MCPWM,频率为MCPWM设置频率的一半
pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // 设定占空比类型 (说人话就是占空比代表的是高电平还是低电平)
// 有源高占空比,即占空比与非对称MCPWM的高时间成正比 或
// 有源低占空比,即占空比与非对称MCPWM的低时间成正比,反相(倒)MCPWM
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //Configure PWM0A & PWM0B with above settings
// 使用以上设置配置PWM0A和PWM0B
carrier configuration
设置高频载波参数,我也不清楚这个功能有什么用,以前没接触过。之后用到了再补,先放出配置流程。
//in carrier mode very high frequency carrier signal is generated at mcpwm high level signal
// 在载波模式下,MCPWM高电平信号产生非常高频的载波信号
mcpwm_carrier_config_t chop_config;
chop_config.carrier_period = 6; //carrier period = (6 + 1)*800ns 载波周期
chop_config.carrier_duty = 3; //carrier duty = (3)*12.5% 重负荷载体
chop_config.carrier_os_mode = MCPWM_ONESHOT_MODE_EN; //If one shot mode is enabled then set pulse width, if disabled no need to set pulse width
// 如果开启了一次拍摄模式,请设置脉冲宽度,如果关闭了,则无需设置脉冲宽度
chop_config.pulse_width_in_os = 3; //first pulse width = (3 + 1)*carrier_period
// 第一脉冲宽度=(3 + 1)*载波周期
chop_config.carrier_ivt_mode = MCPWM_CARRIER_OUT_IVT_EN; //output signal inversion enable 输出信号反转
mcpwm_carrier_init(MCPWM_UNIT_0, MCPWM_TIMER_2, &chop_config); //Enable carrier on PWM2A and PWM2B with above settings
// 在PWM2A和PWM2B上使用上述设置使能运营商
//use mcpwm_carrier_disable function to disable carrier on mcpwm timer on which it was enabled.
// 使用 mcpwm_carrier_disable 功能去使能MCPWM定时器的载波
deadtime configuration
死区时间配置,以前实现过软件死区(不知道是不是同一个概念)。我比较少用。
//add rising edge delay or falling edge delay. There are 8 different types, each explained in mcpwm_deadtime_type_t in mcpwm.h
// 增加上升沿延迟或下降沿延迟。有8种不同的类型,每种类型在mcpwm.h中的mcpwm_deadtime_type_t中解释
// 在PWM2A和PWM2B上启用死时间,红色= (1000)*100ns
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_2, MCPWM_BYPASS_FED, 1000, 1000); //Enable deadtime on PWM2A and PWM2B with red = (1000)*100ns on PWM2A
// 在PWM1B上使用fed = (2000)*100ns使死时间
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_BYPASS_RED, 300, 2000); //Enable deadtime on PWM1A and PWM1B with fed = (2000)*100ns on PWM1B
// 启用死时间PWM0A和PWM0B与红色= (656)*100ns &馈电= (67)*100ns对PWM0A和PWM0B从PWM0A产生
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_ACTIVE_RED_FED_FROM_PWMXA, 656, 67); //Enable deadtime on PWM0A and PWM0B with red = (656)*100ns & fed = (67)*100ns on PWM0A and PWM0B generated from PWM0A
//use mcpwm_deadtime_disable function to disable deadtime on mcpwm timer on which it was enabled
//使用 mcpwm_deadtime_disable 功能关闭MCPWM定时器的死时间
- enable
fault condition
使能故障条件,个人理解,感觉像是硬件实现了外部中断使能pwm,起到保护作用。以往这一步是人为再软件层面判断实现的。之后可能会用到,因为不用再额外软件实现保护措施了。
// 当故障发生时,你可以配置MCPWM信号强制低电平,强制高电平或切换。
//whenever fault occurs you can configure mcpwm signal to either force low, force high or toggle.
// 在循环模式下,一旦故障状态结束,MCPWM信号就会恢复,而在一次性模式下,你需要重置。
//in cycmode, as soon as fault condition is over, the mcpwm signal is resumed, whereas in oneshot mode you need to reset.
// 当FAULT0信号出现高电平时,使能FAULT
mcpwm_fault_init(MCPWM_UNIT_0, MCPWM_HIGH_LEVEL_TGR, MCPWM_SELECT_F0); //Enable FAULT, when high level occurs on FAULT0 signal
mcpwm_fault_init(MCPWM_UNIT_0, MCPWM_HIGH_LEVEL_TGR, MCPWM_SELECT_F1); //Enable FAULT, when high level occurs on FAULT1 signal
mcpwm_fault_init(MCPWM_UNIT_0, MCPWM_HIGH_LEVEL_TGR, MCPWM_SELECT_F2); //Enable FAULT, when high level occurs on FAULT2 signal
// 发生FAULT0故障时,PWM1A和PWM1B的处理方法
mcpwm_fault_set_oneshot_mode(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_SELECT_F0, MCPWM_FORCE_MCPWMXA_HIGH, MCPWM_FORCE_MCPWMXB_LOW); //Action taken on PWM1A and PWM1B, when FAULT0 occurs
mcpwm_fault_set_oneshot_mode(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_SELECT_F1, MCPWM_FORCE_MCPWMXA_LOW, MCPWM_FORCE_MCPWMXB_HIGH); //Action taken on PWM1A and PWM1B, when FAULT1 occurs
mcpwm_fault_set_oneshot_mode(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_SELECT_F2, MCPWM_FORCE_MCPWMXA_HIGH, MCPWM_FORCE_MCPWMXB_LOW); //Action taken on PWM0A and PWM0B, when FAULT2 occurs
mcpwm_fault_set_oneshot_mode(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_SELECT_F1, MCPWM_FORCE_MCPWMXA_LOW, MCPWM_FORCE_MCPWMXB_HIGH); //Action taken on PWM0A and PWM0B, when FAULT1 occurs
// 通过例程可以推测,不同模块是可以连接到同一个错误信号中的,MCPWM_TIMER_1 和 MCPWM_TIMER_0 都有链接到 MCPWM_SELECT_F1 的输入。不过应该要处于统一单元才可以。
Syncronization configuration
同步 配置,手册没有专门来讲述怎么配置。是嵌套在讨论怎么控制电机里讨论(还是注解的部分)。好像使用起来也挺方便的,直接设定和同步源(引脚输入)的相位值就可以了。
// 在PWM1A和PWM1B上同步
//here synchronization occurs on PWM1A and PWM1B
// 当同步0发生时,mcpwm定时器1的周期计数器的20%的负载计数器值
mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_SELECT_SYNC0, 200); //Load counter value with 20% of period counter of mcpwm timer 1 when sync 0 occurs
Capture configuration
捕获 配置,好像不是捕获脉冲计数的,而是捕获脉冲持续时间的。在例程里还写了一个软件拉高拉低引脚的函数,和一个读取捕获中断打印捕获的持续时间的函数,供测试使用。
看了一下介绍,是可以用来捕获霍尔传感器?(和原本的理解霍尔编码器不一样)因为没接触过所以先跳过。 (看来我对“霍尔”一词还很抽象,查查资料感觉眼前一亮:快速扫盲 | 霍尔传感器的工作原理)
// 配置CAP0, CAP1和CAP2信号启动上升沿捕获计数器
//configure CAP0, CAP1 and CAP2 signal to start capture counter on rising edge
// 我们在GPIO 12上生成一个20ms的gpio_test_signal,并将其连接到捕获信号之一,disp_captured_function显示上升边缘之间的时间
//we generate a gpio_test_signal of 20ms on GPIO 12 and connect it to one of the capture signal, the disp_captured_function displays the time between rising edge
// 一般情况下,您可以将Capture连接到外部信号,测量上升沿或下降沿之间的时间,并采取相应的行动
//In general practice you can connect Capture to external signal, measure time between rising edge or falling edge and take action accordingly
// 捕获信号上升边缘,前标度= 0即800000000计数等于1秒
mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, MCPWM_POS_EDGE, 0); //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second
mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP2, MCPWM_POS_EDGE, 0); //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second
mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP1, MCPWM_POS_EDGE, 0); //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second
// 启用中断,这样每一个上升边发生中断就会被触发
//enable interrupt, so each this a rising edge occurs interrupt is triggered
// 对CAP0、CAP1和CAP2信号使能中断
MCPWM[MCPWM_UNIT_0]->int_ena.val = CAP0_INT_EN | CAP1_INT_EN | CAP2_INT_EN; //Enable interrupt on CAP0, CAP1 and CAP2 signal
mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler 设置ISR处理程序
- 以上就是
mcpwm_basic_config_example
例程的主要内容了。主要用到的,应该是gpio初始化
、pwm初始化
,如果需要可能会尝试故障检测
、同步信号
,或是死区时间
。再翻翻其他例程。 - 翻完了,总结:
mcpwm_basic_config_example
是总配置例程,使用了所有模块的功能;mcpwm_bldc_control_hall_sensor_example
是无刷电机的例程,使用了pwm和capture的功能;mcpwm_brushed_dc_control_example
是直流电机的例程,简单的正反转加停止的pwm功能;mcpwm_servo_control_example
是伺服电机的例程,也只使用了单纯的pwm功能,不过其中加了一个计算脉冲和角度之间关系的函数。以上就是4个例程的内容了。
在直流电机的例程中,可以从思路上看出来,模块设置AB通道,就是为了给一个电机正反转用的。如果是只需要单脉冲+方向的驱动。那可以少初始化一个端口了吧(?)
3)PWM控制
- 最后补充,配置了那么多,最重要还是要怎么用pwm,esp32的api提供了功能给我们调用?(以下是编程指南的翻译)
- 我们可以使用函数
mcpwm_set_signal_high()
或mcpwm_set_signal_low()
来驱动特定的信号稳定高或稳定低。这将使电机以最高速度转动或停止。根据选择的输出A或B,电机将旋转或右或左。 - 另一种选择是通过调用
mcpwm_start()
或mcpwm_stop()
来驱动输出。电机的速度将成比例的PWM负荷。 - 改变PWM的占空比调用
mcpwm_set_duty()
并提供pwm的占空比。如果您喜欢以微秒为单位设置占空比,您可以选择调用mcpwm_set_duty_in_us()
。可以通过调用mcpwm_get_duty()
来检查当前设置的值。PWM信号的相位可以通过调用mcpwm_set_duty_type()
来改变。在特定的函数调用中使用mcpwm_generator_t
为每个A和B输出单独设置任务。占空比是指输出信号持续时间的高或低。这是在调用mcpwm_init()
并从mcpwm_duty_type_t
中选择一个选项时配置的,如Configure
部分所述(也就是初始化配置中)。其中的mcpwm_duty_type_t
应该是值占空比表示的是高电平还是低电平,可以根据这个修改。 - 每次在
mcpwm_set_signal_high()
或mcpwm_set_signal_low()
之后调用函数mcpwm_set_duty_type()
以恢复之前设置的工作负载周期。
- 以下是
mcpwm_brushed_dc_control_example
例程的一部分,结合上面的说法,加深理解。应用到电机上的话,改变pwm要三步走。(还要自己实践,不能光看例程介绍)
/** 电机向前移动,占空比=占空比%
* @brief motor moves in forward direction, with duty cycle = duty %
*/
static void brushed_motor_forward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num , float duty_cycle)
{
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B); // 直接将B通道拉低
// !!!!!!!!!!! 特别注意,第三个参数是通道号的枚举,不是引脚号
mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle); // 设置A通道的占空比
// 如果操作员以前处于低/高状态,那么每次都调用它 // 设置A通道的工作模式(同时也是为了恢复占空比输出,即取消持续低的设置)
mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
}
/** 电机反向运动,占空比=占空比%
* @brief motor moves in backward direction, with duty cycle = duty %
*/
static void brushed_motor_backward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num , float duty_cycle)
{
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A); // 同理上面
mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle);
mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
}
/** 电机停
* @brief motor stop
*/
static void brushed_motor_stop(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num)
{
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A); // 2个通道都直接拉低
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);
}
- 有两种方法来调整输出信号和改变电机的工作方式。(以下是编程指南的翻译)
- 通过调用
mcpwm_set_frequency()
来设置特定的PWM频率。这可能需要调整以适应特定电机和驱动器的电气或机械特性。要检查设置了什么频率,使用函数mcpwm_get_frequency()
。 - 当输出a和B改变状态以反转电机旋转方向时,在输出a和B之间引入死区时间。这是为了弥补电机驱动器fet的开/关开关延迟。死区时间选项在
mcpwm_deadtime_type_t
中定义,并通过调用mcpwm_deadtime_enable()
来启用。要禁用此功能,请调用mcpwm_deadtime_disable()
。 - 同步运算子模块的输出,例如使PWM0A/B和PWM1A/B的上升边同时启动,或将它们彼此移动一个给定的相位。同步是由上面
MCPWM框图
中所示的SYNC SIGNALS
触发的,并在mcpwm_sync_signal_t
中定义。将信号附加到GPIO调用mcpwm_gpio_init()
。然后可以使用mcpwm_sync_enable()
函数启用同步。作为MCPWM单元的输入参数,定时器进行同步,同步信号和延时定时器的相位。 - 注释:同步信号使用两个不同的枚举来引用。当选择一个GPIO作为信号输入源时,首先将一个
mcpwm_io_signals_t
与函数mcpwm_gpio_init()
一起使用。第二个参数mcpwm_sync_signal_t
用于启用或禁用与mcpwm_sync_enable()
或mcpwm_sync_disable()
的同步。 - 改变A/B输出信号的模式,让MCPWM计数器计数向上,向下和向上/向下(自动改变计数方向)。在调用
mcpwm_init()
和从mcpwm_counter_type_t
中选择一种计数器类型时,将完成相应的配置(如Configure
小节所述)。有关A/B PWM输出信号如何产生的解释,请参见ESP32 Technical Reference Manual > Motor Control PWM (MCPWM)
PDF。
一些实战的测试代码写在工程里了。只用到了基本的pwm配置,试着调了几个参数,能用,好,过,下一个。
二、编码器脉冲输入 - Pulse Counter
官方例程: esp-idf/examples/peripherals/pcnt/,在github上有2个例程,但是在vscode的插件上看的话只有一个,是介绍用脉冲计数来读ledpwm的脉冲。我看了一下github上的另一个例程,居然是编码器的。这才是我想要的。太棒了。
官方指南: Pulse Counter,虽然是英文,不过看到现在已经习惯的。而且这次的篇幅很少,历程的内容也很少。
数据手册: ESP32 技术参考手册 (PDF),第十七章的:脉冲计数器 (PCNT)。
- 按照惯例,先看看手册上的架构图。根据介绍,我们可以得知框架图中表示的是一个
PCNT
框架,然后esp32共有8组不同的单元。每个单元有两个通道:ch0
和ch1
。这两个通道的功能相似。(言外之意还不一样了??) - 手册上的介绍,对应上架构图:
PCNT
脉冲计数器模块 用于对 输入脉冲的上升沿或下降沿进行计数。每个脉冲计数器单元均有一个带符号的 16-bitadder
计数寄存器以及ch0/1
两个通道,通过EN comparator
配置(一堆失/使能寄存器)可以加减计数器。每个通道均有sig ch0/1_un
一个脉冲输入信号以及ctrl ch0/1_un
一个能够用于控制输入信号的控制信号。输入信号可以打开或关闭filter
滤波功能。最后输出pulse_cnt
和thr_event
这里的“
sig ch0/1_un
一个脉冲输入信号以及ctrl ch0/1_un
一个能够用于控制输入信号的控制信号” 应该就可以指编码器的 脉冲信号 和 脉冲方向??
1) 配置 Configuration
PCNT
模块有8个独立的计数“单元”,编号从0到7。在API中,使用pcnt_unit_t
(枚举0-7)来引用它们。每个单元有两个独立的通道,编号为0和1,并使用pcnt_channel_t
(枚举0-1)指定(发现命名规律,unit
一般用来指代不同单元)。配置是单独提供每个单位的通道使用pcnt_config_t
(初始化结构体)和覆盖:
- 这个配置指的是单元和通道号。 参数
unit
和channel
,分别指 单元号 和 通道号。 - 脉冲输入和脉冲门输入的GPIO编号。
pulse_gpio_num
和ctrl_gpio_num
,只需要填写gpio的序号即可,不需要移位(我发现是不是只有最开始学的那个配置gpio是要移位赋值的,之后几个好像都不用)。 - 两对参数:
pcnt_ctrl_mode_t
和pcnt_count_mode_t
来定义计数器如何根据控制信号的状态作出反应,以及如何计数脉冲的正/负边缘。(这一点略不好描述,2个结构体类型 对应 4个参数。下面结合例程解释) - 两个限制值(最小/最大),用于建立观察点和在脉冲计数达到特定限制时触发中断。参数
counter_h_lim
和counter_l_lim
的配置。
- (常规的结构体+初始化函数)然后通过调用函数
pcnt_unit_config()
,将上面的pcnt_config_t
作为输入参数来设置特定的通道。如果要在配置中禁用脉冲或控制输入引脚,就提供PCNT_PIN_NOT_USED
而不填写GPIO编号。
#define PCNT_TEST_UNIT PCNT_UNIT_0
#define PCNT_H_LIM_VAL 10
#define PCNT_L_LIM_VAL -10
#define PCNT_THRESH1_VAL 5
#define PCNT_THRESH0_VAL -5
#define PCNT_INPUT_SIG_IO 4 // Pulse Input GPIO 脉冲输入GPIO
#define PCNT_INPUT_CTRL_IO 5 // Control GPIO HIGH=count up, LOW=count down 控制GPIO
#define LEDC_OUTPUT_IO 18 // Output GPIO of a sample 1 Hz pulse generator 输出一个采样1hz脉冲发生器的GPIO
/*准备配置的PCNT单位*/
/* Prepare configuration for the PCNT unit */
pcnt_config_t pcnt_config = {
//设置PCNT输入信号和控制gpio
// Set PCNT input signal and control GPios
.pulse_gpio_num = PCNT_INPUT_SIG_IO,
.ctrl_gpio_num = PCNT_INPUT_CTRL_IO,
.channel = PCNT_CHANNEL_0,
.unit = PCNT_TEST_UNIT,
//在脉冲输入的正/负边缘上做什么?
// What to do on the positive / negative edge of pulse input?
.pos_mode = PCNT_COUNT_INC, // Counter mode: Increase counter value 对抗模式:增加对抗值
//PCNT_COUNT_DEC // Counter mode: Decrease counter value 计数器模式:减少计数器值 (枚举中的第三种情况)
.neg_mode = PCNT_COUNT_DIS, // Counter mode: Inhibit counter(counter value will not change in this condition) 计数器模式:禁止计数器(计数器值在此情况下不会改变)
//当控制输入低或高时怎么办?
// What to do when control input is low or high?
.lctrl_mode = PCNT_MODE_REVERSE, // Reverse counting direction if low 如果计数方向低,则反向计数
.hctrl_mode = PCNT_MODE_KEEP, // Keep the primary counter mode if high 如果高,则保持主计数器模式
//设置最大和最小限制值来监视
// Set the maximum and minimum limit values to watch
.counter_h_lim = PCNT_H_LIM_VAL, // 在实践中发现如果累加数达到最大或最小值就会清零
.counter_l_lim = PCNT_L_LIM_VAL,
};
/*初始化PCNT单元*/
/* Initialize PCNT unit */
pcnt_unit_config(&pcnt_config);
其中,
lctrl_mode
和hctrl_mode
参数的注释还是好理解,感觉就是设置ctrl_gpio_num
控制引脚 在高低电平时不同情况。不过按正常情况应该都是例程里的这种情况吧。
对应,pos_mode
和neg_mode
也是,应该是对应设置pulse_gpio_num
脉冲引脚 在高低电平时的不同情况。(例程的注释看不懂,已经换成api手册里枚举的注释)根据枚举的三种情况的注释,可知其实就是指要在上升沿加还是下降沿加,而且可以设置是加还是减还是不变。
2) 操作 计数器 Operating the Counter
- 在使用
pcnt_unit_config()
进行设置之后,计数器立即开始操作。累积的脉冲计数可以通过调用pcnt_get_counter_value()
来获取。 - 有两个函数允许控制计数器的操作:
pcnt_counter_pause()
(暂停PCNT单位的PCNT计数器),pcnt_counter_resume()
(恢复PCNT计数器计数)和pcnt_counter_clear()
(清除并复位PCNT计数器值为零)。 - 也可以通过调用
pcnt_set_mode()
来动态改变之前设置的计数器模式。 - 如果需要,脉冲输入管脚和控制输入管脚可以使用
pcnt_set_pin()
动态地改变。也就说说可以单独设置pulse_io
和ctrl_io
引脚,不过除了引脚其他参数不能设置。如果要禁用特定的输入,提供一个函数参数PCNT_PIN_NOT_USED
,而不是GPIO编号。
注释:为了使计数器不错过任何脉冲,脉冲持续时间应该大于一个
APB_CLK
周期(12.5 ns)。脉冲在APB_CLK
时钟的边缘上采样,如果落在边缘之间,可能会错过。这适用于有或没有文件的计数器操作。
/*初始化PCNT的计数器*/
/* Initialize PCNT's counter */
pcnt_counter_pause(PCNT_TEST_UNIT); // 暂停
pcnt_counter_clear(PCNT_TEST_UNIT); // 清除
/*所有的设置,现在去计数*/
/* Everything is set up, now go to counting */
pcnt_counter_resume(PCNT_TEST_UNIT); // 开始
// 例程初始化后就在主函数中一直循环读取编码器值了。(还加了其他的测试内容,这不重要~)
pcnt_get_counter_value(PCNT_TEST_UNIT, &count);
3) 过滤脉冲 Filtering Pulses
PCNT
单元在每个脉冲和控制输入上都有滤波器,增加了忽略信号中的短故障的选项。- 被忽略的脉冲长度在
APB_CLK
时钟周期中通过调用pcnt_set_filter_value()
来提供。可以使用pcnt_get_filter_value()
检查当前筛选器设置。APB_CLK
时钟以80mhz运行。 - 通过调用
pcnt_filter_enable()
/pcnt_filter_disable()
来运行/挂起这个过滤器。
/*配置和启用输入过滤器*/
/* Configure and enable the input filter */
pcnt_set_filter_value(PCNT_TEST_UNIT, 100); // 第二个参数的注释为 : PCNT信号的滤波值,计数器在APB_CLK周期。任何持续时间比这短的脉冲将被忽略,当过滤器被启用时。
pcnt_filter_enable(PCNT_TEST_UNIT); // 挂起函数 的调用类似。
// 在api的描述中,pcnt_get_filter_value() 函数的获取过程并不是返回值,而是要传入参数:存值的指针
uint16_t filter_val = 0;
pcnt_get_filter_value(PCNT_TEST_UNIT , &filter_val) // 如果要读值应该是这样
4) 使用中断 Using Interrupts
pcnt_evt_type_t
中定义了5个计数器状态监视事件,它们能够触发中断。脉冲计数器到达特定值时发生事件:(可以发生以下3种事件?)
- 最小或最大计数值:
counter_l_lim
或counter_h_lim
在pcnt_config_t
中提供,如1) 配置 Configuration
中所述 - 阈值0或阈值1使用函数
pcnt_set_event_value()
设置。 - 脉冲计数 = 0。(?)
- 开启事件后,还要注册,启用或禁用中断服务上述事件,调用
pcnt_isr_register()
,pcnt_intr_enable()
。和pcnt_intr_disable()
。 - 要在达到阈值时启用或禁用事件,还需要调用函数
pcnt_event_enable()
和pcnt_event_disable()
。 - 为了检查当前设置的阈值,可以使用函数
pcnt_get_event_value()
。
/*设置阈值0和1,并启用事以上是关于ESP32 单片机学习笔记 - 03 - MCPWM脉冲输出/PCNT脉冲计数的主要内容,如果未能解决你的问题,请参考以下文章
ESP32 单片机学习笔记 - 02 - 软件IIC&硬件SPI
ESP32 单片机学习笔记 - 05 - AP/Smart Config