Энкодер "курильщика" от 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 периферии).

Бесплатный
Комментарии
avatar
Здесь будут комментарии к публикации