ESP32, STM32 и Arduino: как заставить нейросеть писать прошивки, которые не виснут на первой же миллисекунде

ESP32, STM32 и Arduino: как заставить нейросеть писать прошивки, которые не виснут на первой же миллисекунде

Embedded-разработка — это мир, где ошибка в один такт может превратить ваш девайс в кирпич, а неверная настройка тактирования — в бесконечный HardFault_Handler. И вот парадокс: нейросети уверенно генерируют код для веб-приложений, но когда дело доходит до регистров STM32 или DMA-трансферов на ESP32, начинается лотерея. Модель может предложить инициализацию таймера, которая компилируется, но не работает, или пропустить критичный volatile в обработчике прерывания.

Проблема не в том, что AI «не умеет» embedded. Проблема в том, что большинство промтов написаны так, будто мы просим написать функцию сортировки массива. Embedded требует контекста: конкретный чип, версия SDK, частота тактирования, требования по энергопотреблению. Без этого нейросеть галлюцинирует регистры, путает HAL с LL, а иногда предлагает API, которого не существует в природе.

Ниже — подборка промтов, которые я собрал и отладил на реальных проектах: от прототипов на Arduino до промышленных задач на STM32 с FreeRTOS. Каждый промт проверен на практике, но помните: AI — это ускоритель, а не замена даташиту. Всегда сверяйтесь с reference manual вашего конкретного чипа.

Промт 1: Инициализация периферии STM32 без магии CubeMX

Задача: Когда нужно настроить периферию вручную или понять, что именно сгенерировал CubeMX.

Промт:

Ты — эксперт по STM32 HAL. Напиши инициализацию TIM2 в режиме PWM для STM32F407VG.
Требования:
- Тактирование APB1 = 84 МГц
- Частота PWM = 1 кГц
- Скважность 50%
- Канал 1, пин PA0 (AF1)
- Использовать только HAL, без LL
Объясни расчёт prescaler и period. Покажи код инициализации GPIO и TIM.

Что получите: Код с корректным расчётом Prescaler = (SystemCoreClock / (2 * APB1_Timer_Clock)) - 1 и Period = (APB1_Timer_Clock / PWM_Freq) - 1. Модель покажет, почему для STM32F4 таймеры на APB1 получают удвоенную частоту.

Пример вывода:

// APB1 Timer Clock = 84 MHz (при APB1 prescaler > 1)
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84 - 1;      // 84 MHz / 84 = 1 MHz
htim2.Init.Period = 1000 - 1;       // 1 MHz / 1000 = 1 kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim2);

sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500;              // 50% duty
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

Совет: Если модель путает APB1_Timer_Clock, уточните: «Проверь, включён ли TIM2 на APB1 или APB2 в твоём чипе. У STM32F407 TIM2 на APB1». Это заставит её свериться с reference manual.

Промт 2: Обработчик прерываний, который не ломает систему

Задача: Написать ISR для кнопки с антидребезгом без блокировки main loop.

Промт:

Напиши обработчик внешнего прерывания (EXTI) для кнопки на PA1 (STM32F103, HAL).
Требования:
- Антидребезг через таймер (не delay в ISR)
- Флаг события для main loop (volatile)
- Учитывать, что ISR должна быть максимально короткой
- Показать, как сбросить EXTI pending bit

Почему это работает: Модель знает, что HAL_Delay в ISR — это ошибка. Она сгенерирует установку флага и запуск таймера, а не блокирующий код.

Пример:

volatile uint8_t button_pressed = 0;

void HAL_GPIO_EXTI_Callback(uint16_t pin) {
    if (pin == GPIO_PIN_1) {
        // Запускаем таймер антидребезга на 50 мс
        __HAL_TIM_SET_COUNTER(&htim3, 0);
        HAL_TIM_Base_Start_IT(&htim3);
    }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3) {
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET) {
            button_pressed = 1;
        }
        HAL_TIM_Base_Stop_IT(&htim3);
    }
}

Важно: Всегда проверяйте, что HAL_GPIO_EXTI_Callback вызывается из правильного контекста. Нейросеть иногда забывает про __HAL_GPIO_EXTI_CLEAR_IT, но если попросить явно — добавит.

Промт 3: ESP32 + FreeRTOS — задачи, которые не конфликтуют

Задача: Создать две задачи с разными приоритетами и обменом данными через очередь.

Промт:

ESP-IDF v5.x, ESP32. Создай две задачи FreeRTOS:
1. Задача считывает данные с датчика DHT22 каждые 2 секунды (приоритет 5)
2. Задача отправляет данные по Wi-Fi раз в 10 секунд (приоритет 3)
Обмен через queue. Покажи создание задач, очередь, обработку ошибок.
Учти watchdog и не блокируй scheduler.

