Начало » Микроконтроллеры » Ассемблер для микроконтроллера с нуля. Часть 4. Система адресации памяти, назначение выводов, тактирование и прерывания МК

 
 

Ассемблер для микроконтроллера с нуля. Часть 4. Система адресации памяти, назначение выводов, тактирование и прерывания МК

26.02.21   erbol   2 236   3  

Привет датагорцам! Сегодня мы остановимся на следующих вопросах касательно рассматриваемых нами МК:
1. Система адресации регистров памяти данных.
2. Дефолтное и альтернативные назначения выводов.
3. Организация системы тактирования.
4. Преимущества использования прерываний и требования к их оформлению в коде.

Система адресации регистров памяти данных

В предыдущих частях статьи мы время от времени, оформляя код примеров, записывали в шаблонные хидер-файлы те или иные макроопределения. Начиная с этой части статьи, процесс наполнения указанных файлов пойдёт особенно активно. В конце концов вам придётся записать в указанные файлы макроопределения всех регистров и номеров битов для выбранного МК. Учитывая их количество (особенно, в случае с Cortex M-4), необходимо определиться, как именно будут оформляться макроопределения с тем, чтобы содержимое хидер-файлов не превратилось в неудобочитаемое месиво.

Самый простой и естественный путь — взять за основу систему адресации регистров памяти данных, принятую в документации микроконтроллера.

Система адресации регистров памяти данных AVR-8

Здесь всё просто: берём данные из Таблицы «Registers Summary» даташита (страница 200 и 309 для ATtiny85 и ATmega8, соответственно) и переносим их в хидер-файл.

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

С учётом того, что в последующих примерах и далее будут использоваться макросы setBit и clearBit, в рамках данной статьи в хидер-файлы AVR-8 будут вноситься макроопределения абсолютных адресов.
Например, записи для регистра ADCSRA модуля АЦП обоих МК будут выглядеть так:
/* Адреса и номера битов регистров модуля АЦП */
ADCSRA = 0×26
  ADEN = 7
  ADSC = 6
  ADATE = 5
  ADIF = 4
  ADIE = 3
  ADPS2 = 2
  ADPS1 = 1
  ADPS0 = 0


Система адресации регистров памяти данных STM32F401

Тема организации памяти STM32F401 отражена в Разделе «Memory and bus architecture» на странице 36 Reference manual.

Адресация регистров этого МК основана на системе граничных (boundary) адресов и смещений (offset).
Поясню, что это означает.
На каждый модуль в памяти данных выделен сектор, границы которого приведены в Таблице 1 «STM32F401x register boundary addresses» из указанного выше раздела. Чтобы понять, где именно внутри этого сектора находится адрес того или иного регистра, необходимо суммировать значения нижней границы сектора и смещения, определённого для данного регистра. Сводная информация по всем регистрам модуля, включая значение offset, приводится в Таблице «Register map», располагающейся, как правило, в самом конце раздела, посвящённого данному модулю.

В качестве примера на Рисунке 1 приведены выдержки из таблиц «STM32F401x register boundary addresses» и «Register map» (страница 164 Reference manual) для портов ввода-вывода.

Рисунок 1. Система адресации регистров памяти данных МК STM32F401.

Как видите, на каждый из шести портов ввода-вывода GPIO выделен сектор памяти данных, ограниченный адресами из столбца «Boundary address» верхней таблицы.

Режим работы порт вывода-вывода определяют несколько регистров настройки, одинаковых для всех GPIO, три из которых представлены в нижней части Рисунка 1.
Нетрудно понять, что значение адреса регистра MODER составляет:
• 0×40020000 + 0×00 = 0×40020000 для GPIOA,
• 0×40020400 + 0×00 = 0×40020400 для GPIOB,

а младшего AFRL из двух регистров AFR:
• 0×40021000 + 0×20 = 0×40021020 для GPIOE,
• 0×40021C00 + 0×20 = 0×40021C20 для GPIOH.

Однако, в вычислениях абсолютного адреса того или иного регистра нет необходимости, поскольку мы можем прописывать его в коде как сумму двух макроопределений: адреса порта и смещения регистра.
Подобный подход справедлив по отношению и к другим модулям МК.

С учетом изложенного, макроопределения для модуля GPIO будут оформляться нами в stm32f401.h нижеприведённым способом:
/* Адреса и номера битов регистров модуля GPIO */
GPIOH = 0×40021C00
GPIOE = 0×40021000
GPIOD = 0×40020C00
GPIOC = 0×40020800
GPIOB = 0×40020400
GPIOA = 0×40020000
  MODER = 0×00
    MODER_15 = 30
    MODER_14 = 28
    …
    MODER_1 = 2
    MODER_0 = 0
  AFRH = 0×24
    AFRH_15 = 28
    AFRH_14 = 24
    …
    AFRH_9 = 4
    AFRH_8 = 0
  AFRL = 0×20
    AFRL_7 = 28
    AFRL_6 = 24
    …
    AFRL_1 = 4
    AFRL_0 = 0

Как вы понимаете, заполнить пробелы, обозначенные многоточием, вам придётся самостоятельно.

Обратите внимание, что в регистрах MODER и AFR за ту или иную настройку отвечает не один бит, а группа из двух или даже четырёх битов.

Учитывая, что такое возможно и для других регистров, вы должны выбрать для себя один из двух вариантов макроопределений для битов группы:

1. Наделить макроопределением каждый бит группы (MODER_1_1, MODER_1_0, AFRL_5_3, AFRL_5_2, AFRL_5_1, AFRL_5_0 и т. п.) и работать с ними по-отдельности посредством макросов setBit и clearBit из macro.h.
При таком подходе запись единиц в биты 22 и 20 регистра AFRL порта B осуществляется следующим образом:
setBit (GPIOB + AFRL), AFRL_5_2
setBit (GPIOB + AFRL), AFRL_5_0


2. Дать макроопределение младшему биту группы битов, как мы это сделали выше, и обращаться к группе с помощью маски из двоичных чисел.
В этом случае пример выше будет выглядеть так:
LDR r0, =(GPIOB + AFRL)
LDR r1, =(0b0101 ≪ AFRL_5)
STR r1, [r0]


Система адресации регистров памяти данных nRF52832

Для nRF52832, аналогично с STM32F401, принята система адресации, согласно которой адрес регистра памяти данных складывается из двух составляющих: значения базового адреса модуля, к которому принадлежит регистр, и значения смещения, определённого для данного регистра.

На Рисунке 2 приведены выдержки из Таблиц 20, 30 и расшифровка назначения битов регистра PIN_CNF[0], ответственного за настройку пина P0.00 модуля GPIO (Раздел «GPIO» на странице 111 Product specification).

Рисунок 2. Система адресации регистров памяти данных МК nRF52832.

Как следует из рисунка, обращение к регистру PIN_CNF[0] осуществляется по адресу 0×50000000 + 0×700 = 0×50000700.

Оформим в хидер-файле nrf52832.h макроопределения представленных на Рисунке 2 регистров и битов модуля GPIO.
/* Адреса и номера битов регистров модуля GPIO */
GPIO = 0×50000000
  PIN_CNF_0 = 0×700
    DIR = 0
    INPUT = 1
    PULL = 2

Вы наверняка заметили, что за некоторые настройки отвечает не один бит, а их группа. Кроме того, для группы битов с одним назначением в документации nRF52832 приводится не комбинация 0 и 1 значений этих битов, как это принято для AVR-8 и STM32F401, а десятичное число, которое выражает указанную комбинацию. В частности, для битов группы PULL столбец «Value» нижней таблицы из Рисунка 2 предусматривает для варианта «Pullup» запись числа 3 (т.е. комбинации 11) в указанные биты.

Всё это ставит вас, как и в случае с STM32F401, перед выбором порядка записи макроопределений для группы битов:
• либо дать макроопределение каждому биту (например, PULL_1, PULL_0) и устанавливать/сбрасывать его в коде с помощью макросов setBit/clearBit, соответственно:
setBit (GPIO + PIN_CNF_0), PULL_1
setBit (GPIO + PIN_CNF_0), PULL_0

• либо, как мы и поступили выше, прописать макроопределение младшего бита группы и записывать в него число из столбца «Value»:
LDR r0, =(GPIO + PIN_CNF_0)
LDR r1, =(3 ≪ PULL)
STR r1, [r0]


Назначение выводов микроконтроллера

В связи с тем, что число выводов микроконтроллера ограничено, каждый из них (за исключением специальных, вроде выводов питания для всех МК или антенны в случае с nRF52832) может иметь несколько назначений: дефолтное (т.е. — по умолчанию), определённое производителем, и альтернативные.
Нюансы настроек выводов при исполнении ими тех или иных функций будут рассмотрены в следующей части статьи, а пока выясним в общих чертах, каково их назначение (или распиновка) по умолчанию и как происходит его смена.

Назначение выводов AVR-8

Подробно о назначении выводов обоих МК можно узнать из Раздела «Pin Configurations» на странице 2 даташита.

В случае с AVR-8 дефолтные и альтернативные функции жёстко привязаны производителем к определённым выводам.
На Рисунке 3 представлена распиновка ATtiny85 и ATmega8 с указанием дефолтного и некоторых дополнительных (в скобках) назначений выводов.

