Embedded HandbookПриложения
Все разделы › I. Основы
К1986ВЕ92QI, STM32F103 · уровень средний · 20 мин чтения
Открыть в интерактивном справочнике

2г. Миландр К1986ВЕ92: отечественный Cortex-M3 рядом с STM32

Раздел 1.9 поставил К1986ВЕ9х рядом с STM32F103 в одной таблице, а 8.7 одной фразой сказал, что порты у него устроены иначе. Здесь — подробно: что меняется в коде, когда программу для F103 переносят на К1986ВЕ92QI. Ядро то же, Cortex-M3, а тактирование, порты, включение периферии и библиотека — свои. Его выбирают, когда изделию нужна отечественная элементная база и приёмка.

Опоры: пак Milandr.MDR32FxQI 1.3.2 для Keil — описание кристаллов (pdsc), заголовок MDR32F9Q2I.h (так К1986ВЕ92QI называется в библиотеке), библиотека SPL MDR32FxQI 1.3.0 и примеры RST_CLK/CPU_Clock и PORT/Joystick_LEDs; те же файлы без изменений лежат в MDR32_SPL_v1.5.0. STM32F103 — пак Keil STM32F1xx_DFP 2.4.1. Мигание светодиодом собрано armclang в четырёх вариантах, частота ядра посчитана функциями пака на ПК. QEMU этот микроконтроллер не моделирует, на плате не запускалось.

Как это называется в документации

Оригинал Перевод
RST_CLK блок сброса и тактирования — аналог RCC
CPU_C1, CPU_C2, CPU_C3 три ступени выбора такта ядра: источник, PLL, делитель
HCLK_SEL переключатель, откуда берётся такт ядра
PER_CLOCK регистр включения такта периферии, один на все блоки
PORT: RXTX, OE, FUNC, ANALOG, PWR данные, направление, функция, цифровой режим, драйвер вывода
MDR_EEPROM контроллер встроенной Flash-памяти у Миландра (блок назван EEPROM)
DUcc встроенный регулятор напряжения ядра
SSP синхронный последовательный порт — это SPI

2г.1 Одно ядро, разная периферия

Описания из паков Keil:

К1986ВЕ92QI (MDR32F9Q2I) STM32F103C8
ядро Cortex-M3 r2p0, есть MPU Cortex-M3
частота до 80 МГц до 72 МГц
Flash 128 КБ с адреса 0x0800 0000 64 КБ с адреса 0x0800 0000
ОЗУ 32 КБ с адреса 0x2000 0000 20 КБ с адреса 0x2000 0000
генераторы HSI 8 МГц, LSI 40 кГц, внешний HSE 2…16 МГц в описании пака не указаны
битов приоритета NVIC 3 4

Карта памяти совпадает: Flash и ОЗУ там же, периферия тоже с 0x4000 0000, есть bit-band (PERIPH_BB_BASE = 0x4200 0000). Регистры ядра, SysTick, NVIC и сам набор команд — те же. Но уже число битов приоритета — не свойство ядра, а решение производителя кристалла: 3 против 4. Дальше расходится всё, что лежит за адресом 0x4000 0000.

Путь такта от кварца до светодиода на двух микроконтроллерах: у К1986ВЕ92QI кварц 8 МГц идёт через CPU_C1, CPU_PLL с множителем 10, CPU_C2 и делитель CPU_C3 в HCLK 80 МГц, порт C включается битом 23 регистра PER_CLOCK и настраивается четырьмя регистрами; у STM32F103 кварц 8 МГц идёт через PLL с множителем 9 и переключатель SW в 72 МГц, порт C включается битом 4 регистра APB2ENR


2г.2 Тактирование: RST_CLK вместо RCC

Блок RST_CLK лежит по адресу 0x4002 0000. Нужные регистры: CLOCK_STATUS (0x00, флаги готовности), PLL_CONTROL (0x04), HS_CONTROL (0x08, кварц), CPU_CLOCK (0x0C) и PER_CLOCK (0x1C). Такт ядра проходит три ступени:

