Тулчейн и первая прошивка 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 |
|---|---|---|
| 3V3 | VDD | 3.3V |
| DIO | PA13 (SWDIO) | SWDIO |
| CLK | PA14 (SWCLK) | SWCLK |
| GND | VSS | GND |
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 -g | C11 с расширениями 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.
Задание
- В файле
blink.mapнайдите адреса функцийReset_Handlerиmainи проверьте, что они лежат в диапазоне флеш-памяти 0x08000000–0x0800FFFF. - Прочитайте отладчиком первые два слова флеш-памяти командой
x/2xw 0x08000000. Какие значения вы ожидаете увидеть и почему второе нечётное?
Ответ и решение
-
В разделе карты с секцией
.textу каждой функции есть строка с адресом, например0x08000xxx Reset_Handler. Если адреса начинаются с 0x0800, скрипт компоновщика сработал: код лёг во флеш-память. Таблица векторовvector_tableдолжна стоять первой, ровно с 0x08000000. -
Первое слово —
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, и флеш-память можно будет стереть.
Источники
- GCC 16.2 Manual: ARM Options и Link Options — -mcpu задаёт процессор, по нему выводятся архитектура и настройка оптимизации, cortex-m3 — допустимое имя; -mthumb и -marm выбирают состояние Thumb или ARM; -nostartfiles отключает стандартные файлы запуска, библиотеки подключаются как обычно; -nostdlib и -nodefaultlibs: компилятор может сам вставить вызовы memcpy, memset, memmove и memcmp
- 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]}
- STMicroelectronics. DS5319 Rev 20. STM32F103x8, STM32F103xB datasheet — табл. 2: STM32F103C8 — 64 КБ флеш-памяти, 20 КБ SRAM; табл. 5: PA13 — JTMS/SWDIO, PA14 — JTCK/SWCLK как основная функция после сброса (выводы 34 и 37 корпуса LQFP48)
- 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
- 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
- Arm. Cortex-M3 Devices Generic User Guide, DUI 0552A — разд. 2.1.3, Thumb state: Cortex-M3 выполняет команды только в состоянии Thumb
Нашли ошибку?
Смотрите также
- STM32 Blue PillSTM32F103C8T6: функции выводов, 5 В-выводы, питание, загрузка
- Программаторы и отладчикиUSBasp, ST-LINK, J-Link, PICkit 5, Snap, CMSIS-DAP, WCH-LinkE: сравнение
- OpenOCDОткрытый сервер отладки: адаптеры, конфигурации, порты GDB и telnet, запись флеш
- GDB-серверПосредник между GDB и целью: протокол RSP, режимы remote и extended-remote
- Файлы прошивки: HEX, BIN, ELFELF, Intel HEX, S-record, BIN и UF2: адреса, секции, objcopy, size