МИФИ · МПС · лабораторная 1 · STM32F103C8T6
GPIO на ассемблере
Конспект для подготовки к лабораторной: от устройства линии порта до готовой программы. Цифры в разделах о плате измерены на живом чипе через ST-Link, а не взяты из документации.
01 · карта
Что нужно сделать и что для этого знать
В работе два эксперимента. В первом RGB-светодиод перебирает восемь сочетаний цветов раз в секунду. Во втором три кнопки переключают каждая свой цвет. Вариант задаёт, какими выводами это делать.
| Задание | Что сделать | Где смотреть |
|---|---|---|
| 1 | Определить константы: адреса регистров | адреса |
| 2 | Переделать код под выводы варианта | маски CRL/CRH, BSRR |
| 3, 6 | Промоделировать | симулятор Keil |
| 4, 7 | Собрать схему | резисторы, кнопки |
| 5 | Переключать цвета кнопками через IDR | алгоритм |
| 8 | Защититься от дребезга | дребезг |
02 · железо
Порты и тактирование
У STM32F103C8T6 48 выводов, 37 из них подключены к портам: GPIOA и GPIOB по 16 линий, GPIOC[15:13] и GPIOD[1:0]. Пин записывают как порт и номер линии: PB15 — порт B, линия 15.
После сброса периферия обесточена ради экономии. Пока тактирование порта выключено, запись в его регистры пропадает, а чтение возвращает нули. Порты сидят на шине APB2, включаются битами RCC_APB2ENR по адресу 0x4002 1018.
| Бит | Что включает | Нужен в варианте 8 |
|---|---|---|
| 0 | AFIO — переназначение выводов | да, из-за PB3 |
| 2 | GPIOA | да |
| 3 | GPIOB | да |
| 4 | GPIOC | нет |
Отсюда маска 0x0D вместо 0x0C из методички: биты 0, 2 и 3. Включают через ORR, чтобы не выключить остальную периферию.
03 · схемотехника
Восемь режимов линии
Внутри линии четыре узла: два выходных транзистора, два резистора подтяжки, триггер Шмитта и отвод на АЦП. Режим — это комбинация того, что включено. Кодируется четырьмя битами: CNF[1:0] и MODE[1:0].
| Тетрада | CNF MODE | Режим | Что внутри |
|---|---|---|---|
| 0x0 | 00 00 | аналоговый вход | триггер и подтяжки выключены, напряжение идёт на АЦП |
| 0x4 | 01 00 | вход без подтяжки | состояние после сброса; уровень задаёт внешняя схема |
| 0x8 | 10 00 | вход с подтяжкой | резистор 30–50 кОм; направление задаёт бит ODR |
| 0x2 | 00 10 | выход push-pull, 2 МГц | оба транзистора: активные 0 и 1 — для светодиода |
| 0x6 | 01 10 | выход open-drain, 2 МГц | только нижний: 0 или «отпущен» |
| 0xA / 0xE | 10 xx / 11 xx | альтернативная функция | выводом управляет таймер, UART, SPI |
MODE — это не частота сигнала
Биты MODE ограничивают крутизну фронтов выходного драйвера: 01 — 10 МГц, 10 — 2 МГц, 11 — 50 МГц. Чем круче фронт, тем больше помехи и импульсные токи, поэтому берут минимально достаточную скорость. Для светодиода хватает 2 МГц.
Триггер Шмитта
У него два порога: из 0 в 1 переход происходит при большем напряжении, чем обратно. Эта разница гасит дребезг медленных фронтов. Значение защёлкивается в IDR каждый такт APB2 и работает даже в режиме выхода — поэтому по IDR видно фактический уровень на ножке.
04 · память
Адреса регистров
У Cortex-M3 нет отдельных команд ввода-вывода: регистры периферии лежат в адресном пространстве. Адрес складывается из базы блока и смещения регистра.
GPIOB_BSRR = 0x4001 0C00 + 0x10 = 0x4001 0C10
| Блок | База |
|---|---|
| AFIO | 0x4001 0000 |
| GPIOA | 0x4001 0800 |
| GPIOB | 0x4001 0C00 |
| GPIOC | 0x4001 1000 |
| RCC | 0x4002 1000 |
| Смещ. | Регистр | Доступ |
|---|---|---|
| +0x00 | CRL — режимы пинов 0–7 | RW |
| +0x04 | CRH — режимы пинов 8–15 | RW |
| +0x08 | IDR — уровни на выводах | R |
| +0x0C | ODR — выход и подтяжка | RW |
| +0x10 | BSRR — установка и сброс | W |
| +0x14 | BRR — только сброс | W |
Плюс два адреса вне портов: RCC_APB2ENR = 0x4002 1018 и AFIO_MAPR = 0x4001 0004. Это и есть задание 1.
RCC_APB2ENR EQU 0x40021018 AFIO_MAPR EQU 0x40010004 GPIOA_BASE EQU 0x40010800 GPIOA_CRL EQU GPIOA_BASE + 0x00 GPIOA_CRH EQU GPIOA_BASE + 0x04 GPIOA_IDR EQU GPIOA_BASE + 0x08 GPIOA_BSRR EQU GPIOA_BASE + 0x10 GPIOB_BASE EQU 0x40010C00 GPIOB_CRL EQU GPIOB_BASE + 0x00 GPIOB_CRH EQU GPIOB_BASE + 0x04 GPIOB_IDR EQU GPIOB_BASE + 0x08 GPIOB_BSRR EQU GPIOB_BASE + 0x10
__Vectors: из методички даст ошибку A1167E.05 · главное
CRL и CRH: одна цифра — один пин
Одна шестнадцатеричная цифра — это 4 бита, и на пин отведено тоже 4 бита. Поэтому регистр удобно читать по цифрам: каждая задаёт режим одного вывода, нумерация справа налево. Пины 0–7 в CRL, 8–15 в CRH.
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ||
|---|---|---|---|---|---|---|---|---|---|
| пин в CRL | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | пины 0–7 |
| пин в CRH | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | номер цифры = пин − 8 |
| после сброса | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | все входы без подтяжки |
Две маски — две операции
Менять нужно цифру одного пина, не задев семь соседних. Поэтому сначала AND с маской очистки стирает тетраду, потом ORR с маской установки вписывает режим. Очистка обязательна: после сброса там 0100, и ORR с 0010 дал бы 0110 — open-drain вместо push-pull.
маска установки = одни 0, и цифра режима на месте пина
формулой: ~(0xF << 4·n) и тетрада << 4·n
Калькулятор масок
Нажимай на выводы: не трогать → выход push-pull → вход с подтяжкой. Открыт вариант 8: порт A с PA7 и PA3.
06 · данные
ODR, BSRR и IDR
ODR задаёт уровень на выходе: бит n — пин n. Во входном режиме с подтяжкой тот же бит выбирает её направление: 1 — вверх, 0 — вниз.
Менять один бит в ODR — это «прочитать, изменить, записать». Если между чтением и записью случится прерывание, которое тоже трогает ODR, его правка пропадёт.
сбросить пин n: BSRR = 1 << (n + 16)
если стоят оба бита, побеждает установка (RM0008, 9.2.5)
Сборщик значения BSRR
Нажимай на выводы: не трогать → установить → сбросить. Открыт вариант 8: зажечь PB3, погасить PB15.
IDR только читается и показывает фактический уровень на выводе — в том числе когда пин настроен на выход. Проверка одного бита делается через TST.
07 · практика
Как прочитать кнопку и зажечь цвет
Что значит «уровень при нажатии»
Третий столбец в таблице вариантов — какое напряжение появляется на выводе в момент нажатия. Кнопка сама по себе только замыкает два провода; всё решает то, куда идёт её второй конец.
| Уровень | Второй конец кнопки | Подтяжка | CRx | ODR | не нажата / нажата |
|---|---|---|---|---|---|
| H | к 3,3 В | вниз (pull-down) | 0x8 | 0 | 0 / 1 |
| L | к земле | вверх (pull-up) | 0x8 | 1 | 1 / 0 |
Чтение кнопки
Отдельного адреса у вывода нет: читается весь регистр IDR, а нужный разряд проверяется маской. TST вычисляет логическое И и выставляет флаг нуля, а дальше условие зависит от уровня.
| Уровень кнопки | Нажата, когда | Условие после TST |
|---|---|---|
| H | на выводе 1 | NE (Z = 0) |
| L | на выводе 0 | EQ (Z = 1) |
Удобно собрать состояние всех трёх кнопок в один регистр, по биту на кнопку. Порт A читается один раз на две кнопки: обе живут в одном регистре.
MOVS R5, #0 ; здесь соберём, кто нажат
LDR R0, =GPIOA_IDR
LDR R1, [R0] ; R1 = все 16 уровней порта A
TST R1, #(1 << 3) ; Кн1 = PA3, уровень H
IT NE
ORRNE R5, R5, #0x1 ; бит 0 — красный
TST R1, #(1 << 10) ; Кн2 = PA10
IT NE
ORRNE R5, R5, #0x2 ; бит 1 — зелёный
LDR R0, =GPIOB_IDR ; третья кнопка в другом порту
LDR R1, [R0]
TST R1, #(1 << 5) ; Кн3 = PB5
IT NE
ORRNE R5, R5, #0x4 ; бит 2 — синий
Вывод на светодиод
Через ODR пришлось бы читать, менять бит и записывать обратно — три операции, и заодно можно сбить биты подтяжек кнопок, которые лежат в том же регистре. Через BSRR это одна запись, соседние выводы не затрагиваются.
выключить вывод n: BSRR = 1 << (n + 16)
| Цвет | Вывод | Регистр | Зажечь | Погасить |
|---|---|---|---|---|
| красный | PA7 | GPIOA_BSRR | 1 << 7 | 1 << 23 |
| зелёный | PB3 | GPIOB_BSRR | 1 << 3 | 1 << 19 |
| синий | PB15 | GPIOB_BSRR | 1 << 15 | 1 << 31 |
Светодиод с общим катодом: единица на выводе — цвет горит, ноль — погашен.
Как связать одно с другим
Состояние цветов держат в регистре — у нас R4, по биту на цвет, — а отдельная подпрограмма выводит его на светодиод. Тогда логика и вывод разделены: в эксперименте 1 R4 просто увеличивается раз в секунду, в эксперименте 2 биты переключаются кнопками.
Show_Colors
LDR R0, =GPIOA_BSRR
TST R4, #0x1 ; бит 0 установлен?
ITE NE
MOVNE R1, #(1 << 7) ; да — зажечь красный
MOVEQ R1, #(1 << 23) ; нет — погасить
STR R1, [R0]
BX LR
Проверить без деталей
- В Keil: окно
Peripherals → General Purpose I/O → GPIOA, строка Pins. Щёлкаешь по биту 3 — и это ровно то же, что нажатие кнопки на PA3. - На плате: воткни провод с 3,3 В в PA3. Или прочитай регистры отладчиком:
mdw 0x40010808— этоGPIOA_IDR, что на входах, иmdw 0x40010C0C—GPIOB_ODR, что выдаём на светодиод.
08 · электрика
Светодиод, резистор, предельные токи
RGB-светодиод — три кристалла в одном корпусе с общим выводом. У варианта с общим катодом общая ножка идёт на землю, выводы работают в push-pull, цвет горит при единице. У варианта с общим анодом всё наоборот: общая ножка на питание, выводы в open-drain, цвет горит при нуле.
Резистор ставится на каждый цвет, а не один на общий провод: иначе цвета делят ток и светят по-разному.
методичка: не более 10 мА на канал, отсюда её 330 Ом
Ток через светодиод
Uпр — типичное падение на кристалле; точное смотри в паспорте светодиода.
Предельные токи
- не более 25 мА на вывод;
- не более 150 мА суммарно через питание и землю;
- при 8 мА выходной уровень уже заметно проседает.
Можно ли 5 В
Только на входы с пометкой FT. У C8T6 не FT: PA0–PA7, PB0, PB1, PB5. На обычном выводе откроется защитный диод и ток пойдёт в шину питания. Выдать 5 В вывод не может в любом случае.
09 · ввод
Кнопки, подтяжки, дребезг
Кнопка только замыкает цепь. Пока она не нажата, вывод висит и ловит наводки, поэтому нужна подтяжка: она задаёт уровень покоя, а кнопка его перебивает — сопротивление контакта почти нулевое против 30–50 кОм подтяжки.
| Уровень при нажатии | Подтяжка | CRx | ODR | Не нажата / нажата |
|---|---|---|---|---|
| H — кнопка на 3,3 В | вниз (pull-down) | 0x8 | 0 | 0 / 1 |
| L — кнопка на землю | вверх (pull-up) | 0x8 | 1 | 1 / 0 |
0x8 в CRx и нули в битах ODR. Внешние резисторы не нужны.Дребезг контактов
Контакты при нажатии пружинят, цепь несколько раз замыкается и размыкается — обычно единицы миллисекунд, у изношенных кнопок до 10–20 мс. Программа, опрашивающая вывод в плотном цикле, увидит несколько нажатий вместо одного.
| Способ | Как работает | Замечание |
|---|---|---|
| Редкий опрос | задержка ~20 мс в цикле, фронт ищется между опросами | проще всего, используется в коде ниже |
| Подтверждение | увидел фронт → подождал 20 мс → перечитал IDR | отсеивает и одиночные помехи |
| Ждать отпускания | крутиться, пока кнопка нажата | программа «замирает» на время удержания |
| Счётчик стабильности | принять состояние после N одинаковых опросов | надёжнее всего, кода больше |
| Аппаратный | RC-цепочка на кнопке | методичка не требует |
10 · язык
Ассемблер: минимум для лабы
Cortex-M3 выполняет только Thumb-2. Синтаксис: КОМАНДА приёмник, операнд1, операнд2, результат всегда в первый регистр.
| Регистр | Роль |
|---|---|
| R0–R3 | временные: адреса, промежуточные значения |
| R4–R11 | сохраняются подпрограммами: счётчик цветов, состояние кнопок |
| R13 = SP | стек, начальное значение из таблицы векторов |
| R14 = LR | адрес возврата, пишется командой BL |
| R15 = PC | счётчик команд |
| xPSR | флаги N, Z, C, V |
| Директива | Смысл |
|---|---|
| AREA | секция: CODE, READONLY или DATA |
| ENTRY | точка входа |
| EXPORT | сделать метку видимой снаружи |
| EQU | имя для числа |
| DCD | положить в память 32-битное слово |
| END | конец файла, обязателен |
Память: LDR и STR
LDR R0, =GPIOA_CRL ; R0 = ЧИСЛО 0x40010800 (адрес)
LDR R1, [R0] ; R1 = СОДЕРЖИМОЕ по этому адресу
STR R1, [R0] ; записать обратно
STR R1, [R0, #0x10] ; записать по адресу R0 + 0x10
LDR Rx, =число — псевдокоманда: ассемблер кладёт число в литеральный пул рядом с кодом и читает его оттуда. Так в регистр попадает любое 32-битное значение.
Непосредственные константы после # ограничены: 8-битное число со сдвигом (0xFF, 1 << 17, 0x80000000) либо повторяющийся байт (0x00XY00XY). Остальное — только через LDR Rn, =….
Логика и сравнения
| Команда | Смысл | Где пригодится |
|---|---|---|
| AND | оставить биты маски | AND R4, R4, #7 — счёт по модулю 8 |
| ORR | установить биты | включить тактирование |
| BIC | очистить биты (AND NOT) | BIC R7, R5, R6 — фронты нажатия |
| EOR | инвертировать биты | переключить цвет кнопкой |
| TST | проверка бита: Z = 1, если бит нулевой | чтение кнопки и счётчика |
| CMP | сравнение: вычитает, меняет все флаги | больше / меньше / равно |
Блок IT и подпрограммы
TST R4, #0x1 ; бит 0 счётчика установлен?
ITE NE ; If (NE) Then ... Else ...
MOVNE R1, #(1 << 7) ; T: зажечь PA7
MOVEQ R1, #(1 << 23) ; E: погасить PA7
STR R1, [R0] ; без условия, после блока
Delay ; задержка: R3 итераций
SUBS R3, R3, #1
BNE Delay
BX LR
В блок IT входит до четырёх команд: буквы T и E задают, какие выполняются при истинном условии, какие при ложном, а суффиксы команд должны совпадать с буквами. Смысл — обойтись без перехода: конвейер не сбрасывается.
BL, её адрес возврата в LR затрётся. Нужно PUSH {LR} в начале и POP {PC} в конце. Вторая ловушка: Delay с R3 = 0 — SUBS даст 0xFFFFFFFF и цикл пройдёт четыре миллиарда итераций.11 · разбор
Чего не хватает в листинге методички
| Что | Последствие | Решение |
|---|---|---|
Константы EQU | ошибка сборки: undefined symbol | задание 1 |
Подпрограмма Delay | ошибка на BL Delay | SUBS / BNE / BX LR |
Директива END | предупреждение ассемблера | последней строкой |
| Освобождение PB3 от JTAG | в симуляторе работает, на плате зелёный не горит | раздел 13 |
Двоеточие в __Vectors: (в лабе 0) | ошибка A1167E: Invalid line start | убрать двоеточие |
Ещё одна тонкость: в примере методички стоит ORR R1, R1, #0xC — тактирование GPIOA и GPIOB. Для варианта 8 нужно #0x0D, потому что добавляется AFIO.
12 · алгоритм
Как кнопки переключают цвета
R4 хранит, какие цвета горят. Меняется только способ его менять: не прибавлять единицу по таймеру, а инвертировать бит в момент нажатия. Если реагировать на уровень, а не на фронт, цвет будет мигать всё время, пока кнопку держат.
| R6 (было) | R5 (стало) | R7 = R5 AND NOT R6 | Смысл |
|---|---|---|---|
| 0 | 0 | 0 | не нажата |
| 0 | 1 | 1 | только что нажали |
| 1 | 1 | 0 | держат |
| 1 | 0 | 0 | отпустили |
В ассемблере это одна команда BIC R7, R5, R6, и она обрабатывает все три кнопки сразу: у каждой свой бит. Затем EOR R4, R4, R7 инвертирует нужные цвета.
MOVS R5, #0
LDR R0, =GPIOA_IDR
LDR R1, [R0]
TST R1, #(1 << 3) ; Кн1 = PA3, уровень H
IT NE ; единица на выводе -> нажата
ORRNE R5, R5, #0x1
BIC R7, R5, R6 ; только что нажатые
MOV R6, R5 ; запомнить состояние
EOR R4, R4, R7 ; переключить цвета
13 · инструменты
Keil и цепочка под Linux
Симулятор Keil
- Project → Options for Target → Debug: Use Simulator, галочка Limit Speed to Real-Time.
- Внизу: Dialog DLL
DARMSTM.DLL, параметр-pSTM32F103C8. Без этого окон периферии не будет. - Пути и имена — только латиницей.
- Ctrl+F5 — старт отладки, F11 — шаг, F10 — шаг без захода, F9 — точка останова.
- Peripherals → General Purpose I/O → GPIOA: поля CRL/CRH, ODR, IDR и строка Pins, где щелчком «нажимают» кнопку.
- View → Analysis Windows → Logic Analyzer: добавить сигналы вида
(PORTB & 0x8000) >> 15, тип отображения BIT.
SUBS + BNE занимает 4 такта на итерацию, на реальном чипе — 3. Поэтому программа, которая в Keil мигает ровно раз в секунду, на плате мигает за 0,75 с. Считать период лучше по формуле 2 · N · 3 / F.Сборка под Linux, без Keil
Тот же исходник в синтаксисе Keil собирается GNU-инструментами: конвертер переводит его строка в строку, а ошибки указывают на исходный файл.
make # собрать: .elf, .hex, .bin make flash # залить на плату через ST-Link make disasm # дизассемблер с адресами make sim # открыть в PICSimLab
14 · железо
Подводные камни
| Проблема | Почему | Что делать | |
|---|---|---|---|
| вариант 8 | Зелёный канал на PB3 не работает на плате | после сброса вывод занят отладочным интерфейсом JTAG | включить AFIO (бит 0) и записать SWJ_CFG = 010 в биты 26:24 AFIO_MAPR |
| часто | Регистры порта не меняются | не включено тактирование | сначала RCC_APB2ENR |
| часто | Вывод 8–15 не настраивается | запись идёт в CRL вместо CRH | n ≥ 8 → CRH, сдвиг 4·(n − 8) |
| логика | Кнопка перестала работать после вывода цвета | запись целого числа в ODR сбила подтяжку | только BSRR |
| логика | Цвет переключается случайно | дребезг | опрос раз в ~20 мс |
| сборка | Цвет не горит | перепутаны ножки или тип светодиода | длинная ножка — общая; проверить по одному каналу |
| сборка | Кнопка замкнута всегда | подключены ножки одной внутренней пары | диагональные ножки, кнопка поперёк канавки |
| питание | — | ошибка в схеме может сжечь вывод | резистор на каждый цвет; собирать при отключённом питании |
; Освободить PB3 от JTAG (AFIO уже должен тактироваться)
LDR R0, =AFIO_MAPR
LDR R1, [R0]
BIC R1, R1, #(7 << 24) ; SWJ_CFG читается как мусор, поэтому чистим
ORR R1, R1, #(2 << 24) ; 010: JTAG выключен, SWD остаётся
STR R1, [R0]
PA13 и PA14 — это SWDIO и SWCLK. Если их перенастроить или записать SWJ_CFG = 100, программатор потеряет связь с платой.Что намерено на этой плате
| Тактовая частота после сброса | 8,03 МГц (HSI) |
| Кварц и PLL | исправны, разгоняются до 72,1 МГц |
Цикл SUBS + BNE | 3 такта на итерацию |
| Флеш | 128 КБ вместо заявленных 64 КБ |
| ОЗУ | 20 КБ |
15 · практика
Вариант 8 целиком
Светодиод с общим катодом: R = PA7, G = PB3, B = PB15. Кнопки: PA3 → красный, PA10 → зелёный, PB5 → синий, все с уровнем H.
| Вывод | Роль | Регистр | Цифра | Очистка | Установка |
|---|---|---|---|---|---|
| PA7 | красный | GPIOA_CRL | 7 | 0x0FFFFFFF | 0x20000000 |
| PB3 | зелёный | GPIOB_CRL | 3 | 0xFFFF0FFF | 0x00002000 |
| PB15 | синий | GPIOB_CRH | 7 | 0x0FFFFFFF | 0x20000000 |
| PA3 | кнопка 1 | GPIOA_CRL | 3 | 0xFFFF0FFF | 0x00008000 |
| PA10 | кнопка 2 | GPIOA_CRH | 2 | 0xFFFFF0FF | 0x00000800 |
| PB5 | кнопка 3 | GPIOB_CRL | 5 | 0xFF0FFFFF | 0x00800000 |
0x0FFF0FFF и установка 0x20008000.Сначала напиши код сам — на защите его придётся объяснять. Готовый нужен для сверки.
Эксперимент 1 — перебор восьми цветов
RCC_APB2ENR EQU 0x40021018
AFIO_MAPR EQU 0x40010004
GPIOA_CRL EQU 0x40010800
GPIOA_BSRR EQU 0x40010810
GPIOB_CRL EQU 0x40010C00
GPIOB_CRH EQU 0x40010C04
GPIOB_BSRR EQU 0x40010C10
DELAY_1S EQU 1333333 ; 3 такта на итерацию при 8 МГц
AREA LAB1, CODE, READONLY
ENTRY
EXPORT Reset_Handler
Reset_Handler
; 1. Тактирование: AFIO (бит 0), GPIOA (бит 2), GPIOB (бит 3)
LDR R0, =RCC_APB2ENR
LDR R1, [R0]
ORR R1, R1, #0x0D
STR R1, [R0]
; 2. Освободить PB3 от JTAG: SWJ_CFG = 010
LDR R0, =AFIO_MAPR
LDR R1, [R0]
BIC R1, R1, #(7 << 24)
ORR R1, R1, #(2 << 24)
STR R1, [R0]
; 3. PA7 -> выход push-pull 2 МГц
LDR R0, =GPIOA_CRL
LDR R1, [R0]
LDR R2, =0x0FFFFFFF
AND R1, R1, R2
LDR R2, =0x20000000
ORR R1, R1, R2
STR R1, [R0]
; 4. PB3 -> выход
LDR R0, =GPIOB_CRL
LDR R1, [R0]
LDR R2, =0xFFFF0FFF
AND R1, R1, R2
LDR R2, =0x00002000
ORR R1, R1, R2
STR R1, [R0]
; 5. PB15 -> выход
LDR R0, =GPIOB_CRH
LDR R1, [R0]
LDR R2, =0x0FFFFFFF
AND R1, R1, R2
LDR R2, =0x20000000
ORR R1, R1, R2
STR R1, [R0]
MOVS R4, #0 ; бит0 R, бит1 G, бит2 B
Main_Loop
BL Show_Colors
LDR R3, =DELAY_1S
BL Delay
ADD R4, R4, #1
AND R4, R4, #0x7
B Main_Loop
; ---------- биты R4 -> светодиод ----------
Show_Colors
LDR R0, =GPIOA_BSRR
TST R4, #0x1 ; красный, PA7
ITE NE
MOVNE R1, #(1 << 7)
MOVEQ R1, #(1 << 23)
STR R1, [R0]
LDR R0, =GPIOB_BSRR
TST R4, #0x2 ; зелёный, PB3
ITE NE
MOVNE R1, #(1 << 3)
MOVEQ R1, #(1 << 19)
STR R1, [R0]
TST R4, #0x4 ; синий, PB15
ITE NE
MOVNE R1, #(1 << 15)
MOVEQ R1, #(1 << 31)
STR R1, [R0]
BX LR
; ---------- задержка ----------
Delay
SUBS R3, R3, #1
BNE Delay
BX LR
ENDЭксперимент 2 — кнопки с антидребезгом
RCC_APB2ENR EQU 0x40021018
AFIO_MAPR EQU 0x40010004
GPIOA_CRL EQU 0x40010800
GPIOA_CRH EQU 0x40010804
GPIOA_IDR EQU 0x40010808
GPIOA_BSRR EQU 0x40010810
GPIOB_CRL EQU 0x40010C00
GPIOB_CRH EQU 0x40010C04
GPIOB_IDR EQU 0x40010C08
GPIOB_BSRR EQU 0x40010C10
DELAY_20MS EQU 26666 ; период опроса кнопок
AREA LAB1, CODE, READONLY
ENTRY
EXPORT Reset_Handler
Reset_Handler
LDR R0, =RCC_APB2ENR
LDR R1, [R0]
ORR R1, R1, #0x0D
STR R1, [R0]
LDR R0, =AFIO_MAPR ; освободить PB3
LDR R1, [R0]
BIC R1, R1, #(7 << 24)
ORR R1, R1, #(2 << 24)
STR R1, [R0]
; GPIOA_CRL: PA7 выход, PA3 вход с подтяжкой
LDR R0, =GPIOA_CRL
LDR R1, [R0]
LDR R2, =0x0FFF0FFF
AND R1, R1, R2
LDR R2, =0x20008000
ORR R1, R1, R2
STR R1, [R0]
; GPIOA_CRH: PA10 вход с подтяжкой
LDR R0, =GPIOA_CRH
LDR R1, [R0]
LDR R2, =0xFFFFF0FF
AND R1, R1, R2
LDR R2, =0x00000800
ORR R1, R1, R2
STR R1, [R0]
; GPIOB_CRL: PB3 выход, PB5 вход с подтяжкой
LDR R0, =GPIOB_CRL
LDR R1, [R0]
LDR R2, =0xFF0F0FFF
AND R1, R1, R2
LDR R2, =0x00802000
ORR R1, R1, R2
STR R1, [R0]
; GPIOB_CRH: PB15 выход
LDR R0, =GPIOB_CRH
LDR R1, [R0]
LDR R2, =0x0FFFFFFF
AND R1, R1, R2
LDR R2, =0x20000000
ORR R1, R1, R2
STR R1, [R0]
; Подтяжки вниз и погасить светодиод (кнопки H -> BRn)
LDR R0, =GPIOA_BSRR
LDR R1, =0x04880000 ; BR7 свет., BR3 Кн1, BR10 Кн2
STR R1, [R0]
LDR R0, =GPIOB_BSRR
LDR R1, =0x80280000 ; BR3 и BR15 свет., BR5 Кн3
STR R1, [R0]
MOVS R4, #0 ; какие цвета горят
MOVS R6, #0 ; кнопки на прошлом опросе
Main_Loop
MOVS R5, #0
LDR R0, =GPIOA_IDR
LDR R1, [R0]
TST R1, #(1 << 3) ; Кн1 = PA3
IT NE
ORRNE R5, R5, #0x1
TST R1, #(1 << 10) ; Кн2 = PA10
IT NE
ORRNE R5, R5, #0x2
LDR R0, =GPIOB_IDR
LDR R1, [R0]
TST R1, #(1 << 5) ; Кн3 = PB5
IT NE
ORRNE R5, R5, #0x4
BIC R7, R5, R6 ; только что нажатые
MOV R6, R5
EOR R4, R4, R7 ; переключить их цвета
BL Show_Colors
LDR R3, =DELAY_20MS ; антидребезг: редкий опрос
BL Delay
B Main_Loop
Show_Colors
LDR R0, =GPIOA_BSRR
TST R4, #0x1
ITE NE
MOVNE R1, #(1 << 7)
MOVEQ R1, #(1 << 23)
STR R1, [R0]
LDR R0, =GPIOB_BSRR
TST R4, #0x2
ITE NE
MOVNE R1, #(1 << 3)
MOVEQ R1, #(1 << 19)
STR R1, [R0]
TST R4, #0x4
ITE NE
MOVNE R1, #(1 << 15)
MOVEQ R1, #(1 << 31)
STR R1, [R0]
BX LR
Delay
SUBS R3, R3, #1
BNE Delay
BX LR
END16 · самопроверка
Задачи и контрольные вопросы
Задачи
З1Кнопка на PB12 с уровнем L. Регистр, маски, значение BSRR?
12 ≥ 8 → GPIOB_CRH, сдвиг 4·(12−8) = 16. Очистка 0xFFF0FFFF, установка 0x00080000. Уровень L → подтяжка вверх → ODR[12] = 1 → BSRR = 0x00001000.
З2Что окажется в GPIOA_CRL после настройки PA7 на выход push-pull 2 МГц сразу после сброса?
Цифры пинов 7…0: 2 4 4 4 4 4 4 4 → 0x24444444.
З3R4 = 6. Чему равен R1 после TST R4,#1 / ITE NE / MOVNE R1,#1 / MOVEQ R1,#2?
6 AND 1 = 0 → Z = 1 → условие NE ложно → выполняется ветка E → R1 = 2.
З4Какие константы можно написать прямо после #: 0x2000, 0x00200020, 0x00208020, 0xFF0FFF0F, 0x80000000?
Можно: 0x2000, 0x00200020, 0x80000000. Нельзя: 0x00208020 и 0xFF0FFF0F — их грузят через LDR R2, =….
З5Программа без задержки переключает цвет, пока IDR показывает «нажата». Что будет на плате?
Цвет будет переключаться на каждой итерации, тысячи раз в секунду: светодиод засветит вполсилы, а после отпускания останется в случайном состоянии. Нужны реакция на фронт (BIC) и антидребезг.
З6Зелёный на PB3 горит в симуляторе, но не на плате. Почему?
PB3 после сброса занят JTAG, и симулятор этого не моделирует. Нужно включить AFIO и записать SWJ_CFG = 010.
Контрольные вопросы методички
1В чём разница между CRL и CRH?
Оба задают режим по 4 бита на вывод. 16 × 4 = 64 бита в одно 32-битное слово не помещаются, поэтому CRL (смещение 0x00) отвечает за пины 0–7, CRH (0x04) — за 8–15. Сдвиг: 4·n и 4·(n−8).
2Зачем нужны подтяжки во входном режиме?
Неподключённый вход имеет высокое сопротивление и ловит наводки, уровень случаен. Подтяжка через 30–50 кОм задаёт уровень покоя: вверх к питанию или вниз к земле. Кнопка с малым сопротивлением контакта перебивает его. Режим CNF=10, MODE=00, направление — бит ODR.
3Чем отличаются push-pull и open-drain?
В push-pull работают оба транзистора: активные 0 и 1, быстрое переключение, режим для однонаправленной передачи. В open-drain верхний ключ всегда закрыт: вывод либо прижат к земле, либо отпущен, и единицу даёт внешняя подтяжка. Зато несколько выходов можно соединять на одну линию (I²C, «монтажное И») и через подтяжку к 5 В на FT-выводе получать 5-вольтовый сигнал.
4Зачем в MODE выбирается частота 2, 10 или 50 МГц?
Это ограничение крутизны фронтов выходного драйвера. Быстрые фронты нужны только для высокочастотных сигналов, а в остальном дают импульсные токи, потребление, помехи и звон. Берут минимально достаточную: для светодиода 2 МГц.
5Какой регистр читает физическое состояние выводов? Работает ли он в режиме выхода?
GPIOx_IDR, смещение 0x08. Да, работает: по RM0008 §9.1.8 в режиме выхода триггер Шмитта остаётся включённым и уровень защёлкивается в IDR каждый такт APB2. ODR при этом хранит то, что записала программа.
6В чём преимущество BSRR перед ODR?
Установка и сброс делаются одной записью, без чтения. Нули ничего не меняют, поэтому соседние выводы не затрагиваются, а операция атомарна: прерывание не может вклиниться между чтением и записью. И быстрее — одна команда вместо трёх.
7Какое ограничение по току у вывода и как его не превысить?
Не более 25 мА на вывод и 150 мА суммарно через питание и землю (datasheet, табл. 7). Методичка требует не выше 10 мА на канал. Ограничивают резистором: R ≥ (3,3 − Uпр)/I. Мощную нагрузку подключают через транзистор.
8Что будет, если подать 5 В на входной вывод?
У FT-выводов защитный диод подключён к отдельной шине и 5 В допустимы. У обычных (у C8T6 это PA0–PA7, PB0, PB1, PB5) откроется диод на VDD, ток пойдёт в шину питания, вывод может выйти из строя. Выдать 5 В вывод не может в любом случае.
9Чем MOVNE отличается от MOV?
MOV выполняется всегда, MOVNE — только при Z = 0, иначе пропускается и регистр не меняется. В Thumb-2 она должна стоять в блоке IT с соответствующей буквой. Флаги обе не меняют, если нет суффикса S.