Последний переключатель HCLK_SEL выбирает, что пойдёт на ядро: HSI, CPU_C3, LSE или LSI. Множитель PLL записывается как «N − 1»: ×10 — это 9 (у F103 ×9 записывается как 7).

Перед разгоном до 80 МГц нужны ещё два шага, которых на F103 нет или они выглядят иначе. Задержка чтения Flash задаётся в контроллере с именем EEPROM: по комментариям SPL 2 такта — до 75 МГц, 3 — до 100 МГц, так что для 80 МГц нужно 3. И встроенный регулятор ядра DUcc (поле в BKP->REG_0E) переводится в режим «до 80 МГц» — значение 6. Пример CPU_Clock ставит задержку Flash до повышения частоты. Всё по регистрам:

void mdr_clock_80mhz(void)
{
    MDR_RST_CLK->PER_CLOCK |= RST_CLK_PER_CLOCK_PCLK_EN_EEPROM    /* иначе не записать CMD */
                            | RST_CLK_PER_CLOCK_PCLK_EN_BKP;      /* и REG_0E */
    MDR_RST_CLK->HS_CONTROL = RST_CLK_HS_CONTROL_HSE_ON;          /* кварц 8 МГц */
    while ((MDR_RST_CLK->CLOCK_STATUS & RST_CLK_CLOCK_STATUS_HSE_RDY) == 0) { }

    MDR_RST_CLK->CPU_CLOCK = 2u << RST_CLK_CPU_CLOCK_CPU_C1_SEL_Pos;   /* CPU_C1 = HSE, ядро пока от HSI */
    MDR_RST_CLK->PLL_CONTROL = (9u << RST_CLK_PLL_CONTROL_PLL_CPU_MUL_Pos)   /* x10 */
                             | RST_CLK_PLL_CONTROL_PLL_CPU_ON;
    while ((MDR_RST_CLK->CLOCK_STATUS & RST_CLK_CLOCK_STATUS_PLL_CPU_RDY) == 0) { }

    MDR_EEPROM->CMD = 3u << EEPROM_CMD_DELAY_Pos;                 /* Flash: 3 такта, до 100 МГц */
    MDR_BKP->REG_0E = (MDR_BKP->REG_0E & ~(BKP_REG_0E_LOW_Msk | BKP_REG_0E_SELECTRI_Msk))
                    | (6u << BKP_REG_0E_LOW_Pos) | (6u << BKP_REG_0E_SELECTRI_Pos);  /* регулятор: до 80 МГц */

    MDR_RST_CLK->CPU_CLOCK = (2u << RST_CLK_CPU_CLOCK_CPU_C1_SEL_Pos)  /* C1 = HSE */
                           | RST_CLK_CPU_CLOCK_CPU_C2_SEL               /* C2 = выход PLL */
                           | (0u << RST_CLK_CPU_CLOCK_CPU_C3_SEL_Pos)   /* C3 = C2 / 1 */
                           | (1u << RST_CLK_CPU_CLOCK_HCLK_SEL_Pos);    /* HCLK = CPU_C3 */
}

Первая запись в CPU_CLOCK готовит CPU_C1, но оставляет ядро на HSI; последняя переключает всё сразу. Итог — 8 МГц × 10 = 80 МГц, 0x106 в CPU_CLOCK.


2г.3 Порты: четыре регистра вместо CRL и CRH

У F103 режим вывода — четыре бита MODE и CNF в CRL или CRH. У К1986ВЕ92 порт — восемь регистров подряд: RXTX (0x00), OE (0x04), FUNC (0x08), ANALOG (0x0C), PULL, PD, PWR (0x18) и GFEN. Каждое свойство вывода — в своём регистре:

Светодиод VD3 отладочной платы — на PC0 (описание примера PORT/Joystick_LEDs):

