Перейти к содержанию

Тулчейн и первая прошивка Blue Pill

Прежде чем трогать регистры, нужно собрать программу для Cortex-M3 и записать её во флеш-память. Для этого хватает компилятора arm-none-eabi-gcc, утилиты make, OpenOCD с адаптером ST-LINK и двух репозиториев с заголовками — без STM32CubeIDE и без сгенерированного проекта.

Обновлено Редакция GAW

STM32 с нуляурок 1 из 835 мин

Цель урока: Собрать проект для STM32F103C8 из Makefile, стартового файла и скрипта компоновщика, записать его в Blue Pill через ST-LINK и OpenOCD и посмотреть переменные программы в GDB.

Программа для STM32 пишется на обычном C, но собирается не для компьютера, а для ядра Cortex-M3. Для этого нужен кросс-тулчейн: компилятор с ассемблером, компоновщиком и библиотеками, который работает на ПК и выдаёт код для ARM. Здесь это Arm GNU Toolchain с программами arm-none-eabi-gcc, arm-none-eabi-objcopy, arm-none-eabi-size и отладчиком arm-none-eabi-gdb. Записывать программу в микроконтроллер и связывать отладчик с платой будет OpenOCD — бесплатная программа, которая работает с адаптерами ST-LINK, J-Link и CMSIS-DAP (о ней — в статье глоссария OpenOCD).

Плата и подключение

На Blue Pill стоит STM32F103C8 в корпусе LQFP48. По таблице 2 даташита DS5319 у него 64 КБ флеш-памяти и 20 КБ ОЗУ; про то, почему на многих платах читается 128 КБ, — в ответе «STM32F103C8 и CB». Отладчик подключается к четырём контактам SWD внизу платы:

Контакт платыВывод STM32Провод ST-LINK
3V3VDD3.3V
DIOPA13 (SWDIO)SWDIO
CLKPA14 (SWCLK)SWCLK
GNDVSSGND

PA13 и PA14 после сброса работают как SWDIO и SWCLK — это их основная функция по таблице 5 DS5319, настраивать ничего не нужно. Питайте плату либо от 3,3 В адаптера, либо от USB, но не от обоих сразу: при питании от USB провод 3.3V адаптера не подключайте. Перемычка BOOT0 должна стоять в 0 — тогда после сброса выполняется программа из флеш-памяти (раздел 3.4 RM0008, табл. 9).

Тулчейн

Нужны три программы:

  • Arm GNU Toolchain — сборка GCC, binutils, newlib и GDB от Arm для «голого железа» с префиксом программ arm-none-eabi-;
  • GNU make — в Linux и macOS обычно уже есть, в Windows его ставят отдельно;
  • OpenOCD версии 0.12.0 или новее.

После установки проверьте, что программы находятся из командной строки: arm-none-eabi-gcc --version, make --version, openocd --version.

Заголовки CMSIS

Имена регистров вроде RCC->APB2ENR и макросы битов вроде GPIO_BSRR_BS13 берутся из заголовков CMSIS. Их два слоя: описание ядра Cortex-M3 (core_cm3.h с NVIC и SysTick) публикует Arm, описание периферии STM32F1 (stm32f103xb.h) — ST. Оба под лицензией Apache-2.0, и оба можно взять прямо с GitHub, не ставя STM32Cube:

mkdir blink && cd blink
git clone --depth 1 --branch v4.3.5 https://github.com/STMicroelectronics/cmsis-device-f1.git
git clone --depth 1 --branch 5.9.0 https://github.com/ARM-software/CMSIS_5.git

Из первого репозитория нужна папка cmsis-device-f1/Include, из второго — CMSIS_5/CMSIS/Core/Include. Заголовок stm32f1xx.h выбирает файл конкретной линейки по макросу: для STM32F103C8 и CB это -DSTM32F103xB, и тогда подключаются stm32f103xb.h, а через него core_cm3.h и system_stm32f1xx.h. Стартовые файлы и скрипты компоновщика из папки Source/Templates мы не берём: в уроке 2 напишем свои и разберём каждую строку.

Файлы проекта

blink/
├── Makefile
├── stm32f103c8.ld      linker script: memory map of the chip
├── startup.c           vector table and Reset_Handler
├── main.c
├── cmsis-device-f1/    ST headers (git clone)
└── CMSIS_5/            Arm headers (git clone)

