Таймеры ЛР3

МИФИ · МПС · лабораторная 3 · STM32F103C8T6 · Си

Таймеры: частота и время

Во всех прошлых лабах чип работал на 8 МГц от внутреннего генератора, и время ты отмерял пустым циклом. В ЛР3 время отмеряет железо: сначала ты сам собираешь частоту из кварца, потом раздаёшь её таймерам. Конспект ведёт по порядку работы: частота по варианту, светодиод, SysTick и код Морзе (эксперимент 1), затем то же на TIM1 (эксперимент 2). Эксперименты 3–5 — коротко в конце.

Вариант 8 AHB 18 МГц · SysTick от AHB · TIM1, центрированный счёт Язык Си Источники RM0008 rev 21, PM0056 rev 6, методички ЛР3 2025 и 2026 годов

00 · задание

Задание и план

Конспект писался по прошлогодней методичке. В 2026 году вышла новая с той же таблицей вариантов: числа ниже в силе, но задания перенумерованы, а шаг 1 перенастройки частоты расписан подробнее. Что поменялось — глава «Методичка 2026».

ПараметрВариант 8Где нужен
Частота AHB18 МГцзадание 1 (главы 02–05)
Частота системного таймераAHB (без деления на 8)эксперимент 1 (глава 07)
Таймер и направление счётаTIM1, Ц (центрированный)эксперименты 2–4 (главы 09–10)
Предделитель таймера9000эксперимент 2 (глава 10)
Режим PWM1эксперимент 4
Последовательность цветовY M K G C W B Rэксперимент 4
Энкодер: режим / направление2+эксперимент 5
частота AHB
частота главной, высокоскоростной шины чипа (HCLK, до 72 МГц): на ней ядро, память и SysTick. От неё через делители питаются две шины периферии: APB2 (до 72 МГц — порты GPIO, TIM1) и APB1 — низкоскоростная, до 36 МГц (TIM2–TIM4). Подробно — глава 02
кварц
кварцевый резонатор 8 МГц на плате рядом с чипом; вместе со схемой внутри чипа он образует генератор HSE (high speed external). После сброса чип работает не от него, а от внутреннего RC-генератора HSI — он проще, но менее точен. «Частота от кварца» значит: источник — HSE, умноженный и поделённый до нужной
частота системного таймера
откуда SysTick берёт такты: прямо с AHB или с AHB, поделённой на 8 — глава 07
таймер и направление
какой таймер (TIM1) и как идёт его счётчик: И — инкрементный (вверх), Д — декрементный (вниз), Ц — центрированный (вверх, потом вниз) — глава 09
предделитель
делитель частоты, стоящий перед счётчиком таймера: пропускает к счётчику только каждый N-й входной такт. При N = 9000 и 18 МГц счётчик прибавляет единицу не 18 миллионов, а 2000 раз в секунду. Зачем это нужно — глава 01
режим PWM
как канал таймера формирует импульсы ШИМ: PWM1 или PWM2 — эксперимент 4
цвета
порядок смены цветов в эксперименте 4: Y жёлтый, M магента, K чёрный, G зелёный, C циан, W белый, B синий, R красный (названия как в методичке)
энкодер
«2+»: режим энкодера 2; вращение по часовой стрелке поворачивает сервопривод по часовой — эксперимент 5

Первые два эксперимента — это одна и та же программа, у которой меняется источник «тиков».

  • Эксперимент 1. При старте программа переводит чип с внутреннего генератора HSI на кварц HSE и получает из него частоту AHB 18 МГц. По пути ни одна частота не должна превысить свой предел: 72 МГц для SYSCLK, AHB и APB2, 36 МГц для APB1 (задание 1). Потом она бесконечно передаёт светодиодом код Морзе — своё имя и номер в группе, точка 0,5 с. Время отмеряют прерывания системного таймера SysTick (задание 2).
  • Эксперимент 2. То же сообщение, но прерывания раз в 100 мс даёт TIM1 с предделителем и режимом счёта из варианта (задание 3).

как читаемМеста методички, которые можно понять двояко

В методичке (дословно)Как понимаемПочему
«передавайте в цикле свое имя и порядковый номер в группе» (стр. 8) — какими буквамилатиницей, например «Иван» → IVANв таблице кода методички (стр. 7) только латиница и цифры
то же место — как разделить имя, номер и повторыдва слова: имя, пауза 7 точек, номер цифрами, снова пауза 7 точек — и сначаламежду словами по правилам Морзе — 7 точек; конец цикла — тоже граница между словами
«формируйте сообщение в коде Морзе за светодиоде РС13 или на одном из цветов RGB» (стр. 8)один цвет RGB, в конспекте — синий на PB15годится любой; PB15 не требует отключать JTAG (глава 06)
задание 3: «Настроить базовый таймер на формирование прерываний с периодом 100мс. Используя полученные прерывания, выполнять передачу сообщений в коде Морзе» (стр. 8)точка остаётся 0,5 с, то есть 5 прерываний TIM1в эксперименте 2 «надо повторить задания первого эксперимента» (стр. 8), а там точка 0,5 с; 100 мс — только шаг таймера. Уточни у преподавателя: если точка должна стать 100 мс, меняется одно число (глава 08)

планПорядок работы

Каждый шаг опирается на предыдущий и проверяется отдельно. Если что-то не работает, сразу видно, на каком шаге ошибка.

  1. Частота 18 МГц — главы 02–05. Проверка: значение RCC_CFGR в отладчике.
  2. Светодиод — глава 06. Проверка: зажигается и гаснет записью в BSRR.
  3. SysTick на 0,5 с — глава 07. Проверка: ровное мигание, 20 переключений за 10 с.
  4. Код Морзе — глава 08. Проверка: передача совпадает со шкалой в этой главе.
  5. Эксперимент 2 — главы 09 и 10: тик даёт TIM1, логика Морзе не меняется.

Глава 01 — общая теория таймеров, она нужна для защиты и для понимания глав 07 и 09. Глава «Си» — как записывать всё это на Си: проект, регистры, биты, ожидание, данные, обработчики.

, : задания 1 и 2; таблица вариантов — .

2026 · сверка

Методичка 2026: что поменялось

12 октября 2026 года вышла новая методичка ЛР3: 21 страница, в сборнике это страницы 63–83. Конспект писался по прошлогодней, поэтому здесь сверка: что изменилось, что из уже написанного кода годится и чего в нём не хватает. Таблица вариантов не изменилась ни в одной строке (), так что все числа варианта 8 в конспекте остаются в силе.

номераЗадания перенумерованы

Эксперимент 2026Задания 2026В 2025Главы
1 · программа на Си1–3: оптимизация и volatile, файлы .sct и .map, CMSIS ()не былоСи · сдан
2 · системный таймер4 — частота AHB (); 5 — Морзе на SysTick ()эксперимент 1, задания 1 и 202–08
2 (так в методичке) · базовый таймер6 — прерывания раз в 100 мс ()эксперимент 2, задание 309–10
3 · входной канал7 — длительность нажатий ()задание 4→
4 · выходной канал8 — ШИМ, 100 оттенков ()задание 5→
5 · энкодер9 — сервопривод ()тоже «задание 5»: номер повторялся→
УточнитьДва раздела подряд называются «Эксперимент 2» ( и ). По порядку базовый таймер третий, а следующие разделы снова пронумерованы 3, 4, 5. Как нумеровать в отчёте, спроси у преподавателя.
В этом конспектеВ главах 02–10 номера прошлогодние: «задание 1» там — это задание 4, «эксперимент 1» — эксперимент 2, «задание 3» — задание 6.

текстЧто изменилось в тексте

Место20252026
Запуск программы на Си—свой __main на ассемблере: копирует .data из флеша в ОЗУ и обнуляет .bss (–); стандартные библиотеки отключаются галочкой «Don't Search Standard Libraries» ()
Шаг 1 перенастройкисброс по образцу SystemInitпять подпунктов: включить HSI, дождаться готовности, переключить SW на HSI, дождаться подтверждения, очистить делители и выключить другие источники ()
Шаг 2ссылка «[1], стр 137» — ошибочная, глава 04ссылки нет; добавлен список тактов ожидания по SYSCLK ()
Шаг 5делитель AHBделители AHB, APB1 и APB2 ()
Почему нельзя трогать текущую частотуне объясненово время настройки частота нестабильна, ядро может зависнуть ()
Ссылки на RM и PM[1] стр. 295, 365, 384; [2] стр. 150убраны все. Конспект ссылается на RM0008 rev 21 и PM0056 rev 6 сам
ШИМ, задание 8«не менее 100» оттенковровно 100 оттенков (); добавлено определение скважности ()
PWM1выход 0, пока счётчик меньше CCRтак же (). По RM0008 наоборот — глава →
.data и .bss
секции глобальных и статических переменных. В .data — переменные с начальным значением (int a = 5;): значения хранятся во флеше и при старте копируются в ОЗУ. В .bss — переменные без значения: при старте их обнуляют (). Константы (const) остаются во флеше и никуда не копируются
__main
код между Reset_Handler и main, который готовит .data и .bss. Обычно его даёт библиотека Си. С галочкой «Don't Search Standard Libraries» библиотеки нет, и __main пишут сами — листинг на –
volatile
пометка переменной: её может менять кто-то, кроме этого кода, поэтому компилятор не выбрасывает обращения к ней. Без неё цикл задержки при оптимизации исчезает ()

эксп. 2Можно ли сдать прошлогодний эксперимент 1

Эксперимент 2 по содержанию — прошлогодний эксперимент 1: частота по варианту и Морзе на SysTick. Свой __main в нём отдельно не требуется: он относится к устройству проекта с эксперимента 1. Поэтому обработчик Морзе можно сдавать как есть, а частоту нет. Задание 4 теперь проверяет шаг 1 по подпунктам, а в lab3.c работает только строка HPRE /4 поверх SystemInit. Полный блок перенастройки стоит в комментарии.

