МИФИ · МПС · лабораторная 3 · STM32F103C8T6 · Си
Таймеры: частота и время
Во всех прошлых лабах чип работал на 8 МГц от внутреннего генератора, и время ты отмерял пустым циклом. В ЛР3 время отмеряет железо: сначала ты сам собираешь частоту из кварца, потом раздаёшь её таймерам. Конспект ведёт по порядку работы: частота по варианту, светодиод, SysTick и код Морзе (эксперимент 1), затем то же на TIM1 (эксперимент 2). Эксперименты 3–5 — коротко в конце.
00 · задание
Задание и план
Конспект писался по прошлогодней методичке. В 2026 году вышла новая с той же таблицей вариантов: числа ниже в силе, но задания перенумерованы, а шаг 1 перенастройки частоты расписан подробнее. Что поменялось — глава «Методичка 2026».
| Параметр | Вариант 8 | Где нужен |
|---|---|---|
| Частота AHB | 18 МГц | задание 1 (главы 02–05) |
| Частота системного таймера | AHB (без деления на 8) | эксперимент 1 (глава 07) |
| Таймер и направление счёта | TIM1, Ц (центрированный) | эксперименты 2–4 (главы 09–10) |
| Предделитель таймера | 9000 | эксперимент 2 (глава 10) |
| Режим PWM | 1 | эксперимент 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) |
планПорядок работы
Каждый шаг опирается на предыдущий и проверяется отдельно. Если что-то не работает, сразу видно, на каком шаге ошибка.
- Частота 18 МГц — главы 02–05. Проверка: значение RCC_CFGR в отладчике.
- Светодиод — глава 06. Проверка: зажигается и гаснет записью в BSRR.
- SysTick на 0,5 с — глава 07. Проверка: ровное мигание, 20 переключений за 10 с.
- Код Морзе — глава 08. Проверка: передача совпадает со шкалой в этой главе.
- Эксперимент 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 и 2 | 02–08 |
| 2 (так в методичке) · базовый таймер | 6 — прерывания раз в 100 мс () | эксперимент 2, задание 3 | 09–10 |
| 3 · входной канал | 7 — длительность нажатий () | задание 4 | → |
| 4 · выходной канал | 8 — ШИМ, 100 оттенков () | задание 5 | → |
| 5 · энкодер | 9 — сервопривод () | тоже «задание 5»: номер повторялся | → |
текстЧто изменилось в тексте
| Место | 2025 | 2026 |
|---|---|---|
| Запуск программы на Си | — | свой __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 уже включён. Если он выключен, переключения не будет: источник меняется, только когда новый готов «A switch from one clock source to another occurs only if the target clock source is ready». Тогда ожидание SWS не закончится |
| 1c–d · SW = HSI, дождаться SWS | есть | — |
| 1e · выключить другие источники | только PLL | ещё HSE и связанные с ним биты CSSON и HSEBYP. Порядок важен: HSEBYP «can be written only if the HSE oscillator is disabled» |
| 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, )
system_stm32f10x.c (пакет ST в Keil). Сверь с ней порядок своего шага 1, но маски не копируй: на защите спросят, что значит каждая.«в соответствии с планируемым значением частоты для шины AHB», а список на считает по SYSCLK. Верен список, и так же в RM0008 (). В варианте B SYSCLK 72 МГц, значит 2 такта, хотя AHB 18. С 0 тактами программа на плате сбивается, это проверено (глава 04).
SysTick->VAL = 0x33: число ни на что не влияет, «A write of any value clears the field to 0». Текст сообщения ты уже показывал преподавателю; формально задание 5 требует имя и номер в группе ().
стартSystemInit или свой старт
Прямо методичка этого не решает. В эксперименте 1 подключается только CMSIS → CORE (рис. 31, ), а пункт Device на скриншоте свёрнут. Ссылку на SystemInit из шага 1 убрали и расписали сброс по подпунктам: его ждут от тебя. Сейчас в lab3.uvprojx подключён Device:Startup, то есть SystemInit вызывается.
| Device:Startup | Свой старт | |
|---|---|---|
Что идёт до main | Reset_Handler из startup_stm32f10x_md.s: SystemInit, потом __main | твой Reset_Handler, сразу __main |
Частота при входе в main | 72 МГц | 8 МГц (HSI) |
| Только HPRE /4 даст | 18 МГц | 2 МГц, точка 4,5 с |
| Таблица векторов | готовая, SysTick_Handler подставится по имени | своя: SysTick_Handler по смещению 0x3C и IMPORT, как в ЛР2 |
__main | твой: стартовый файл ищет его через IMPORT __main | твой |
| SystemCoreClock | 72 000 000 при реальных 18 МГц: значение обновляет только SystemCoreClockUpdate() | переменной нет |
С полной последовательностью из шести шагов разницы нет: шаг 1 возвращает чип к HSI без делителей, дальше всё одинаково. Оставь старт таким, как в сданном эксперименте 1. Если сомневаешься, спроси одной фразой, оставлять ли Device:Startup.
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: она делит таймеры, а не возможности; всё вне её притушено. Нажми на часть внутри зоны — увидишь именно наши таймеры; на часть снаружи или на название — весь вид целиком.
Выводы каналов по умолчанию — 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».
Таблица — по даташиту нашего чипа «an advanced-control timer, three general-purpose timers, two watchdog timers and a SysTick timer»
. Сторожевые таймеры (IWDG, WWDG) тоже считают, но их работа — перезагрузить зависшую программу; в ЛР3 они не нужны и в схему не попали.
| Таймер | Вид | Бит | Счёт | Предделитель | Каналы | DMA | Шина · NVIC | В ЛР3 |
|---|---|---|---|---|---|---|---|---|
| TIM1 | расширенный | 16 | вверх, вниз, центр. | 1 … 65 536 | 4 + 3 компл. | да | APB2 · IRQ 24–27 | эксп. 2–5 |
| TIM2, TIM3, TIM4 | универсальный | 16 | вверх, вниз, центр. | 1 … 65 536 | 4 | да | 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 и посмотри, как меняются делитель, счётчик и флаг; нажми на блок — под схемой откроется его регистр.
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.s | Keil, 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_APB2ENR | RCC->CR, RCC->CFGR, RCC->APB2ENR | главы 05, 06, 10 |
| FLASH_ACR | FLASH->ACR | глава 04 |
| GPIOx_CRL, GPIOx_CRH, GPIOx_BSRR | GPIOB->CRH, GPIOB->BSRR … | глава 06 |
| STK_CTRL, STK_LOAD, STK_VAL | SysTick->CTRL, SysTick->LOAD, SysTick->VAL | глава 07 |
| TIMx_CR1, TIMx_PSC, TIMx_ARR, TIMx_SR … | TIM1->CR1, TIM1->PSC … | главы 09–10 |
| NVIC_ISER0, NVIC_ISER1 | NVIC->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 |
|---|---|---|
| SysTick | SysTick_Handler | не нужен |
| TIM1, событие обновления | TIM1_UP_IRQHandler | TIM1_UP_IRQn = 25 |
| TIM1, захват и сравнение | TIM1_CC_IRQHandler | TIM1_CC_IRQn = 27 |
| TIM2, TIM3, TIM4 | TIM2_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 с.
заданиеЧто требует методичка
Требований три, и все записаны прямо в методичке:
- Частота AHB — по варианту. Так сказано в задании 1
«Выполните настройку тактирования шины AHB на частоте, заданной вариантом»
. В таблице вариантов (стр. 12) у варианта 8 в столбце «Частота AHB, МГц» стоит 18. - Источник — кварц через PLL. Так требуют введение к заданию
«перенастроить тактирование микроконтроллера на использование внешнего генератора 8Мгц, и получить из него заданную в варианте частоту»
и шаг 4«Источником тактирования PLL должен выступать HSE (или HSE/2)»
. - Нигде не превысить предел. Задание 1 ограничивает все частоты 72 МГц
«Никакая частота не должна превышать это ограничение»
. У шины APB1 по RM0008 предел ниже — 36 МГц.
Частота AHB не задаётся одним числом: её дают генератор, умножитель, переключатель и делитель, и чтобы получить 18 МГц от кварца, надо понимать, как они связаны.
устройствоДороги и один переключатель
Дерево тактирования легче понять не как цепочку, через которую частота проходит целиком, а как несколько дорог и один переключатель между ними. Генераторы и умножитель — только кандидаты: каждый можно включить, настроить и дождаться его готовности, но частота ядра от этого не меняется. Решает переключатель SW. Всё, что стоит после него, только делит частоту. Ниже это разобрано по порядку; каждую ситуацию кнопка ставит на стенде справа, а клик по блоку стенда открывает его регистр.
Исходное положение. После сброса ядро работает от HSI — генератора внутри чипа на 8 МГц «After a system reset, the HSI oscillator is selected as system clock»
. Кварц и умножитель выключены. Так работали ЛР1 и ЛР2. В проекте Keil до main ещё успевает отработать SystemInit и меняет эту картину — к этому вернёмся в главе 05.
Включить генератор — ещё не подключить его. Кварцевый генератор HSE запускают битом HSEON. Частоту он даёт не сразу: кристаллу нужно время раскачаться, и пока колебания не станут стабильными, железо держит бит готовности HSERDY = 0. Но и готовый HSE ни к чему не подключён — ядро продолжает работать от HSI. Включённый генератор лишь становится доступен тем, кто стоит дальше: умножителю и переключателю.
Переключатель SW решает, от чего работает ядро. Это поле регистра RCC_CFGR, и входов у него три «Three different clock sources can be used to drive the system clock (SYSCLK)»
: HSI, HSE и PLL. Первые два — генераторы напрямую, ровно 8 МГц, без деления и умножения. Третий — выход умножителя PLL. То, что выбрано в SW, и становится системной частотой SYSCLK.
Переключатель не даст ядру остаться без частоты: он переходит только на готовый источник, а если выбран неготовый — ждёт, пока тот заработает «A switch from one clock source to another occurs only if the target clock source is ready (clock stable after startup delay or PLL locked). If a clock source which is not yet ready is selected, the switch will occur when the clock source will be ready»
. Поэтому записать SW мало: что выбрано на самом деле, показывает соседнее поле SWS, и его дожидаются.
PLL — отдельная дорога, а не ступень на пути. Напрямую получается только 8 МГц; любую другую частоту даёт третий вход — PLL. У этой ветки свой вход, и выбирают его два переключателя по очереди. PLLSRC решает, какой генератор подать: HSI или HSE. HSI в PLL всегда приходит поделённым на 2 (4 МГц) — этот делитель не настраивается. Если выбран HSE, второй бит, PLLXTPRE, решает, подать его целиком (8 МГц) или пополам (4 МГц); на ветку HSI он не влияет. Поэтому HSI без деления попадает в систему только напрямую, через SW = 00 «can be used directly as a system clock or divided by 2 to be used as PLL input»
. Вход PLL умножает на целое число PLLMUL — от 2 до 16.
У PLL строгий порядок: настроить, включить, дождаться, выбрать. Вход и множитель задают, пока PLL выключен; после включения их уже не поменять «The PLL configuration (selection of HSI oscillator divided by 2 or HSE oscillator for PLL input clock, and multiplication factor) must be done before enabling the PLL. Once the PLL enabled, these parameters cannot be changed»
. Потом PLLON = 1 — и, как кварцу, PLL нужно время, чтобы подстроить выход под вход (в RM0008 это называется «PLL locked»); готовность показывает PLLRDY. И даже готовый PLL ничего не переключает: его выход просто появляется на третьем входе SW. Ядро перейдёт на него только после записи SW = 10.
Занятый источник не выключить. Пока источник питает ядро — напрямую или через PLL, — остановить его нельзя «When a clock source is used directly or through the PLL as system clock, it is not possible to stop it»
. Запись 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. Итого — ровно два способа:
| Вход PLL | PLLMUL | SYSCLK | HPRE | HCLK | Флеш (глава 04) | |
|---|---|---|---|---|---|---|
| A | HSE/2 = 4 МГц | × 9 | 36 МГц | / 2 | 18 МГц | 1 такт ожидания |
| B | HSE = 8 МГц | × 9 | 72 МГц | / 4 | 18 МГц | 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.
| SYSCLK | LATENCY |
|---|---|
| до 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 «The prefetch buffer must be kept on when using a prescaler different from 1 on the AHB clock»
. При /1 он может быть и выключен. У нас HPRE = /2, значит буфер нужен. После сброса он и так включён, и трогать его не стоит: включать и выключать буфер разрешено только при SYSCLK ниже 24 МГц и HPRE = /1 «The prefetch buffer must be switched on/off only when SYSCLK is lower than 24 MHz and no prescaler is applied on the AHB clock (SYSCLK must be equal to HCLK)»
.
Порядок важен: LATENCY выставляется до переключения на быструю частоту. Если переключиться раньше, первые же команды прочитаются из флеша с ошибками.
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) |
|---|---|---|
| SYSCLK | PLL: 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.LATENCY | 2 | 1 |
Обязательно меняются SYSCLK, HPRE и LATENCY (последнее — шагом 2 методички); делители APB — по желанию (глава 03). И начинать нужно с того, что ядро работает от того самого PLL, который мы собираемся менять.
регистрыRCC_CR и RCC_CFGR
Вся перенастройка — это два регистра блока RCC. RCC_CR включает генераторы и PLL и сообщает, готовы ли они. RCC_CFGR выбирает источники, делители и множитель.
| Поле RCC_CFGR | Биты | Коды | Имена в stm32f10x.h |
|---|---|---|---|
| SW — что выбрать системной частотой | 1:0 | 00 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 — делитель AHB | 7:4 | 0xxx /1 · 1000 /2 · 1001 /4 · 1010 /8 · 1011 /16 · 1100 /64 · 1101 /128 · 1110 /256 · 1111 /512 | RCC_CFGR_HPRE, RCC_CFGR_HPRE_DIV2, RCC_CFGR_HPRE_DIV4 … |
| PPRE1, PPRE2 — делители APB1, APB2 | 10:8, 13:11 | 0xx /1 · 100 /2 · 101 /4 · 110 /8 · 111 /16 | RCC_CFGR_PPRE1, RCC_CFGR_PPRE1_DIV1 … |
| PLLSRC — вход PLL | 16 | 0 HSI/2 · 1 HSE (через PLLXTPRE) | RCC_CFGR_PLLSRC, RCC_CFGR_PLLSRC_HSE |
| PLLXTPRE — делить ли HSE | 17 | 0 HSE · 1 HSE/2 | RCC_CFGR_PLLXTPRE, RCC_CFGR_PLLXTPRE_HSE_Div2 |
| PLLMUL — множитель | 21:18 | 0000 ×2 · 0001 ×3 … 0111 ×9 … 1101 ×15 · 1110 ×16 · 1111 ×16 | RCC_CFGR_PLLMULL, RCC_CFGR_PLLMULL9 — с двумя L |
какШесть шагов
Шаги — из методички, в её порядке. В последнем столбце — почему шаг стоит именно здесь.
| Шаг | Что записать | Чего дождаться | Почему здесь |
|---|---|---|---|
| 1. Вернуться на HSI | HSION = 1 (он и так включён); SW = 00. После подтверждения — PLLON = 0. HSE можно выключить (HSEON = 0), как это делает сброс в SystemInit, или оставить включённым. По методичке 2026 (подпункт 1e) HSE выключают, а делители очищают — глава «Методичка 2026» | SWS = 00, затем PLLRDY = 0 | PLL нельзя выключить и перенастроить, пока от него работает ядро |
| 2. Флеш | LATENCY = 1 (B: 2), поле перед записью очистить; PRFTBE не трогать — он включён | — | такты ожидания ставят до ускорения ядра (глава 04) |
| 3. Включить HSE | HSEON = 1 | HSERDY = 1 (если HSE не выключали — уже 1) | кварцу нужно время, чтобы запуститься |
| 4. Настроить и включить PLL | очистить PLLSRC, PLLXTPRE, PLLMUL; записать PLLSRC = 1, PLLXTPRE = 1 (B: 0), PLLMUL = 0111. Потом PLLON = 1 | PLLRDY = 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. Переключиться на PLL | SW = 10 | SWS = 10 | SWS подтверждает, что переключение произошло |
Дождаться — пустой цикл, пока бит готовности не примет нужное значение (приём «ждать» в главе Си). Поле 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.
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 одна — теперь пишем на Си.
| Светодиод | Вывод | Поле режима | Особенность |
|---|---|---|---|
| красный | PA7 | GPIOA_CRL, биты 31:28 | — |
| зелёный | PB3 | GPIOB_CRL, биты 15:12 | после сброса занят отладкой JTAG: нужно AFIO_MAPR.SWJ_CFG = 010 и тактирование AFIO, как в ЛР1 |
| синий | PB15 | GPIOB_CRH, биты 31:28 | ничего лишнего — берём его |
| на плате | PC13 | GPIOC_CRH, биты 23:20 | методичка разрешает; на Blue Pill он тоже подключён к питанию и горит при 0 — проверь на своей плате |
какНастройка PB15
- Тактирование порта B: бит IOPBEN в RCC_APB2ENR (
RCC_APB2ENR_IOPBEN). Без него запись в регистры GPIOB игнорируется. - Режим вывода: в GPIOB_CRH четыре бита пина 15 — MODE15 = 10 (выход, 2 МГц) и CNF15 = 00 (двухтактный), то есть 0010. Остальные пины не трогать: очистить четвёрку маской
GPIO_CRH_MODE15 | GPIO_CRH_CNF15и записатьGPIO_CRH_MODE15_1. - Сразу погасить: записать в BSRR
GPIO_BSRR_BS15. После сброса в ODR лежит 0, поэтому сразу после настройки режима светодиод с общим анодом загорается.
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 МГц
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.
Лекция 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 точек |
таблицаКод букв и цифр
Таблица — , (рисунок «Кодирование символов в коде Морзе»).
кириллицаРусские буквы
В методичке таблица только латинская (стр. 7). Если хочешь передавать имя по-русски, нужна русская азбука Морзе — она общепринятая, но в материалах курса её нет, поэтому сначала спроси преподавателя, засчитают ли так. Большинство русских букв передаётся тем же кодом, что и латинская «пара» (справа в клетке); своего латинского двойника нет только у Ч, Ш, Ъ, Э, Ю, Я. Ё передают как Е.
c - 'A' (глава «Си») работает только для однобайтовых символов. Русская буква в исходнике — это, в зависимости от кодировки файла, 2 байта (UTF-8, например «Я» = 0xD0 0xAF) или 1 байт (Windows-1251, «Я» = 0xDF), и Keil ничего об этом не скажет. Надёжнее не хранить в программе русский текст: либо записать имя латинскими «двойниками» из таблицы (у букв с одинаковым кодом результат на светодиоде тот же), либо держать сообщение массивом номеров букв или прямо строками кода вроде ".-.-".попробуйСообщение на шкале времени
Впиши своё сообщение латиницей, кириллицей или цифрами — сейчас там пример IVAN 8. Шкала покажет, когда горит светодиод и сколько длится один цикл передачи. По ней же потом сверяешь плату. Это же сообщение мигает светодиод на стенде справа.
какКак устроить программу
Программа делится на три части, и у каждой одна задача.
- Данные — что передавать. Сообщение раскладывается в последовательность элементов «уровень + длительность в точках»: горит 1, не горит 1, горит 3, не горит 3… Например, IE = горит 1, не горит 1, горит 1, не горит 3, горит 1, не горит 7, и сначала. Последовательность можно один раз построить в
mainиз строки и таблицы кода, а можно записать вручную. Построить из строки надёжнее: меньше мест для опечатки, и легко поменять сообщение. - Обработчик — когда переключать. Вызывается на каждый тик. Уменьшает счётчик «тиков осталось». Когда тот дошёл до нуля, переходит к следующему элементу (после последнего — к первому), выставляет светодиод по его уровню через BSRR (глава 06) и загружает «тиков осталось» = длительность × тиков на точку.
main— порядок запуска: частота (глава 05) → светодиод (глава 06) → последовательность → выставить светодиод по первому элементу → таймер (глава 07). После этого — пустой бесконечный цикл, вся работа идёт в обработчике.
Тиков на точку — единственное, что отличает эксперимент 2: у SysTick тик равен точке, значит 1. У TIM1 тик 100 мс, значит 5. Если сразу вынести это число в константу, эксперимент 2 не потребует трогать логику Морзе.
Обработчик короткий и ничего не ждёт в цикле — в этом и смысл задания по сравнению с задержками ЛР1. Переменные, которые меняет обработчик, объявляются volatile (конспект по Си, глава 04).
Правила длительностей и таблица кода — , ; длительность точки 0,5 с — .
09 · эксперимент 2
Режимы счёта, повторения и теневые регистры
зачемПочему у варианта 8 ARR = 200
Вариант 8 задаёт TIM1 в центрированном режиме. На лекции он описан как счёт от ARR до нуля и обратно «счет идет от значения, заданного в регистре TIMx_ARR до нуля и обратно»
. Но для расчёта периода важна точная формулировка RM0008: счётчик идёт от 0 до ARR − 1, на этом — переполнение, потом от ARR вниз до 1, на этом — переполнение снизу, и снова с нуля. Полный цикл — 2 × ARR тиков, а событие обновления бывает на обоих концах, то есть каждые ARR тиков. Это я проверил на твоей плате.
попробуйСчётчик на шкале времени
| Режим | Как считает | UEV каждые | 100 мс при 2000 тиках/с |
|---|---|---|---|
| вверх (CMS = 00, DIR = 0) | 0 … ARR, 0 … | ARR + 1 | ARR = 199 |
| вниз (CMS = 00, DIR = 1) | ARR … 0, ARR … | ARR + 1 | ARR = 199 |
| центрированный (CMS ≠ 00) | 0 … ARR−1, ARR … 1, 0 … | ARR | ARR = 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 не ставит.
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 мс
такт счёта = 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.
какДевять шагов настройки
| Шаг | Регистр и поле | Значение | Зачем |
|---|---|---|---|
| 1 | RCC_APB2ENR.TIM1EN (бит 11) | 1 | без тактирования регистры TIM1 не пишутся |
| 2 | TIM1_PSC | 8999 | делит 18 МГц до 2000 Гц |
| 3 | TIM1_ARR | 200 | граница счёта |
| 4 | TIM1_CR1.CMS | 01 | центрированный счёт; DIR не трогать |
| 5 | TIM1_EGR.UG | 1 | применить PSC и ARR сразу, а не через период (теневые регистры, глава 09) |
| 6 | TIM1_SR.UIF | 0 в бите UIF, 1 в остальных — приём «сброс записью нуля» (глава Си) | UG сам поставил UIF — без сброса прерывание придёт сразу |
| 7 | TIM1_DIER.UIE | 1 | событие обновления даёт запрос прерывания |
| 8 | NVIC_ISER0, бит 25 | 1 | вход TIM1_UP в NVIC, как ISER в ЛР2; на Си — функция CMSIS из главы Си с номером TIM1_UP_IRQn |
| 9 | TIM1_CR1.CEN | 1 | запуск — последним, когда всё готово |
Обработчик называется TIM1_UP_IRQHandler, не TIM1_IRQHandler: у TIM1 четыре входа NVIC, и событию обновления отвечает TIM1_UP (глава 01). Первым делом в нём сбрасывают UIF записью нуля (глава 09). Если флаг не сбросить, обработчик будет вызываться снова и снова, как с EXTI_PR в ЛР2. Потом — тот же тик Морзе.
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 — системная частота «can not be reset if the PLL clock is used as system clock»
: запись проигнорирована, 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,5 | CLKSOURCE = 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) |
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
«channel 1 is active as long as TIMx_CNT<TIMx_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 пока значение таймера меньше, чем значения регистра канала, выход равен нулю».