Скрипт компоновщика сообщает, где лежат флеш-память и ОЗУ и как разложить по ним код и переменные. Сейчас просто сохраните его как stm32f103c8.ld — в следующем уроке разберём его построчно:

/* stm32f103c8.ld: STM32F103C8, 64 KB flash, 20 KB SRAM (DS5319, Table 2) */
ENTRY(Reset_Handler)

MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64K
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

_estack = ORIGIN(RAM) + LENGTH(RAM);    /* 0x20005000 */

SECTIONS
{
    .isr_vector :
    {
        KEEP(*(.isr_vector))
    } > FLASH

    .text :
    {
        *(.text*)
        *(.rodata*)
        . = ALIGN(4);
    } > FLASH

    .data :
    {
        . = ALIGN(4);
        _sdata = .;
        *(.data*)
        . = ALIGN(4);
        _edata = .;
    } > RAM AT > FLASH

    _sidata = LOADADDR(.data);

    .bss :
    {
        . = ALIGN(4);
        _sbss = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;
    } > RAM

    ASSERT(_ebss + 1K <= _estack, "less than 1 KB of RAM left for the stack")
}

Стартовый файл — таблица векторов прерываний и функция Reset_Handler, с которой ядро начинает работу после сброса. Его тоже пока сохраните как есть, как startup.c:

/* startup.c: vector table and reset handler, STM32F103x8/xB (medium density) */
#include <stdint.h>

/* Symbols defined in stm32f103c8.ld */
extern uint32_t _estack[];
extern uint32_t _sidata[], _sdata[], _edata[], _sbss[], _ebss[];

int main(void);
void Reset_Handler(void);
void Default_Handler(void);

#define WEAK __attribute__((weak, alias("Default_Handler")))

/* Cortex-M3 exceptions */
void NMI_Handler(void) WEAK;
void HardFault_Handler(void) WEAK;
void MemManage_Handler(void) WEAK;
void BusFault_Handler(void) WEAK;
void UsageFault_Handler(void) WEAK;
void SVC_Handler(void) WEAK;
void DebugMon_Handler(void) WEAK;
void PendSV_Handler(void) WEAK;
void SysTick_Handler(void) WEAK;

/* STM32F103 medium-density interrupts, IRQ 0..42 (RM0008, Table 63) */
void WWDG_IRQHandler(void) WEAK;            void PVD_IRQHandler(void) WEAK;
void TAMPER_IRQHandler(void) WEAK;          void RTC_IRQHandler(void) WEAK;
void FLASH_IRQHandler(void) WEAK;           void RCC_IRQHandler(void) WEAK;
void EXTI0_IRQHandler(void) WEAK;           void EXTI1_IRQHandler(void) WEAK;
void EXTI2_IRQHandler(void) WEAK;           void EXTI3_IRQHandler(void) WEAK;
void EXTI4_IRQHandler(void) WEAK;           void DMA1_Channel1_IRQHandler(void) WEAK;
void DMA1_Channel2_IRQHandler(void) WEAK;   void DMA1_Channel3_IRQHandler(void) WEAK;
void DMA1_Channel4_IRQHandler(void) WEAK;   void DMA1_Channel5_IRQHandler(void) WEAK;
void DMA1_Channel6_IRQHandler(void) WEAK;   void DMA1_Channel7_IRQHandler(void) WEAK;
void ADC1_2_IRQHandler(void) WEAK;          void USB_HP_CAN1_TX_IRQHandler(void) WEAK;
void USB_LP_CAN1_RX0_IRQHandler(void) WEAK; void CAN1_RX1_IRQHandler(void) WEAK;
void CAN1_SCE_IRQHandler(void) WEAK;        void EXTI9_5_IRQHandler(void) WEAK;
void TIM1_BRK_IRQHandler(void) WEAK;        void TIM1_UP_IRQHandler(void) WEAK;
void TIM1_TRG_COM_IRQHandler(void) WEAK;    void TIM1_CC_IRQHandler(void) WEAK;
void TIM2_IRQHandler(void) WEAK;            void TIM3_IRQHandler(void) WEAK;
void TIM4_IRQHandler(void) WEAK;            void I2C1_EV_IRQHandler(void) WEAK;
void I2C1_ER_IRQHandler(void) WEAK;         void I2C2_EV_IRQHandler(void) WEAK;
void I2C2_ER_IRQHandler(void) WEAK;         void SPI1_IRQHandler(void) WEAK;
void SPI2_IRQHandler(void) WEAK;            void USART1_IRQHandler(void) WEAK;
void USART2_IRQHandler(void) WEAK;          void USART3_IRQHandler(void) WEAK;
void EXTI15_10_IRQHandler(void) WEAK;       void RTC_Alarm_IRQHandler(void) WEAK;
void USBWakeUp_IRQHandler(void) WEAK;