void mdr_led_init(void)
{
    MDR_RST_CLK->PER_CLOCK |= RST_CLK_PER_CLOCK_PCLK_EN_PORTC;    /* без такта порт не отвечает */
    MDR_PORTC->OE     |=  (1u << LED_PIN);                        /* 1 — выход */
    MDR_PORTC->FUNC   &= ~PORT_FUNC_MODE0_Msk;                    /* 00 — функция «порт» */
    MDR_PORTC->ANALOG |=  (1u << LED_PIN);                        /* 1 — цифровой, 0 — аналоговый */
    MDR_PORTC->PWR     = (MDR_PORTC->PWR & ~PORT_PWR0_Msk)
                       | (1u << PORT_PWR0_Pos);                   /* 01 — драйвер включён, медленный фронт */
}

void mdr_led_toggle(void)
{
    MDR_PORTC->RXTX ^= (1u << LED_PIN);       /* чтение-изменение-запись: регистра вроде BSRR нет */
}

То же на F103 (раздел 8.4, PC13 платы BluePill):

void f103_led_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;                       /* такт порта C */
    GPIOC->CRH = (GPIOC->CRH & ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13))
               | GPIO_CRH_MODE13_1;                           /* выход 2 МГц, двухтактный */
}

У порта К1986ВЕ92 нет регистра атомарной установки и сброса вроде BSRR: в структуре ровно восемь регистров, и функция SPL PORT_SetBits для этой модели делает чтение-изменение-запись RXTX. Регистры SETTX и CLRTX библиотека использует только у других моделей серии.

Выводы отладки совмещены с портами: JTAG_A — PB0…PB4, JTAG_B — PD0…PD4. SPL защищает их маской при каждой записи в порт, но только тот интерфейс, что выбран в MDR32FxQI_config.h; по умолчанию там включён JTAG_B.


2г.4 PER_CLOCK и таблица соответствия блоков

У F103 такт периферии включают три регистра — AHBENR, APB1ENR, APB2ENR. У К1986ВЕ92 — один PER_CLOCK, и номер бита в нём совпадает с битами 19:15 базового адреса блока. SPL так и считает: макрос PCLK_BIT(BASE) — это 1 << ((BASE >> 15) & 0x1F). Порт C по адресу 0x400B 8000 — бит 23. После RST_CLK_DeInit() в PER_CLOCK включены только сам RST_CLK и BKP, всё остальное — выключено.

Соответствие блоков по двум заголовкам:

STM32F103 Адрес Такт К1986ВЕ92 Адрес Бит PER_CLOCK
RCC 0x4002 1000 — RST_CLK 0x4002 0000 4
FLASH 0x4002 2000 — EEPROM 0x4001 8000 3
GPIOC 0x4001 1000 APB2ENR, бит 4 PORTC 0x400B 8000 23
AFIO 0x4001 0000 APB2ENR, бит 0 нет, функция — в FUNC порта — —
USART1 0x4001 3800 APB2ENR, бит 14 UART1 0x4003 0000 6
SPI1 0x4001 3000 APB2ENR, бит 12 SSP1 0x4004 0000 8
I2C1 0x4000 5400 APB1ENR, бит 21 I2C 0x4005 0000 10
TIM1 0x4001 2C00 APB2ENR, бит 11 TIMER1 0x4007 0000 14
ADC1 0x4001 2400 APB2ENR, бит 9 ADC 0x4008 8000 17
CAN1 0x4000 6400 APB1ENR, бит 25 CAN1 0x4000 0000 0
DMA1 0x4002 0000 AHBENR, бит 0 DMA 0x4002 8000 5
BKP 0x4000 6C00 APB1ENR, бит 27 BKP 0x400D 8000 27
IWDG 0x4000 3000 — IWDG 0x4006 8000 13
нет в заголовке — — COMP 0x4009 8000 19

