Энкодер "курильщика" от Qwen
Попросил бесплатный Qwen Code в VS Code создать BLE HID регулятор громкости на энкодере для ESP32-C3 в ESP-IDF.
Ну то, что код не откомпилируется, сомнений не было никаких. И даже попытка его причесать самому успехом не увенчалась. Придуманные ИИ библиотеки не гуглятся.
Но давайте посмотрим на то, как ИИ реализовал опрос энкодера (выжимка из кода только по энкодеру):
// Метки событий для RTOS
#define ENCODER_EVENT (1 << 0)
#define BUTTON_EVENT (1 << 1)
#define BLE_EVENT (1 << 2)
#define SLEEP_EVENT (1 << 3)
// Глобальные переменные
static EventGroupHandle_t event_group;
// Очередь для событий энкодера
typedef struct {
int8_t direction; // 1 = clockwise, -1 = counter-clockwise, 0 = button
bool button_pressed;
} encoder_event_t;
static QueueHandle_t encoder_queue = NULL;
// Состояние энкодера
static volatile int8_t encoder_state = 0;
static volatile int8_t last_encoded_state = 0;
// Обработка прерывания энкодера
static void IRAM_ATTR encoder_isr_handler(void *arg) {
int8_t pin_a = gpio_get_level(ENCODER_PIN_A);
int8_t pin_b = gpio_get_level(ENCODER_PIN_B);
// Gray code decoding
static const int8_t enc_states[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
encoder_state = ((encoder_state << 2) | (pin_b << 1) | pin_a) & 0x0F;
int8_t direction = enc_states[encoder_state];
if (direction != 0) {
encoder_event_t event = {
.direction = direction,
.button_pressed = false
};
xQueueSendFromISR(encoder_queue, &event, NULL);
}
}
// Обработка прерывания кнопки
static void IRAM_ATTR button_isr_handler(void *arg) {
encoder_event_t event = {
.direction = 0,
.button_pressed = true
};
xQueueSendFromISR(encoder_queue, &event, NULL);
}
// Инициализация энкодера
static void init_encoder(void) {
encoder_queue = xQueueCreate(10, sizeof(encoder_event_t));
// Конфигурация пинов энкодера
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << ENCODER_PIN_A) | (1ULL << ENCODER_PIN_B),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_ANYEDGE
};
gpio_config(&io_conf);
// Установка прерываний
gpio_install_isr_service(0);
gpio_isr_handler_add(ENCODER_PIN_A, encoder_isr_handler, NULL);
gpio_isr_handler_add(ENCODER_PIN_B, encoder_isr_handler, NULL);
// Конфигурация кнопки (если подключена)
if (ENCODER_PIN_BUTTON >= 0) {
gpio_config_t btn_conf = {
.pin_bit_mask = (1ULL << ENCODER_PIN_BUTTON),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_NEGEDGE
};
gpio_config(&btn_conf);
gpio_isr_handler_add(ENCODER_PIN_BUTTON, button_isr_handler, NULL);
}
ESP_LOGI(TAG, "Encoder initialized (A: GPIO%d, B: GPIO%d, BTN: GPIO%d)",
ENCODER_PIN_A, ENCODER_PIN_B, ENCODER_PIN_BUTTON);
}
// Задача обработки энкодера
static void encoder_task(void *pvParameters) {
encoder_event_t event;
while (1) {
if (xQueueReceive(encoder_queue, &event, portMAX_DELAY) == pdTRUE) {
if (event.button_pressed) {
// Дебаунс кнопки
vTaskDelay(pdMS_TO_TICKS(DEBOUNCE_DELAY_MS));
if (gpio_get_level(ENCODER_PIN_BUTTON) == 0) {
xEventGroupSetBits(event_group, BUTTON_EVENT);
}
} else if (event.direction != 0) {
xEventGroupSetBits(event_group, ENCODER_EVENT);
}
}
}
}
// Задача обработки событий
static void event_handler_task(void *pvParameters) {
EventBits_t uxBits;
static int8_t last_direction = 0;
static uint32_t last_event_time = 0;
while (1) {
uxBits = xEventGroupWaitBits(event_group,
ENCODER_EVENT | BUTTON_EVENT | SLEEP_EVENT,
pdTRUE, // Clear bits on exit
pdFALSE, // Any bit
portMAX_DELAY);
uint32_t current_time = xTaskGetTickCount();
if (uxBits & SLEEP_EVENT) {
...
}
if (uxBits & BUTTON_EVENT) {
send_button_press();
last_event_time = current_time;
last_direction = 0;
}
if (uxBits & ENCODER_EVENT) {
int8_t pin_a = gpio_get_level(ENCODER_PIN_A);
int8_t pin_b = gpio_get_level(ENCODER_PIN_B);
// Определение направления
int8_t direction = 0;
if (pin_a != pin_b) {
direction = (pin_a > pin_b) ? 1 : -1;
}
// Защита от дребезга и слишком быстрой прокрутки
if (direction != 0 && (direction != last_direction ||
(current_time - last_event_time) > pdMS_TO_TICKS(100))) {
send_volume_control(direction);
last_direction = direction;
last_event_time = current_time;
}
}
}
}
Т.е. он сделал два обработчика прерываний (для кнопки и двух контактов энкодера), в них помещает события в очередь, в одной задаче эту очередь перебирает, в ней зачем-то борется с дребезгом кнопки, хотя это можно было бы сделать в самом прерывании, и устанавливает биты событий, которые разбирает в еще одной задаче, где и принимает решение, куда же энкодер повернулся. А чтобы уснуть, в таймере также устанавливается бит события, который обрабатывается в задаче №2...
Надо ли говорить, что эффективность этого мягко скажем сомнительна... Оленекод во всей красе, беспощадный в своей избыточности. Не хватает еще пары задач, ну чтобы просто нескучно было. :)
И даже если платные генераторы кода могут написать что-то, что сразу компилируется, это максимум основа для ручной доводки. Правда если закрытая часть ESP-IDF и пр. проприетарных библиотек уже некоторое время пишется ИИ... :)
PS: следующий видеоролик будет посвящен как раз разбору кода чтения энкодера "здорового человека" (надеюсь :) ) для МК ESP32-C3 (не имеющего PCNT периферии).