Что получите: Код с xQueueCreate, xTaskCreatePinnedToCore (важно для ESP32 — можно закрепить задачи за ядрами), корректными задержками через vTaskDelay.

Пример:

QueueHandle_t sensor_queue;

typedef struct {
    float temperature;
    float humidity;
} sensor_data_t;

void sensor_task(void *pvParameters) {
    sensor_data_t data;
    while (1) {
        if (dht_read_data(&data) == ESP_OK) {
            xQueueSend(sensor_queue, &data, portMAX_DELAY);
        }
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

void wifi_task(void *pvParameters) {
    sensor_data_t received;
    while (1) {
        if (xQueueReceive(sensor_queue, &received, pdMS_TO_TICKS(10000))) {
            send_to_server(&received);
        }
    }
}

void app_main() {
    sensor_queue = xQueueCreate(5, sizeof(sensor_data_t));
    xTaskCreatePinnedToCore(sensor_task, "sensor", 4096, NULL, 5, NULL, 0);
    xTaskCreatePinnedToCore(wifi_task, "wifi", 4096, NULL, 3, NULL, 1);
}

Совет: Уточните в промте «Покажи, как избежать priority inversion». Модель добавит мьютекс или объяснит, почему очередь здесь безопасна.

Промт 4: Отладка HardFault на STM32

Задача: Когда плата уходит в HardFault, а вы не понимаете почему.

Промт:

STM32F4, HardFault_Handler срабатывает случайно. Напиши код для извлечения причины:
- Чтение регистров CFSR, HFSR, MMFAR, BFAR
- Вывод через SWO или UART
- Расшифровка битов (IMPRECISERR, PRECISERR, IBUSERR и т.д.)
Покажи, как встроить это в обработчик.

Пример вывода:

void HardFault_Handler(void) {
    volatile uint32_t cfsr = SCB->CFSR;
    volatile uint32_t hfsr = SCB->HFSR;
    volatile uint32_t mmfar = SCB->MMFAR;
    volatile uint32_t bfar = SCB->BFAR;

    printf("CFSR: 0x%08lX\n", cfsr);
    printf("HFSR: 0x%08lX\n", hfsr);
    if (cfsr & (1 << 1)) printf("DACCVIOL: Data access violation\n");
    if (cfsr & (1 << 9)) printf("PRECISERR: Precise data bus error at 0x%08lX\n", bfar);

    while (1);
}

Кейс: На одном проекте модель помогла выявить, что HardFault возникал из-за доступа к неинициализированному указателю на структуру DMA. Расшифровка CFSR показала IMPRECISERR, что сузило поиск до работы с периферией.

Промт 5: Работа с I2C на Arduino без блокировок

Задача: Чтение нескольких датчиков по I2C без зависаний.

Промт:

Arduino (AVR, Wire library). Напиши неблокирующее чтение датчика BMP280 по I2C.
Требования:
- Проверка наличия устройства через Wire.endTransmission()
- Таймаут на чтение (если датчик не отвечает)
- Обработка ошибок без while(1)
Покажи код с комментариями.

Пример:

#include <Wire.h>
#define BMP280_ADDR 0x76

bool read_bmp280(float *temp) {
    Wire.beginTransmission(BMP280_ADDR);
    Wire.write(0xFA); // регистр температуры
    if (Wire.endTransmission(false) != 0) return false;

    if (Wire.requestFrom(BMP280_ADDR, 3) != 3) return false;

uint32_t raw = (Wire.read() << 12)

| (Wire.read() << 4) | (Wire.read() >> 4);
    *temp = raw / 100.0; // упрощённо, нужна калибровка
    return true;
}

void loop() {
    float temp;
    if (read_bmp280(&temp)) {
        Serial.println(temp);
    }
    delay(1000);
}

Совет: Нейросеть часто забывает про endTransmission(false) для repeated start. Уточните: «Используй repeated start, не завершай передачу перед чтением».

Промт 6: Энергосбережение на ESP32 — deep sleep с пробуждением по таймеру

Задача: Устройство на батарейке должно работать месяцами.

Промт:

ESP32, ESP-IDF. Настрой deep sleep с пробуждением по таймеру каждые 10 минут.
Требования:
- Отключение Wi-Fi перед сном
- Сохранение данных в RTC memory
- Пробуждение, отправка данных, снова сон
Покажи код с расчётом времени сна.

Пример:

RTC_DATA_ATTR int boot_count = 0;

void app_main() {
    boot_count++;
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();

    if (cause == ESP_SLEEP_WAKEUP_TIMER) {
        // Проснулись по таймеру — отправляем данные
        wifi_init_and_send();
    }

    esp_wifi_stop();
    esp_deep_sleep_enable_timer_wakeup(10 * 60 * 1000000ULL); // 10 минут
    esp_deep_sleep_start();
}

Важно: RTC_DATA_ATTR сохраняет переменные между пробуждениями. Модель это знает, но иногда забывает про esp_wifi_stop() перед сном — уточните явно.

Промт 7: АЦП на STM32 с DMA и усреднением

Задача: Читать аналоговый сигнал без нагрузки на CPU.

Промт:

STM32F4, HAL. Настрой ADC1 в режиме continuous conversion с DMA.
Требования:
- Буфер на 100 значений
- Усреднение в main loop
- Тактирование ADC = 21 МГц (APB2 / 4)
Покажи инициализацию и обработку.

Пример:

#define ADC_BUFFER_SIZE 100
uint16_t adc_buffer[ADC_BUFFER_SIZE];

// В main:
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);

// Обработка:
uint32_t sum = 0;
for (int i = 0; i < ADC_BUFFER_SIZE; i++) {
    sum += adc_buffer[i];
}
uint16_t avg = sum / ADC_BUFFER_SIZE;

Совет: Если модель путает HAL_ADC_Start_DMA с HAL_ADC_Start_IT, попросите: «Покажи разницу между DMA и IT режимами для ADC».

Промт 8: UART с кольцевым буфером на Arduino

Задача: Принимать данные без потерь на высокой скорости.

Промт:

Arduino Mega, UART на 115200 бод. Реализуй кольцевой буфер для приёма.
Требования:
- Размер буфера 64 байта
- Обработка переполнения
- Чтение в main loop без блокировки
Покажи код с ISR.

Пример:

#define BUF_SIZE 64
volatile uint8_t rx_buffer[BUF_SIZE];
volatile uint8_t rx_head = 0, rx_tail = 0;

ISR(USART0_RX_vect) {
    uint8_t data = UDR0;
    uint8_t next = (rx_head + 1) % BUF_SIZE;
    if (next != rx_tail) {
        rx_buffer[rx_head] = data;
        rx_head = next;
    }
}

bool uart_available() { return rx_head != rx_tail; }
uint8_t uart_read() {
    uint8_t data = rx_buffer[rx_tail];
    rx_tail = (rx_tail + 1) % BUF_SIZE;
    return data;
}

Промт 9: Протокол Modbus RTU на STM32

Задача: Подключить промышленный датчик.

Промт:

STM32F103, HAL. Реализуй Modbus RTU slave на UART2.
Требования:
- Функция 0x03 (Read Holding Registers)
- CRC16 расчёт
- Таймаут 3.5 символа для определения конца кадра
Покажи код с обработкой.

Пример CRC:

uint16_t modbus_crc(uint8_t *buf, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 1) crc = (crc >> 1) ^ 0xA001;
            else crc >>= 1;
        }
    }
    return crc;
}