typedef void (*handler_t)(void);

typedef struct {
    uint32_t *initial_sp;       /* 0x000: loaded into MSP on reset */
    handler_t exceptions[15];   /* 0x004..0x03C: Reset .. SysTick */
    handler_t irq[43];          /* 0x040..0x0E8: IRQ 0..42 */
} vector_table_t;

_Static_assert(sizeof(vector_table_t) == 59 * 4, "vector table must be 59 words");

__attribute__((section(".isr_vector"), used))
const vector_table_t vector_table = {
    .initial_sp = _estack,
    .exceptions = {
        Reset_Handler,      NMI_Handler,      HardFault_Handler,
        MemManage_Handler,  BusFault_Handler, UsageFault_Handler,
        0, 0, 0, 0,                                     /* reserved */
        SVC_Handler,        DebugMon_Handler, 0,        /* 0: reserved */
        PendSV_Handler,     SysTick_Handler,
    },
    .irq = {
        WWDG_IRQHandler,          PVD_IRQHandler,             TAMPER_IRQHandler,
        RTC_IRQHandler,           FLASH_IRQHandler,           RCC_IRQHandler,
        EXTI0_IRQHandler,         EXTI1_IRQHandler,           EXTI2_IRQHandler,
        EXTI3_IRQHandler,         EXTI4_IRQHandler,           DMA1_Channel1_IRQHandler,
        DMA1_Channel2_IRQHandler, DMA1_Channel3_IRQHandler,   DMA1_Channel4_IRQHandler,
        DMA1_Channel5_IRQHandler, DMA1_Channel6_IRQHandler,   DMA1_Channel7_IRQHandler,
        ADC1_2_IRQHandler,        USB_HP_CAN1_TX_IRQHandler,  USB_LP_CAN1_RX0_IRQHandler,
        CAN1_RX1_IRQHandler,      CAN1_SCE_IRQHandler,        EXTI9_5_IRQHandler,
        TIM1_BRK_IRQHandler,      TIM1_UP_IRQHandler,         TIM1_TRG_COM_IRQHandler,
        TIM1_CC_IRQHandler,       TIM2_IRQHandler,            TIM3_IRQHandler,
        TIM4_IRQHandler,          I2C1_EV_IRQHandler,         I2C1_ER_IRQHandler,
        I2C2_EV_IRQHandler,       I2C2_ER_IRQHandler,         SPI1_IRQHandler,
        SPI2_IRQHandler,          USART1_IRQHandler,          USART2_IRQHandler,
        USART3_IRQHandler,        EXTI15_10_IRQHandler,       RTC_Alarm_IRQHandler,
        USBWakeUp_IRQHandler,
    },
};

void Reset_Handler(void)
{
    /* Copy initial values of .data from flash to RAM */
    uint32_t *src = _sidata;
    uint32_t *dst = _sdata;
    while ((uintptr_t)dst < (uintptr_t)_edata)
        *dst++ = *src++;

    /* Zero .bss */
    for (dst = _sbss; (uintptr_t)dst < (uintptr_t)_ebss; dst++)
        *dst = 0u;

    (void)main();
    for (;;) { }                /* main() must not return */
}

void Default_Handler(void)
{
    for (;;) { }                /* unexpected interrupt: stop here for the debugger */
}

Первая программа ещё не трогает периферию: она увеличивает счётчик, а мы посмотрим на него отладчиком. Две переменные выбраны так, чтобы проверить стартовый файл: counter без начального значения попадёт в секцию .bss и должна начинаться с нуля, а magic — в .data, и её начальное значение должно скопироваться из флеш-памяти. Программа читает magic один раз до цикла: переменную, которую никто не читает, ключ --gc-sections из Makefile выбросил бы вместе с её секцией, и GDB её не нашёл бы.