Ни один адрес не совпадает по смыслу, а один совпадает численно: 0x4002 0000 у F103 — DMA1, у К1986ВЕ92 — RST_CLK. Код с «зашитыми» адресами не упадёт при сборке, а молча запишет не в тот блок.

В заголовке MDR32F9Q2I.h есть и прямая ошибка: макрос RST_CLK_PER_CLOCK_PCLK_EN_COMP ссылается на несуществующий RST_CLK_PER_CLOCK_PCLK_EN_TIMER4_Msk, и строка PER_CLOCK |= RST_CLK_PER_CLOCK_PCLK_EN_COMP не собирается. Компаратор включают макросом SPL RST_CLK_PCLK_COMP — он даёт верный бит 19.


2г.5 SPL «Миландра» против HAL и SPL ST

В паке «Миландра» нет ни HAL, ни генератора кода вроде CubeMX: в каталоге Libraries только CMSIS и SPL. Зато SPL устроена так же, как Standard Peripheral Library от ST (StdPeriph_Driver в паке F1), — те же структуры …_InitTypeDef, функции …_Init, …_StructInit, …_SetBits:

Действие SPL ST для F103 SPL «Миландра»
такт порта RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE) RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE)
кварц RCC_HSEConfig(RCC_HSE_ON) RST_CLK_HSEconfig(RST_CLK_HSE_ON)
PLL RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9) RST_CLK_CPU_PLLconfig(RST_CLK_CPU_PLLsrcHSEdiv1, RST_CLK_CPU_PLLmul10)
задержка Flash FLASH_SetLatency(FLASH_Latency_2) EEPROM_SetLatency(EEPROM_Latency_3)
регулятор ядра — BKP_DUccMode(BKP_DUcc_upto_80MHz)
источник такта RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK) RST_CLK_CPUclkSelection(RST_CLK_CPUclkCPU_C3)
вывод GPIO_Init(GPIOC, &g) PORT_Init(MDR_PORTC, &p)

Настройка вывода в SPL «Миландра» — те же четыре регистра, только полями структуры:

void mdr_spl_led_init(void)
{
    PORT_InitTypeDef p;

    RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE);
    PORT_StructInit(&p);
    p.PORT_Pin   = PORT_Pin_0;
    p.PORT_OE    = PORT_OE_OUT;
    p.PORT_FUNC  = PORT_FUNC_PORT;
    p.PORT_MODE  = PORT_MODE_DIGITAL;
    p.PORT_SPEED = PORT_SPEED_SLOW;
    PORT_Init(MDR_PORTC, &p);
}

Модель микроконтроллера и частоту кварца задаёт MDR32FxQI_config.h (HSE_Value — 8 МГц); модель выбирает строка #define USE_MDR32F9Q2I, которую в Keil пишет в RTE_Components.h сам пак, а вне Keil — вы.

В паке два способа узнать частоту ядра, и они расходятся. SystemCoreClockUpdate() из system_MDR32F9Q2I.c делит на «поле CPU_C3 плюс один», а RST_CLK_GetClocksFreq() из SPL читает то же поле как делитель 1…256. Обе функции взяты из пака без правок и прогнаны на ПК над «регистрами» в памяти:

Настройка CPU_CLOCK RST_CLK_GetClocksFreq SystemCoreClock
×10, делитель 1 0x106 80 000 000 Гц 80 000 000 Гц
×7, делитель 2 (пример CPU_Clock) 0x186 28 000 000 Гц 6 222 222 Гц
×10, делитель 4 0x196 20 000 000 Гц 8 000 000 Гц
×10, делитель 256 0x1F6 312 500 Гц 5 000 000 Гц

С делителем 1 совпадают, с любым другим SystemCoreClock врёт. UART в SPL считает скорость через RST_CLK_GetClocksFreq() и не ошибается, а программные задержки DELAY_Init() берут SystemCoreClock — и промахиваются.


2г.6 Сборка и размер кода