Годится как есть
Обработчик Морзе: тайминг проверен по тикам — точка 1, тире 3, внутри буквы 1, между буквами 3, между словами и в конце цикла 7. LOAD 8 999 999 с CLKSOURCE = 1 даёт 0,5 с при 18 МГц. Гашение и зажигание через BSRR под общий анод.
Надо доделать
Перенастройку частоты: блок из комментария сделать рабочим и дополнить шаги 1 и 5 по таблице ниже. Строка HPRE /4 после блока станет лишней.
Шаг ()В lab3.cЧего не хватает
1a–b · включить HSI, дождаться HSIRDYнетСейчас работает, потому что HSI уже включён. Если он выключен, переключения не будет: источник меняется, только когда новый готов . Тогда ожидание SWS не закончится
1c–d · SW = HSI, дождаться SWSесть—
1e · выключить другие источникитолько PLLещё HSE и связанные с ним биты CSSON и HSEBYP. Порядок важен: HSEBYP
1e · очистить делителинетHPRE, PPRE1, PPRE2 и ADCPRE (RM0008, ). После SystemInit остаётся PPRE1 = /2: без очистки состояние зависит от того, что было до main
2 · LATENCY и предвыборкаесть: 2 такта, PRFTBE—
3–4 · HSE, PLLесть—
5 · делители AHB, APB1, APB2только HPRE /4выбрать PPRE1 и PPRE2 и обосновать пределами шин: APB1 до 36 МГц, APB2 до 72. От PPRE2 зависит частота TIM1 по правилу «×2, если делитель не 1» () — это задание 6
6 · SW = PLL, дождаться SWSесть—
CSSON, HSEBYP
биты RCC_CR. CSSON включает слежение за HSE: при отказе кварца чип сам переходит на HSI. HSEBYP подаёт внешний сигнал в обход кварцевого генератора. Оба относятся к HSE, поэтому при полном сбросе их снимают вместе с HSEON (RM0008, )
ADCPRE
поле RCC_CFGR, биты 15:14: делитель частоты АЦП от APB2. В ЛР3 АЦП нет, но подпункт 1e велит очистить все делители (RM0008, )
ОбразецПолный сброс по шагам a и e есть в функции SystemInit файла system_stm32f10x.c (пакет ST в Keil). Сверь с ней порядок своего шага 1, но маски не копируй: на защите спросят, что значит каждая.
AHB или SYSCLKШаг 2 велит настроить флеш , а список на считает по SYSCLK. Верен список, и так же в RM0008 (). В варианте B SYSCLK 72 МГц, значит 2 такта, хотя AHB 18. С 0 тактами программа на плате сбивается, это проверено (глава 04).
Мелочи в lab3.cСтрока с комментарием «B15 & B3» записывает BS15 дважды, а BS3 нет. Сейчас это не мешает: строкой выше уже погашены оба. SysTick->VAL = 0x33: число ни на что не влияет, . Текст сообщения ты уже показывал преподавателю; формально задание 5 требует имя и номер в группе ().

стартSystemInit или свой старт

Прямо методичка этого не решает. В эксперименте 1 подключается только CMSIS → CORE (рис. 31, ), а пункт Device на скриншоте свёрнут. Ссылку на SystemInit из шага 1 убрали и расписали сброс по подпунктам: его ждут от тебя. Сейчас в lab3.uvprojx подключён Device:Startup, то есть SystemInit вызывается.

Device:StartupСвой старт
Что идёт до mainReset_Handler из startup_stm32f10x_md.s: SystemInit, потом __mainтвой Reset_Handler, сразу __main
Частота при входе в main72 МГц8 МГц (HSI)
Только HPRE /4 даст18 МГц2 МГц, точка 4,5 с
Таблица векторовготовая, SysTick_Handler подставится по именисвоя: SysTick_Handler по смещению 0x3C и IMPORT, как в ЛР2
__mainтвой: стартовый файл ищет его через IMPORT __mainтвой
SystemCoreClock72 000 000 при реальных 18 МГц: значение обновляет только SystemCoreClockUpdate()переменной нет

С полной последовательностью из шести шагов разницы нет: шаг 1 возвращает чип к HSI без делителей, дальше всё одинаково. Оставь старт таким, как в сданном эксперименте 1. Если сомневаешься, спроси одной фразой, оставлять ли Device:Startup.

Листинг __mainДословно он не соберётся. ENDP стоит в первой колонке, а armasm всё, что в первой колонке, считает меткой. Нет AREA и END. Имена Image$$RW_IRAM1$$… должны совпасть с областью в .sct — проверь в .map. Если копирование .data не сработает, ticks_left = GAP после сброса будет мусором, и сообщение начнётся не с начала.

Методичка ЛР3 2026: , –, –, –, .

01 · теория

Что такое таймер

зачемВремя без пустых циклов

В ЛР1 и ЛР2 время отмерял процессор: крутил SUBS/BNE и больше ничего не делал. Таймер — отдельный блок, который считает такты сам, параллельно с программой, и в нужный момент поднимает флаг или вызывает прерывание. Процессор в это время свободен. На лекции таймеры названы устройствами, которые позволяют реализовывать такие функции, как «отсчет временных интервалов, измерение длин импульсов, формирование периодических сигналов с заданной скважностью (ШИМ), подсчет событий на входе таймера» (лекция 08, слайд 2) — ровно эксперименты 1–5 ЛР3.

видыЧетыре вида и что в чём содержится

На лекции таймеры STM32 делятся на четыре вида: SysTick, базовые, универсальные (в RM0008 — general-purpose, «общего назначения») и расширенные (advanced-control). Три последних вкладываются друг в друга по возможностям: функционал базового таймера есть во всех, универсальный — это базовый плюс каналы ввода-вывода, расширенный — универсальный «со всеми возможностями + еще чуть-чуть» (лекция 08, слайд 10). Поэтому, разобравшись с базовой частью один раз (ниже, «Как любой таймер отмеряет время»), ты разобрался с ней у TIM1, TIM2, TIM3 и TIM4 сразу.

SysTick в эту схему вложений не входит. Он не периферия ST, а часть ядра Cortex-M3: описан не в RM0008, а в PM0056, у него нет ни предделителя, ни ARR, ни события обновления.

Прямоугольники вложены по возможностям, как на слайде: больший умеет всё, что умеют лежащие в нём. Метки — конкретные таймеры каждого вида во всём семействе STM32F1, то есть во всех чипах STM32F10x, а не только в нашем. Оранжевая зона — какие из них есть на нашем STM32F103C8: она делит таймеры, а не возможности; всё вне её притушено. Нажми на часть внутри зоны — увидишь именно наши таймеры; на часть снаружи или на название — весь вид целиком.

ядро Cortex-M3
SysTick
периферия STM32 · RM0008
ЕСТЬ НА STM32F103C8 Расширенный Универсальный Базовый TIM1 TIM8 TIM2 · TIM3 · TIM4 TIM5 · TIM9–TIM14 TIM6 · TIM7

Выводы каналов по умолчанию — DS5319 Table 5 «Medium-density STM32F103xx pin definitions», .

Базовый прямоугольник целиком вне зоны: отдельного базового таймера на нашем чипе нет. Но всё, что он умеет, умеют TIM1–TIM4 — они лежат в больших прямоугольниках, которые его содержат. То же с TIM5 и TIM9–TIM14: их нет, а их возможности есть у TIM2.

Что значит «high-density» и «XL-density». RM0008 описывает сразу всё семейство STM32F10x и делит его по объёму флеш-памяти: low — 16–32 КБ, medium — 64–128 КБ, high — 256–512 КБ, XL — 768 КБ – 1 МБ, плюс connectivity line — STM32F105/107. Чем больше чип, тем больше в нём периферии. Наш STM32F103C8 — medium density, поэтому TIM5, TIM6, TIM7, TIM8 и TIM9–TIM14 в нём просто не распаяны: это отдельные таймеры других, более крупных STM32, а не функции, которых не хватает нашему. Например, TIM8 — точная копия TIM1 (у них общая глава RM0008), просто второй экземпляр.

Категории чипов — RM0008 §2.3 «Glossary», : «Medium-density devices are STM32F101xx, STM32F102xx and STM32F103xx microcontrollers where the Flash memory density ranges between 64 and 128 Kbytes».

отличие от слайдаНа слайде лекции универсальных прямоугольников два — «универсальные таймеры могут иметь различный функционал». Здесь один: по RM0008 урезанные TIM9–TIM14 не умеют ничего сверх TIM2–TIM5 (сравни списки §16.2.1 и §15.2), так что отдельный прямоугольник ничего бы не добавил. Строго говоря, у TIM9–TIM14 нет DMA, который есть у базовых, — поэтому базовый на слайде не может лежать внутри них целиком.

Таблица — по даташиту нашего чипа . Сторожевые таймеры (IWDG, WWDG) тоже считают, но их работа — перезагрузить зависшую программу; в ЛР3 они не нужны и в схему не попали.

ТаймерВидБитСчётПредделительКаналыDMAШина · NVICВ ЛР3
TIM1расширенный16вверх, вниз, центр.1 … 65 5364 + 3 компл.даAPB2 · IRQ 24–27эксп. 2–5
TIM2, TIM3, TIM4универсальный16вверх, вниз, центр.1 … 65 5364даAPB1 · IRQ 28, 29, 30у других вариантов
SysTickсистемный24внизнет: AHB или AHB / 8—нетядро · исключение 15эксп. 1
IWDGсторожевой12вниз8 бит, от LSI 40 кГц——свой генераторне нужен
WWDGсторожевой7внизот PCLK1——APB1 · IRQ 0не нужен

Лекция 08 «Таймеры», (четыре вида) и : «Функционал базового таймера присутствует во всех», состав базового и универсального таймеров. DS5319 §2.3.15, : «an advanced-control timer, three general-purpose timers, two watchdog timers and a SysTick timer»; Table 4 «Timer feature comparison» — . RM0008: TIM1 — §14.2, ; TIM2–TIM5 — §15.2, ; TIM9–TIM14 — §16.2.1, : «This section applies to XL-density devices only»; TIM6, TIM7 — §17.2, : «This section applies to high-density and XL-density»; входы NVIC — Table 63, . SysTick — PM0056 §4.5, .

какКак любой таймер отмеряет время

Всё, что умеют таймеры в ЛР3, держится на одной схеме. На вход приходит тактовая частота. Делитель уменьшает её. Счётчик считает получившиеся тики до заданной границы, на границе начинает заново и поднимает флаг — это событие. Если прерывание разрешено, флаг вызывает обработчик.

На стенде справа эта цепочка нарисована целиком — от кварца до светодиода — и работает в реальном времени. Блоки 5–6 — сам таймер, флаг и светодиод: переключи SysTick или TIM1 и посмотри, как меняются делитель, счётчик и флаг; нажми на блок — под схемой откроется его регистр.

SysTick: период = (LOAD + 1) / частота тиков = 9 000 000 / 18 000 000 Гц = 0,5 с
TIM1: частота тиков = 18 000 000 / (PSC + 1) = 2000 Гц; центрированный счёт — событие каждые ARR = 200 тиков = 100 мс
SystemInit
функция из файла system_stm32f10x.c, который Keil сам добавляет в проект на Си. Её вызывает стартовый код — ещё до первой строки твоей main. По умолчанию она переводит чип на кварц HSE с умножителем PLL × 9: SYSCLK и AHB — 72 МГц, APB2 — 72 МГц, APB1 — 36 МГц (делитель /2), флеш — 2 такта ожидания. В ЛР1 и ЛР2 на ассемблере её не было, поэтому чип работал от HSI на 8 МГц. В проекте на Си к началу main уже 72 МГц, и для 18 МГц эту настройку придётся переделать (глава 05)

SystemInit — system_stm32f10x.c из пакета Keil STM32F1xx_DFP 2.4.1: выбор 72 МГц — строка 114, задержка флеша — строка 1017, делители и PLL × 9 — строки 1021–1055; вызов из startup_stm32f10x_md.s — строки 131–139.

зачемЗачем делить частоту перед счётчиком

Счётчик прибавляет единицу на каждый такт — каждый импульс тактового сигнала, а их миллионы в секунду. Досчитав до максимума своей разрядности, он начинает сначала. Если каждый такт сразу идёт в счётчик, весь его диапазон пробегается слишком быстро: на один круг счёта приходится меньше времени, чем нужно отмерить:

Таймер при 18 МГцБез деленияС делением
TIM1, 16 бит: максимум 65 536 тиков65 536 / 18 МГц = 3,6 мс÷ 9000: тик 0,5 мс → до 32,8 с
SysTick, 24 бита: максимум 16 777 216 тиков16 777 216 / 18 МГц = 0,93 с÷ 8: до 7,5 с