/* main.c, lesson 1: no peripherals yet, just variables to watch in GDB */
#include <stdint.h>

volatile uint32_t counter;              /* .bss: zeroed by Reset_Handler */
volatile uint32_t magic = 0xC0FFEE42u;  /* .data: copied from flash */

int main(void)
{
    (void)magic;                        /* one read keeps .data.magic in the image */
    for (;;) {
        counter++;
    }
}

Makefile

# Makefile: STM32F103C8 (Blue Pill), GNU make + Arm GNU Toolchain
TARGET = blink
SRCS   = startup.c main.c

CC      = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy
SIZE    = arm-none-eabi-size

CPU     = -mcpu=cortex-m3 -mthumb
CFLAGS  = $(CPU) -std=gnu11 -Os -g -Wall -Wextra
CFLAGS += -ffunction-sections -fdata-sections
CFLAGS += -DSTM32F103xB -Icmsis-device-f1/Include -ICMSIS_5/CMSIS/Core/Include
LDFLAGS = $(CPU) -nostartfiles -T stm32f103c8.ld
LDFLAGS += -Wl,--gc-sections -Wl,-Map=$(TARGET).map -Wl,--print-memory-usage

all: $(TARGET).bin

$(TARGET).elf: $(SRCS) stm32f103c8.ld
	$(CC) $(CFLAGS) $(SRCS) $(LDFLAGS) -o $@
	$(SIZE) $@

$(TARGET).bin: $(TARGET).elf
	$(OBJCOPY) -O binary $< $@

flash: $(TARGET).elf
	openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
	        -c "program $< verify reset exit"

clean:
	rm -f $(TARGET).elf $(TARGET).bin $(TARGET).map

.PHONY: all flash clean

Строки с командами в Makefile начинаются с символа табуляции, а не с пробелов — иначе make остановится с ошибкой «missing separator». Что делают ключи:

КлючЗачем
-mcpu=cortex-m3 -mthumbкод для ядра Cortex-M3 в наборе команд Thumb: другой набор это ядро не выполняет (DUI 0552A, разд. 2.1.3). С -mcpu=cortex-m3 GCC и так генерирует Thumb, -mthumb лишь фиксирует это явно
-std=gnu11 -Os -gC11 с расширениями GCC, оптимизация по размеру, отладочная информация для GDB (во флеш-память она не попадает)
-DSTM32F103xBвыбор заголовка stm32f103xb.h в stm32f1xx.h
-ffunction-sections -fdata-sections и -Wl,--gc-sectionsкаждая функция и переменная в своей секции, компоновщик выбрасывает неиспользуемые
-nostartfilesне подключать стандартный код запуска тулчейна: у нас свой startup.c. Библиотеки libc и libgcc остаются — из них берутся memcpy и memset, если компилятор сам вставит их вызов
-T stm32f103c8.ldнаш скрипт компоновщика вместо стандартного
-Wl,-Map=… и --print-memory-usageкарта памяти в файл и сводка занятой флеш-памяти и ОЗУ после сборки

Команда make соберёт blink.elf — файл ELF с кодом, адресами и отладочной информацией — и сделает из него blink.bin, «сырой» образ флеш-памяти. Чем отличаются ELF, HEX и BIN, разобрано в статье «Файлы прошивки».

Прошивка

Подключите ST-LINK к USB и выполните make flash. Make запустит OpenOCD с двумя файлами настроек: interface/stlink.cfg описывает адаптер, target/stm32f1x.cfg — микроконтроллер. В версии 0.12.0 скрипт stm32f1x.cfg задаёт частоту SWD 1000 кГц и банк флеш-памяти с драйвером stm32f1x по адресу 0x08000000, а размер флеш-памяти драйвер узнаёт у самого кристалла. Строка cortex_m reset_config sysresetreq (сброс через запрос ядра SYSRESETREQ) в этом скрипте стоит внутри условия if {![using_hla]}, поэтому с interface/stlink.cfg, который работает через драйвер hla, она не применяется.