Рисунок 3. Назначение выводов МК ATtiny85 и ATmega8.

Как видите:
1. Вывод 1 обоих МК по умолчанию исполняет функцию входа Reset для подключения активно-низкой кнопки сброса.
2. Дефолтная функция всех остальных не специальных выводов — пин порта ввода-вывода (PB — пин порта B, РС — пин порта С, PD — пин порта D) для обмена цифровыми данными (логические 1 и 0) с внешними устройствами.

В большинстве случаев переход вывода в альтернативное состояние автоматически происходит после подключения и настройки того или иного модуля периферии МК. К примеру, выводы 7 ATtiny85 и 24 ATmega8, являясь по умолчанию пинами PB2 порта В и РС1 порта С, соответственно, при включении канала ADC1 модуля АЦП автоматически становятся входом для измерения аналогового напряжения. Отключение канала ADC1 АЦП вновь возвращает указанным выводам функцию пина порта.

Из всего многообразия функций выводов рассматриваемых нами МК есть несколько, реализация которых потребует доступа к так называемым фьюз-байтам, подробнее о которых вы узнаете в следующей главе:
а) Функция XTAL, когда выводы 2/3 ATtiny85 и 9/10 ATmega8 должны работать как входы для подключения внешнего кварцевого резонатора.
б) Альтернативные функции выводов 1 обоих МК.

Назначение выводов STM43F401

Информация о дефолтных, альтернативных и дополнительных функциях выводов МК STM32F401 сведена в Таблицы 8 «Pin definitions» и 9 «Alternate functions mapping» на страницах 37 и 44 даташита, выдержки из которых для одного из выводов МК приведены на Рисунке 4.

Рисунок 4. Выдержка из Таблиц 8 и 9 даташита МК STM32F401.

Как следует из верхней таблицы Рисунка 4 вывод, который в зависимости от типа корпуса МК может иметь номер 13, E4, 17, 26 или L3, по сбросу/подаче питания становится пином РА3 порта А.

В качестве альтернативных для этого вывода могут быть следующие функции:
а) Линия RX модуля протокола UART.
б) Вход для внешнего сигнала тактирования таймеров 2, 5 и 9.
в) Выход для генерируемых FPU событий (EVENT).

В дополнение ко всему этому, указанный вывод может работать и как вход третьего канала аналого-цифрового преобразователя ADC1.

Так же, как и в случае с AVR-8, альтернативные и дополнительные функции жёстко привязаны к конкретным выводам STM32F401 и выбираются путём записи требуемых чисел в регистры MODER и один из двух AFR (AFRL или AFRH) модуля GPIO, (страница 158 Reference manual), представленных на Рисунке 5.

Рисунок 5. Регистры MODER и AFRL выбора назначения вывода МК STM32F401.

Как видите, основной выбор назначения осуществляется посредством записи того или иного числа в биты регистра MODER. Кроме того, в случае с альтернативной функцией необходимо через регистр AFRL выбрать требуемую, исходя из данных нижней таблицы на Рисунке 4.

Например, чтобы упомянутый выше 3-й пин порта A стал входом для измерения аналогового напряжения, необходимо записать в биты MODER3 (6 и 7) комбинацию 11.
Выбор альтернативного назначения для того же вывода обуславливается комбинацией 10 указанных битов регистра MODER. При этом, согласно Рисунка 4, для выбора функции входа тактирования таймера 2 (функция AF01) потребуется записать комбинацию 0001 в биты AFRL3 (12-15) регистра AFRL, а выбор функции линии RX протокола UART (функция AF07) осуществляется комбинацией 0111 тех же битов регистра AFRL.

Назначение выводов nRF52832

Таблица «Pin assignment» назначений выводов этого МК представлена в разделе с одноимённым названием на странице 13 Product specification.

За исключением выводов специального назначения (питание, антенна, внешнее программирование и др.), оставшиеся 32 вывода по умолчанию исполняют роль пинов P0.00P0.31 порта ввода-вывода. Помимо этого, большинство из указанных выводов могут быть использованы и для реализации альтернативных функций. К примеру, как видно из Рисунка 6, вывод 5 МК, работающий по умолчанию как пин Р0.03 порта ввода-вывода, автоматически переходит в режим аналогового входа AIN1 при включении модуля АЦП.

Рисунок 6. Выдержка из таблицы назначений выводов МК nRF52832.

Если функция входа АЦП жёстко привязана к определённым выводам (4—7 и 40—43), то в отношении других альтернативных функций nRF52832 отличается от других рассматриваемых нами МК очень полезной особенностью: выбор вывода для реализации такой функции (например линии протоколов UART и SPI) — произвольный и производится через регистр PSEL соответствующего модуля.

Тактирование микроконтроллера

Как вы помните из первой части статьи одним из важных узлов МК является генератор тактовых импульсов (ГТИ), частота которых определяет скорость работы процессора и модулей периферии.

В целом, системы тактирования рассматриваемых нами МК очень похожи:
1. Формируются тактовые импульсы базовой частоты FCPU, которые и подаются на процессор. В качестве элемента, определяющего FCPU могут выступать:
а) Внутренняя RC-цепочка.
б) Внешний кварцевый резонатор. Эта схема, по сравнению с предыдущей, обеспечивает менее зависимую от напряжения питания и температуры частоту тактовых импульсов, вследствие чего предпочтительна при разработке устройств, требующих высокой точности по времени.

2. Базовая частота путём деления уменьшается до частоты FPERIPHERAL тактирования периферии. Для одних МК такое деление происходит автоматически, а в случае с другими осуществляется посредством записи соответствующего числа в один из регистров микроконтроллера.

Зная частоту тактирования процессора вы можете рассчитать время исполнения той или иной инструкции (а следовательно — и всей программы), для чего необходимо:
а) Вычислить период одного тактового импульса, разделив единицу на FCPU.
б) Умножить полученное значение на количество тактов, которое требуется для исполнения той или иной инструкции и приведено в соответствующей графе Таблицы «Instructions Set Summary» (графа «Clocks» на странице 202/311 — для ATtiny85/ATmega8, соответственно, графа «Cycles» на странице 3-30 Technical Reference Manual — для Cortex M-4).

В отношении периферии так же полезно понимать, какова текущая частота её тактирования. К примеру, учитывая, что таймер увеличивает значение своего счётчика на 1 с каждым тактовым импульсом, несложно определить время, в течении которого он досчитает до заданного значения, умножив период тактирования таймера 1/FPERIPHERAL на указанное значение. Следовательно, мы можем с помощью таймера реализовать отсчёт временных интервалов с точностью до одного периода его тактирования.

Выясним нюансы настройки тактовой частоты для рассматриваемых нами МК.

Тактирование для AVR-8

Для ATtiny85 и ATmega8 подробности работы и настройки системы тактирования можно узнать из Раздела «System Clock and Clock Options» на странице даташита 23 и 25, соответственно.

Производитель МК предлагает несколько источников тактирования (как внутренних, так и внешних), полный перечень которых можно найти в пункте «Clock Sources» вышеуказанного раздела.
Выбор схемы тактирования процессора и значении частоты FCPU тактовых импульсов осуществляется через CKSEL биты младшего из упоминавшихся выше фьюз-байтов, обращение к которым производится извне с помощью программы загрузки. В нашем случае это — avrdude.exe.
Добавим в шаблонные Makefile ещё несколько строк, приведя их к следующему виду:

ATtiny85
all:
  avr-as main.S -mmcu=attiny85 -o main.o
  avr-ld main.o -T LinkerScript.ld -o main.elf
  avr-objcopy main.elf -j .text -j .data -O ihex main.hex
  avrdude -p attiny85 -c usbasp -P usb -e -U flash:w:main.hex:i

rf:
  avrdude -p attiny85 -c usbasp -P usb -U lfuse:r:lf.hex:h -U hfuse:r:hf.hex:h

wf:
  avrdude -p attiny85 -c usbasp -P usb -U lfuse:w:LF_VALUE:m -U hfuse:w:HF_VALUE:m

ATmega8
all:
  avr-as main.S -mmcu=atmega8 -o main.o
  avr-ld main.o -T LinkerScript.ld -o main.elf
  avr-objcopy main.elf -j .text -j .data -O ihex main.hex
  avrdude -p atmega8 -c usbasp -P usb -e -U flash:w:main.hex:i

rf:
  avrdude -p atmega8 -c usbasp -P usb -U lfuse:r:lf.hex:h -U hfuse:r:hf.hex:h

wf:
  avrdude -p atmega8 -c usbasp -P usb -U lfuse:w:LF_VALUE:m -U hfuse:w:HF_VALUE:m

Теперь ход работы make определяет одна из трёх комбинаций клавиш, набранных в консоли:
1. make/Enter будет по-прежнему запускать компиляцию программы и загрузку её в МК.

2. make rf/Enter создаст в папке проекта файлы lfuse.hex и hfuse.hex и запишет в них текущие значения младшего и старшего фьюз-байтов, соответственно, в шестнадцатеричном формате.

3. make wf/Enter запишет в младший и старший фьюз-байты значения LF_VALUE и HF_VALUE, соответственно.