Для 100 мс в эксперименте 2 нужно 1 800 000 тактов по 18 МГц — в 16 бит TIM1 они не помещаются. Поэтому частоту сначала делят: предделитель — это ещё один маленький счётчик перед основным. Он считает входные такты до N и на каждом N-м такте даёт основному счётчику один тик. Основной счётчик видит уже 18 МГц / N.

Так время раскладывается на два множителя: предделитель задаёт «цену» одного тика (при N = 9000 — 0,5 мс), а граница ARR — сколько тиков отсчитать (200 тиков по 0,5 мс = 100 мс). У SysTick полноценного предделителя нет, есть только выбор «делить на 8 или нет»; для точки 0,5 с хватает и этого — глава 07.

PSC
регистр TIMx_PSC (смещение 0x28), 16 бит. Программа один раз записывает в него делитель: такт счёта = частота таймера / (PSC + 1). Делит на число на единицу больше записанного: 8999 делит на 9000, PSC = 0 — без деления. Так в 16 бит помещается деление от 1 до 65 536
CNT
регистр TIMx_CNT (смещение 0x24), 16 бит, — текущее число тиков. Его меняет сам таймер: на каждый такт счёта прибавляет 1 (или вычитает при счёте вниз). Программа может прочитать его, чтобы узнать, сколько натикало, или записать, чтобы начать с нужного числа
ARR
регистр TIMx_ARR (смещение 0x2C), 16 бит, — число, до которого считает CNT. Программа записывает его при настройке. Когда CNT доходит до ARR, таймер сам начинает счёт заново с нуля и выдаёт событие обновления. Отсюда имя: auto-reload, «автоматическая перезагрузка»
UEV
событие update event, «событие обновления» — момент, когда CNT дошёл до ARR и начал заново. В этот момент таймер ставит флаг UIF и применяет новые значения PSC и ARR (глава 09)
UIF
бит 0 регистра TIMx_SR (смещение 0x10) — флаг «было событие обновления». Ставит его таймер, сбрасывает программа записью 0. Если в регистре TIMx_DIER разрешён бит UIE, поставленный UIF вызывает прерывание

Лекция 08 «Таймеры», и (виды таймеров, базовая часть); : «Делит частоту на PSC+1». RM0008 §14.3.1, ; формула — §14.4.11, : «The counter clock frequency (CK_CNT) is equal to fCK_PSC / (PSC[15:0] + 1)». Вход TIM1_UP — RM0008 Table 63, .

Регистры TIMx_CNT, TIMx_PSC, TIMx_ARR — RM0008 §14.4.10–14.4.12, ; флаг UIF — §14.4.5, ; смещения — карта регистров §14.4.21, .

COUNTFLAG, TICKINT, CLKSOURCE — PM0056 §4.5.1, ; входы NVIC TIM1_UP и TIM2–TIM4 — RM0008 Table 63, .

Си · инструмент

Си для микроконтроллера

зачемЧто делает Си, а что остаётся за тобой

В ЛР1 и ЛР2 ты сам складывал базу и смещение, писал тройки LDR → операция → STR, следил за R0–R3 и заполнял таблицу векторов. На Си это делает компилятор. За тобой остаётся то же, что и было главным: какие регистры, какие биты и в каком порядке. Главы 02–10 описывают именно это. Здесь — как их записать.

Примеры ниже нарочно на соседних регистрах — порт A, генератор LSI, TIM2. Программу ЛР3 из этих приёмов собираешь ты. Подробнее, с дизассемблером и сравнением с твоим ассемблером, — в отдельном конспекте «Си для STM32F103», но для ЛР3 достаточно этой главы.

проектИз чего собирается программа

ФайлОткудаЧто делаетВ ЛР2 это было
startup_stm32f10x_md.sKeil, Device → Startupтаблица векторов со всеми именами обработчиков, стек, Reset_Handlerтвои DCD и Reset_Handler
system_stm32f10x.cтам жеSystemInit() — до main включает 72 МГц (глава 05)не было — работали от HSI 8 МГц
stm32f10x.hпакет Keilвсе регистры как структуры, имена битов, номера прерыванийтвои EQU
core_cm3.hпакет CMSISрегистры ядра (NVIC, SysTick) и функции NVIC_…EQU для NVIC
main.cтынастройка и логикаостальной lab2.s

Новый проект в Keil: Project → New µVision Project, устройство STM32F103C8. В окне Manage Run-Time Environment отметить CMSIS → CORE и Device → Startup — Keil сам добавит стартовый файл и system_stm32f10x.c. Потом Add New Item → C File → main.c. Собрать — F7, прошить — tools/flash-keil, как в ЛР2. Через make — Makefile из одной строки include ../tools/rules-c.mk.

После сброса порядок такой: Reset_Handler → SystemInit() → служебная __main (обнуляет глобальные переменные) → твоя main(). Самый маленький main.c:

#include "stm32f10x.h"          /* все регистры, биты и номера прерываний чипа */

int main(void)
{
    /* настройка: частота, выводы, таймеры — по шагам */

    while (1) {
        /* главный цикл; в ЛР3 он пустой — работа идёт в обработчиках */
    }
}

регистрыРегистр — поле структуры

В stm32f10x.h каждый блок периферии описан как структура — список регистров в том порядке, в каком они лежат в памяти, по 4 байта на регистр. Имя блока — указатель на структуру по базовому адресу. Поэтому TIM2->ARR — это регистр по адресу база TIM2 + смещение ARR, ровно как в таблице регистров RM0008. Стрелка -> читается «поле структуры, на которую указывает указатель».

В RM0008 и PM0056На СиГде в ЛР3
RCC_CR, RCC_CFGR, RCC_APB2ENRRCC->CR, RCC->CFGR, RCC->APB2ENRглавы 05, 06, 10
FLASH_ACRFLASH->ACRглава 04
GPIOx_CRL, GPIOx_CRH, GPIOx_BSRRGPIOB->CRH, GPIOB->BSRR …глава 06
STK_CTRL, STK_LOAD, STK_VALSysTick->CTRL, SysTick->LOAD, SysTick->VALглава 07
TIMx_CR1, TIMx_PSC, TIMx_ARR, TIMx_SR …TIM1->CR1, TIM1->PSC …главы 09–10
NVIC_ISER0, NVIC_ISER1NVIC->ISER[0], NVIC->ISER[1]глава 10

Имена битов и полей устроены по схеме БЛОК_РЕГИСТР_ПОЛЕ, как в RM0008: RCC_CR_HSERDY — бит HSERDY регистра RCC_CR. У полей есть два вида имён: маска всего поля (RCC_CFGR_HPRE = 0x000000F0) и готовые значения (RCC_CFGR_HPRE_DIV2 = 0x00000080). Маской очищают, значением записывают. Маску можно писать и числом: 1u << 9, 0x7u << 24. Буква u делает число беззнаковым — со сдвигом на 31 бит без неё в Си выходит неопределённое поведение.

битыПять операций

Каждая твоя ассемблерная тройка — одна строка. Компилятор сам развернёт её в те же команды.

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;   /* поставить бит:   LDR / ORR / STR */
GPIOA->CRL   &= ~GPIO_CRL_CNF1;       /* сбросить биты:   LDR / BIC / STR */
GPIOA->ODR   ^= GPIO_ODR_ODR1;        /* переключить:     LDR / EOR / STR */
GPIOA->BSRR   = GPIO_BSRR_BR1;        /* записать целиком: STR, без чтения */
if (GPIOA->IDR & GPIO_IDR_IDR0) {     /* проверить бит:   LDR / TST */
    /* ... */
}
Флаги: сброс единицей или нулёмРегистры флагов не трогают через |= и &=: это чтение-изменение-запись, и между чтением и записью может встать чужой флаг. В EXTI_PR флаг сбрасывается записью 1 (rc_w1) — пишут только нужный бит: EXTI->PR = EXTI_PR_PR3;. В TIMx_SR — записью 0 (rc_w0) — пишут слово, где в нужном бите 0, а в остальных 1: TIM2->SR = ~TIM_SR_UIF;. Единицы в остальных битах ничего не меняют.

полеЗаписать поле из нескольких бит

Поле — несколько бит с одним смыслом: делитель, множитель, режим вывода. Просто |= только добавляет единицы и не убирает старые, поэтому поле сначала очищают маской, потом вставляют значение.

/* одной строкой: прочитать, очистить поле, вставить новое значение, записать */
GPIOA->CRL = (GPIOA->CRL & ~(GPIO_CRL_MODE1 | GPIO_CRL_CNF1)) | GPIO_CRL_MODE1_1;

/* двумя строками — то же, но между ними в поле на мгновение лежат нули */
RCC->CFGR &= ~RCC_CFGR_MCO;
RCC->CFGR |=  RCC_CFGR_MCO_SYSCLK;

Обычно годится любой вариант. Но если ноль в поле что-то значит, пиши одной строкой. Например, в поле SW регистра RCC_CFGR код 00 — это «переключиться на HSI», и двухстрочная запись на мгновение именно это и попросит.

ждатьДождаться бита или значения поля

«Дождаться готовности» из методички — пустой цикл, который крутится, пока условие не выполнится. Фигурные скобки пустые: делать нечего, только проверять.

RCC->CSR |= RCC_CSR_LSION;                 /* включить генератор LSI */
while (!(RCC->CSR & RCC_CSR_LSIRDY)) { }   /* ждать, пока бит готовности станет 1 */

while ((RCC->CFGR & RCC_CFGR_MCO) != RCC_CFGR_MCO_SYSCLK) { }   /* ждать значение поля */

Как читать последнюю строку. Имена в ней — просто числа. В stm32f10x.h они заданы через #define: RCC_CFGR_MCO = 0x0F000000 — маска поля MCO, единицы ровно на месте поля; RCC_CFGR_MCO_SYSCLK = 0x04000000 — код 0100, уже сдвинутый на место поля. Перед компиляцией вместо имён подставляются эти числа, и компилятор видит while ((RCC->CFGR & 0x0F000000) != 0x04000000) { }.

На каждом круге цикл делает три вещи. Читает регистр: RCC->CFGR — это адрес 0x4002 1000 + 4, а регистры объявлены volatile, поэтому он читается заново каждый раз. Обнуляет & всё, кроме поля, чтобы соседние поля не мешали сравнению. Сравнивает остаток с нужным кодом: не равно — ещё круг. Это тот же цикл, что ты писал на ассемблере:

wait    LDR   r1, [r0, #4]          ; r0 = 0x40021000 (RCC), читаем RCC_CFGR
        AND   r1, r1, #0x0F000000   ; оставить только поле MCO
        CMP   r1, #0x04000000       ; MCO == 0100?
        BNE   wait                  ; нет — ещё раз

Поле сравнивают целиком, а не по одному биту: у поля несколько кодов, и только полное совпадение однозначно говорит, какой из них сейчас записан. Число за любым именем показывает метка c рядом с именем в главах 02–10, а в Keil — правый клик по имени → Go To Definition.

Скобки при сравнении поляВ Си != и == выполняются раньше, чем &. Без скобок REG & MASK != VALUE читается как REG & (MASK != VALUE) — то есть REG & 1 или REG & 0 в зависимости от того, равны ли MASK и VALUE, и цикл проверяет совсем не то. Поэтому выделение поля всегда в скобках: (REG & MASK) != VALUE. gcc с ключом -Wall об этом предупреждает.

данныеПеременные, volatile, массивы и строки

volatile uint32_t ticks;       /* меняет обработчик, читает main — нужен volatile */
const uint16_t limit = 500;    /* константа: лежит во флеше, менять нельзя */
uint32_t count;                /* глобальная: живёт всю программу, в начале 0 */

Типы с размером в имени: uint32_t — 32 бита без знака (слово, как DCD), uint16_t и uint8_t — 16 и 8. Регистры периферии — 32-битные.

volatile запрещает компилятору выбрасывать или объединять обращения к переменной. Он нужен, когда значение меняется «само»: так объявлены все регистры в заголовке. И так же нужно объявлять переменную, которую меняет обработчик прерывания, а читает main. Без volatile main прочитает её один раз и дальше будет работать со старым значением.

const uint8_t pattern[] = { 1, 3, 1, 1 };                  /* 4 числа; pattern[0] — первое */
const uint32_t n = sizeof pattern / sizeof pattern[0];     /* = 4: длина массива */

/* дальше — внутри функции: */
const char text[] = "AB12";        /* 5 байт: 'A', 'B', '1', '2' и завершающий '\0' */
for (uint32_t i = 0; text[i] != '\0'; i++) {      /* пройти строку до конца */
    char c = text[i];
    if (c >= 'A' && c <= 'Z') {
        uint32_t k = c - 'A';      /* буква → номер 0…25: 'A' → 0, 'B' → 1 */
    } else if (c >= '0' && c <= '9') {
        uint32_t k = c - '0';      /* символ цифры → число 0…9 */
    }
}

const char *const colors[] = { "red", "green", "blue" };   /* массив строк: colors[1] — "green" */

Массив нумеруется с нуля, длину знает sizeof. Строка — массив символов, который заканчивается нулевым байтом '\0'; по нему цикл понимает, что строка кончилась. Символ в Си — это просто число (код ASCII), и буквы идут подряд, поэтому c - 'A' превращает букву в её номер — удобный индекс в таблицу. const у данных кладёт их во флеш, а не в ОЗУ.

Если элемент данных — не одно число, а несколько связанных, их объединяют в структуру:

typedef struct {
    uint8_t  pin;      /* номер вывода */
    uint16_t ms;       /* сколько миллисекунд */
} blink_t;             /* новый тип: «вывод + длительность» */

blink_t list[16];      /* место под 16 таких пар в ОЗУ */
uint32_t list_len = 0;

/* внутри функции: */
list[list_len++] = (blink_t){ 3, 100 };   /* дописать пару в конец */

функцииРазбить main на шаги

Функция без результата объявляется как void имя(void). Если вынести каждый шаг настройки в свою функцию, main читается как план из главы 00, а ошибку легче локализовать: можно временно закомментировать один вызов. Функцию надо определить выше места вызова (или объявить заранее).

static void port_a_init(void)       /* static — видна только в этом файле */
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    /* ... */
}

int main(void)
{
    port_a_init();                  /* main читается как план: шаг за шагом */
    while (1) { }
}

прерыванияОбработчик — функция с именем из таблицы

В стартовом файле каждая строка таблицы векторов уже ссылается на имя, например DCD TIM2_IRQHandler, и все эти имена объявлены «слабыми»: пока ты не написал функцию с таким именем, строка ведёт в заглушку Default_Handler — бесконечный цикл. Написал функцию — компоновщик подставит её. Считать строки таблицы больше не нужно.

volatile uint32_t ticks;

void TIM2_IRQHandler(void)           /* имя — точно как в startup_stm32f10x_md.s */
{
    TIM2->SR = ~TIM_SR_UIF;          /* флаг таймера сбрасывается записью 0 — первым делом */
    ticks++;
}

/* в main, при настройке: */
NVIC_EnableIRQ(TIM2_IRQn);           /* то же, что NVIC->ISER[0] = 1u << 28; */

Внешнему прерыванию нужно ещё разрешение в NVIC — функция CMSIS NVIC_EnableIRQ(номер), номера вида TIM2_IRQn лежат в заголовке. SysTick — исключение ядра, ему NVIC не нужен. Регистры R0–R3 и R12 компилятор, как и ты в ЛР2, использует свободно, а остальные сам сохранит в стеке, если понадобятся.

ИсточникИмя функцииНомер для NVIC
SysTickSysTick_Handlerне нужен
TIM1, событие обновленияTIM1_UP_IRQHandlerTIM1_UP_IRQn = 25
TIM1, захват и сравнениеTIM1_CC_IRQHandlerTIM1_CC_IRQn = 27
TIM2, TIM3, TIM4TIM2_IRQHandler …TIM2_IRQn = 28, 29, 30
кнопки EXTI (ЛР2)EXTI3_IRQHandler …EXTI3_IRQn = 9 …

ошибкиЧто чаще всего ломается

СимптомПричина
обработчик не вызывается, программа висит в Default_Handlerопечатка в имени: SysTick_handler, TIM1_IRQHandler — сборка проходит, но функцию никто не зовёт
«undeclared identifier» у имени битаопечатка в имени из заголовка: RCC_CFGR_PLLMUL9 вместо RCC_CFGR_PLLMULL9
main не видит изменений из обработчикаобщая переменная без volatile
цикл ожидания не заканчивается или проскакиваетнет скобок вокруг REG & MASK перед сравнением
условие всегда истинно= вместо == внутри if или while
поле получило «смесь» значений|= без предварительной очистки маской
теряются флагифлаговый регистр изменён через |= или &=
в отладчике переменная «not in scope»оптимизатор держит её в регистре — смотри глобальные и volatile; для отладки уровень -O0

Все имена — из stm32f10x.h (Keil STM32F1xx_DFP 2.4.1) и core_cm3.h (CMSIS 6.1.0); примеры собраны arm-none-eabi-gcc 16.2 с этими заголовками без предупреждений. Регистры — RM0008 Table 3, (базы блоков); SysTick — PM0056 §4.5, .

02 · тактирование

Дерево тактирования

зачемОткуда у таймера «секунды»

Таймер ничего не знает о секундах — он считает такты. Сколько тактов в секунде, решает цепочка делителей и умножителей от генератора до таймера. Поэтому задание 1 стоит первым: пока частота не известна точно, ни SysTick, ни TIM1 не отмерят 0,5 с.

заданиеЧто требует методичка

Требований три, и все записаны прямо в методичке:

  1. Частота AHB — по варианту. Так сказано в задании 1 . В таблице вариантов (стр. 12) у варианта 8 в столбце «Частота AHB, МГц» стоит 18.
  2. Источник — кварц через PLL. Так требуют введение к заданию и шаг 4 .
  3. Нигде не превысить предел. Задание 1 ограничивает все частоты 72 МГц . У шины APB1 по RM0008 предел ниже — 36 МГц.

Частота AHB не задаётся одним числом: её дают генератор, умножитель, переключатель и делитель, и чтобы получить 18 МГц от кварца, надо понимать, как они связаны.

устройствоДороги и один переключатель

Дерево тактирования легче понять не как цепочку, через которую частота проходит целиком, а как несколько дорог и один переключатель между ними. Генераторы и умножитель — только кандидаты: каждый можно включить, настроить и дождаться его готовности, но частота ядра от этого не меняется. Решает переключатель SW. Всё, что стоит после него, только делит частоту. Ниже это разобрано по порядку; каждую ситуацию кнопка ставит на стенде справа, а клик по блоку стенда открывает его регистр.

Исходное положение. После сброса ядро работает от HSI — генератора внутри чипа на 8 МГц . Кварц и умножитель выключены. Так работали ЛР1 и ЛР2. В проекте Keil до main ещё успевает отработать SystemInit и меняет эту картину — к этому вернёмся в главе 05.

Включить генератор — ещё не подключить его. Кварцевый генератор HSE запускают битом HSEON. Частоту он даёт не сразу: кристаллу нужно время раскачаться, и пока колебания не станут стабильными, железо держит бит готовности HSERDY = 0. Но и готовый HSE ни к чему не подключён — ядро продолжает работать от HSI. Включённый генератор лишь становится доступен тем, кто стоит дальше: умножителю и переключателю.

Переключатель SW решает, от чего работает ядро. Это поле регистра RCC_CFGR, и входов у него три : HSI, HSE и PLL. Первые два — генераторы напрямую, ровно 8 МГц, без деления и умножения. Третий — выход умножителя PLL. То, что выбрано в SW, и становится системной частотой SYSCLK.

Переключатель не даст ядру остаться без частоты: он переходит только на готовый источник, а если выбран неготовый — ждёт, пока тот заработает . Поэтому записать SW мало: что выбрано на самом деле, показывает соседнее поле SWS, и его дожидаются.

PLL — отдельная дорога, а не ступень на пути. Напрямую получается только 8 МГц; любую другую частоту даёт третий вход — PLL. У этой ветки свой вход, и выбирают его два переключателя по очереди. PLLSRC решает, какой генератор подать: HSI или HSE. HSI в PLL всегда приходит поделённым на 2 (4 МГц) — этот делитель не настраивается. Если выбран HSE, второй бит, PLLXTPRE, решает, подать его целиком (8 МГц) или пополам (4 МГц); на ветку HSI он не влияет. Поэтому HSI без деления попадает в систему только напрямую, через SW = 00 . Вход PLL умножает на целое число PLLMUL — от 2 до 16.

У PLL строгий порядок: настроить, включить, дождаться, выбрать. Вход и множитель задают, пока PLL выключен; после включения их уже не поменять . Потом PLLON = 1 — и, как кварцу, PLL нужно время, чтобы подстроить выход под вход (в RM0008 это называется «PLL locked»); готовность показывает PLLRDY. И даже готовый PLL ничего не переключает: его выход просто появляется на третьем входе SW. Ядро перейдёт на него только после записи SW = 10.

Занятый источник не выключить. Пока источник питает ядро — напрямую или через PLL, — остановить его нельзя . Запись PLLON = 0 или HSEON = 0 в этот момент просто не выполнится. А раз работающий PLL не выключить, то и множитель его не поменять. Отсюда первый шаг перенастройки в главе 05: увести ядро на HSI и только потом трогать PLL. На стенде это видно, если при включённом SW = PLL нажать на PLL: стенд откажет и подсветит, что выставить сначала.

После переключателя — только деление. У делителей нет бита включения и флага готовности: записал код — деление сразу другое. HPRE делит SYSCLK для шины AHB; получается HCLK — на ней работают ядро, память и SysTick, это и есть «частота AHB» из варианта. PPRE1 и PPRE2 делят HCLK для шин периферии APB1 и APB2. Таймер берёт частоту своей шины, а если её делитель не 1 — удвоенную (TIMxCLK). Пределы — 72 МГц для AHB и APB2, 36 МГц для APB1.

Что из этого собирает вариант 8. Включить кварц и дождаться HSERDY → подать на PLL HSE / 2 = 4 МГц → умножить на 9, получится 36 МГц → включить PLL и дождаться PLLRDY → SW = PLL, SYSCLK = 36 МГц → HPRE = 2, AHB = 18 МГц. Почему именно 4 × 9 и деление на 2 — глава 03; в каком порядке писать это в регистры, когда программа уже работает от PLL, — глава 05.

