Подробный гайд: Программирование STM32 (теория и практика)

Подробный гайд по программированию STM32: архитектура, регистры, HAL, периферия, примеры кода, отладка и лучшие практики.

2026.09.23                  


Подробный гайд: Программирование STM32 (теория и практика)Подробный гайд: Программирование STM32 (теория и практика)

1. Что такое STM32 и с чего начать

STM32 — семейство 32-битных микроконтроллеров на ядре ARM Cortex-M (M0/M0+, M3, M4, M7, M33).

Выпускается STMicroelectronics. Ключевые особенности:

  • Разные линейки под разные задачи (от дешёвых до высокопроизводительных)
  • Богатая периферия: таймеры, АЦП, DAC, UART, SPI, I2C, CAN, USB, Ethernet
  • Гибкая система тактирования
  • Несколько уровней программирования: регистры → LL → HAL

2. Линейки: какую выбрать



Серия Ядро Особенности Типичное применение
STM32C0 M0+ Самые дешёвые, простые Замена простых 8-бит
STM32F0/G0 M0/M0+ Недорогие, базовая периферия Управление, датчики
STM32F1 M3 Классика, много примеров (Blue Pill) Обучение, простые устройства
STM32F4 M4 + FPU Популярная, высокая производительность Моторы, аудио, DSP
STM32F7 M7 Кэш, высокая частота Дисплеи, обработка
STM32H7 M7 (до 480 МГц) Максимальная производительность Графика, AI
STM32L0/L4 M0+/M4 Ультранизкое потребление Батарейные устройства, IoT
STM32U5 M33 TrustZone Безопасность + низкое потребление Носимые, медицина
STM32G4 M4 + FPU Моторы, аналоговая периферия Приводы, питание
STM32WB/WL M4+M0+ / LoRa Беспроводные: BLE, 802.15.4, LoRa IoT, радио

Для обучения рекомендую:

STM32F103C8T6 (Blue Pill) или STM32F401/F411 (Black Pill / Nucleo). Для новых проектов чаще берут G0, G4, L4, H7.


3. Инструментарий

Среды разработки

Инструмент Плюсы Минусы
STM32CubeIDE (Eclipse-based, бесплатно) Официальная, интеграция с CubeMX, отладчик Медленная, тяжёлая
Keil MDK Быстрая, удобная Платная, ограничения бесплатной версии
IAR EWARM Качественный код Платная
VS Code + CMake + Makefile Гибкая, лёгкая Требует ручной настройки

Генератор кода — STM32CubeMX

Графический конфигуратор: выбираете чип → настраиваете пины, тактирование, периферию → генерирует проект (HAL или LL).

Программаторы / отладчики

  • ST-LINK (v2, v3) — официальный, поддержка SWD и JTAG
  • J-Link — быстрый, удобный, но платный
  • DAPLink / CMSIS-DAP — дешёвая альтернатива

Отладчики командной строки

  • OpenOCD — открытый, работает с ST-LINK
  • pyOCD
  • ST-Link Utility / STM32CubeProgrammer — прошивка, стирание, чтение памяти

4. Уровни программирования

┌─────────────────────────────────────────────┐
│  Регистры (прямой доступ)                   │ ← максимум контроля, сложно
├─────────────────────────────────────────────┤
│  LL (Low-Layer) — тонкие инлайн-обёртки     │ ← быстрый код, читаемость
├─────────────────────────────────────────────┤
│  HAL — высокоуровневые драйверы             │ ← быстро старт, много готового
└─────────────────────────────────────────────┘

Когда что использовать

  • Регистры — обучение, понимание железа, критичные по времени участки
  • LL — нужна производительность + контроль (ШИМ моторов, высокоскоростной обмен)
  • HAL — быстрый старт, прототип, не критичная по времени задача

Важно:

HAL медленнее и «тяжелее», но экономит время. В реальном проекте часто смешивают: HAL для логики, регистры/LL для горячих участков.


5. Архитектура кратко

Ядро и память

  • Ядро Cortex-M: регистры R0-R12, SP, PC, xPSR
  • Память: Flash (код), RAM (данные), часто с раздельными шинами
  • Карта памяти: периферия отображена в адресное пространство (0x40000000+)