Запустим посредством комбинации make rf/Enter чтение фьюз-байтов и получим дефолтные значения 0×62/0xdf и 0xe1/0xd9 младшего/старшего байтов для ATtiny85 и ATmega8, соответственно. Чтобы понять смысл этих чисел, можно пройти к Разделу «Memory Programming» даташита (страница 147 — для ATtiny85, страница 215 — для ATmega8) и разобраться в назначении каждого бита указанных байтов. Однако, гораздо проще и быстрее открыть калькулятор фьюзов, выбрать свой МК, ввести вышеприведённые числа в соответствующие окошки внизу и нажать кнопу «Apply values», получив в результате следующее:

Рисунок 7. Расшифровка дефолтных значений фьюз-байтов МК ATtiny85 и ATmega8.

Как видите:
1. В качестве ГТИ по умолчанию для обоих МК производителем выбран внутренний источник тактирования. Среди прочего, это означает, что выводы 3 ATtiny85 и 10 ATmega8 в качестве входов для подключения кварцевого резонатора не требуются и могут исполнять свою дефолтную или другие дополнительные функции.

2. В случае с ATmega8 частота FCPU тактирования процессора составляет 1МГц, а с ATtiny85 — 8МГц, но с делением на 8 через бит CKDIV8 младшего фьюз-байта. Следует отметить, что указанное деление для ATtiny85 является не преобразованием FCPU в  FPERIPHERAL, а именно уменьшением значения самой частоты тактирования процессора. Возможность такого деления — особенность ATtiny85, которая может быть реализована не только через бит CKDIV8 фьюз-байта, но и программным путём через регистр CLKPR.

3. Галочка напротив бита RSTDISBL старшего фьюз-байта отсутствует, поэтому вывод 1 обоих МК будет работать в режиме входа Reset.

Чтобы изменить тип источника и частоту тактирования, необходимо выбрать их в калькуляторе фьюзов и появившимися в окошках снизу числами заменить LF_VALUE и HF_VALUE в Makefile, а затем набрать в консоли комбинацию make wf/Enter. Предположим, мы выбрали для обоих МК вариант тактирования с внешним кварцевым резонатором и частотой 8 МГц. Тогда, искомыми значениями младшего/старшего фьюз-байтов будут 0xcf/0xdf и 0xff/0xd9 для ATtiny85 и ATmega8, соответственно:

Рисунок 8. Выбор источника тактирования для МК ATtiny85 и ATmega8.

Обратите внимание, что для Attiny85 галочка напротив бита CKDIV8 снята, чтобы исключить деление FCPU на 8.

Поскольку в качестве источника тактирования выбрана схема с подключением внешнего кварцевого резонатора, то выводы 3 ATtiny85 и 10 ATmega8 теперь могут функционировать только как входы для подключения последнего. Вернуть им способность исполнять другие функции можно, вновь перейдя к внутреннему источнику тактирования.

Если выставить галочку напротив бита RSTDISBL, то после записи итоговых значений фьюз-байтов в МК вывод 1 обоих микроконтроллеров перестанет быть входом Reset и сможет выполнять свои дополнительные функции (пин порта и другие). Однако, не торопитесь это делать, если в вашем распоряжении нет высоковольтного программатора. Дело в том, что низковольтные программаторы, к коим относится и используемый нами USBasp, для общения с МК используют функцию сброса, отсутствие которой превратит микроконтроллер для USBasp в «кирпич», реанимировать который можно только с помощью упомянутого высоковольтного программатора. Поэтому, выставлять галочку напротив бита RSTDISBL следует только, если ваша программа, использующая дополнительные функции вывода 1, полностью отлажена и готова к окончательной загрузке в МК.

В следующей части статьи, на примере отдельных модулей МК, будет показано, как именно нужно делить FCPU для получения требуемого значения FPERIPHERAL.

Тактирование для STM32F401

Система тактирования данного МК подробно описана в Разделе 6 «Reset and clock control (RCC)» на странице 91 Reference Manual и наглядно отображена на Рисунке 12 «Clock tree» из того же раздела.

В распоряжение программиста предоставлены три ГТИ:
1. HSI (High Speed Internal) с частотой FCPU 16 Мгц на базе внутреннего осциллятора.

2. HSE (High Speed External) от внешнего кварцевого осциллятора с частотой FCPU от 4 до 26 МГц.

3. Разогнанный до 84 МГц посредством ФАПЧ HSE под именем PLLCLK.

Выбор и настройка источников тактирования процессора и периферии осуществляется через регистры модуля RCC, для которого согласно упомянутой выше Таблицы 1 Раздела «Memory and bus architecture» Reference manual выделен сектор памяти данных с адресами 0×40023800 — 0×40023BFF.

Для выбора того или иного ГТИ необходимо записать соответствующее число в биты SW регистра RCC_CFGR, представленного на Рисунке 9.

Рисунок 9. Биты регистра RCC_CFGR МК STM32F401.

Как видите, дефолтное значение регистра RCC_CFGR — нулевое, т. е. источником тактирования по умолчанию производитель определил внутренний HSI.

Здесь же, в RCC_CFGR, частоту FCPU можно разделить, записав требуемые числа в биты HPRE и PPRE, и уже после этого направить тактовые импульсы пониженной частоты FPERIPHERAL на шину тактирования модулей периферии. В свою очередь, для некоторых из модулей периферии частоту FPERIPHERAL можно дополнительно уменьшить делением. По умолчанию значение всех делителей равно 1, то есть FPERIPHERAL = FCPU = 16 МГц.

После того, как источник тактирования выбран, следует ещё и включить его, записав 1 в соответствующий бит регистра RCC_CR, изображённого на Рисунке 10.

Рисунок 10. Биты регистра RCC_CR МК STM32F401.

Обратите внимание, что дефолтное значение регистра содержит 1 в младшем бите HSION, следовательно внутренний источник тактирования HSI не только выбирается по сбросу/подаче питания, но и автоматически включается.

В следующей части мы рассмотрим вопрос настройки периферии посредством записи требуемых чисел в тот или иной регистр. Однако, ни один из этих регистров не будет доступен для записи до тех пор, пока вы не включите тактирование самого модуля периферии. Осуществляется такое включение записью 1 в соответствующий бит одного из регистров всё того же модуля RCC.
В частности, за включение тактирования портов ввода-вывода AE и H ответственны биты 0–4 и 7, соответственно, регистра RCC_AHB1ENR, как показано на Рисунке 11.

Рисунок 11. Биты регистра RCC_AHB1ENR МК STM32F401.

Тактирование таймеров общего назначения TIM2TIM5 обеспечивается, как видно из Рисунка 12, записью 1 в биты 0-3, соответственно, регистра RCC_APB1ENR.

Рисунок 12. Биты регистра RCC_APB1ENR МК STM32F401.

Включение же тактирования модуля аналого-цифрового преобразователя ADC1 обуславливается, как следует из Рисунка 13, битом 8 регистра RCC_APB2ENR.

Рисунок 13. Биты регистра RCC_APB2ENR МК STM32F401.

Уверен, вы догадались, что по умолчанию тактирование всех упомянутых модулей периферии отключено, поскольку дефолтные значения ответственных регистров равны нулю.

Внесём в шаблонный файл stm32f401.h макроопределения адресов и номеров битов вышеупомянутых регистров модуля RCC:
/* Адреса и номера битов регистров модуля RCC */
RCC = 0×40023800
  AHB1ENR = 0×30
    GPIOHEN = 7
    GPIOEEN = 4
    GPIODEN = 3
    GPIOCEN = 2
    GPIOBEN = 1
    GPIOAEN = 0
  APB1ENR = 0×40
    TIM5EN = 3
    TIM4EN = 2
    TIM3EN = 1
    TIM2EN = 0
  APB2ENR = 0×44
    ADC1EN = 8

и оформим код включения тактирования отдельных модулей периферии:
.include «stm32f401.h»
.include «macro.h»
  .text
    .org 0
      stackPointerInit
    .org Reset_vector
      .word main + 1

    .global main
    main:
      /* Включить тактирование порта ввода-вывода В */
      setBit (RCC + AHB1ENR), GPIOBEN
      /* Включить тактирование таймера TIM5 */
      setBit (RCC + APB1ENR), TIM5EN
      /* Включить тактирование АЦП */
      setBit (RCC + APB2ENR), ADC1EN
    main_loop:
      B main_loop
.end


Тактирование для nRF52832

Вся информация о тактировании этого МК представлена в Разделе «Clock — clock control» на странице 101 Product Specification.

В nRF52832 доступными для выбора программиста являются следующие источники тактирования процессора:
1. Внутренний осциллятор с частотой FCPU 64 МГц, являющийся ГТИ по умолчанию.

2. Внешний кварцевый резонатор с частотой 32 МГц, которая автоматически разгоняется до FCPU 64 Мгц.

Тактовый сигнал ГТИ с частотой FCPU поступает на контроллер HFCLCK, откуда перенаправляется на процессор. Кроме того, контроллер HFCLCK преобразует входящий сигнал в тактовые импульсы c тремя разными частотами FPERIPHERAL (1, 16 и 32 МГц), которые автоматически распределяет между модулями периферии. В частности, частота тактирования таймера составляет 16 МГц и при этом может дополнительно делиться уже через один из регистров самого таймера, о чём — в следующей части статьи.