Команда program blink.elf verify reset exit (раздел Flash Programming руководства OpenOCD) сбрасывает и останавливает ядро, стирает и записывает флеш-память по адресам из ELF, сверяет записанное, запускает программу и завершает OpenOCD. Для файла .bin адресов нет, и начальный адрес указывают последним параметром: program blink.bin verify reset exit 0x08000000.

Флеш-память с адреса 0x0800 0000 при BOOT0 = 0 видна ещё и с адреса 0x0000 0000. После сброса ядро читает по адресу 0x0000 0000 начальное значение указателя стека, а по адресу 0x0000 0004 — адрес первой команды (RM0008, разд. 3.4, с. 60). Это два первых слова нашей таблицы векторов: _estack и Reset_Handler.

Смотрим программу в GDB

Светодиод пока не мигает, поэтому убедимся, что программа работает, отладчиком. В одном окне запустите OpenOCD как сервер:

openocd -f interface/stlink.cfg -f target/stm32f1x.cfg

Он будет ждать подключения GDB на порту 3333. Во втором окне:

arm-none-eabi-gdb blink.elf
(gdb) target extended-remote localhost:3333
(gdb) load
(gdb) monitor reset halt
(gdb) break main
(gdb) continue
(gdb) print counter
(gdb) print/x magic
(gdb) continue

Команда load записывает программу во флеш-память, а monitor передаёт строку самому OpenOCD: reset halt сбрасывает кристалл и останавливает ядро до первой команды, так что программа стартует с Reset_Handler, как после включения питания. На точке останова в начале main должно быть counter = 0 и magic = 0xc0ffee42 — значит, Reset_Handler обнулил .bss и скопировал .data. Нажмите Ctrl+C после второго continue и снова выполните print counter: значение будет большим и при каждой остановке разным. Как OpenOCD работает сервером для GDB, коротко рассказано в статье «GDB-сервер».

На STM32F4

Для Black Pill с STM32F411CEU6 меняются ключи, заголовки, скрипт компоновщика и таблица векторов:

  • ядро Cortex-M4 с FPU: -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 — в руководстве GCC это допустимые значения, а hard означает команды FPU и передачу чисел с плавающей точкой в его регистрах. До первой такой команды программа должна включить сопроцессоры CP10 и CP11 в регистре SCB->CPACR; в шаблонах ST это делает SystemInit (подробно — в статье «Код запуска микроконтроллера»);
  • заголовки из репозитория cmsis-device-f4 и макрос -DSTM32F411xE;
  • память по заголовку stm32f411xe.h: флеш-память с 0x08000000 до 0x0807FFFF (512 КБ), ОЗУ SRAM1 — 128 КБ с адреса 0x20000000;
  • в таблице векторов 86 позиций прерываний с номерами 0–85, из них 29 в стартовом файле ST для F411 пустые (зарезервированы), последнее — SPI5;
  • в OpenOCD — -f target/stm32f4x.cfg; банк флеш-памяти там описан драйвером stm32f2x.

Задание

  1. В файле blink.map найдите адреса функций Reset_Handler и main и проверьте, что они лежат в диапазоне флеш-памяти 0x08000000–0x0800FFFF.
  2. Прочитайте отладчиком первые два слова флеш-памяти командой x/2xw 0x08000000. Какие значения вы ожидаете увидеть и почему второе нечётное?
Ответ и решение
  1. В разделе карты с секцией .text у каждой функции есть строка с адресом, например 0x08000xxx Reset_Handler. Если адреса начинаются с 0x0800, скрипт компоновщика сработал: код лёг во флеш-память. Таблица векторов vector_table должна стоять первой, ровно с 0x08000000.

  2. Первое слово — 0x20005000, вершина стека _estack = ORIGIN(RAM) + LENGTH(RAM) = 0x20000000 + 20 × 1024. Второе — адрес Reset_Handler с единицей в младшем бите: Cortex-M3 выполняет только команды Thumb, и единица в бите 0 вектора сообщает ядру, что обработчик — код Thumb (DUI 0552A, разд. 2.3.4). Компилятор ставит этот бит сам, когда записывает в таблицу адрес Thumb-функции. Его значение совпадает с адресом из map-файла плюс 1.

Что дальше