словаКаждый блок по отдельности

HSI
генератор внутри чипа на RC-цепочке, 8 МГц. От него чип работает после сброса — так было в ЛР1 и ЛР2. Прост, но частота плавает с температурой. Бит HSION регистра RCC_CR
HSE
генератор на внешнем кварцевом резонаторе; на Blue Pill кварц 8 МГц. Точнее HSI, но после включения (бит HSEON в RCC_CR) ему нужно время, чтобы запуститься: готовность показывает бит HSERDY
PLLXTPRE
бит 17 регистра RCC_CFGR: делить ли HSE на 2 по пути в PLL. 0 — HSE как есть (8 МГц), 1 — HSE / 2 (4 МГц)
PLLSRC
бит 16 регистра RCC_CFGR: что подать на вход PLL. 0 — HSI / 2 (4 МГц), 1 — ветку HSE (8 или 4 МГц — решает PLLXTPRE). Методичка требует HSE, значит 1
PLL
phase-locked loop, «умножитель частоты»: схема, которая выдаёт частоту входа, умноженную на целое число PLLMUL. Только так из кварца 8 МГц получить больше 8. Включается битом PLLON в RCC_CR, готовность — PLLRDY. Настраивать можно только выключенным. Выход — не больше 72 МГц
PLLMUL
поле 21:18 регистра RCC_CFGR, 4 бита: во сколько раз PLL умножает вход — от ×2 (0000) до ×16. Для ×9 — 0111
SW, SWS
поля 1:0 и 3:2 регистра RCC_CFGR. SW — переключатель: что станет системной частотой — HSI (00), HSE (01) или PLL (10). SWS — только для чтения: что выбрано на самом деле; переключение происходит не мгновенно, его дожидаются по SWS
SYSCLK
системная частота — то, что выбрал переключатель SW. Не больше 72 МГц. От неё зависят такты ожидания флеша (глава 04)
HPRE
поле 7:4 регистра RCC_CFGR: делитель между SYSCLK и шиной AHB — 1, 2, 4, 8, 16, 64, 128, 256, 512. Код 1000 — /2, 1001 — /4
HCLK (AHB)
частота шины AHB = SYSCLK / HPRE, не больше 72 МГц. На ней работают ядро, память и SysTick. Это и есть «частота AHB» из варианта — 18 МГц
PPRE1, PPRE2
поля 10:8 и 13:11 регистра RCC_CFGR: делители между AHB и шинами APB1 и APB2 — 1, 2, 4, 8, 16. Вариант их не задаёт
PCLK1, PCLK2
частоты шин периферии: APB1 — низкоскоростная, не больше 36 МГц; APB2 — высокоскоростная, до 72. PCLK1 = HCLK / PPRE1, PCLK2 = HCLK / PPRE2, поэтому быстрее AHB не бывают
TIMxCLK
частота таймера: равна PCLK, если делитель APB = 1, иначе удвоенной PCLK. TIM1 — на APB2, TIM2–4 — на APB1

попробуйСобери частоту сам

Та же цепочка, но с переключателями: меняй источник, множитель и делители, и схема пересчитается сразу. Красным — то, что нарушает пределы RM0008; под схемой — что записать в регистры для выбранной настройки. Кнопка «После SystemInit» показывает, с чего начинает программа в Keil.

Готовые варианты:

Лекция 04 «RCC», и : источники частоты, поля RCC_CR и RCC_CFGR. Дерево тактирования — RM0008 §7.2, Figure 8, ; таймеры ×2 и SysTick от HCLK/8 — там же, : «The RCC feeds the Cortex® System Timer (SysTick) external clock with the AHB clock (HCLK) divided by 8». PLL — §7.2.3, . Ограничение 36 МГц для APB1 — §7.3.2, .

Требования — , , и ; вариант — . Поля RCC_CFGR — RM0008 §7.3.2, ; биты RCC_CR — §7.3.1, .

Пределы шин — RM0008 §7.2, : «The maximum frequency of the AHB and the APB2 domains is 72 MHz. The maximum allowed frequency of the APB1 domain is 36 MHz»; HSE и HSI — §7.2.1–7.2.2, .

Три источника SYSCLK — RM0008 §7.2, ; HSI «directly … or divided by 2» — §7.2.2, ; порядок настройки PLL — §7.2.3, ; выбор SYSCLK, переключение только на готовый источник, занятый источник не остановить — §7.2.6, .

03 · расчёт

Откуда взять 18 МГц

Число 18 МГц задано вариантом (глава 02). Выбирать надо не частоту, а способ её получить. Рассуждение в три шага.

1. Что может выдать PLL. Вход PLL — ветка HSE, то есть 8 МГц (PLLXTPRE = 0) или 4 МГц (PLLXTPRE = 1). PLLMUL — целое от 2 до 16, выход — не больше 72 МГц:

Вход PLLВозможный выход PLL, МГц
4 МГц (HSE / 2)8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64
8 МГц (HSE)16, 24, 32, 40, 48, 56, 64, 72 (×10 и больше — уже выше 72)

Все числа кратны 4, и 18 среди них нет. Поэтому просто «включить PLL на 18» невозможно.

2. Чем можно поделить. После переключателя стоит делитель HPRE: HCLK = SYSCLK / HPRE, где HPRE — 1, 2, 4, 8, 16… Значит, SYSCLK должна быть равна 18 × HPRE: 18, 36, 72, 144… 18 PLL не даёт, 144 больше предела, остаются 36 и 72.

3. Итого — ровно два способа:

Вход PLLPLLMULSYSCLKHPREHCLKФлеш (глава 04)
AHSE/2 = 4 МГц× 936 МГц/ 218 МГц1 такт ожидания
BHSE = 8 МГц× 972 МГц/ 418 МГц2 такта ожидания

Обе правильные. На защите спросят не какую ты выбрал, а почему без делителя AHB 18 МГц не получить. Дальше в конспекте везде вариант A: у него меньше тактов ожидания флеша (глава 04), и именно он проверен на твоей плате. Где у варианта B другие числа, они даны в скобках.

Делители APB вариант не задаёт. После SystemInit PPRE2 = /1, а PPRE1 = /2. С нашей AHB это значит APB2 = 18 МГц и APB1 = 9 МГц — оба в пределах нормы, так тоже правильно. В конспекте обе ставим /1 ради простоты: все шины по 18 МГц, правило удвоения для таймеров не участвует. Главное для эксперимента 2 в любом случае выполняется: TIM1 тактируется ровно 18 МГц.

PLLMUL от ×2 до ×16, «The PLL output frequency must not exceed 72 MHz» — RM0008 §7.3.2, ; значения HPRE — . Источник PLL — методичка ЛР3, стр. 7.

04 · флеш

Флеш не успевает за ядром

зачемТакты ожидания

Флеш-память читается медленнее, чем умеет ядро на высокой частоте. Чтобы ядро не получило недочитанную команду, в FLASH_ACR задают число тактов ожидания (LATENCY). Важная деталь: считается оно от SYSCLK, а не от частоты AHB.

SYSCLKLATENCY
до 24 МГц0
24 … 48 МГц1
48 … 72 МГц2

Отсюда «1 такт» у варианта A (SYSCLK 36) и «2 такта» у варианта B (SYSCLK 72), хотя HCLK у обоих 18. Второе правило — про буфер предвыборки (prefetch): он обязан быть включён при любом делителе HPRE, кроме /1, то есть при /2, /4, /8 … /512 . При /1 он может быть и выключен. У нас HPRE = /2, значит буфер нужен. После сброса он и так включён, и трогать его не стоит: включать и выключать буфер разрешено только при SYSCLK ниже 24 МГц и HPRE = /1 .

Порядок важен: LATENCY выставляется до переключения на быструю частоту. Если переключиться раньше, первые же команды прочитаются из флеша с ошибками.

Методичка ссылается не на ту главуМетодичка отправляет к «[1], стр 137, описание поля HPRE». В RM0008 на стр. 137 — глава 8, это RCC для connectivity line (STM32F105/107). У нашего STM32F103C8 (medium density) те же регистры описаны в главе 7, стр. 99–103, а правила для флеша — на стр. 58. Поля в целом совпадают, но ссылаться на защите лучше на главу 7.

RM0008 §3.3.3, : «The wait states represent the ratio of the SYSCLK (system clock) period to the Flash memory access time»; буфер предвыборки — там же, : «The prefetch buffer must be kept on when using a prescaler different from 1 on the AHB clock». Адрес FLASH — Table 3, ; поля FLASH_ACR — там же, §3.3.3, .

05 · задание 1

Перенастройка по шагам

откудаС чего начинает программа

В проекте Keil на Си к началу main частота уже не 8 МГц. Стартовый файл до main вызывает SystemInit (конспект по Си, глава 07), и она оставляет вот что:

ЧтоПосле SystemInitНужно (вариант A)
SYSCLKPLL: HSE × 9 = 72 МГцPLL: HSE/2 × 9 = 36 МГц
HPRE (AHB)/1 → 72 МГц/2 → 18 МГц
PPRE1 (APB1)/2 → 36 МГц/1 → 18 МГц (по желанию, /2 тоже допустимо)
PPRE2 (APB2)/1 → 72 МГц/1 → 18 МГц
FLASH_ACR.LATENCY21

Обязательно меняются SYSCLK, HPRE и LATENCY (последнее — шагом 2 методички); делители APB — по желанию (глава 03). И начинать нужно с того, что ядро работает от того самого PLL, который мы собираемся менять.

Железо молча игнорирует записьЗанятый источник не выключить (глава 02). Если записать PLLON = 0, пока ядро ещё работает от PLL, запись пропадёт: PLL останется включён, новые PLLMUL и PLLXTPRE не применятся. Ошибки не будет — программа продолжит работать на 72 МГц. Поэтому сначала SW = HSI и дождаться SWS, потом выключать PLL.

регистрыRCC_CR и RCC_CFGR

Вся перенастройка — это два регистра блока RCC. RCC_CR включает генераторы и PLL и сообщает, готовы ли они. RCC_CFGR выбирает источники, делители и множитель.

Поле RCC_CFGRБитыКодыИмена в stm32f10x.h
SW — что выбрать системной частотой1:000 HSI · 01 HSE · 10 PLL · 11 нельзяRCC_CFGR_SW, RCC_CFGR_SW_HSI, RCC_CFGR_SW_PLL
SWS — что выбрано на самом деле (только чтение)3:2те же кодыRCC_CFGR_SWS, RCC_CFGR_SWS_HSI, RCC_CFGR_SWS_PLL
HPRE — делитель AHB7:40xxx /1 · 1000 /2 · 1001 /4 · 1010 /8 · 1011 /16 · 1100 /64 · 1101 /128 · 1110 /256 · 1111 /512RCC_CFGR_HPRE, RCC_CFGR_HPRE_DIV2, RCC_CFGR_HPRE_DIV4 …
PPRE1, PPRE2 — делители APB1, APB210:8, 13:110xx /1 · 100 /2 · 101 /4 · 110 /8 · 111 /16RCC_CFGR_PPRE1, RCC_CFGR_PPRE1_DIV1 …
PLLSRC — вход PLL160 HSI/2 · 1 HSE (через PLLXTPRE)RCC_CFGR_PLLSRC, RCC_CFGR_PLLSRC_HSE
PLLXTPRE — делить ли HSE170 HSE · 1 HSE/2RCC_CFGR_PLLXTPRE, RCC_CFGR_PLLXTPRE_HSE_Div2
PLLMUL — множитель21:180000 ×2 · 0001 ×3 … 0111 ×9 … 1101 ×15 · 1110 ×16 · 1111 ×16RCC_CFGR_PLLMULL, RCC_CFGR_PLLMULL9 — с двумя L