Значения базового адреса модуля тактирования CLOCK и смещений некоторых задач и событий представлены на Рисунке 14.

Рисунок 14. Адреса блока тактирования МК nRF52832.


Для выбора, например, схемы тактирования от внешнего источника необходимо запустить задачу TASK_HFCLKSTART посредством записи 1 по соответствующему адресу, а затем дождаться события EVENTS_HFCLKSTARTED, когда регистр со смещением 0×100 принимает значение 1, после чего необходимо обнулить указанный регистр.
Чтобы вновь вернуть внутренний осциллятор на роль ГТИ, потребуется запустить задачу TASK_HFCLKSTOP, записав 1 по адресу этой задачи.

Макроопределения вышеприведённых задач и событий, а также код запуска/выключения внешнего ГТИ будут выглядеть так:
nrf52832.h
/* Адреса регистров модуля CLOCK */
CLOCK = 0×40000000
  TASKS_HFCLKSTART = 0×000
  TASKS_HFCLKSTOP = 0×004
  EVENTS_HFCLKSTARTED = 0×100

main.S
.include «nRF52832.h»
.include «macro.h»
  .text
    .org 0
      stackPointerInit
    .org Reset_vector
      .word main + 1

    .global main
    main:
      /* Включить внешний ГТИ */
      LDR r0, =(CLOCK + TASKS_HFCLKSTART)
      LDR r1, =1
      STR r1, [r0]
     /* Дождаться события EVENTS_HFCLKSTARTED */
     wait:
      LDR r0, =(CLOCK + EVENTS_HFCLKSTARTED)
      LDR r1, [r0]
      CMP r1, 0
      BEQ wait
      /* Обнулить регистр события EVENTS_HFCLKSTARTED */
      LDR r1, =0
      STR r1, [r0]

      /* Выключить внешний ГТИ */
      LDR r0, =(CLOCK + TASKS_HFCLKSTOP)
      LDR r1, =1
      STR r1, [r0]
    main_loop:
      B main_loop
.end


Прерывания микроконтроллера

Важным свойством микроконтроллера является способность его модулей генерировать прерывания по наступлению определённых событий.

В частности, это может быть прерывание:
а) свидетельствующее, что определённый пин порта ввода-вывода перешёл из одного логического состояния в другое (из 0 в 1 или наоборот). Такое прерывание обычно называют внешним, поскольку инициируется оно внешними по отношению к МК устройствами (кнопкой, цифровым датчиком и пр.).

б) генерируемое АЦП по завершению измерения. Как и следующее, это прерывание является внутренним в связи с тем, что его источник — внутренний модуль МК.

в) исходящее от таймера по достижении его счётчиком заданного значения.

Чтобы понять пользу от применения прерываний, представим устройство на базе микроконтроллера, которое должно:
• Включать/выключать красный светодиод в зависимости от состояния кнопки, подключённой к одному из выводов МК.
• Включать/выключать синий светодиод в зависимости от положения ручки потенциометра, подключённого к другому выводу МК.
• Обрабатывать массив каких-то данных.

Алгоритм основного цикла программы такого устройства без использования прерываний будет выглядеть следующим образом:
main_loop:
   1. Опросить вывод кнопки.
   2. Если состояние вывода — 1, включить красный светодиод.
   3. Если состояние вывода — 0, включить красный светодиод.
   4. Запустить измерение уровня аналогового напряжения на выводе потенциометра.
   5. Дождаться, пока АЦП завершит измерение.
   6. Сравнить полученное значение напряжения с пороговым.
   7. Если значение напряжения ниже порогового, включить синий светодиод.
   8. Если значение напряжения выше порогового, выключить синий светодиод.
   9. Обработать массив данных.
  10. Вернуться к метке main_loop.


Подобная структура программы несёт в себе ряд недостатков:
1. Включение/выключение красного светодиода происходит без учёта его текущего состояния, следовательно будут иметь случаи включения уже включенного и выключения выключенного светодиода, т. е. бесполезной траты времени процессора.

2. Измерение аналогового напряжения не происходит мгновенно, следовательно на ожидание его завершения процессор так же непродуктивно будет расходовать какое-то конечное время.

3. Вследствие того, что все этапы процесса исполняются последовательно, неизбежна задержка реакции устройства на нажатие кнопки и поворот ручки потенциометра, величина которой будет зависеть от размера массива: чем дольше происходит его обработка, тем реже за единицу времени опрашиваются кнопка и потенциометр. При достижении же массивом критического размера процессор, будучи занятым его обработкой, может вообще пропустить факт нажатия кнопки.

Внесём в программу следующие дополнения:
а) Настройку внешнего прерывания от изменения состояния вывода, к которому подключена кнопки. Алгоритм кода обработчика этого прерывания будет следующим:
• Если произошёл переход состояния вывода с 0 на 1, включить красный светодиод.
• Если произошёл переход состояния вывода с 1 на 0, выключить красный светодиод.

б) Настройку таймера с тем, чтобы он выдавал прерывания с заданным периодом, к примеру, каждые 20 мс. В обработчике прерывания таймера пропишем запуск измерения АЦП.

в) Настройку АЦП на генерацию прерывания по завершению измерения, в обработчик которого перенесём действия устройства по включению/выключению синего светодиода.

Тогда, структура программы примет следующий вид:
вектор внешнего прерывания от кнопки
  Перейти к обработчику внешнего прерывания
вектор прерывания таймера
  Перейти к обработчику прерывания таймера
вектор прерывания АЦП
  Перейти к обработчику прерывания АЦП

main:
  1. Настроить внешнее прерывание.
  2. Настроить таймер на прерывание через каждые 20 мс.
  3. Настроить АЦП на прерывание по завершению измерения.
main_loop:
  1. Обработать массив данных.
  2. Вернуться к метке main_loop.

обработчик внешнего прерывания:
  1. Если переход на выводе кнопки — с 1 на 0, включить красный светодиод.
  2. Если переход на выводе кнопки — с 0 на 1, выключить красный светодиод.
  3. Вернуться из обработчика прерывания в основную программу.

обработчик прерывания таймера:
  1. Запустить измерение АЦП.
  2. Вернуться из обработчика прерывания в основную программу.

обработчик прерывания АЦП:
  1. Сравнить полученное значение напряжения с пороговым.
  2. Если значение напряжения ниже порогового, включить синий светодиод.
  3. Если значение напряжения выше порогового, выключить синий светодиод.
  4. Вернуться из обработчика прерывания в основную программу.


Как вы помните из первой части статьи, при возникновении прерывания ЦПУ приостанавливает исполнение основной программы и отправляется по адресу памяти программ, где располагается соответствующий вектор прерывания, откуда перенаправляется к обработчику прерывания. Выполнив инструкции последнего, процессор возвращается к тому месту основной программы, где его застигло прерывание.

Учитывая изложенное, прикинем выигрыш от нововведений в программу:
1. Переход к обработчику внешнего прерывания происходит сразу по факту нажатия/отпускания кнопки, следовательно исключается риск пропуска этих событий, а величина задержки включения/выключения красного светодиода сводится к нескольким тактовым периодам, которые требуются процессору для перехода от основной программы к обработчику. Кроме того, применение прерывания избавляет нас от холостых включений/выключений красного светодиода.

2. Поскольку АЦП тактируется автономно, измерение аналогового напряжения происходит параллельно с обработкой массива процессором, не занимая попусту время последнего на ожидание завершения измерения.

3. Если время обработки массива не ограничено какими-либо рамками, т. е. позволяет прерываться на запуск измерений АЦП, можно уменьшить период счёта таймера, увеличив тем самым частоту измерений уровня аналогового напряжения, что, в свою очередь, уменьшит задержку реакции устройства на поворот ручки потенциометра.

Важно помнить, что, пока ЦПУ исполняет инструкции обработчика прерывания, все процессы, прописанные в основной программе приостанавливаются. Поэтому, при оформлении обработчика следует ограничиваться минимально возможным объёмом кода. Тем не менее, бывают ситуации, когда увеличение размера обработчика прерывания неизбежно. В частности, реализация приёма байта в программном варианте протокола UART, которым мы пользовались в предыдущих частях статьи, вряд ли возможна без применения внешних прерываний. При этом, обработчик должен содержать полный код приёма одного байта, иначе вы рискуете получить искажённые данные.

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

В следующей части статьи мы подробно поговорим о работе наиболее часто используемых на начальных этапах обучения модулей микроконтроллера — порта ввода-вывода, таймера и АЦП, а пока остановимся лишь на вопросах настройки и оформления в коде прерываний указанных модулей для рассматриваемых нами МК.

Настройка и оформление прерываний для AVR-8

Полная информация о прерываниях изложена в Разделах «Interrupts» и «External interrupts» даташита (страница 48 — для ATtiny85 и 46, 66 — для ATmega8).

Выберем к рассмотрению одинаковые для обоих МК прерывания:
а) Внешнее прерывание от состояния вывода INT0 (7 и 4 для ATtiny85 и ATmega8, соответственно). Следует отметить, что все внешние прерывания AVR-8 генерируются не зависимо от того, настроен соответствующий вывод как вход или выход.
б) Прерывания таймера TIMER0 по переполнению счётчика.
в) Прерывание АЦП по завершению измерения.