Кейс: На производстве модель сгенерировала код, который корректно обрабатывал запросы, но не учитывал таймаут. После уточнения промта добавилась проверка через HAL_GetTick().

Промт 10: Прошивка ESP32 по OTA

Задача: Обновлять устройство удалённо.

Промт:

ESP-IDF v5.x. Реализуй OTA-обновление через HTTPS.
Требования:
- Проверка версии
- Откат при ошибке
- Прогресс-бар через callback
Покажи код с esp_https_ota.

Пример:

esp_http_client_config_t config = {
    .url = "https://example.com/firmware.bin",
    .cert_pem = server_cert_pem,
};
esp_https_ota_config_t ota_config = {
    .http_config = &config,
};
esp_err_t ret = esp_https_ota(&ota_config);
if (ret == ESP_OK) {
    esp_restart();
} else {
    ESP_LOGE(TAG, "OTA failed");
}

Совет: Уточните «Добавь проверку подписи прошивки». Модель добавит esp_ota_get_app_description и проверку хеша.

Промт 11: Таймеры STM32 — Input Capture для измерения частоты

Задача: Измерить частоту внешнего сигнала.

Промт:

STM32F4, HAL. Настрой TIM1 в режиме Input Capture на канале 1 (PA8).
Требования:
- Измерение периода между фронтами
- Расчёт частоты
- Обработка переполнения таймера
Покажи код с обработчиком прерывания.

Пример:

volatile uint32_t capture1 = 0, capture2 = 0;
volatile float freq = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        capture2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        if (capture2 > capture1) {
            uint32_t diff = capture2 - capture1;
            freq = 84000000.0 / diff; // тактовая частота таймера
        }
        capture1 = capture2;
    }
}

Промт 12: Отладка через SWO на STM32

Задача: Логи без UART.

Промт:

STM32F4, SWO вывод через ITM. Настрой вывод printf в SWO.
Покажи инициализацию и функцию _write.

Пример:

int _write(int file, char *ptr, int len) {
    for (int i = 0; i < len; i++) {
        ITM_SendChar(*ptr++);
    }
    return len;
}

Совет: В STM32CubeIDE нужно включить SWO в настройках debug. Модель это знает, но лучше уточнить: «Покажи настройки в CubeIDE».