какШесть шагов

Шаги — из методички, в её порядке. В последнем столбце — почему шаг стоит именно здесь.

ШагЧто записатьЧего дождатьсяПочему здесь
1. Вернуться на HSIHSION = 1 (он и так включён); SW = 00. После подтверждения — PLLON = 0. HSE можно выключить (HSEON = 0), как это делает сброс в SystemInit, или оставить включённым. По методичке 2026 (подпункт 1e) HSE выключают, а делители очищают — глава «Методичка 2026»SWS = 00, затем PLLRDY = 0PLL нельзя выключить и перенастроить, пока от него работает ядро
2. ФлешLATENCY = 1 (B: 2), поле перед записью очистить; PRFTBE не трогать — он включён—такты ожидания ставят до ускорения ядра (глава 04)
3. Включить HSEHSEON = 1HSERDY = 1 (если HSE не выключали — уже 1)кварцу нужно время, чтобы запуститься
4. Настроить и включить PLLочистить PLLSRC, PLLXTPRE, PLLMUL; записать PLLSRC = 1, PLLXTPRE = 1 (B: 0), PLLMUL = 0111. Потом PLLON = 1PLLRDY = 1параметры PLL принимаются только при выключенном PLL
5. ДелителиHPRE = 1000 (B: 1001). По желанию PPRE1 = PPRE2 = 000 (/1): PPRE2 и так /1, а PPRE1 после SystemInit /2 — если нужна /1, его очищают явно. В методичке 2026 шаг 5 — делители AHB, APB1 и APB2: выбери PPRE1 и PPRE2 явно (глава «Методичка 2026»)—когда ядро переключится на PLL, оно сразу получит итоговые 18 МГц
6. Переключиться на PLLSW = 10SWS = 10SWS подтверждает, что переключение произошло

Дождаться — пустой цикл, пока бит готовности не примет нужное значение (приём «ждать» в главе Си). Поле SWS сравнивают целиком: выделить маской RCC_CFGR_SWS и сравнить с RCC_CFGR_SWS_PLL — со скобками вокруг выделения.

Перед записью поля его очищают маской (приём «поле» в главе Си), как ты делал с CRL в ЛР1. Поле SW пиши одной записью: промежуточный 00 — это «переключиться на HSI». После SystemInit в полях лежат старые значения, и простое |= только добавляет единицы, но не убирает их. Пример: в PLLMUL лежит 0111 (×9), записываем 0110 (×8) — получится 0111 | 0110 = 0111, то есть снова ×9. А PPRE1 = 100 (/2) без очистки так и останется /2.

Проверка задания 1Останови программу после шага 6 и посмотри RCC_CFGR в отладчике Keil (Peripherals → System Viewer → RCC). Должно быть 0x001F008A (B: 0x001D009A) — ровно картинка выше. Совпадение регистра ещё не доказывает частоту: её проверит SysTick в главе 07.

RCC_CR — RM0008 §7.3.1, ; RCC_CFGR — §7.3.2, ; «The PLL configuration … must be done before enabling the PLL. Once the PLL enabled, these parameters cannot be changed» — §7.2.3, . Шесть шагов — методичка ЛР3, .

«This bit can not be reset if the PLL clock is used as system clock or is selected to become the system clock» — RM0008 §7.3.1, ; то же для HSEON — . Коды HPRE, PPRE, PLLMUL — §7.3.2, ; SW и SWS — .

06 · эксперимент 1

Светодиод для передачи

зачемОдин цвет, три настройки

Морзе передаём одним цветом RGB-светодиода. Вывод нужно подготовить так же, как в ЛР1: включить тактирование порта, перевести вывод в режим выхода и дальше управлять им через BSRR. Разница с ЛР1 одна — теперь пишем на Си.

Общий анод: горит при нулеУ твоего RGB общий анод: он на +3,3 В, а каждый катод через резистор идёт на вывод. Ток течёт, когда на выводе 0. Значит, «зажечь» — это сбросить вывод (бит BRn в BSRR), а «погасить» — установить (бит BSn). Если перепутать, Морзе пойдёт «негативом»: светодиод будет гореть в паузах.
СветодиодВыводПоле режимаОсобенность
красныйPA7GPIOA_CRL, биты 31:28—
зелёныйPB3GPIOB_CRL, биты 15:12после сброса занят отладкой JTAG: нужно AFIO_MAPR.SWJ_CFG = 010 и тактирование AFIO, как в ЛР1
синийPB15GPIOB_CRH, биты 31:28ничего лишнего — берём его
на платеPC13GPIOC_CRH, биты 23:20методичка разрешает; на Blue Pill он тоже подключён к питанию и горит при 0 — проверь на своей плате

какНастройка PB15

  1. Тактирование порта B: бит IOPBEN в RCC_APB2ENR (RCC_APB2ENR_IOPBEN). Без него запись в регистры GPIOB игнорируется.
  2. Режим вывода: в GPIOB_CRH четыре бита пина 15 — MODE15 = 10 (выход, 2 МГц) и CNF15 = 00 (двухтактный), то есть 0010. Остальные пины не трогать: очистить четвёрку маской GPIO_CRH_MODE15 | GPIO_CRH_CNF15 и записать GPIO_CRH_MODE15_1.
  3. Сразу погасить: записать в BSRR GPIO_BSRR_BS15. После сброса в ODR лежит 0, поэтому сразу после настройки режима светодиод с общим анодом загорается.
ПроверкаПоставь точку останова после настройки и по шагам запиши в BSRR то BR15, то BS15: светодиод должен загораться от BR15 и гаснуть от BS15.

RCC_APB2ENR — RM0008 §7.3.7, ; GPIOx_CRH — §9.2.2, ; коды MODE и CNF — Table 20 и Table 21, ; BSRR — §9.2.5, ; SWJ_CFG — §9.4.2, .

07 · эксперимент 1

SysTick — таймер ядра

зачемТик ровно раз в точку

SysTick — 24-битный счётчик внутри ядра Cortex-M3. Он считает вниз от числа LOAD до нуля, на нуле перезагружается и, если разрешено, вызывает исключение SysTick_Handler (строка 15 таблицы векторов — та, что у тебя в ЛР2 была заглушкой). Если настроить его на одну «точку» кода Морзе, всё остальное — просто счёт тиков.

слова

STK_CTRL
включение (ENABLE), прерывание (TICKINT), источник частоты (CLKSOURCE), флаг «досчитал» (COUNTFLAG)
STK_LOAD
с какого числа считать вниз. Период = LOAD + 1 тактов
STK_VAL
текущее значение счётчика; запись любого числа обнуляет его
CLKSOURCE
0 — AHB/8, 1 — частота ядра (AHB). Вариант 8: AHB, то есть 1

расчётТочка 0,5 с при 18 МГц

тактов на точку = 18 000 000 × 0,5 = 9 000 000
LOAD = 9 000 000 − 1 = 8 999 999 = 0x89 543F
предел LOAD — 24 бита: 0xFF FFFF = 16 777 215 → помещается

При AHB/8 было бы 1 125 000 тактов (LOAD = 1 124 999), но вариант требует AHB. А вот при 72 МГц точка 0,5 с — 36 млн тактов — в 24 бита уже не влезает: это ещё одна причина сначала выполнить задание 1.

Когда настраивать: после задания 1. LOAD посчитан для 18 МГц; если включить SysTick раньше, первые тики пойдут на 72 МГц.

Порядок: записать LOAD, обнулить VAL (любой записью), затем в CTRL одной записью CLKSOURCE = 1, TICKINT = 1, ENABLE = 1. На Си регистры называются SysTick->LOAD, SysTick->VAL, SysTick->CTRL, маски битов — SysTick_CTRL_CLKSOURCE_Msk, SysTick_CTRL_TICKINT_Msk, SysTick_CTRL_ENABLE_Msk (core_cm3.h).

Как устроена запись в CTRL. В CTRL всего четыре бита, и три из них ставит программа (картинка регистра ниже). Каждая маска — просто число с одной единицей на месте своего бита: ENABLE = 0x1 (бит 0), TICKINT = 0x2 (бит 1), CLKSOURCE = 0x4 (бит 2). Операция | складывает их в одно число 0x7 — «все три бита в 1». Записывают его через =, а не |=: весь регистр наш, остальные биты либо зарезервированы, либо только для чтения (COUNTFLAG), так что читать старое значение незачем. Все три бита идут одной записью и последними: как только ENABLE станет 1, счёт пойдёт от того, что лежит в LOAD и VAL, поэтому они должны быть готовы заранее. Если по ошибке написать & вместо |, получится 0 — у масок нет общих единиц, и SysTick не запустится.

Обработчик — функция void SysTick_Handler(void) ровно с таким именем: под это имя в стартовом файле уже отведена строка 15 таблицы векторов. NVIC включать не нужно: SysTick — исключение ядра, а не внешнее прерывание. Флаг сбрасывать тоже не нужно.

CMSIS-функция SysTick_Config(ticks) делает то же самое и вдобавок ставит SysTick самый низкий приоритет. Но в LOAD она пишет ticks − 1, поэтому передавать ей надо 9 000 000, а не 8 999 999.

Проверка до МорзеСначала пусть обработчик просто переключает светодиод. Он должен мигать ровно: 0,5 с горит, 0,5 с нет, то есть 20 переключений за 10 с по секундомеру. Если вышло 80 с — SysTick считает от AHB/8 (тик в 8 раз длиннее: 4 с). Если 2,5 с — частота осталась 72 МГц и задание 1 не применилось. Только когда мигание верное, переходи к Морзе.

Лекция 08 «Таймеры», : SysTick и его четыре регистра. PM0056 §4.5, : CTRL — («0: AHB/8, 1: Processor clock (AHB)»), LOAD — , VAL — ; база 0xE000E010 — Table 33, .

08 · эксперимент 1

Код Морзе на тиках

правилаВсё меряется в точках

ЭлементСветодиодДлительность
точкагорит1 точка = 0,5 с
тирегорит3 точки = 1,5 с
пауза между знаками внутри буквыне горит1 точка
пауза между буквамине горит3 точки
пауза между словами (и в конце цикла)не горит7 точек
Паузы не складываютсяПосле последнего знака буквы идёт одна пауза в 3 точки, а не 1 + 3. После последней буквы слова — одна пауза в 7 точек, а не 3 + 7. Пауза — это промежуток между соседними знаками, и он один.

таблицаКод букв и цифр

A·–
B–···
C–·–·
D–··
E·
F··–·
G––·
H····
I··
J·–––
K–·–
L·–··
M––
N–·
O–––
P·––·
Q––·–
R·–·
S···
T–
U··–
V···–
W·––
X–··–
Y–·––
Z––··
1·––––
2··–––
3···––
4····–
5·····
6–····
7––···
8–––··
9––––·
0–––––