Начать следует с того, что глобальное разрешение — единое для всех прерываний AVR-8, включая и вышеперечисленные. Реализуется оно установкой в 1 бита I спец-регистра SREG посредством применения инструкции SEI, которую принято прописывать перед самым входом в цикл main_loop основной функции.
В случае, если по каким-то соображениям необходимо глобально приостановить механизм прерываний, используется инструкция CLI.

Локальные разрешение или запрет рассматриваемых нами прерываний обеспечиваются записью 1 или 0, соответственно, в:
а) одноимённый бит INT0 регистров GIMSK (ATtiny85) и GICR (ATmega8) в случае с прерыванием INT0,
б) бит TOIE0 регистра TIMSK в случае с прерыванием таймера TIMER0 по переполнению счётчика,
в) бит ADIE регистра ADCSRA в случае с прерыванием АЦП.

Для некоторых прерываний предусмотрены дополнительные настройки.
К примеру, комбинация битов ISC01 и ISC00 регистра MCUCR определяет условие генерации внешнего прерывания:

Рисунок 15. Условия генерации внешнего прерывания от вывода INT0.

Вместе с генерацией каждого из рассматриваемых нами прерываний устанавливается в 1 соответствующий бит одного из регистров:
а) бит INTF0 регистра GIFR в случае внешнего прерывания,
а) бит TOV0 регистра TIFR для прерывания таймера TIMER0 по переполнению счётчика,
в) бит ADIF регистра ADCSRA по прерыванию АЦП.

Все указанные биты сбрасываются автоматически при входе в обработчик соответствующего прерывания либо вручную посредством записи в них единицы.

Перейдём к оформлению прерываний в коде.
Добавим в шаблонные хидер-файлы макроопределения:
• векторов рассматриваемых нами прерываний в соответствии с Таблицей «Reset and Interrupt Vectors» даташита,
• абсолютных адресов регистров и номеров их битов, ответственных за разрешение и настройку прерываний, ориентируясь на Таблицу «Register Summary» даташита.

attiny85.h
/* Таблица векторов прерываний */
Reset_vector = 0×00
INT0_vector = 0×01
TIMER0_OVF_vector = 0×05
ADC_vector = 0×08
/* Адреса и номера битов регистров внешних прерываний */
MCUCR = 0×55
  ISC01 = 1
  ISC00 = 0
GIMSK = 0×5B
  INT0 = 6
/* Адреса и номера битов регистров таймера TIMER0 */
TIMSK = 0×59
  TOIE0 = 1
/* Адреса и номера битов регистров АЦП */
ADCSRA = 0×26
  ADIE = 3

atmega8.h
/* Таблица векторов прерываний */
Reset_vector = 0×00
INT0_vector = 0×01
TIMER0_OVF_vector = 0×09
ADC_vector = 0×0E
/* Адреса и номера битов регистров внешних прерываний */
MCUCR = 0×55
  ISC01 = 1
  ISC00 = 0
GICR = 0×5B
  INT0 = 6
/* Адреса и номера битов регистров таймера TIMER0 */
TIMSK = 0×59
  TOIE0 = 0
/* Адреса и номера битов регистров АЦП */
ADCSRA = 0×26
  ADIE = 3

Далее, по аналогии с вектором сброса, необходимо посредством директивы .org разместить в нужном месте памяти программ инструкцию перехода RJMP с именем обработчика прерывания в качестве аргумента.

И здесь в случае с AVR-8 есть один важный нюанс.
В вышеупомянутой таблице векторов прерываний даташита указаны адреса регистров памяти программ, т. е. двух-байтных слов, в то время как директива .org в GCC оперирует адресами байтов и в качестве аргумента ей требуется значение адреса младшего байта вектора прерывания. Взаимосвязь между двумя вариантами адресации продемонстрирована на Рисунке 16.
Ассемблер для микроконтроллера с нуля. Часть 4. Система адресации памяти, назначение выводов, тактирование и прерывания МК
Рисунок 16. Взаимосвязь между адресами байта и двух-байтного слова.

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

С учётом изложенного, в качестве аргумента директивы .org следует использовать удвоенное значение макроопределения вектора прерывания. При оформлении вектора сброса мы не использовали умножение, поскольку значение его адреса — 0×00 и указанная операция не имеет смысла.
Прописываются директива .org и инструкция RJMP в main.S после таковых для вектора сброса в последовательности, с которой вектора следуют в Таблице «Reset and Interrupt Vectors».

Обработчик прерывания размещается ниже основной функции или в отдельном ассемблер-файле, вместе с функциями настройки и работы модуля, генерирующего данное прерывание.
К оформлению обработчика предъявляются те же требования, что и для обычной функции, за одним исключением: возврат из него осуществляется посредством инструкции RETI, а не RET.
Название обработчика — произвольно, однако, придерживаясь общепринятых негласных правил, будем комбинировать его из имени вектора и слова «Handler»: INT0_Handler, ADC_Handler и т. п.

Чтобы не дублировать примеры кода, применим по отношению к регистрам GIMSK и GICR двойное макроопределение (когда одно макроопределение выступает аргументом второго), прописав его в самом верху main.S.
Тогда, настройка и разрешение рассматриваемых нами прерываний, а также их обработчики, оформленные в main.S, будут выглядеть следующим образом:
.include «attiny85.h» /* или «atmega8.h» */
.include «macro.h»

INT0_REG = GIMSK /* GICR — для ATmega8 */
INT0_FLAG = 0
TIMER0_OVF_FLAG = 1
ADC_FLAG = 2

  .data
    flags: .byte 0

  .text
    .org Reset_vector
      RJMP main
    .org INT0_vector * 2
      RJMP INT0_Handler
    .org TIMER0_OVF_vector * 2
      RJMP TIMER0_OVF_Handler
    .org ADC_vector * 2
      RJMP ADC_Handler

    .global main
    main:
      /* Настроить внешнее прерывание на любое
         изменение состояния вывода INT0
         и разрешить его локально */
      setBit MCUCR, ISC00
      setBit INT0_REG, INT0
      /* Разрешить прерывание таймера TIMER0
         по переполнению счётчика локально */
      setBit TIMSK, TOIE0
      /* Разрешить прерывание АЦП локально */
      setBit ADCSRA, ADIE
      /* Разрешить прерывания глобально */
      SEI
    main_loop:
      /* Проанализировать биты переменной flags
         и совершить соответствующие действия */
      RJMP main_loop

    INT0_Handler:
      setBit flags, INT0_FLAG
      RETI

    TIMER0_OVF_Handler:
      setBit flags, TIMER0_OVF_FLAG
      RETI

    ADC_Handler:
      setBit flags, ADC_FLAG
      RETI
.end

Как видите, в примере представлен вариант, когда в обработчиках прерываний лишь устанавливается в 1 соответствующий бит переменной flags, объявленной в секции данных, а уже в цикле main_loop состояние указанных битов должно анализироваться с принятием соответствующего решения по каждому случаю.

Настройка и оформление прерываний для Cortex M-4

Прежде, чем перейти к особенностям каждого МК в вопросах настройки и оформления прерываний, обсудим общие для них моменты.

Глобальный контроль за прерываниями для STM32F401 и nRF52832 осуществляется через модуль ядра, именуемый Nested vectored interrupt controller (NVIC), информацию о котором вы найдёте в разделе с одноимённым названием на странице 6-60 Cortex-M4 Technical Reference Manual.

В отличии от AVR-8, глобальные разрешение и запрет в Cortex M-4 осуществляются не оптом, а персонально для каждого отдельного прерывания или, в крайнем случае, группы однотипных прерываний (например, внешние прерывания nRF52832) через соответствующий бит 32-битных регистров ISER и IСER, соответственно, контроллера NVIC, коих — по восемь каждых:
ISER0 — ISER7 с адресами от 0xE000E100 по 0xE000E11C,
IСER0 — IСER7 с адресами от 0xE000E180 по 0xE000E19C.

Теоретически, такое количество регистров позволяет контролировать 32×8 = 256 прерываний, хотя в рассматриваемых нами МК их намного меньше.

Для STM32F401 номера битов указанных регистров связаны со значением числа из столбца «Position» Таблицы 38 «Vector table» на странице 202 Reference manual, выдержка из которой приведена на Рисунке 17.

Рисунок 17. Выдержка из таблицы прерываний МК STM32F401.

Например, глобальные разрешение/запрет внешнего прерывания EXTI0 порта ввода-вывода и прерывание АЦП реализуются записью единицы в биты 6 и 18 регистров ISER0/IСER0, соответственно.
Число 50 превышает количество битов в регистрах ISER0/IСER0, поэтому прерывание таймера TIM5 глобально контролируется битом 50 — 32 = 18 регистров ISER1/IСER1.

Для nRF52832 подобная связь существует между битами регистров ISER/IСER контроллера NVIC и идентификационным номером ID модуля периферии. Полный перечень указанных номеров представлен в Таблице 11 «Instantiation table» на странице 24 Product specification, выдержку их которой вы можете наблюдать на Рисунке 18.

Рисунок 18. Выдержка из Таблицы «Instantiation table» Product specification МК nRF52832.