Четыре варианта одного мигания — по регистрам и через библиотеку, для обоих микроконтроллеров — собраны armclang (-mcpu=cortex-m3 -Os) с заголовками из паков. _Static_assert в начале файлов сверяют с заголовками адреса, смещения регистров и биты: MDR_PORTC_BASE = 0x400B 8000, PER_CLOCK — по смещению 0x1C, порт C — бит 23, ×10 записывается как 9. Наши файлы собираются без предупреждений; в файле библиотеки MDR32FxQI_eeprom.c одно предупреждение о неиспользуемой переменной.

Код программы после компоновки (наши объекты и библиотека периферии, без библиотеки C):

Вариант К1986ВЕ92QI STM32F103C8
по регистрам 180 байт 152 байта
через SPL 936 байт 696 байт

По функциям: настройка такта — 84 байта против 80, настройка вывода — 58 против 36 (четыре регистра против одного CRH), переключение светодиода — 18 и 18. Разница в такте — шаги с EEPROM и регулятором, в порте — четыре чтения-записи вместо одной.

Стартовые файлы в паке — только для компиляторов ARM и IAR (startup/arm, startup/iar). mdr_blink.c собирается и arm-none-eabi-gcc 12.2.1 из образа stm-arm:1 без предупреждений, но стартовый код и скрипт компоновщика для gcc придётся написать самому (раздел 4в). Заголовок SPL MDR32FxQI_eeprom.h gcc не собирает: макрос __RAMFUNC определён только для компиляторов ARM, IAR и Phyton.

Как ведёт себя один и тот же вызов CMSIS на двух ядрах, видно по листингу. Приоритет 8 для прерывания UART — строка, привычная по F103:

mdr_uart_prio8
    MOV      r0,#0xe406
    MOVT     r0,#0xe000
    MOVS     r1,#0
    STRB     r1,[r0,#0]
    BX       lr

NVIC_SetPriority(UART1_IRQn, 8) записала в байт приоритета 0: при трёх битах число сдвигается на 5 и 8 << 5 = 0x100 не помещается в байт. Прерывание получило высший приоритет. На F103 та же строка пишет 0x80.


2г.7 Грабли

Чек-лист переноса кода с STM32F103 на К1986ВЕ92QI; всё подтверждено заголовками и сборкой:


2г.8 Итоги

Полные исходники

mdr_blink.c — мигание светодиодом на К1986ВЕ92QI по регистрам: такт 80 МГц от RST_CLK и порт.

/* Раздел 2г: мигание светодиодом по регистрам на К1986ВЕ92QI (в библиотеке — MDR32F9Q2I).
   Заголовки — из пака Milandr.MDR32FxQI 1.3.2, модель задаёт RTE_Components.h (USE_MDR32F9Q2I).
   Такт 80 МГц: кварц 8 МГц, CPU_PLL x10. Светодиод VD3 отладочной платы — PC0
   (readme примера PORT/Joystick_LEDs). Библиотека SPL не линкуется: из её заголовков
   берутся только макросы и константы для проверок (MDR32FxQI_eeprom.h не подключается:
   с gcc он не собирается — __RAMFUNC определён только для компиляторов ARM, IAR и Phyton). */
#include <stddef.h>
#include "MDR32FxQI_config.h"
#include "MDR32FxQI_rst_clk.h"
#include "MDR32FxQI_port.h"
#include "MDR32FxQI_bkp.h"

/* адреса, смещения и биты — по заголовкам пака */
_Static_assert(MDR_RST_CLK_BASE == 0x40020000u, "RST_CLK");
_Static_assert(MDR_PORTC_BASE == 0x400B8000u, "PORTC");
_Static_assert(offsetof(MDR_RST_CLK_TypeDef, CPU_CLOCK) == 0x0C, "CPU_CLOCK");
_Static_assert(offsetof(MDR_RST_CLK_TypeDef, PER_CLOCK) == 0x1C, "PER_CLOCK");
_Static_assert(offsetof(MDR_PORT_TypeDef, OE) == 0x04 && offsetof(MDR_PORT_TypeDef, FUNC) == 0x08
               && offsetof(MDR_PORT_TypeDef, ANALOG) == 0x0C && offsetof(MDR_PORT_TypeDef, PWR) == 0x18, "PORT");
_Static_assert(sizeof(MDR_PORT_TypeDef) == 8 * 4, "PORT: восемь регистров");
_Static_assert(RST_CLK_PER_CLOCK_PCLK_EN_PORTC == (1u << 23), "PORTC: бит 23 в PER_CLOCK");
_Static_assert(PCLK_BIT(MDR_PORTC_BASE) == RST_CLK_PER_CLOCK_PCLK_EN_PORTC
               && PCLK_BIT(MDR_EEPROM_BASE) == RST_CLK_PER_CLOCK_PCLK_EN_EEPROM
               && PCLK_BIT(MDR_BKP_BASE) == RST_CLK_PER_CLOCK_PCLK_EN_BKP, "номер бита = биты 19:15 адреса");
_Static_assert(RST_CLK_CPU_PLLmul10 == 9u, "множитель 10 записывается как 9");
_Static_assert(RST_CLK_CPU_C1srcHSEdiv1 == 2u && RST_CLK_CPUclkCPU_C3 == (1u << RST_CLK_CPU_CLOCK_HCLK_SEL_Pos), "CPU_CLOCK");
_Static_assert(EEPROM_CMD_DELAY_Pos == 3 && EEPROM_CMD_DELAY_Msk == 0x38u, "EEPROM: поле задержки — биты 5:3");
_Static_assert(RST_CLK_PCLK_COMP == (1u << RST_CLK_PER_CLOCK_PCLK_EN_COMP_Pos), "компаратор: бит 19 — через макрос SPL");
_Static_assert(BKP_DUcc_upto_80MHz == ((6u << BKP_REG_0E_LOW_Pos) | (6u << BKP_REG_0E_SELECTRI_Pos)), "регулятор: до 80 МГц");
_Static_assert(PORT_OE_OUT == 1 && PORT_FUNC_PORT == 0 && PORT_MODE_DIGITAL == 1 && PORT_SPEED_SLOW == 1, "PORT: значения полей");

#define LED_PIN 0u                                            /* PC0 */

void mdr_clock_80mhz(void)
{
    MDR_RST_CLK->PER_CLOCK |= RST_CLK_PER_CLOCK_PCLK_EN_EEPROM    /* иначе не записать CMD */
                            | RST_CLK_PER_CLOCK_PCLK_EN_BKP;      /* и REG_0E */
    MDR_RST_CLK->HS_CONTROL = RST_CLK_HS_CONTROL_HSE_ON;          /* кварц 8 МГц */
    while ((MDR_RST_CLK->CLOCK_STATUS & RST_CLK_CLOCK_STATUS_HSE_RDY) == 0) { }

    MDR_RST_CLK->CPU_CLOCK = 2u << RST_CLK_CPU_CLOCK_CPU_C1_SEL_Pos;   /* CPU_C1 = HSE, ядро пока от HSI */
    MDR_RST_CLK->PLL_CONTROL = (9u << RST_CLK_PLL_CONTROL_PLL_CPU_MUL_Pos)   /* x10 */
                             | RST_CLK_PLL_CONTROL_PLL_CPU_ON;
    while ((MDR_RST_CLK->CLOCK_STATUS & RST_CLK_CLOCK_STATUS_PLL_CPU_RDY) == 0) { }

    MDR_EEPROM->CMD = 3u << EEPROM_CMD_DELAY_Pos;                 /* Flash: 3 такта, до 100 МГц */
    MDR_BKP->REG_0E = (MDR_BKP->REG_0E & ~(BKP_REG_0E_LOW_Msk | BKP_REG_0E_SELECTRI_Msk))
                    | (6u << BKP_REG_0E_LOW_Pos) | (6u << BKP_REG_0E_SELECTRI_Pos);  /* регулятор: до 80 МГц */

    MDR_RST_CLK->CPU_CLOCK = (2u << RST_CLK_CPU_CLOCK_CPU_C1_SEL_Pos)  /* C1 = HSE */
                           | RST_CLK_CPU_CLOCK_CPU_C2_SEL               /* C2 = выход PLL */
                           | (0u << RST_CLK_CPU_CLOCK_CPU_C3_SEL_Pos)   /* C3 = C2 / 1 */
                           | (1u << RST_CLK_CPU_CLOCK_HCLK_SEL_Pos);    /* HCLK = CPU_C3 */
}

void mdr_led_init(void)
{
    MDR_RST_CLK->PER_CLOCK |= RST_CLK_PER_CLOCK_PCLK_EN_PORTC;    /* без такта порт не отвечает */
    MDR_PORTC->OE     |=  (1u << LED_PIN);                        /* 1 — выход */
    MDR_PORTC->FUNC   &= ~PORT_FUNC_MODE0_Msk;                    /* 00 — функция «порт» */
    MDR_PORTC->ANALOG |=  (1u << LED_PIN);                        /* 1 — цифровой, 0 — аналоговый */
    MDR_PORTC->PWR     = (MDR_PORTC->PWR & ~PORT_PWR0_Msk)
                       | (1u << PORT_PWR0_Pos);                   /* 01 — драйвер включён, медленный фронт */
}

void mdr_led_toggle(void)
{
    MDR_PORTC->RXTX ^= (1u << LED_PIN);       /* чтение-изменение-запись: регистра вроде BSRR нет */
}

/* приоритет 8 из кода для F103 (4 бита приоритета); в main не вызывается — только для листинга */
void mdr_uart_prio8(void)
{
    NVIC_SetPriority(UART1_IRQn, 8);
}

static void delay(volatile uint32_t n)
{
    while (n--) { }
}

int main(void)
{
    mdr_clock_80mhz();
    mdr_led_init();
    for (;;) {
        mdr_led_toggle();
        delay(4000000u);
    }
}

mdr_spl.c — то же мигание через библиотеку SPL «Миландра».

/* Раздел 2г: то же мигание на К1986ВЕ92QI через библиотеку SPL «Миландра» (MDR32FxQI 1.3.0 из пака 1.3.2).
   Линкуется с MDR32FxQI_rst_clk.c, _port.c, _bkp.c, _eeprom.c. Порядок шагов — как в примере RST_CLK/CPU_Clock. */
#include "MDR32FxQI_config.h"
#include "MDR32FxQI_rst_clk.h"
#include "MDR32FxQI_port.h"
#include "MDR32FxQI_bkp.h"
#include "MDR32FxQI_eeprom.h"

void mdr_spl_clock_80mhz(void)
{
    RST_CLK_PCLKcmd(RST_CLK_PCLK_EEPROM | RST_CLK_PCLK_BKP, ENABLE);
    RST_CLK_HSEconfig(RST_CLK_HSE_ON);
    while (RST_CLK_HSEstatus() != SUCCESS) { }
    RST_CLK_CPU_PLLconfig(RST_CLK_CPU_PLLsrcHSEdiv1, RST_CLK_CPU_PLLmul10);
    RST_CLK_CPU_PLLcmd(ENABLE);
    while (RST_CLK_CPU_PLLstatus() != SUCCESS) { }
    RST_CLK_CPUclkPrescaler(RST_CLK_CPUclkDIV1);
    RST_CLK_CPU_PLLuse(ENABLE);
    BKP_DUccMode(BKP_DUcc_upto_80MHz);
    EEPROM_SetLatency(EEPROM_Latency_3);
    RST_CLK_CPUclkSelection(RST_CLK_CPUclkCPU_C3);
}

void mdr_spl_led_init(void)
{
    PORT_InitTypeDef p;

    RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE);
    PORT_StructInit(&p);
    p.PORT_Pin   = PORT_Pin_0;
    p.PORT_OE    = PORT_OE_OUT;
    p.PORT_FUNC  = PORT_FUNC_PORT;
    p.PORT_MODE  = PORT_MODE_DIGITAL;
    p.PORT_SPEED = PORT_SPEED_SLOW;
    PORT_Init(MDR_PORTC, &p);
}

