GPIO на ассемблере

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

GPIO на ассемблере

Конспект для подготовки к лабораторной: от устройства линии порта до готовой программы. Цифры в разделах о плате измерены на живом чипе через ST-Link, а не взяты из документации.

Вариант 8 R = PA7, G = PB3, B = PB15 Кнопки PA3, PA10, PB5 — все H Среда Keil µVision, armasm

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
0AFIO — переназначение выводовда, из-за PB3
2GPIOAда
3GPIOBда
4GPIOCнет

Отсюда маска 0x0D вместо 0x0C из методички: биты 0, 2 и 3. Включают через ORR, чтобы не выключить остальную периферию.

03 · схемотехника

Восемь режимов линии

Внутри линии четыре узла: два выходных транзистора, два резистора подтяжки, триггер Шмитта и отвод на АЦП. Режим — это комбинация того, что включено. Кодируется четырьмя битами: CNF[1:0] и MODE[1:0].

ТетрадаCNF MODEРежимЧто внутри
0x000 00аналоговый входтриггер и подтяжки выключены, напряжение идёт на АЦП
0x401 00вход без подтяжкисостояние после сброса; уровень задаёт внешняя схема
0x810 00вход с подтяжкойрезистор 30–50 кОм; направление задаёт бит ODR
0x200 10выход push-pull, 2 МГцоба транзистора: активные 0 и 1 — для светодиода
0x601 10выход open-drain, 2 МГцтолько нижний: 0 или «отпущен»
0xA / 0xE10 xx / 11 xxальтернативная функциявыводом управляет таймер, UART, SPI

MODE — это не частота сигнала

Биты MODE ограничивают крутизну фронтов выходного драйвера: 01 — 10 МГц, 10 — 2 МГц, 11 — 50 МГц. Чем круче фронт, тем больше помехи и импульсные токи, поэтому берут минимально достаточную скорость. Для светодиода хватает 2 МГц.

Триггер Шмитта

У него два порога: из 0 в 1 переход происходит при большем напряжении, чем обратно. Эта разница гасит дребезг медленных фронтов. Значение защёлкивается в IDR каждый такт APB2 и работает даже в режиме выхода — поэтому по IDR видно фактический уровень на ножке.

ИнтерактивСхема линии с подсветкой того, что работает в каждом режиме, — на странице «Карта выводов Blue Pill», третий уровень карточки вывода.

04 · память

Адреса регистров

У Cortex-M3 нет отдельных команд ввода-вывода: регистры периферии лежат в адресном пространстве. Адрес складывается из базы блока и смещения регистра.

адрес = база порта + смещение регистра
GPIOB_BSRR = 0x4001 0C00 + 0x10 = 0x4001 0C10
БлокБаза
AFIO0x4001 0000
GPIOA0x4001 0800
GPIOB0x4001 0C00
GPIOC0x4001 1000
RCC0x4002 1000
Смещ.РегистрДоступ
+0x00CRL — режимы пинов 0–7RW
+0x04CRH — режимы пинов 8–15RW
+0x08IDR — уровни на выводахR
+0x0CODR — выход и подтяжкаRW
+0x10BSRR — установка и сбросW
+0x14BRR — только сброс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
ЛовушкаВ armasm всё, что начинается с первой колонки, — это метка. Команды пишутся с отступом. И двоеточие после метки armasm не принимает: __Vectors: из методички даст ошибку A1167E.

05 · главное

CRL и CRH: одна цифра — один пин

Одна шестнадцатеричная цифра — это 4 бита, и на пин отведено тоже 4 бита. Поэтому регистр удобно читать по цифрам: каждая задаёт режим одного вывода, нумерация справа налево. Пины 0–7 в CRL, 8–15 в CRH.

76543210
пин в CRL76543210пины 0–7
пин в CRH15141312111098номер цифры = пин − 8
после сброса44444444все входы без подтяжки

Две маски — две операции

Менять нужно цифру одного пина, не задев семь соседних. Поэтому сначала AND с маской очистки стирает тетраду, потом ORR с маской установки вписывает режим. Очистка обязательна: после сброса там 0100, и ORR с 0010 дал бы 0110 — open-drain вместо push-pull.