В уроке 2 разберём построчно стартовый файл и скрипт компоновщика: что делает ядро до main, зачем таблице 59 слов, как устроены слабые обработчики и как компоновщик раскладывает .data и .bss. Подробнее об адаптерах ST-LINK, J-Link и CMSIS-DAP и о программах для них — в статье «Программаторы и отладчики», а о разъёмах — на странице распиновки JTAG и SWD. Если плата не отвечает, помогут ответы «ST-LINK: No target connected» и «STM32 не запускается после прошивки».

Частые вопросы

Можно ли прошить Blue Pill без ST-LINK?

Да, через встроенный загрузчик ST: у STM32F103 средней плотности он работает по USART1 на выводах PA9 и PA10 и включается, если при сбросе BOOT0 = 1, а BOOT1 = 0. Нужен преобразователь USB-UART на 3,3 В и STM32CubeProgrammer. Но отлаживать программу — останавливать, смотреть переменные — так нельзя, поэтому в уроках используется SWD.

Зачем указывать -mcpu=cortex-m3 и -mthumb?

Ключ -mcpu сообщает GCC, для какого процессора собирать код: по нему выбираются архитектура ARMv7-M и набор команд. Cortex-M3 выполняет только Thumb, и с -mcpu=cortex-m3 GCC и так генерирует Thumb; -mthumb ничего не меняет, но явно фиксирует набор команд. Те же ключи нужны и при компоновке: по ним компилятор выбирает подходящую версию библиотек libc и libgcc.

Что делать, если OpenOCD не видит микроконтроллер?

Сначала проверьте четыре провода: 3V3 или питание платы от USB, GND, DIO на PA13 и CLK на PA14. Если связь пропала после прошивки, программа могла перевести PA13 и PA14 в режим обычных выводов. Тогда переставьте BOOT0 в 1 и нажмите RESET: вместо вашей программы запустится встроенный загрузчик, программа не успеет отключить SWD, и флеш-память можно будет стереть.

Источники

  1. GCC 16.2 Manual: ARM Options и Link Options — -mcpu задаёт процессор, по нему выводятся архитектура и настройка оптимизации, cortex-m3 — допустимое имя; -mthumb и -marm выбирают состояние Thumb или ARM; -nostartfiles отключает стандартные файлы запуска, библиотеки подключаются как обычно; -nostdlib и -nodefaultlibs: компилятор может сам вставить вызовы memcpy, memset, memmove и memcmp
  2. OpenOCD 0.12.0 User's Guide (doc/openocd.texi), tcl/target/stm32f1x.cfg — команда program filename [preverify] [verify] [reset] [exit] [offset]: reset init, запись, verify_image, reset run, выход; пример openocd -f interface/stlink.cfg … -f target/…; раздел GDB and OpenOCD: target extended-remote localhost:3333, monitor reset halt, load, continue, arm-none-eabi-gdb из тулчейна; stm32f1x.cfg: банк флеш-памяти stm32f1x с адреса 0x08000000, adapter speed 1000, строка cortex_m reset_config sysresetreq стоит внутри условия if {![using_hla]}
  3. STMicroelectronics. DS5319 Rev 20. STM32F103x8, STM32F103xB datasheet — табл. 2: STM32F103C8 — 64 КБ флеш-памяти, 20 КБ SRAM; табл. 5: PA13 — JTMS/SWDIO, PA14 — JTCK/SWCLK как основная функция после сброса (выводы 34 и 37 корпуса LQFP48)
  4. STMicroelectronics. RM0008 Rev 21. Reference manual STM32F101xx–STM32F107xx — разд. 3.4, табл. 9 (с. 60): режимы загрузки BOOT1/BOOT0; флеш-память с адреса 0x0800 0000 отображается на 0x0000 0000; после сброса ядро читает вершину стека по адресу 0x0000 0000 и начинает выполнение с вектора по адресу 0x0000 0004; встроенный загрузчик medium-density работает через USART1
  5. STMicroelectronics. cmsis-device-f1 v4.3.5; Arm CMSIS_5 5.9.0 (CMSIS/Core/Include) — Include/stm32f1xx.h выбирает заголовок по макросу STM32F103xB; stm32f103xb.h подключает core_cm3.h и system_stm32f1xx.h; лицензия Apache-2.0
  6. Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A — разд. 2.1.3, Thumb state: Cortex-M3 выполняет команды только в состоянии Thumb

Нашли ошибку?