void mdr_spl_led_toggle(void)
{
    if (PORT_ReadInputDataBit(MDR_PORTC, PORT_Pin_0))
        PORT_ResetBits(MDR_PORTC, PORT_Pin_0);
    else
        PORT_SetBits(MDR_PORTC, PORT_Pin_0);
}

static void delay(volatile uint32_t n)
{
    while (n--) { }
}

int main(void)
{
    mdr_spl_clock_80mhz();
    mdr_spl_led_init();
    for (;;) {
        mdr_spl_led_toggle();
        delay(4000000u);
    }
}

f103_blink.c — то же мигание по регистрам на STM32F103C8 — для сравнения.

/* Раздел 2г: то же мигание по регистрам на STM32F103C8 (пак Keil STM32F1xx_DFP 2.4.1, -DSTM32F10X_MD).
   Такт 72 МГц: кварц 8 МГц, PLL x9, APB1 / 2. Светодиод BluePill — PC13 (раздел 8.4). */
#include <stddef.h>
#include "stm32f10x.h"

_Static_assert(RCC_BASE == 0x40021000u, "RCC");
_Static_assert(GPIOC_BASE == 0x40011000u, "GPIOC");
_Static_assert(offsetof(GPIO_TypeDef, CRH) == 0x04 && offsetof(GPIO_TypeDef, ODR) == 0x0C
               && offsetof(GPIO_TypeDef, BSRR) == 0x10, "GPIO");