Шины и тактирование

                    ┌────────────┐
   HSI (внутр. 8М)─►│            │
   HSE (внеш. кв.)─►│   RCC      │──► SYSCLK ──► AHB  ──► GPIO, DMA, Flash
   PLL (умножитель)─►│ (такты)    │──► APB1 ──► UART2, I2C, Timer2-7
                    │            │──► APB2 ──► UART1, SPI1, Timer1
                    └────────────┘
  • HSI — внутренний RC-генератор, ~8 МГц, неточный
  • HSE — внешний кварц, точный
  • PLL — умножение частоты (например, 8 МГц × 9 = 72 МГц)
  • RCC — блок управления тактами: включает/выключает такты на периферию

Прерывания: NVIC

  • NVIC — контроллер прерываний
  • Приоритеты: прем-приоритет и субприоритет
  • Векторы в таблице прерываний (начало Flash)
  • Обработчики: EXTI0_IRQHandler, TIM2_IRQHandler, USART1_IRQHandler и т.д.



6. Тактирование: первое, что нужно понять

По умолчанию после сброса микроконтроллер работает от внутреннего генератора (HSI), обычно 8 МГц.

Чтобы получить полную скорость (например, 72 МГц на F103):

  1. Включить внешний кварц (HSE)
  2. Настроить PLL: HSE × множитель = нужная частота
  3. Настроить предделители шин
  4. Переключить SYSCLK на PLL
  5. Подстроить задержки Flash (латентность)

В CubeMX это делается визуально. Вручную — через регистры RCC.


7. Периферия с примерами

7.1. GPIO — мигание светодиодом на регистрах (STM32F103, Blue Pill)

На Blue Pill светодиод на PC13.

#include "stm32f103xb.h"   // заголовок с регистрами

void delay_ms(uint32_t ms)
{
    while (ms--) {
        volatile uint32_t i;
        for (i = 0; i < 1000; i++);   // грубо для 8 МГц
    }
}

int main(void)
{
    // 1. Включаем тактирование порта C
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

    // 2. Настраиваем PC13 как push-pull выход, 2 МГц
    //    В F103 регистры конфигурации — CRL (пины 0-7) и CRH (пины 8-15)
    //    Пин 13 → биты [23:20] в CRH
    GPIOC->CRH &= ~(0xF << ((13 - 8) * 4));   // очистить
    GPIOC->CRH |=  (0x2 << ((13 - 8) * 4));   // MODE=10 (2МГц), CNF=00 (push-pull)

    while (1)
    {
        GPIOC->ODR ^= GPIO_ODR_ODR13;         // инвертируем пин
        delay_ms(500);
    }
}

Что происходит по шагам:

  1. RCC->APB2ENR — регистр включения тактов. Без него порт не работает.
  2. CRH — конфигурационный регистр для пинов 8-15. На каждый пин 4 бита: 2 на режим, 2 на тип.
  3. ODR — выходной регистр данных. ^= — инверсия.

Альтернатива: инверсия через BRR/BSRR:

GPIOC->BSRR = GPIO_BSRR_BR13;   // сбросить пин
GPIOC->BSRR = GPIO_BSRR_BS13;   // установить пин

7.2. То же самое на HAL (сгенерировано в CubeMX)

#include "main.h"

int main(void)
{
    HAL_Init();
    SystemInit();      // обычно вызывается автоматически
    SystemClock_Config();  // сгенерирована в CubeMX

    // GPIO настраивается в MX_GPIO_Init()
    MX_GPIO_Init();

    while (1)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        HAL_Delay(500);
    }
}

HAL-подход: - HAL_Init() — инициализирует SysTick на 1 мс - SystemClock_Config() — настройка тактов - MX_GPIO_Init() — настройка пинов из CubeMX - HAL_Delay() — задержка в мс через SysTick


7.3. Прерывания и EXTI — кнопка

Подключаем кнопку к PA0, по нажатию зажигаем/тушим светодиод.