Таблица — , (рисунок «Кодирование символов в коде Морзе»).

кириллицаРусские буквы

В методичке таблица только латинская (стр. 7). Если хочешь передавать имя по-русски, нужна русская азбука Морзе — она общепринятая, но в материалах курса её нет, поэтому сначала спроси преподавателя, засчитают ли так. Большинство русских букв передаётся тем же кодом, что и латинская «пара» (справа в клетке); своего латинского двойника нет только у Ч, Ш, Ъ, Э, Ю, Я. Ё передают как Е.

А·–= A
Б–···= B
В·––= W
Г––·= G
Д–··= D
Е·= E
Ж···–= V
З––··= Z
И··= I
Й·–––= J
К–·–= K
Л·–··= L
М––= M
Н–·= N
О–––= O
П·––·= P
Р·–·= R
С···= S
Т–= T
У··–= U
Ф··–·= F
Х····= H
Ц–·–·= C
Ч–––·нет пары
Ш––––нет пары
Щ––·–= Q
Ъ––·––нет пары
Ы–·––= Y
Ь–··–= X
Э··–··нет пары
Ю··––нет пары
Я·–·–нет пары
Кириллица в строке СиПриём c - 'A' (глава «Си») работает только для однобайтовых символов. Русская буква в исходнике — это, в зависимости от кодировки файла, 2 байта (UTF-8, например «Я» = 0xD0 0xAF) или 1 байт (Windows-1251, «Я» = 0xDF), и Keil ничего об этом не скажет. Надёжнее не хранить в программе русский текст: либо записать имя латинскими «двойниками» из таблицы (у букв с одинаковым кодом результат на светодиоде тот же), либо держать сообщение массивом номеров букв или прямо строками кода вроде ".-.-".

попробуйСообщение на шкале времени

Впиши своё сообщение латиницей, кириллицей или цифрами — сейчас там пример IVAN 8. Шкала покажет, когда горит светодиод и сколько длится один цикл передачи. По ней же потом сверяешь плату. Это же сообщение мигает светодиод на стенде справа.

какКак устроить программу

Программа делится на три части, и у каждой одна задача.

  1. Данные — что передавать. Сообщение раскладывается в последовательность элементов «уровень + длительность в точках»: горит 1, не горит 1, горит 3, не горит 3… Например, IE = горит 1, не горит 1, горит 1, не горит 3, горит 1, не горит 7, и сначала. Последовательность можно один раз построить в main из строки и таблицы кода, а можно записать вручную. Построить из строки надёжнее: меньше мест для опечатки, и легко поменять сообщение.
  2. Обработчик — когда переключать. Вызывается на каждый тик. Уменьшает счётчик «тиков осталось». Когда тот дошёл до нуля, переходит к следующему элементу (после последнего — к первому), выставляет светодиод по его уровню через BSRR (глава 06) и загружает «тиков осталось» = длительность × тиков на точку.
  3. main — порядок запуска: частота (глава 05) → светодиод (глава 06) → последовательность → выставить светодиод по первому элементу → таймер (глава 07). После этого — пустой бесконечный цикл, вся работа идёт в обработчике.

Тиков на точку — единственное, что отличает эксперимент 2: у SysTick тик равен точке, значит 1. У TIM1 тик 100 мс, значит 5. Если сразу вынести это число в константу, эксперимент 2 не потребует трогать логику Морзе.

Обработчик короткий и ничего не ждёт в цикле — в этом и смысл задания по сравнению с задержками ЛР1. Переменные, которые меняет обработчик, объявляются volatile (конспект по Си, глава 04).

Правила длительностей и таблица кода — , ; длительность точки 0,5 с — .

09 · эксперимент 2

Режимы счёта, повторения и теневые регистры

зачемПочему у варианта 8 ARR = 200

Вариант 8 задаёт TIM1 в центрированном режиме. На лекции он описан как счёт от ARR до нуля и обратно . Но для расчёта периода важна точная формулировка RM0008: счётчик идёт от 0 до ARR − 1, на этом — переполнение, потом от ARR вниз до 1, на этом — переполнение снизу, и снова с нуля. Полный цикл — 2 × ARR тиков, а событие обновления бывает на обоих концах, то есть каждые ARR тиков. Это я проверил на твоей плате.

попробуйСчётчик на шкале времени

РежимКак считаетUEV каждые100 мс при 2000 тиках/с
вверх (CMS = 00, DIR = 0)0 … ARR, 0 …ARR + 1ARR = 199
вниз (CMS = 00, DIR = 1)ARR … 0, ARR …ARR + 1ARR = 199
центрированный (CMS ≠ 00)0 … ARR−1, ARR … 1, 0 …ARRARR = 200

CMS = 01, 10 или 11 — три центрированных режима — отличаются только тем, когда ставятся флаги каналов (на счёте вниз, вверх или в обе стороны). Для периода и события обновления они одинаковы; в варианте указано «Ц» — подойдёт любой, например 01. В центрированном режиме бит направления DIR только читается: таймер меняет его сам.

Счётчик повторений RCR (есть у TIM1, нет у TIM2–4): событие обновления выдаётся не на каждом переполнении, а на каждом (RCR + 1)-м. В центрированном режиме RCR уменьшается и на переполнении, и на переполнении снизу — поэтому RCR = 1 даёт ровно одно событие на полный цикл вверх-вниз. Попробуй на шкале.

тонкостьТеневые регистры: записал — не значит применилось

PSC и ARR, которые ты пишешь, — «предварительные» регистры. Таймер работает по их теневым копиям, а копирование происходит по событию обновления:

  • PSC всегда применяется только на следующем UEV. Если просто записать PSC = 8999 и включить таймер, первый период пройдёт со старым предделителем 0 — в 9000 раз быстрее;
  • ARR — сразу, если ARPE = 0, или на следующем UEV, если ARPE = 1 (бит в TIMx_CR1).

Чтобы новые значения применились сразу, после настройки генерируют событие обновления программно: бит UG в TIMx_EGR. Но у него побочный эффект — он ставит UIF, и если прерывание уже разрешено, обработчик вызовется сразу, «на нулевой секунде». Поэтому порядок: PSC, ARR → UG → сбросить UIF → разрешить UIE и включить счёт (CEN). Есть и второй путь — бит URS в CR1: с ним UG обновляет регистры, но UIF не ставит.

Флаг сбрасывается нулёмВ TIMx_SR флаги помечены rc_w0: сбрасываются записью 0, а запись 1 ничего не делает. У EXTI_PR в ЛР2 было наоборот — запись 1. Поэтому в обработчике в SR пишут слово, где в бите UIF 0, а во всех остальных 1 — их это не трогает (как записать на Си — глава Си). Вариант «прочитать SR, сбросить бит, записать обратно» почти всегда работает, но флаг, вставший между чтением и записью, сотрётся — та же ловушка, что с EXTI_PR.

Лекция 08 «Таймеры», : режимы счёта, счётчик повторений, предделитель, теневые регистры. RM0008 §14.3.2, : «the counter counts from 0 to the auto-reload value (content of the TIMx_ARR register) – 1, generates a counter overflow event, then counts from the auto-reload value down to 1 and generates a counter underflow event»; повторения — §14.3.3, : «At each counter overflow and at each counter underflow in center-aligned mode»; теневые регистры — §14.3.1, ; PSC применяется по UEV — §14.4.11, ; UIF — §14.4.5, (rc_w0).

10 · эксперимент 2

TIM1 вместо SysTick

зачемМеняется только источник тика

Задание 3 — то же сообщение, но тик раз в 100 мс даёт TIM1 с предделителем 9000 и центрированным счётом. Последовательность и логика обработчика из главы 08 остаются; меняются настройка таймера, имя обработчика и «тиков на точку» — 5 вместо 1.

расчётPSC, ARR и RCR для 100 мс

TIM1CLK = PCLK2 = 18 МГц (PPRE2 = /1 — удвоения нет, глава 03)
такт счёта = 18 000 000 / (PSC + 1) = 2000 Гц → PSC = 8999
центрированный счёт: UEV каждые ARR тиков → ARR = 2000 × 0,1 = 200, RCR = 0
проверка: 18 000 000 / (9000 × 200) = 10 событий в секунду

Почему 200, а не 199, — глава 09: в центрированном режиме событие приходит и на вершине, и внизу, то есть каждые ARR тиков, а не ARR + 1.

какДевять шагов настройки

ШагРегистр и полеЗначениеЗачем
1RCC_APB2ENR.TIM1EN (бит 11)1без тактирования регистры TIM1 не пишутся
2TIM1_PSC8999делит 18 МГц до 2000 Гц
3TIM1_ARR200граница счёта
4TIM1_CR1.CMS01центрированный счёт; DIR не трогать
5TIM1_EGR.UG1применить PSC и ARR сразу, а не через период (теневые регистры, глава 09)
6TIM1_SR.UIF0 в бите UIF, 1 в остальных — приём «сброс записью нуля» (глава Си)UG сам поставил UIF — без сброса прерывание придёт сразу
7TIM1_DIER.UIE1событие обновления даёт запрос прерывания
8NVIC_ISER0, бит 251вход TIM1_UP в NVIC, как ISER в ЛР2; на Си — функция CMSIS из главы Си с номером TIM1_UP_IRQn
9TIM1_CR1.CEN1запуск — последним, когда всё готово

Обработчик называется TIM1_UP_IRQHandler, не TIM1_IRQHandler: у TIM1 четыре входа NVIC, и событию обновления отвечает TIM1_UP (глава 01). Первым делом в нём сбрасывают UIF записью нуля (глава 09). Если флаг не сбросить, обработчик будет вызываться снова и снова, как с EXTI_PR в ЛР2. Потом — тот же тик Морзе.

ПроверкаПри 5 тиках на точку передача должна совпасть с экспериментом 1 секунда в секунду: та же шкала из главы 08, тот же секундомер. Если вдвое медленнее — стоит ARR = 400 («на весь цикл вверх-вниз»). Если на 0,5 % быстрее — ARR = 199 при центрированном счёте: формула обычного режима.

TIM1 — RM0008 Table 3, ; TIMx_CR1 — §14.4.1, ; DIER — §14.4.4, ; SR — §14.4.5, ; EGR — §14.4.6, ; PSC и ARR — §14.4.11–14.4.12, ; RCR — §14.4.13, ; карта регистров — §14.4.21, . TIM1EN — §7.3.7, . Вход TIM1_UP — Table 63, . NVIC_ISER — PM0056 §4.3.2, .

11 · защита

Вопросы на защите

Вопросы по экспериментам 1 и 2 — с короткими ответами и ссылкой на главу. Как с равными приоритетами в ЛР2: ответ «на глаз» часто неверный, а правильный следует из одной строки документации.

Сколько длится пауза после последней точки в букве?

3 точки — пауза между буквами. Паузы не складываются: не 1 + 3, а 3. Глава 08.

Записал PLLON = 0 и новый PLLMUL, а частота осталась 72 МГц. Почему?

PLLON нельзя сбросить, пока PLL — системная частота : запись проигнорирована, PLL остался включён, а параметры включённого PLL не меняются. Сначала SW = HSI и дождаться SWS = HSI. Глава 05.