_Static_assert(RCC_APB2ENR_IOPCEN == (1u << 4), "GPIOC: бит 4 в APB2ENR");
_Static_assert(RCC_CFGR_PLLMULL9 == (7u << 18), "множитель 9 записывается как 7");
_Static_assert(FLASH_ACR_LATENCY_2 == 2u, "Flash: 2 такта ожидания");

#define LED_PIN 13u                                           /* PC13 */

void f103_clock_72mhz(void)
{
    RCC->CR |= RCC_CR_HSEON;                                  /* кварц 8 МГц */
    while ((RCC->CR & RCC_CR_HSERDY) == 0) { }

    FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;      /* Flash: 2 такта */
    RCC->CFGR = RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9           /* PLL = HSE x9 */
              | RCC_CFGR_PPRE1_DIV2;                          /* APB1 не выше 36 МГц */
    RCC->CR |= RCC_CR_PLLON;
    while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }

    RCC->CFGR |= RCC_CFGR_SW_PLL;                             /* SYSCLK = PLL */
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) { }
}

void f103_led_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;                       /* такт порта C */
    GPIOC->CRH = (GPIOC->CRH & ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13))
               | GPIO_CRH_MODE13_1;                           /* выход 2 МГц, двухтактный */
}

void f103_led_toggle(void)
{
    GPIOC->ODR ^= (1u << LED_PIN);
}

/* приоритет 8 из кода для F103 (4 бита приоритета); в main не вызывается — только для листинга */
void f103_uart_prio8(void)
{
    NVIC_SetPriority(USART1_IRQn, 8);
}

static void delay(volatile uint32_t n)
{
    while (n--) { }
}

int main(void)
{
    f103_clock_72mhz();
    f103_led_init();
    for (;;) {
        f103_led_toggle();
        delay(4000000u);
    }
}

Дальше: раздел 2д «STM32U585CIU6 в корпусе на 48 выводов: что остаётся от U585 и на что его ставить» — какая периферия U585 выведена в 48 выводах, SMPS или LDO и три изделия на одном камне.

Открыть в интерактивном справочнике