Описание

Классический “Hello World” в мире микроконтроллеров — мигание светодиодом. Плата: STM32F103C8T6 (Blue Pill), встроенный светодиод на PC13.

В каждой статье я даю два варианта одного и того же кода:

  • HAL — быстрый старт, Cube-стиль, абстракция поверх регистров
  • CMSIS — bare metal, напрямую регистры, минимальная прошивка

Переключай кнопкой вверху страницы — выбор запомнится для всех статей.

Схема подключения

На Blue Pill светодиод уже распаян на PC13 — внешних деталей не нужно.

Пин STM32Подключение
PC13LED на плате (встроенный)

Код

main.c (HAL)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "stm32f1xx_hal.h"

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

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

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

static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOC_CLK_ENABLE();

    GPIO_InitStruct.Pin   = GPIO_PIN_13;
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

Как это работает

  1. HAL_Init() — инициализирует HAL, настраивает SysTick на 1 мс
  2. MX_GPIO_Init() — включает тактирование GPIOC, настраивает PC13 как push-pull выход
  3. HAL_GPIO_TogglePin переключает пин, HAL_Delay(500) ждёт 500 мс

main.c (CMSIS)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "stm32f1xx.h"

static volatile uint32_t ticks_ms;

void SysTick_Handler(void) {
    ticks_ms++;
}

static void delay_ms(uint32_t ms) {
    uint32_t start = ticks_ms;
    while ((ticks_ms - start) < ms) { __NOP(); }
}

int main(void)
{
    // SysTick: 8 МГц (HSI после сброса) / 1000 = 8000 тиков на 1 мс
    SysTick_Config(8000);

    // 1. Тактирование порта C
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

    // 2. PC13 как выход push-pull 2 МГц: MODE=10, CNF=00
    GPIOC->CRH &= ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13);
    GPIOC->CRH |=  GPIO_CRH_MODE13_1;

    while (1) {
        GPIOC->ODR ^= GPIO_ODR_ODR13;
        delay_ms(500);
    }
}

Как это работает

У STM32F103 конфигурация пинов хранится в двух регистрах: CRL (пины 0–7) и CRH (пины 8–15). На каждый пин 4 бита (MODE + CNF). Для PC13 это биты [23:20] регистра GPIOC->CRH.

  1. SysTick_Config(8000) — прерывание каждые 8000 тактов HSI = 1 мс
  2. RCC->APB2ENR |= RCC_APB2ENR_IOPCEN — включаем тактирование GPIOC
  3. Чистим и выставляем 4 бита для PC13: MODE13 = 0b10 (выход 2 МГц push-pull)
  4. ODR ^= GPIO_ODR_ODR13 — инверсия состояния пина

Атомарная запись — BSRR

Вместо ODR ^= (read-modify-write) лучше использовать атомарный BSRR:

1
2
GPIOC->BSRR = GPIO_BSRR_BS13;   // PC13 = 1
GPIOC->BSRR = GPIO_BSRR_BR13;   // PC13 = 0

Подводные камни

  • Тактирование GPIO обязательно — забыл включить → пин не шевелится, без ошибок
  • На Blue Pill LED горит при LOW — логика инверсная
  • После сброса ядро работает на HSI 8 МГц (без PLL) — учитывай при расчёте SysTick
  • В HAL при использовании CubeMX PC13 может быть занят под RTC — проверь конфигурацию
  • CMSIS-имена регистров (RCC_APB2ENR_IOPCEN, GPIO_CRH_MODE13_1) берутся из stm32f103xb.h