маска очистки = одни F, и 0 на месте нужного пина
маска установки = одни 0, и цифра режима на месте пина
формулой: ~(0xF << 4·n) и тетрада << 4·n

Калькулятор масок

Нажимай на выводы: не трогать → выход push-pull → вход с подтяжкой. Открыт вариант 8: порт A с PA7 и PA3.


 

06 · данные

ODR, BSRR и IDR

ODR задаёт уровень на выходе: бит n — пин n. Во входном режиме с подтяжкой тот же бит выбирает её направление: 1 — вверх, 0 — вниз.

Менять один бит в ODR — это «прочитать, изменить, записать». Если между чтением и записью случится прерывание, которое тоже трогает ODR, его правка пропадёт.

программа прерывание ODR LDR R1, [ODR] ODR |= бит 2 ORR R1, #2 STR R1, [ODR] 0b0001 0b0101 0b0011 бит 2 потерян
Ради этого и существует BSRR: он делает то же самое одной записью, которую прервать нельзя.
установить пин n: BSRR = 1 << n
сбросить пин n: BSRR = 1 << (n + 16)
если стоят оба бита, побеждает установка (RM0008, 9.2.5)

Сборщик значения BSRR

Нажимай на выводы: не трогать → установить → сбросить. Открыт вариант 8: зажечь PB3, погасить PB15.

IDR только читается и показывает фактический уровень на выводе — в том числе когда пин настроен на выход. Проверка одного бита делается через TST.

ЛовушкаПодтяжки кнопок хранятся в тех же битах ODR, что и выходы. Если управлять светодиодами записью целого числа в ODR, подтяжки собьются. Выходами управляй только через BSRR.

07 · практика

Как прочитать кнопку и зажечь цвет

Что значит «уровень при нажатии»

Третий столбец в таблице вариантов — какое напряжение появляется на выводе в момент нажатия. Кнопка сама по себе только замыкает два провода; всё решает то, куда идёт её второй конец.

УровеньВторой конец кнопкиПодтяжкаCRxODRне нажата / нажата
Hк 3,3 Ввниз (pull-down)0x800 / 1
Lк землевверх (pull-up)0x811 / 0
Вариант 8Все три кнопки с уровнем H: каждая одним концом сидит на 3,3 В, вторым — на своём выводе. Значит, всем нужна подтяжка вниз, а нажатие читается как единица. У соседа с уровнем L будет и схема другая, и условие в коде противоположное — вариант нельзя просто скопировать.

Чтение кнопки

Отдельного адреса у вывода нет: читается весь регистр IDR, а нужный разряд проверяется маской. TST вычисляет логическое И и выставляет флаг нуля, а дальше условие зависит от уровня.