Почему светодиод зажигается записью в BR, а не в BS?

Светодиод с общим анодом: анод на +3,3 В, ток течёт при 0 на выводе. BR сбрасывает вывод в 0 — горит. Глава 06.

Почему в эксперименте 2 на одну точку приходится 5 прерываний?

Точка 0,5 с, как в эксперименте 1, а TIM1 даёт событие раз в 100 мс: 0,5 / 0,1 = 5. Главы 00 и 10.

Почему в PSC пишут 8999, если в варианте предделитель 9000?

Таймер делит на PSC + 1: при PSC = 0 деления нет вовсе. Чтобы делить на 9000, пишут 8999. Глава 01.

Почему для 100 мс у тебя ARR = 200, а у соседа с обычным счётом — 199?

В обычном режиме событие — раз в ARR + 1 тиков, в центрированном — раз в ARR (событие и на переполнении вверх, и снизу). 2000 тиков/с × 0,1 с = 200. Глава 09, проверено на плате.

Записал PSC, включил таймер — а первое прерывание пришло почти сразу. Почему?

PSC теневой: новое значение применяется только по событию обновления, первый период шёл со старым предделителем. Лечится битом UG после настройки — но UG сам ставит UIF, поэтому после него флаг сбрасывают до разрешения прерывания. Глава 09.

Как сбросить флаг прерывания таймера и чем это отличается от EXTI?

В TIMx_SR флаги сбрасываются записью нуля (rc_w0), в EXTI_PR — записью единицы (rc_w1). Поэтому TIM1->SR = ~TIM_SR_UIF, а не = TIM_SR_UIF. Глава 09.

Почему 18 МГц нельзя получить прямо из PLL?

Вход PLL — 4 или 8 МГц (HSE/2 или HSE), множитель целый от 2 до 16: на выходе только кратное 4. Нужен делитель AHB: 36 / 2 или 72 / 4. Глава 03.

Сколько тактов ожидания флеша при 18 МГц?

Зависит не от 18, а от SYSCLK: при 36 МГц — 1, при 72 — 2. На плате проверено: с 0 тактами при SYSCLK 36 программа сбивается, хотя HCLK 18. Глава 04.

Зачем ждать HSERDY, PLLRDY и SWS — нельзя просто записать и идти дальше?

Генератор и PLL запускаются не мгновенно. Переключиться на неустановившийся источник — значит тактировать ядро «мусором». SWS подтверждает, что переключение реально произошло. Глава 05.

Почему нельзя сразу поменять множитель PLL после SystemInit?

Параметры PLL меняются только при выключенном PLL, а выключить его нельзя, пока от него работает ядро. Сначала SW = HSI и дождаться SWS, потом PLLON = 0. Глава 05.

Точка должна быть 2 с, а не 0,5. Что сломается?

2 с при 18 МГц — 36 млн тактов, а LOAD у SysTick 24-битный (до 16 777 215). Выходы: CLKSOURCE = AHB/8 (4,5 млн — влезает) или оставить тик 0,5 с и считать по 4 тика на точку. Глава 07.

Чем SysTick отличается от TIM1?

SysTick — часть ядра: 24 бита, считает только вниз, нет предделителя (только /8), прерывание — исключение 15, включается битом TICKINT, без ISER. TIM1 — периферия на APB2: 16 бит, предделитель до 65 536, режимы счёта, каналы, повторения, четыре своих входа NVIC. Главы 01 и 07.

Какие таймеры есть на нашем STM32F103C8 и почему нет базовых?

SysTick в ядре, расширенный TIM1 и универсальные TIM2, TIM3, TIM4 (плюс два сторожевых). Базовые TIM6/TIM7 бывают только у high- и XL-density, а C8 — medium density. Но базовая часть (PSC, CNT, ARR, событие обновления) есть внутри каждого из TIM1–TIM4. Глава 01.

Что лежит в STK_CALIB?

TENMS — сколько тактов SysTick в 1 мс при «эталонной» частоте: 9000 = 1 мс при 9 МГц (72 / 8). На твоей плате регистр равен 0x40002328: TENMS = 9000, плюс бит SKEW — у оригинала его нет. Для ЛР3 не нужен: частоту мы знаем сами.

Почему без volatile цикл задержки исчезает, а с -O0 — нет?

Компилятор видит, что cnt меняется только в этом цикле и результат никому не нужен, и сразу ставит ноль. volatile запрещает выбрасывать обращения к переменной, -O0 отключает оптимизацию целиком. Методичка, стр. 76.

Где лежит int a = 5; до старта и после?

Пятёрка хранится во флеше в образе .data, переменная — в ОЗУ. __main копирует одно в другое до вызова main. Оба адреса видно в .map, области описаны в .sct. Глава «Методичка 2026».

Что будет, если __main не обнулит .bss?

Переменные без начального значения получат то, что лежало в ОЗУ после включения. Стандарт Си требует нуля, поэтому код вроде static uint32_t i; начнёт не с начала.

Зачем ждать HSIRDY, если HSI и так работает?

Код не должен зависеть от того, что было до него. Если HSI выключен, переключение на него не произойдёт, пока он не готов, и ожидание SWS не закончится. Глава «Методичка 2026».

Почему HSEBYP сбрасывают после HSEON = 0?

HSEBYP можно записать только при выключенном HSE (RM0008, стр. 99). А HSE выключится, только когда ядро уже работает от HSI.

SystemCoreClock показывает 72 000 000, а частота 18 МГц. Почему?

Переменную пишет только SystemCoreClockUpdate(). SystemInit и ручная перенастройка её не обновляют. Мы LOAD считаем сами, поэтому на работу это не влияет.

Записал SysTick->VAL = 0x33. С чего начнёт счёт SysTick?

С LOAD: любая запись в VAL обнуляет счётчик и COUNTFLAG (PM0056, стр. 152), на следующем такте он перезагружается из LOAD.

Зачем в обработчике SysTick менять светодиод через BSRR?

Чтобы не было гонки с другими обработчиками и main: BSRR меняет только записанные биты одной записью, без чтения ODR — тот же вывод, что в эксперименте 2 ЛР2. Глава 06.

12 · сводка

Ловушки

СимптомПричина
задание 1 — частота
частота осталась 72 МГц, хотя все шаги «выполнены»PLLON = 0 записан до того, как SWS показал HSI, — запись проигнорирована (глава 05)
зависает при переключении частотыне дождались SWS или PLLRDY; ожидание бита, который никогда не установится (например, PLLRDY при выключенном PLLON)
после переключения — HardFault или «мусор»LATENCY выставлен после переключения или посчитан по HCLK, а не по SYSCLK (глава 04)
зависает на ожидании HSERDYкварц не запустился или его нет на плате
множитель или делитель не тотполе CFGR не очищено перед |= — смешались значения SystemInit и новые; PLLMULL пишется с двумя L
эксперимент 1 — светодиод и SysTick
светодиод горит сразу после настройки выводаODR после сброса — 0, а при общем аноде 0 зажигает; сразу запиши BS (глава 06)
Морзе передаётся «негативом» — горит в паузахперепутаны BS и BR (глава 06)
точка 2 с вместо 0,5CLKSOURCE = 0 (AHB/8), а LOAD считали для AHB
точка в 4 раза корочечастота осталась 72 МГц — задание 1 не применилось
SysTick_Handler не вызываетсяопечатка в имени — прерывание уходит в заглушку стартового файла; не выставлен TICKINT
паузы между буквами длиннее, чем на шкалепаузы сложены: 1 + 3 вместо 3 (глава 08)
эксперимент 2 — TIM1
первое прерывание пришло почти сразу после запускаUG поставил UIF, а флаг не сброшен до UIE; или UG не было вовсе — тогда первый период прошёл со старым PSC = 0, это 200 тактов по 18 МГц (главы 09–10)
обработчик вызывается бесконечноUIF не сброшен или «сброшен» записью 1 — в SR сбрасывают нулём
TIM1_UP_IRQHandler не вызываетсянет TIM1EN, UIE или бита 25 в ISER0; обработчик назван TIM1_IRQHandler
передача вдвое медленнееARR = 400 «на весь цикл» вместо 200 — событие бывает дважды за цикл (глава 09)
Проверено на твоей платеКлон, но частоты и делители ведут себя как в RM0008: HSI — 8,024 МГц (+0,3 %), HSE — 7,999, PLL ×9 — 72,01; оба варианта 18 МГц дают 18,00 МГц, а записанные значения RCC_CFGR совпали с картинкой в главе 05. SysTick от AHB и AHB/8 — ровно 1 и 8. Такты ожидания флеша тоже нужны «по SYSCLK»: при SYSCLK 36 МГц и 0 тактов ожидания программа сбивается, хотя HCLK всего 18. Цикл SUBS/BNE из флеша: 3 такта на итерацию на 8 МГц, 4 — на 18 МГц (вариант A), 6 — на 72 МГц. Центрированный TIM1 даёт событие раз в ARR тиков, как в RM0008.

→ · дальше

Эксперименты 3–5: что уже видно

Подробные главы появятся, когда откроют ЛР3. Но три вещи стоит знать заранее: каждая влияет на то, как собирать стенд.

  • Эксперимент 3, захват — кнопку придётся перенести. Длительность нажатия меряют режимом «PWM input»: два канала от одного входа, и фронт входа сбрасывает счётчик. Источник сброса выбирает поле TS регистра TIM1_SMCR, а оно умеет только TI1FP1 и TI2FP2 — входы каналов 1 и 2. Значит, кнопка должна стоять на PA8 (CH1) или PA9 (CH2), а не на PA10 (CH3).
  • Эксперимент 4, ШИМ — светодиод придётся перенести, а методичка описывает PWM1 наоборот. Каналы 1–3 TIM1 — это PA8, PA9, PA10, и RGB надо переключить на них. В методичке сказано, что в PWM1 выход равен 0, пока счётчик меньше CCR. По RM0008 наоборот: канал активен, пока счётчик меньше CCR1 . С общим анодом активный уровень гасит светодиод, поэтому яркость будет обратной заполнению. Кроме того, при предделителе 9000 у ШИМ либо мало ступеней яркости, либо заметное мерцание — это и есть доказательство, которого методичка требует, чтобы разрешить уменьшить предделитель.
  • Эксперимент 5, энкодер и сервопривод — нужны железо и второй таймер. Энкодер подключается к каналам 1 и 2; режим 2 варианта — это SMS = 010. Счётчик в режиме энкодера занят целиком, так что ШИМ для сервопривода (период 20 мс, импульс 0,5–2,5 мс) придётся формировать на другом таймере. Выход — толерантный к 5 В, в режиме open-drain; питание сервопривода — 5 В от программатора. Проверь заранее, есть ли у тебя энкодер и сервопривод.

Поле TS — RM0008 §14.4.3, ; SMS = 010 «Encoder mode 2» — ; PWM mode 1 — §14.4.7, : «In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1 else inactive». Описание PWM1 — , : «В режиме PWM1 пока значение таймера меньше, чем значения регистра канала, выход равен нулю».

Конспект по методичке ЛР3 2025 года, сверен с методичкой 2026 года (глава «Методичка 2026»). Числа варианта 8 пересчитаны по RM0008 rev 21 (глава 7, medium density) и PM0056 rev 6.