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.
2г.2 Тактирование: RST_CLK вместо RCC
Блок RST_CLK лежит по адресу 0x4002 0000. Нужные регистры: CLOCK_STATUS
(0x00, флаги готовности), PLL_CONTROL (0x04), HS_CONTROL (0x08, кварц),
CPU_CLOCK (0x0C) и PER_CLOCK (0x1C). Такт ядра проходит три ступени:
CPU_C1— источник: HSI, HSI/2, HSE, HSE/2 (полеC1_SEL, значения 0…3);CPU_C2—CPU_C1как есть или выходCPU_PLL;CPU_C3— делитель: значения 0…7 — деление на 1, 8 — на 2, 9 — на 4 и так до 15 — на 256.
Последний переключатель 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.
Каждое свойство вывода — в своём регистре:
OE: 1 — выход, 0 — вход;FUNC: два бита на вывод, 00 — порт, 01 — основная функция, 10 — альтернативная, 11 — переопределённая; отдельного блока AFIO нет;ANALOG: 1 — цифровой, 0 — аналоговый;PWR: два бита на вывод, 00 — драйвер выключен, 01 — медленный фронт, 10 — быстрый, 11 — самый быстрый.
Светодиод 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; всё подтверждено заголовками и сборкой:
- Порт не отвечает. Не включён его бит в
PER_CLOCK. Бит считается по адресу: биты 19:15 базы. - Вывод настроен как выход, но молчит. В
ANALOGноль — вывод аналоговый; или вPWR00 — драйвер выключен. Нужны все четыре регистра:OE,FUNC,ANALOG,PWR. - Пропадает связь с отладчиком. Запись целого слова в
RXTXилиOEпорта B или D задела выводы JTAG (0…4). SPL бережёт только тот JTAG, что выбран в конфиге. - Нет
BSRR. Переключение вывода — чтение-изменение-записьRXTX; если тот же порт трогает прерывание, нужна защита от гонки. - Приоритет 8 стал высшим. Битов приоритета 3, а не 4: значения от 8 и выше обрезаются. Приоритеты для К1986ВЕ92 — 0…7.
- Задержки и таймауты не той длины.
SystemCoreClockневерен при делителеCPU_C3, отличном от 1; частоту брать изRST_CLK_GetClocksFreq(). - Не собирается включение компаратора. Макрос
RST_CLK_PER_CLOCK_PCLK_EN_COMPв заголовке сломан; братьRST_CLK_PCLK_COMPиз SPL. - Сбой после разгона. Задержку Flash (
EEPROM->CMD, 3 такта для 80 МГц) и режим регулятораDUccставят до перехода на PLL. - Множитель на единицу больше. В
PLL_CONTROLпишут N − 1, у F103 — N − 2. - Запись по «зашитому» адресу. 0x4002 0000 у F103 — DMA1, здесь — блок тактирования. Только имена из заголовка.
2г.8 Итоги
- Ядро Cortex-M3 общее, периферия своя: К1986ВЕ92QI — 80 МГц, 128 КБ Flash, 32 КБ ОЗУ; F103C8 — 72 МГц, 64 КБ, 20 КБ.
- Такт:
CPU_C1→CPU_PLL→CPU_C3→HCLK_SEL; для 80 МГц ещё 3 такта Flash и режим 6 регулятораDUcc. - Порт — восемь регистров; вывод настраивают
OE,FUNC,ANALOG,PWR;BSRRнет. - Такт периферии — один
PER_CLOCK, бит = биты 19:15 адреса; порт C — 23. - SPL «Миландра» повторяет стиль SPL ST;
SystemCoreClockиз пака ошибается при делителе, отличном от 1. - Мигание по регистрам — 180 байт против 152, через SPL — 936 против 696.
- Битов приоритета 3:
NVIC_SetPriority(…, 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 и три изделия на одном камне.