Как видите, значения ID всех трёх модулей — порта ввода-вывода, АЦП и таймера TIMER2 — меньше 32, поэтому контроль за их прерываниями осуществляется через биты регистров ISER0/IСER0, а именно 6-й, 7-й и 10-й.

Номер позиции прерывания в STM32F401 и ID в nRF52832 можно использовать для вычисления адреса вектора прерывания, что особенно актуально в случае с МК nRF52832, в документации которого эти данные по какой-то причине отсутствуют.
Дело в том, ARM зарезервировала под прерывания и исключения ядра шестнадцать младших адресов пространства векторов. Учитывая, что длина адресов в Cortex M-4 составляет 4 байта, адреса остальных векторов прерываний рассчитываются по формуле:
(16 + n) x 4
где, n — номер позиции прерывания и ID для STM32F401 и nRF52832, соответственно.

Например, адрес вектора прерывания EXTI0 STM32F401 равен (16 + 6) х 4 = 88 или 0×58 в шестнадцатеричной системе счисления, что подтверждается столбцом «Address» таблицы из Рисунка 17.

Адреса же прерываний nRF52832 из Рисунка 18 согласно формуле выше составляют:
• 0×58 — для внешних прерываний от GPIO,
• 0×5C — для прерывания АЦП,
• 0×68 — для прерываний таймера TIMER2.

Генерация любого прерывания в обоих МК сопровождается установкой в 1 определённого бита одно из регистров. Сброс указанного бита осуществляется не автоматически, как в случае с AVR-8, а вручную, посредством записи единицы или нуля (в зависимости от регистра и МК) в тот же бит при входе в обработчик прерывания. Делать это надо обязательно, иначе ЦПУ зациклится между вектором и обработчиком прерывания.

Оформление векторов прерываний для обоих МК производится точно так же, как мы это делали во второй части статьи для вектора сброса: посредством директив .org с адресом вектора прерывания в качестве аргумента и .word с аргументом в виде имени обработчика прерывания и приплюсованной к нему единицы.
Размещаться вектора должны в main.S вслед за вектором сброса в том же порядке, в котором следуют позиции или ID на Рисунке 17 или 18, соответственно.

Требования к оформлению обработчика прерывания полностью совпадают с описанными для обычной функции в предыдущей части статьи. Как и в случае с AVR-8, в названии обработчика будем использовать комбинацию из имени соответствующего вектора и слова «Handler»: EXTI0_Handler, TIMER2_Handler и т. д.

Добавим в шаблонные хидер-файлы макроопределения вышеупомянутых регистров модуля NVIC и их битов, а также адреса векторов представленных на Рисунках 17, 18 прерываний:

stm32f401.h
/* Таблица векторов прерываний */
Reset_vector = 0×00000004
EXTI0_vector = 0×00000058
ADC1_vector = 0×00000088
TIM5_vector = 0×00000108
/* Адреса и номера битов модуля NVIC */
ISER0 = 0xE000E100
ICER0 = 0xE000E180
  EXTI0_INTERRUPT_BIT = 6
  ADC1_INTERRUPT_BIT = 18
ISER1 = 0xE000E104
ICER1 = 0xE000E184
  TIM5_INTERRUPT_BIT = 18

nrf52832.h
/* Таблица векторов прерываний */
Reset_vector = 0×00000004
GPIOTE_vector = 0×00000058
SAADC_vector = 0×0000005C
TIMER2_vector = 0×00000068
/* Адреса и номера битов модуля NVIC */
ISER0 = 0xE000E100
ICER0 = 0xE000E180
  GPIOTE_INTERRUPT_BIT = 6
  SAADC_INTERRUPT_BIT = 7
  TIMER2_INTERRUPT_BIT = 10

Тогда, вектора прерываний с пустыми обработчиками и их глобальное разрешение/запрет, оформленные в main.S, будут выглядеть следующим образом:

STM2F401
.include «stm32f401.h»
.include «macro.h»
  .text
    .org 0
      /* Указать на вершину стека */
      .word RAMEND
    .org Reset_vector
      .word main + 1
    .org EXTI0_vector
      .word EXTI0_Handler + 1
    .org ADC1_vector
      .word ADC1_Handler + 1
    .org TIM5_vector
      .word TIM5_Handler + 1

    .global main
    main:
      /* Разрешить прерывания глобально */
      setBit ISER0, EXTI0_INTERRUPT_BIT
      setBit ISER0, ADC1_INTERRUPT_BIT
      setBit ISER1, TIM5_INTERRUPT_BIT
      /* Запретить прерывания глобально */
      setBit ICER0, EXTI0_INTERRUPT_BIT
      setBit ICER0, ADC1_INTERRUPT_BIT
      setBit ICER1, TIM5_INTERRUPT_BIT
    main_loop:
      B main_loop

    EXTI0_Handler:
      PUSH {LR}
      POP {PC}

    ADC1_Handler:
      PUSH {LR}
      POP {PC}

    TIM5_Handler:
      PUSH {LR}
      POP {PC}
.end

nRF52832
.include «nrf52832.h»
.include «macro.h»
  .text
    .org 0
      /* Указать на вершину стека */
      .word RAMEND
    .org Reset_vector
      .word main + 1
    .org GPIOTE_vector
      .word GPIOTE_Handler + 1
    .org SAADC_vector
      .word SAADC_Handler + 1
    .org TIMER2_vector
      .word TIMER2_Handler + 1

    .global main
    main:
      /* Разрешить прерывания глобально */
      setBit ISER0, GPIOTE_INTERRUPT_BIT
      setBit ISER0, SAADC_INTERRUPT_BIT
      setBit ISER0, TIMER2_INTERRUPT_BIT
      /* Запретить прерывания глобально */
      setBit ICER0, GPIOTE_INTERRUPT_BIT
      setBit ICER0, SAADC_INTERRUPT_BIT
      setBit ICER0, TIMER2_INTERRUPT_BIT
    main_loop:
      B main_loop

    GPIOTE_Handler:
      PUSH {LR}
      POP {PC}

    SAADC_Handler:
      PUSH {LR}
      POP {PC}

    TIMER2_Handler:
      PUSH {LR}
      POP {PC}
.end

В отличие от AVR-8, для работы внешних прерываний STM32F401 и nRF52832 необходимо настроить соответствующий пин порта ввода-вывода как вход. Однако, поскольку по умолчанию все не специальные выводы обоих МК и без того являются входами, в нижеследующих примерах код соответствующей настройки будет опущен тем более, что вопросы конфигурирования портов ввода-вывода будут подробно рассмотрены в следующей части статьи.

Внешние прерывания STM32F401

Для всех шести портов ввода-вывода этого МК, предусмотрены внешние прерывания EXTI0EXTI15 от пинов 0–15, соответственно, в настройке которых принимают участие два модуля:

1. Модуль External interrupt/event controller (EXTI), подробно описанный в Разделе «Interrupts and events» на странице 202 Reference manual.
Младший адрес выделенного под этот модуль сектора памяти данных — 0×40013C00.
Для доступа к регистрам модуля EXTI включение тактирования через модуль RCC не требуется.

2. Модуль System configuration controller (SYSCFG) с младшим адресом 0×40013800 в памяти данных, информацию о котором вы найдёте в разделе с одноимённым названием на странице 140 Reference manual.
Запись в регистры SYSCFG невозможна без предварительного включения его тактирования посредством четырнадцатого бита SYSCFGEN ранее упоминавшегося регистра APB2ENR модуля RCC.

Выдержка из таблиц расшифровки назначений битов для регистров указанных модулей приведена на Рисунке 19.

Рисунок 19. Регистры, ответственные за работу внешних прерываний МК STM2F401.

Обратите внимание, что под названием каждого регистра указано смещение его адреса относительно нижней границы сектора памяти данных, выделенного под данный модуль согласно Таблицы 1 «STM32F401x register boundary addresses».

Как следует из Рисунка 19, для настройки генерации внешнего прерывания EXTI0 от ниспадающего фронта сигнала на пине PB0 порта B необходимо как минимум:

а) Включить тактирование модуля SYSCFG и записать комбинацию 0001 в биты 0—3 его регистра EXTICR1, выбрав тем самым пин PB0 как вход для прерывания.

б) Выбрать в качестве триггера прерывания ниспадающий фронт сигнала, установив в 1 бит TR0 регистра EXTI_FTSR модуля EXTI. Следует отметить, что в число регистров этого модуля входит также и EXTI_RTSR, посредством которого можно выбрать в качестве триггера прерывания нарастающий фронт сигнала на пине МК.

в) Записать 1 в бит MR0 регистра EXTI_IMR модуля EXTI, сбросив тем самым маску прерывания и, по сути, разрешив его локально.

Далее необходимо разрешить прерывание глобально так, как было показано выше.

С генерацией прерывания EXTI0 устанавливается в 1 бит PR0 регистра EXTI_PR модуля EXTI, который, как вы помните, должен быть сброшен в обработчике прерывания записью в него единицы.

Добавим к уже имеющимся макроопределения впервые упомянутых регистров и их битов в stm32f401.h:
/* Адреса и номера битов регистров модуля RCC */
APB2ENR = 0×44
  SYSCFGEN = 14