// В обработчике прерывания (имя фиксировано таблицей векторов)
void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & EXTI_PR_PR0)      // проверяем флаг
    {
        GPIOC->ODR ^= GPIO_ODR_ODR13; // переключаем светодиод
        EXTI->PR = EXTI_PR_PR0;       // сбрасываем флаг (записью 1)
    }
}

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN   // такт порта A
                  | RCC_APB2ENR_IOPCEN   // такт порта C
                  | RCC_APB2ENR_AFIOEN;  // такт альтернативных функций

    // PA0 — вход с подтяжкой вверх
    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0x8 << 0);   // CNF=10 (вход с подтяжкой), MODE=00
    GPIOA->ODR |= GPIO_ODR_ODR0; // подтяжка вверх

    // PC13 — выход (аналогично примеру выше)
    GPIOC->CRH &= ~(0xF << 20);
    GPIOC->CRH |=  (0x2 << 20);

    // EXTI: привязка линии 0 к порту A
    AFIO->EXTICR[0] &= ~AFIO_EXTICR0_EXTI0; // PA0

    // Разрешаем прерывание по линии 0
    EXTI->IMR |= EXTI_IMR_MR0;
    EXTI->RTSR |= EXTI_RTSR_TR0;  // по переднему фронту

    // Разрешаем прерывание в NVIC
    NVIC_EnableIRQ(EXTI0_IRQn);

    while (1) { /* спим или работаем */ }
}


Важные моменты:

  • Флаг в EXTI->PR сбрасывается записью 1
  • Подтяжка: у входа тип push-pull нет, подтяжка делается через ODR
  • Обработчик прерывания должен быть коротким

На HAL:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0) {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    }
}

7.4. Таймеры и ШИМ

Таймеры STM32 — один из самых мощных блоков.

Типы:

  • Базовые (TIM6, TIM7): простой счётчик
  • Общего назначения (TIM2-5): ШИМ, захват, сравнение
  • Расширенные (TIM1, TIM8): моторы, комплементарные выходы, dead-time

Пример:

ШИМ на TIM2, канал 1 (PA0), частота ~1 кГц при 8 МГц тактировании.

void PWM_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;  // GPIOA
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;  // TIM2

    // PA0 — альтернативная функция, push-pull
    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0xB << 0);   // MODE=11 (50МГц), CNF=10 (AF push-pull)

    // Настройка таймера
    TIM2->PSC = 8 - 1;          // предделитель: 8 МГц / 8 = 1 МГц
    TIM2->ARR = 1000 - 1;       // период: 1 МГц / 1000 = 1 кГц ШИМ

    // Канал 1: ШИМ режим 1
    TIM2->CCMR1 &= ~(TIM_CCMR1_CC1S | TIM_CCMR1_OC1M);
    TIM2->CCMR1 |= (6 << 4);    // OC1M = 110 (PWM mode 1)
    TIM2->CCMR1 |= TIM_CCMR1_OC1PE;  // preload

    TIM2->CCR1 = 500;           // скважность 50%

    TIM2->CCER |= TIM_CCER_CC1E; // включить канал 1
    TIM2->CR1  |= TIM_CR1_CEN;   // запустить таймер
}

Скважность:

TIM2->CCR1 = 250; → 25%, 750 → 75%.


7.5. UART — эхо на прерываниях

Пример на HAL: принимаем байт и сразу отправляем обратно.

UART_HandleTypeDef huart2;

void UART2_Init(void)
{
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 115200;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    HAL_UART_Init(&huart2);
}

uint8_t rx_byte;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    UART2_Init();

    // Запускаем приём одного байта по прерыванию
    HAL_UART_Receive_IT(&huart2, &rx_byte, 1);

    while (1) { }
}

// Колбэк: вызывается после приёма байта
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART2) {
        HAL_UART_Transmit(huart, &rx_byte, 1, HAL_MAX_DELAY);
        HAL_UART_Receive_IT(huart, &rx_byte, 1); // перезапуск приёма
    }
}

Для эффективности:

Приём по DMA + кольцевой буфер или прерывание IDLE для пакетов переменной длины.


7.6. АЦП с DMA

Пример на HAL: непрерывное измерение с DMA в буфер.

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

uint16_t adc_buf[64];

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    // MX_ADC1_Init() и MX_DMA_Init() сгенерированы из CubeMX

    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 64);

    while (1) {
        // adc_buf заполняется автоматически
    }
}

На регистрах: настроить канал, режим непрерывного преобразования, включить прерывание или DMA.


8. Отладка

Подключение

  • SWD (2 провода: SWDIO + SWCLK) — основной способ
  • JTAG (5 проводов) — для старых чипов и сложных случаев