Уровень кнопкиНажата, когдаУсловие после TST
Hна выводе 1NE (Z = 0)
Lна выводе 0EQ (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
выключить вывод n: BSRR = 1 << (n + 16)
ЦветВыводРегистрЗажечьПогасить
красныйPA7GPIOA_BSRR1 << 71 << 23
зелёныйPB3GPIOB_BSRR1 << 31 << 19
синийPB15GPIOB_BSRR1 << 151 << 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, цвет горит при нуле.

Резистор ставится на каждый цвет, а не один на общий провод: иначе цвета делят ток и светят по-разному.

I = (3,3 В − Uпр) / R
методичка: не более 10 мА на канал, отсюда её 330 Ом

Ток через светодиод

Uпр — типичное падение на кристалле; точное смотри в паспорте светодиода.

010 мА25 мА

Предельные токи

  • не более 25 мА на вывод;
  • не более 150 мА суммарно через питание и землю;
  • при 8 мА выходной уровень уже заметно проседает.

Можно ли 5 В

Только на входы с пометкой FT. У C8T6 не FT: PA0–PA7, PB0, PB1, PB5. На обычном выводе откроется защитный диод и ток пойдёт в шину питания. Выдать 5 В вывод не может в любом случае.

09 · ввод

Кнопки, подтяжки, дребезг

Кнопка только замыкает цепь. Пока она не нажата, вывод висит и ловит наводки, поэтому нужна подтяжка: она задаёт уровень покоя, а кнопка его перебивает — сопротивление контакта почти нулевое против 30–50 кОм подтяжки.

Уровень при нажатииПодтяжкаCRxODRНе нажата / нажата
H — кнопка на 3,3 Ввниз (pull-down)0x800 / 1
L — кнопка на землювверх (pull-up)0x811 / 0
Вариант 8Все три кнопки с уровнем H, значит всем нужна подтяжка вниз: тетрада 0x8 в CRx и нули в битах ODR. Внешние резисторы не нужны.

Дребезг контактов

Контакты при нажатии пружинят, цепь несколько раз замыкается и размыкается — обычно единицы миллисекунд, у изношенных кнопок до 10–20 мс. Программа, опрашивающая вывод в плотном цикле, увидит несколько нажатий вместо одного.

Опрос каждые 0,6 мс 10 IDR: 1 0 1 0 1 0 1 0 0 0 четыре перехода 1→0 = «четыре нажатия» Опрос раз в 20 мс 0204060 мс IDR: 1 0 0 0 один переход = одно нажатие
Одно и то же нажатие, прочитанное с разной частотой. Оранжевые точки — моменты чтения IDR. Пока дребезг короче периода опроса, между двумя чтениями он не виден.
СпособКак работаетЗамечание
Редкий опросзадержка ~20 мс в цикле, фронт ищется между опросамипроще всего, используется в коде ниже
Подтверждениеувидел фронт → подождал 20 мс → перечитал IDRотсеивает и одиночные помехи
Ждать отпусканиякрутиться, пока кнопка нажатапрограмма «замирает» на время удержания
Счётчик стабильностипринять состояние после N одинаковых опросовнадёжнее всего, кода больше
АппаратныйRC-цепочка на кнопкеметодичка не требует
СимуляторВ Keil дребезга нет: кнопка нажимается идеально. Код без антидребезга в симуляторе работает правильно, а на плате ошибается. Задание 8 проверяется только вживую.

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 DelaySUBS / 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Смысл
000не нажата
011только что нажали
110держат
100отпустили

В ассемблере это одна команда 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

  1. Project → Options for Target → Debug: Use Simulator, галочка Limit Speed to Real-Time.
  2. Внизу: Dialog DLL DARMSTM.DLL, параметр -pSTM32F103C8. Без этого окон периферии не будет.
  3. Пути и имена — только латиницей.
  4. Ctrl+F5 — старт отладки, F11 — шаг, F10 — шаг без захода, F9 — точка останова.
  5. Peripherals → General Purpose I/O → GPIOA: поля CRL/CRH, ODR, IDR и строка Pins, где щелчком «нажимают» кнопку.
  6. 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 вместо CRHn ≥ 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 + BNE3 такта на итерацию
Флеш128 КБ вместо заявленных 64 КБ
ОЗУ20 КБ

15 · практика

Вариант 8 целиком

Светодиод с общим катодом: R = PA7, G = PB3, B = PB15. Кнопки: PA3 → красный, PA10 → зелёный, PB5 → синий, все с уровнем H.

ВыводРольРегистрЦифраОчисткаУстановка
PA7красныйGPIOA_CRL70x0FFFFFFF0x20000000
PB3зелёныйGPIOB_CRL30xFFFF0FFF0x00002000
PB15синийGPIOB_CRH70x0FFFFFFF0x20000000
PA3кнопка 1GPIOA_CRL30xFFFF0FFF0x00008000
PA10кнопка 2GPIOA_CRH20xFFFFF0FF0x00000800
PB5кнопка 3GPIOB_CRL50xFF0FFFFF0x00800000
Два регистраВ эксперименте 2 порт A настраивается дважды (PA7 и PA3 в CRL, PA10 в CRH) и порт B тоже дважды (PB3 и PB5 в CRL, PB15 в CRH). Маски для пар выводов объединяются: например, для GPIOA_CRL это очистка 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

        END

16 · самопроверка

Задачи и контрольные вопросы

Задачи

З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.