/* Адреса и номера битов регистров модуля SYSCFG */
SYSCFG = 0×40013800
  EXTICR1 = 0×08
    EXTI0_0 = 0
/* Адреса и номера битов регистров модуля EXTI */
EXTI = 0×40013C00
  EXTI_IMR = 0×00
    MR0 = 0
  EXTI_FTSR = 0×0C
    TR0 = 0
  EXTI_PR = 0×14
    PR0 = 0

и с учётом вышеизложенного, оформим фрагменты настройки внешнего прерывания EXTI0 и сброса в обработчике прерывания бита PR0:
main:
  /* Включить тактирование модуля SYSCFG */
  setBit (RCC + APB2ENR), SYSCFGEN
  /* Выбрать пин РВ0 как вход внешнего прерывания */
  setBit (SYSCFG + EXTICR1), EXTI0_0
  /* Выбрать как триггер ниспадающий фронт сигнала */
  setBit (EXTI + EXTI_FTSR), TR0
  /* Сбросить маску прерывания */
  setBit (EXTI + EXTI_IMR), MR0
  /* Разрешить прерывание EXTI0 глобально */
  setBit ISER0, EXTI0_INTERRUPT_BIT
main_loop:
   B main_loop

  EXTI0_Handler:
    PUSH {LR}
    /* сбросить бит PR0 */
    setBit (EXTI + EXTI_PR), PR0
    POP {PC}
.end


Прерывания таймера STM32F401

В качестве примера рассмотрим настройку прерывания по достижению заданного значения счётчиком таймера общего назначения TIM5, описанного в Разделе «General-purpose timers (TIM2 to TIM5)» на странице 316 Reference manual.

Для модуля таймеров общего назначения TIM2TIM5 в памяти данных STM2F401 выделены сектора, граничные значения адресов которых вы можете увидеть в верхней части Рисунка 20.

Рисунок 20. Выдержки из таблиц «STM32F401x register boundary addresses» и «Register map» для таймеров TIM2TIM5 МК STM2F401.

Регистры, ответственные за настройку прерываний указанных таймеров, представлены выдержкой из Таблицы «Register map» (страница 374 Reference manual) в нижней части Рисунка 20.

Как вы помните из п. 3.2 настоящей части статьи, первым делом необходимо через модуль RCC включить тактирование таймера, чтобы иметь доступ к его регистрам.

Далее следует:
1. Записать в регистр ARR значение счётчика (например, 2500), по достижению которого будет генерироваться прерывание.

2. Разрешить обновление содержимого ARR после каждого прерывания, установив в 1 бит UG регистра EGR.

3. Разрешить прерывание локально посредством записи 1 в бит UIE регистра DIER.

4. Разрешить прерывание глобально.

Кроме того, в обработчике прерывания необходимо обеспечить сброс бита UIF регистра SR через запись нуля в указанный бит.

stm32f401.h
/* Адреса и номера битов регистров модуля таймеров */
TIM5 = 0×40000C00
  TIM_DIER = 0×0C
    UIE = 0
  TIM_SR = 0×10
    UIF = 0
  TIM_EGR = 0×14
    UG = 0
  TIM_ARR = 0×2C

main.S
main:
  /* Включить тактирование таймера TIM5 */
  setBit (RCC + APB1ENR), TIM5EN
  /* Установить значение счётчика TIM5,
     после которого генерируется прерывание */
  LDR r0, =(TIM5 + TIM_ARR)
  LDR r1, =2500
  STR r1, [r0]
  /* Разрешить обновление ARR */
  setBit (TIM5 + TIM_EGR), UG
  /* Разрешить прерывание TIM5 локально */
  setBit (TIM5 + TIM_DIER), UIE
  /* Разрешить прерывание TIM5 глобально */
  setBit ISER1, TIM5_INTERRUPT_BIT
main_loop:
  B main_loop

TIM5_Handler:
  PUSH {LR}
  /* сбросить бит UIF */
  clearBit (TIM5 + TIM_SR), UIF
  POP {PC}
.end


Прерывания АЦП STM32F401

Заглянув в Таблицу «STM32F401x register boundary addresses», вы увидите, что под модуль аналого-цифрового преобразователя ADC1 этого МК выделен сектор памяти данных в границах адресов 0×40012000—0×400123FF.
На Рисунке 21 представлены регистры, имеющие отношение к прерыванию АЦП по завершению измерения (подробнее, смотрите в Разделе «Analog-to-digital converter (ADC)» на странице 213 Reference manual).

Рисунок 21. Выдержки из Таблицы «Register map» для АЦП МК STM2F401.

Для настройки указанного прерывания помимо описанного выше глобального, необходимо обеспечить локальное разрешение через запись единицы в бит EOCIE регистра CR1, не забыв предварительно включить тактирование АЦП.

Бит EOC регистра SR сбрасывается в обработчике прерывания посредством записи в него нуля.

nrf52832.h
/* Адреса и номера битов регистров модуля АЦП */
ADC1 = 0×40012000
  ADC1_SR = 0×00
    EOC = 1
  ADC1_CR1 = 0×04
    EOCIE = 5

main.S
main:
  /* Включить тактирование АЦП */
  setBit (RCC + APB2ENR), ADC1EN
  /* Разрешить прерывание АЦП локально */
  setBit (ADC1 + ADC1_CR1), EOCIE
  /* Разрешить прерывание АЦП глобально */
  setBit ICER0, ADC1_INTERRUPT_BIT
main_loop:
  B main_loop

ADC1_Handler:
  PUSH {LR}
  /* Сбросить бит EOC */
  clearBit (ADC1 + ADC1_SR), EOC
  POP {PC}
.end


Внешние прерывания nRF52832

Внешние прерывания в nRF52832 организуются через так называемые события (events) порта, подробнее о чём можно узнать из Раздела «GPIOTE — GPIO tasks and events» на странице 157 Product specification.

Всего, можно реализовать восемь внешних прерываний (с номерами от 0 по 7), выбрав для каждого в качестве входа любой из 32 пинов порта ввода-вывода.

На Рисунке 22 отображена информация о базовом адресе модуля GPIOTE и регистрах, ответственных за настройку внешних прерываний.

Рисунок 22. Регистры настройки внешних прерываний МК nRF52832.

Чтобы, к примеру, настроить внешнее прерывание с номером 0 по ниспадающему фронту сигнала на пине P0.2, необходимо:
1. Через регистр CONFIG[0]:
а) выбрать режим «Event», записав число 1 в группу битов MODE,
б) выбрать указанный пин как вход прерывания, записав число 2 в группу битов PSEL,
в) выбрать в качестве триггера прерывания ниспадающий фронт сигнала, записав число 2 в группу битов POLARITY.

2. Разрешить прерывание локально, установив в 1 бит IN0 регистра INTENSET. Локальный запрет на прерывание обеспечивается записью 1 в такой же бит регистра INTENCLR.

3. Разрешить прерывание глобально.

Сброс флага прерывания обеспечивается записью нуля по адресу события EVENTS_IN[0].

nrf52832.h
/* Адреса и номера битов регистров модуля GPIOTE */
GPIOTE = 0×40006000
  GPIOTE_EVENTS_IN0 = 0×100
  GPIOTE_CONFIG_0 = 0×510
    MODE = 0
    PSEL = 8
    POLARITY = 16
  GPIOTE_INTENSET = 0×304
  GPIOTE_INTENCLR = 0×308
    IN0 = 0

main.S
main:
  /* Выбрать следующие условия прерывания:
     — режим Event
     — пин P0.2 как вход прерывания
     — ниспадающий фронт в качестве триггера */
  LDR r0, =(GPIOTE + GPIOTE_CONFIG_0)
  LDR r1, =(1 ≪ MODE) | (2 ≪ PSEL) | (2 ≪ POLARITY)
  STR r1, [r0]
  /* Разрешить прерывания локально */
  setBit (GPIOTE + GPIOTE_INTENSET), IN0
  /* Разрешить прерывания глобально */
  setBit ISER0, GPIOTE_INTERRUPT_BIT
main_loop:
  B main_loop

GPIOTE_Handler:
  PUSH {LR}
  /* Сбросить флаг GPIOTE_EVENTS_IN0 */
  LDR r0, =(GPIOTE + GPIOTE_EVENTS_IN0)
  LDR r1, =0
  STR r1, [r0]
  POP {PC}
.end


Прерывания таймера nRF52832

Информация о таймерах nRF52832 и настройке их прерываний изложена в Разделе «TIMER — Timer/counter» на странице 234 Product specification.

Все пять таймеров этого МК могу генерировать от 4 до 6 прерываний по достижению счётчиком каждого из четырёх (или шести) заданных значений.

Выдержки из таблиц указанного выше раздела для таймера TIMER2 приведены на рисунке 23.

Рисунок 23. Регистры настройки прерываний таймера TIMER2 МК nRF52832.

Настройка одного из четырёх возможных для TIMER2 прерываний по совпадению осуществляется следующим образом:
1. В регистр CC[0] Записывается значение совпадения (например, 2500).

2. Прерывание локально разрешается посредством записи 1 в бит COMPARE0 регистра INTENSET. Запретить данное прерывание локально можно записав 1 в тот же бит регистра INTENCLR.