Методы отладки

  1. Точки останова в IDE — классика
  2. Просмотр регистров периферии (Registers view в STM32CubeIDE)
  3. Просмотр памяти (память, массивы)
  4. ITM / SWO — вывод printf через отладочный порт без UART
  5. Логический анализатор — смотреть SPI, I2C, UART
  6. Осциллограф — ШИМ, аналоговые сигналы


Пример вывода через ITM (printf без UART)

#include <stdio.h>

// Перенаправление stdout в ITM
int _write(int fd, char *ptr, int len)
{
    for (int i = 0; i < len; i++) {
        ITM_SendChar(ptr[i]);
    }
    return len;
}

printf("Hello from STM32\r\n");

В отладчике нужно включить ITM Stimulus и посмотреть вывод.


9. Режимы энергопотребления

Режим Что работает Ток Выход
Run Всё мА —
Sleep Ядро спит, периферия работает мА Прерывание
Stop Такты остановлены, RAM сохранён мкА EXTI
Standby Почти всё выключено нА WKUP, RTC

Пример входа в Stop:

HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

После выхода из Stop нужно перенастроить тактирование.


10. Лучшие практики

Организация кода

  • Разделяйте слои: драйверы / логика / приложение
  • Конечные автоматы для логики вместо бесконечных задержек
  • Кольцевые буферы для UART, DMA
  • Очередь команд между прерыванием и основным циклом

Прерывания

  • Обработчик должен быть максимально коротким
  • Данные между прерыванием и основным циклом — volatile
  • Не использовать HAL_Delay() внутри прерывания
  • Флаги прерываний сбрасывать корректно

Тактирование

  • Всегда проверять задержки Flash при смене частоты
  • Подключать конденсаторы на питание (100 нФ на каждый VDD)
  • Кварц: нагрузка (2×15-22 пФ)

Память и стек

  • Следить за размером стека (особенно при рекурсии)
  • Не выделять память динамически в прерываниях
  • Для больших буферов — статическое выделение

Периферия

  • I2C: не забывать подтягивающие резисторы (обычно 4.7 кОм)
  • АЦП: входное сопротивление источника, фильтр
  • ШИМ: учитывать мёртвое время для моторов
  • UART: проверять уровни (3.3 В или 5 В, нужны преобразователи)

11. Типичные ошибки

  1. Забыть включить такт порта/периферии — ничего не работает
  2. Неправильный режим GPIO — пин настроен как вход, а мы в него пишем
  3. Неправильная частота таймера — ШИМ не той частоты
  4. Зависание в прерывании — не сброшен флаг
  5. Дребезг кнопки — нет программной или аппаратной фильтрации
  6. Конфликт пинов — один пин использует две периферии
  7. Нет подтяжки на входе — «плавающий» вход ловит помехи
  8. Неправильный уровень сигнала — подаём 5 В на вход 3.3 В чипа
  9. Перегруженный стек — переполнение при глубоких вызовах
  10. Кэш на H7/F7 — DMA видит старые данные из кэша (нужно MPU или обслуживание кэша)

12. Полезные ресурсы

  • Документация: datasheet и reference manual на конкретный чип (обязательно)
  • STM32CubeMX — конфигуратор
  • GitHub STMicroelectronics — примеры HAL/LL
  • Форумы: STM32 Community, cxem.net, easyelectronics.ru
  • Книги: «The Definitive Guide to ARM Cortex-M» (Joseph Yiu), «Making Embedded Systems» (Elecia White)

13. Пример проекта: от нуля до прошивки

  1. Открыть STM32CubeIDE → New STM32 Project
  2. Выбрать чип (например, STM32F103C8T6)
  3. В CubeMX:
    • Настроить RCC: HSE, PLL → 72 МГц
    • SYS: Debug → Serial Wire
    • GPIO: PC13 → GPIO_Output
    • UART1: 115200
  4. Сгенерировать код
  5. Добавить логику в main()
  6. Подключить ST-LINK
  7. Run (или Debug)

Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.


Статью подготовил: Аверко Денис Сергеевич @Nymexis г. Омск (специалист по ЗИ)

Комментарии

Загрузка...
Если комментарии не загружаются, можете попробовать отключить блокировщик рекламы для этого сайта