Промт 13: Питание от батареи — расчёт времени работы

Задача: Оценить, сколько проживёт устройство.

Промт:

ESP32, deep sleep 10 минут, активность 2 секунды, средний ток 80 мА в активном режиме, 10 мкА в сне.
Батарея 2000 мАч. Рассчитай время работы.
Покажи формулу и результат.

Пример расчёта:

Активная фаза: 2 сек * 80 мА = 160 мА*с
Сон: 598 сек * 0.01 мА = 5.98 мА*с
Итого за цикл: 165.98 мА*с = 0.046 мА*ч
Время работы: 2000 / 0.046 ≈ 43478 циклов ≈ 302 дней

Важно: Модель может ошибиться в единицах. Перепроверьте расчёт вручную.

Промт 14: Код для датчика с I2C на ESP32

Задача: Подключить BME280.

Промт:

ESP-IDF, ESP32. Напиши драйвер для BME280 по I2C.
Требования:
- Чтение температуры, давления, влажности
- Калибровочные коэффициенты
- Проверка ID (0x60)
Покажи код с i2c_master.

Пример:

i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (BME280_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, 0xD0, true); // регистр ID
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (BME280_ADDR << 1) | I2C_MASTER_READ, true);
uint8_t id;
i2c_master_read_byte(cmd, &id, I2C_MASTER_NACK);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);

Совет: Нейросеть иногда путает i2c_master_read_byte с i2c_master_read. Уточните версию ESP-IDF.

Промт 15: Проверка кода на типичные ошибки embedded

Задача: Найти баги до прошивки.

Промт:

Проверь этот код для STM32 на типичные ошибки embedded:
- Отсутствие volatile в переменных, изменяемых в ISR
- Блокирующие вызовы в прерываниях
- Неверная инициализация периферии
- Утечки памяти (если есть malloc)
Вот код: [вставьте код]

Пример вывода: Модель укажет: «Переменная flag изменяется в ISR, но не объявлена volatile — компилятор может оптимизировать проверку в main loop».

Кейс: На одном проекте этот промт выявил, что HAL_UART_Transmit вызывается внутри HAL_UART_RxCpltCallback — это блокирует систему. Заменили на очередь.

Что важно помнить при работе с AI в embedded

  1. Всегда проверяйте регистры. Модель может выдумать бит, которого нет в вашем чипе. Сверяйтесь с reference manual.
  2. Указывайте версию SDK. ESP-IDF v4 и v5 имеют разные API. HAL для F1 и F4 тоже отличается.
  3. Тестируйте на железе. Код, который компилируется, не всегда работает. Особенно это касается таймингов и прерываний.
  4. Не доверяйте расчётам. Перепроверяйте prescaler, период, время работы от батареи.
  5. Используйте промты для отладки. HardFault, зависания, неверные данные — AI помогает сузить поиск.

Embedded — это область, где AI пока не заменит инженера, но может стать мощным ускорителем. Главное — давать контекст и проверять результат. Если вы только начинаете, начните с простых промтов: инициализация GPIO, UART, таймеров. Постепенно переходите к DMA, RTOS, OTA. И помните: лучший код — тот, который вы понимаете, а не тот, который сгенерировала нейросеть.

Попробуйте эти промты на своём следующем проекте. Начните с одного — например, с инициализации таймера. Сравните с тем, что генерирует CubeMX. Вы увидите, где AI ошибается, а где экономит часы работы. И тогда вы сможете использовать его осознанно, без выгорания и без кирпичей.

← Все статьи

Комментарии

Читайте также

Почему AI формирует предвзятость при найме чаще, чем человек: разбор на реальных кейсах

20 июля 2026

Родительство и развитие ребёнка: как воспитывать без криков и наказаний — курс на asibiont.com

19 октября 2026

Почему лидерам SaaS нужен курс «Ведущий аудитор SOC 2 Type II»: ваш план построения доверия со стороны корпоративных клиентов

15 июля 2026

Мастерство контент-стратегии с помощью ИИ: глубокое погружение в курс «Контент-стратегия — контент-стратегия и контент-маркетинг» на Asibiont

15 июля 2026

10 промтов для React Native: компоненты, навигация, API — ускорь мобильную разработку

30 июня 2026

ESP32 против Arduino против Raspberry Pi: какую плату учить в 2026 году? (И как курс *Arduino, IoT и встраиваемые системы* приведёт вас к цели)

11 сентября 2026

AI-автоматизация и агенты: как превратить рутину в прибыль без единой строки кода

13 июня 2026

Rust и Tokio без боли: 7 промтов, которые объяснят ownership и async на реальных задачах

14 октября 2026

Django и FastAPI — Python Web: ваш ускоритель карьеры в эпоху ИИ

29 июня 2026