3. Через модуль NVIC обеспечивается глобальное разрешение прерывания.

Для корректной работы прерывания необходимо в обработчике прерывания сбрасывать флаг события EVENTS_COMPARE[0] записью нуля по соответствующему адресу.

nrf52832.h
/* Адреса и номера битов регистров модуля таймеров */
TIMER2 = 0×4000A000
  TIM_EVENTS_COMPARE_0 = 0×140
  TIM_INTENSET = 0×304
  TIM_INTENCLR = 0×308
    COMPARE_0 = 16
  TIM_CC_0 = 0×540

main.S
main:
  /* Установить значение счётчика TIMER2,
     после которого генерируется прерывание */
  LDR r0, =(TIMER2 + TIM_CC_0)
  LDR r1, =2500
  STR r1, [r0]
  /* Разрешить прерывание по совпадению локально */
  setBit (TIMER2 + TIM_INTENSET), COMPARE_0
  /* Разрешить прерывание по совпадению глобально */
  setBit ISER0, TIMER2_INTERRUPT_BIT
main_loop:
  B main_loop

TIMER2_Handler:
  PUSH {LR}
  /* Сбросить флаг EVENTS_COMPARE_0 */
  LDR r0, =(TIMER2 + TIM_EVENTS_COMPARE_0)
  LDR r1, =0
  STR r1, [r0]
  POP {PC}
.end


Прерывания АЦП nRF52832

Подробности работы аналого-цифрового преобразователя, включая настройку прерываний, приведены в Разделе «SAADC» Product specification на странице 357.

Этот модуль nRF52832 может генерировать несколько прерываний, в т. ч. по:
• запуску АЦП,
• переполнению буфера для хранения результатов измерений,
• завершению измерения,
• записи результата измерений в ОЗУ,
• завершению калибровки АЦП.
• остановке АЦП.

Информация об адресах и регистрах, связанных с одним из вышеперечисленных прерываний — по записи результата измерений в ОЗУ — приведена на Рисунке 24.

Рисунок 24. Регистры настройки прерываний АЦП МК nRF52832.

Для настройки этого прерывания достаточно:
1. Разрешить его локально, установив в 1 бит RESULTDONE регистра INTENSET. Запись 1 в такой же бит регистра INTENCLR обуславливает локальный запрет на данное прерывание.
2. Обеспечить глобальное разрешение прерывания.

Флаг прерывания сбрасывается записью нуля по адресу события EVENTS_ RESULTDONE.

nrf52832.h
/* Адреса и номера битов регистров модуля АЦП */
SAADC = 0×40007000
  SAADC_EVENTS_RESULTDONE = 0×10C
  SAADC_INTENSET = 0×304
  SAADC_INTENCLR = 0×308
    RESULTDONE = 3

main.S
main:
  /* Разрешить прерывание АЦП локально */
  setBit (SAADC + SAADC_INTENSET), RESULTDONE
  /* Разрешить прерывание АЦП глобально */
  setBit ICER0, SAADC_INTERRUPT_BIT
main_loop:
  B main_loop

SAADC_Handler:
  PUSH {LR}
  /* Сбросить флаг EVENTS_RESULTDONE */
  LDR r0, =(SAADC + SAADC_EVENTS_RESULTDONE)
  LDR r1, =0
  STR r1, [r0]
  POP {PC}
.end


Файлы

Шаблонные хидер-файлы, пополненные макроопределениями адресов упомянутых в настоящей части статьи регистров и номеров их битов, выложены в архив.
Файловый сервис недоступен. Зарегистрируйтесь или авторизуйтесь на сайте.



Спасибо за внимание. 😊
Продолжение следует!


Камрад, рассмотри датагорские рекомендации

🌻 Купон до 1000₽ для новичка на Aliexpress

Никогда не затаривался у китайцев? Пришло время начать!
Камрад, регистрируйся на Али по нашей ссылке. Ты получишь скидочный купон на первый заказ. Не тяни, условия акции меняются.

🌼 Полезные и проверенные железяки, можно брать

Куплено и опробовано читателями или в лаборатории редакции.



 

Читательское голосование

Нравится

Статью одобрили 12 читателей.

Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.
 

Поделись с друзьями!

 

 

Связанные материалы

 

Схема на Датагоре. Новая статья Микроконтроллеры AVR в радиолюбительской практике. А. В. Белов... А. В. Белов Микроконтроллеры AVR в радиолюбительской практике Данная книга представляет собой...
Схема на Датагоре. Новая статья Как освоить радиоэлектронику с нуля. Учимся собирать конструкции любой сложности. Дригалкин В. В.... Если у вас есть огромное желание дружить с электроникой, если вы хотите создавать свои самоделки,...
Схема на Датагоре. Новая статья Блок питания с защитой + микроконтроллер ATMEGA16, ATMEGA8535, PIC16F877. Часть первая, лирическая... Вниманию сограждан Датагорода предлагаю мой вариант лабораторного блока питания с...
Схема на Датагоре. Новая статья Проект DMX512. Микроконтроллер управляет профессиональным шоу. Ч.3... Я думаю, что прочитав теоретическую часть, в которой не всё сразу понятно, лучше сразу потихонечку...
Схема на Датагоре. Новая статья Програмирование в AVR Studio 5 с самого начала. Часть 1... Каждый человек, который только начинает осваивать программирование микроконтроллеров, да и вообще...
Схема на Датагоре. Новая статья Датчик угла поворота. Сельсин-датчик и приёмник на микроконтроллере.... Схема сельсин-датчика и программа микроконтроллера практически полностью взяты из журнала Радио №4...
Схема на Датагоре. Новая статья Програмирование в AVR Studio 5 с самого начала. Часть 6... Продолжим разбор теоретических основ, без которых невозможно полноценное создание программ....
Схема на Датагоре. Новая статья Регулятор нагрева паяльника из диммера (светорегулятора)... Понадобился мне регулятор нагрева паяльника, так как новый паяльник (да и старый на 80 Ватт)...
Схема на Датагоре. Новая статья Современный тюнер своими руками: УКВ стерео + микроконтроллер. Б.Ю.Семенов... Борис Юрьевич Семенов Современный тюнер своими руками: УКВ стерео + микроконтроллер В последние...
Схема на Датагоре. Новая статья Грызем микроконтроллеры. Урок заключительный. Прошивка.... Ну вот, писать программы для микроконтроллеров мы научились. Работоспособность прошивки тоже...
Схема на Датагоре. Новая статья PIC микроконтроллеры. Все, что вам необходимо знать. Сид Катцен... PIC микроконтроллеры. Все, что вам необходимо знать. Сид Катцен пер. с англ. Евстифеева А. В. — М.:...
Схема на Датагоре. Новая статья PowerTrans - программа для расчета параметров трансформатора линейного БП... Еще одна программа для расчета параметров трансформатора. Существует множество программ для расчета...
 

Общаемся по статье 💬

Ассемблер для микроконтроллера с нуля. Часть 4. Система адресации памяти, назначение выводов, тактирование и прерывания МК

Комментарии, вопросы, ответы, дополнения, отзывы

 

<
Читатель Датагора

galrad

Комментарий # 1 от 14-03-21, 16:34.
  • С нами с 23.08.2011
  • 104 комментария
  • 12 публикаций
 
Ербол, Спасибо! Очередной, кропотливый труд. Говоря честно, я бы уже поленился писать в элементарных кодах, пытаясь унифицировать код под С или С++, но систиматика, с которой вы выкладываете информацию просто завораживает. Как всегда - компактный и быстрый код пишется на асме! Изложение напоминает - хакерский уровень, а сам принцип легко переносится на компьютерное железо, подозреваю, что так оно и есть!

<
Читатель Датагора

erbol

Комментарий # 2 от 14-03-21, 17:33.
  • С нами с 11.12.2014
  • 100 комментариев
  • 15 публикаций
 
Спасибо, Радик!
Очень приятно читать такой отзыв от Профессионала в разных областях!
На самом деле изначальная задумка - подвести читателя через ассемблер к С/С++.
Даже метку цикла основной функции в первом варианте я назвал не main_loop, а while_1 😊
Но, потом решил, что избыточная информация может сбить начинающего с толку.

<
Читатель Датагора

erbol

Комментарий # 3 от 19-03-21, 8:44.
  • С нами с 11.12.2014
  • 100 комментариев
  • 15 публикаций
 
Цитата galrad
  ... а сам принцип легко переносится на компьютерное железо, подозреваю, что так оно и есть!


Радик, извините, не заметил вторую часть комментария.
Могу подтвердить, что так оно и есть, поскольку пришёл к микроконтроллерам из компьютерного железа, включая микропроцессоры.
Там и ассемблер принципиально мало чем отличается.
Более того, GCC ведь тоже изначально писался по микропроцессоры, что заметно из формы скрипта линкера для AVR-8.

Информация
Вы не можете участвовать в комментировании. Вероятные причины:
— Администратор остановил комментирование этой статьи.
— Вы не авторизовались на сайте. Войдите с паролем.
— Вы не зарегистрированы у нас. Зарегистрируйтесь.
— Вы зарегистрированы, но имеете низкий уровень доступа. Получите полный доступ.