SOC-ESP32S3部分:12-2、编码器驱动
飞书文档https://x509p6c8to.feishu.cn/wiki/XtAhwrOuMigRxgkgSHZc8pd2nAd
编码器的驱动比较简单,我们在基础课程已经有学习过编码器的原理,我们这里就不重复讲解原理了,大家可以跳转32部分课程了解下原理,这里主要给大家演示下ESP32S3结合IO中断、消息队列实现的编码器方向和按下事件。
本节课,我们使用拨轮滚轮旋转编码器EC11E153440D,带大家了解下编码器如何驱动。
编码器有四根线,A\B是编码输出,S是按钮状态,C是公共端。
那么我们该怎样去使用这个编码器呢,从给出的数据手册上面我们看到一个波形,我们就是通过这个波形去判断编码器是否转动以及编码器转动的方向?
可以从图中可以看出当从CW方向转动的时候,A的波形上升沿比B波形的上升沿快,具体快多少,这里数据手册给出24±3°,当从CCW方向转动的这个时候恰好相反,B的相位上升沿快于A的上升沿。这样可以通过捕获上升沿的顺序来判断编码器的方向。
首先我们查看原理图,了解编码器的接线如下
编码器有三个引脚,A、B、SW,分别连接到GPIO38、GPIO39、GPIO40中
代码实现如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"static const char *TAG = "example";#define GPIO_EN_A_IO 38
#define GPIO_EN_B_IO 39
#define GPIO_EN_SW_IO 40
#define GPIO_EN_A_PIN_SEL (1ULL << GPIO_EN_A_IO)
#define GPIO_EN_B_PIN_SEL (1ULL << GPIO_EN_B_IO)
#define GPIO_EN_SW_PIN_SEL (1ULL << GPIO_EN_SW_IO)static bool is_ccw_start = false;
static bool is_cw_start = false;#define DEBOUNCE_TIME_MS 100 // 去抖动时间(毫秒)
static uint64_t last_sw_interrupt_time = 0; //记录上次中断时间,用于去抖动typedef enum {ENCODER_EVENT_NONE = 0,ENCODER_EVENT_CW,ENCODER_EVENT_CCW,ENCODER_EVENT_CLICK,
} encoder_event_t;static QueueHandle_t queue = NULL;
static QueueHandle_t sw_queue = NULL;//设置ccw方向标志位,起始或结束标志
void set_ccw_status(){encoder_event_t event = ENCODER_EVENT_CCW;if(is_ccw_start == true){xQueueSendFromISR(queue, &event, NULL);}elseis_ccw_start = true; //首次触发为开始is_cw_start = false; //如果是CCW方向的触发,可以把CW方向的标志清空
}//设置cw方向标志位,起始或结束标志
void set_cw_status(){encoder_event_t event = ENCODER_EVENT_CW;if(is_cw_start == true){xQueueSendFromISR(queue, &event, NULL);}elseis_cw_start = true;is_ccw_start = false; //如果是CW方向的触发,可以把CCW方向的标志清空
}static void IRAM_ATTR gpio_isr_handler(void* arg)
{uint32_t gpio_num = (uint32_t) arg;if(gpio_num == GPIO_EN_A_IO) //判断中断来自哪一条中断线{//进入中断之后,是低电平,那就是下降沿if(gpio_get_level(GPIO_EN_A_IO) == 0){if(gpio_get_level(GPIO_EN_B_IO) == 0){//CCW方向set_ccw_status();}else{//CW方向set_cw_status();}}//进入中断之后,是高电平,那就是上升沿else{if(gpio_get_level(GPIO_EN_B_IO) == 0){//CW方向set_cw_status();}else{//CCW方向set_ccw_status();}}}
}/* 外部中断服务函数定义 */
static void IRAM_ATTR sw_gpio_isr_handler(void* arg)
{uint64_t now = esp_timer_get_time() / 1000; // 获取当前时间(毫秒)// 硬件去抖: 检查是否在去抖时间窗口内if ((now - last_sw_interrupt_time) < DEBOUNCE_TIME_MS) {return;}last_sw_interrupt_time = now;encoder_event_t event = ENCODER_EVENT_CLICK;xQueueSendFromISR(sw_queue, &event, NULL);
}static void gpio_task_example(void* arg){encoder_event_t event;while (1){if(xQueueReceive(sw_queue, &event, 0)){ESP_LOGI(TAG, "按钮按下");}if(xQueueReceive(queue, &event, 0)){if(event == ENCODER_EVENT_CW) {ESP_LOGI(TAG, "顺时针旋转");} else if(event == ENCODER_EVENT_CCW) {ESP_LOGI(TAG, "逆时针旋转");}}vTaskDelay(10 / portTICK_PERIOD_MS);}
}void app_main(void)
{// 初始化IO SW为中断输入gpio_config_t io_conf = {};io_conf.intr_type = GPIO_INTR_NEGEDGE;io_conf.pin_bit_mask = GPIO_EN_SW_PIN_SEL;io_conf.mode = GPIO_MODE_INPUT;io_conf.pull_up_en = GPIO_PULLUP_ENABLE;gpio_config(&io_conf);// 初始化IO A为中断输入io_conf.intr_type = GPIO_INTR_NEGEDGE;io_conf.pin_bit_mask = GPIO_EN_A_PIN_SEL;io_conf.mode = GPIO_MODE_INPUT;io_conf.pull_up_en = GPIO_PULLUP_DISABLE;gpio_config(&io_conf);// 初始化IO B为普通输入io_conf.intr_type = GPIO_INTR_DISABLE;io_conf.pin_bit_mask = GPIO_EN_B_PIN_SEL;io_conf.mode = GPIO_MODE_INPUT;io_conf.pull_up_en = GPIO_PULLUP_DISABLE;gpio_config(&io_conf);gpio_install_isr_service(0);//注册IO SW和 IO A的中断回调函数gpio_isr_handler_add(GPIO_EN_SW_IO, sw_gpio_isr_handler, (void*)GPIO_EN_SW_IO);gpio_isr_handler_add(GPIO_EN_A_IO, gpio_isr_handler, (void*)GPIO_EN_A_IO);queue = xQueueCreate(10, sizeof(uint32_t));sw_queue = xQueueCreate(10, sizeof(uint32_t));xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 1, NULL);while(1){vTaskDelay(10 / portTICK_PERIOD_MS);}
}