Вредоносное ПО (malware) - это назойливые или опасные программы,...
Недавно коллега меня подсадил на идею создания умного дома, я даже успел заказать себе десятки разных датчиков. Встал вопрос о выборе Микроконтроллера (далее МК) или платы. После некоторых поисков нашёл несколько вариантов. Среди них были и Arduino (включая его клоны, один из которых себе заказал ради того, чтобы просто побаловаться) и Launchpad , но всё это избыточно и громоздко (хотя в плане программирования гораздо проще, но тему холиваров поднимать не буду, у каждого свои вкусы). В итоге решил определяться не с готовой платой, а взять только МК и делать всё с нуля. В итоге выбирал между Atmel ATtiny (2313), Atmel ATmega (решил отказаться т.к. не смог найти за адекватные деньги), STM32 (Cortex на ядре ARM ). С тинькой я уже успел побаловаться, так что взял себе STM32VL-Discovery . Это можно назвать вступлением к циклу статей по STM32 . Оговорюсь сразу, автором большинства этих статей буду являться не я, т.к. сам только познаю, здесь я публикую их в первую очередь для себя, чтоб удобнее было искать если что-то забуду. И так поехали!
Общие сведения
Микроконтроллеры семейства STM32 содержат в своём составе до семи 16-разрядных портов ввода-вывода c именами от PORTA до PORTG. В конкретной модели микроконтроллера без исключений доступны все выводы портов, общее количество которых зависит от типа корпуса и оговорено в DataSheet на соответствующее подсемейство.
Для включения в работу порта x необходимо предварительно подключить его к шине APB2 установкой соответствующего бита IOPxEN в регистре разрешения тактирования периферийных блоков RCC_APB2ENR :
RCC->APB2ENR |= RCC_APB2ENR_IOPxEN; // Разрешить тактирование PORTx.
Управление портами STM32 осуществляется при помощи наборов из семи 32-разрядных регистров:
- GPIOx_CRL, GPIOx_CRH – задают режимы работы каждого из битов порта в качестве входа или выхода, определяют конфигурацию входных и выходных каскадов.
- GPIOx_IDR – входной регистр данных для чтения физического состояния выводов порта x.
- GPIOx_ODR – выходной регистр осуществляет запись данных непосредственно в порт.
- GPIOx_BSRR – регистр атомарного сброса и установки битов порта.
- GPIOx_BSR – регистр сброса битов порта.
- GPIOx_LCKR – регистр блокировки конфигурации выводов.
Режимы работы выводов GPIO
Режимы работы отдельных выводов определяются комбинацией битов MODEy и CNFy регистров GPIOx_CRL и GPIOx_CRH (здесь и далее: x-имя порта, y- номер бита порта).
GPIOx_CRL - регистр конфигурации выводов 0...7 порта x :
Структура регистра GPIOx_CRH аналогична структуре GPIOx_CRL и предназначена для управления режимами работы старших выводов порта (биты 8...15).
Биты MODEy указанных регистров определяют направление вывода и ограничение скорости переключения в режиме выхода:
- MODEy = 00: Режим входа (состояние после сброса);
- MODEy = 01: Режим выхода, максимальная скорость – 10МГц;
- MODEy = 10: Режим выхода, максимальная скорость – 2МГц;
- MODEy = 11: Режим выхода, максимальная скорость – 50МГц.
Биты CNF задают конфигурацию выходных каскадов соответствующих выводов:
в режиме входа:
- CNFy = 00: Аналоговый вход;
- CNFy = 01: Вход в третьем состоянии (состояние после сброса);
- CNFy = 10: Вход с притягивающим резистором pull-up (если PxODR=1) или pull-down (если PxODR=0);
- CNFy = 11: Зарезервировано.
в режиме выхода:
- CNFy = 00: Двухтактный выход общего назначения;
- CNFy = 01: Выход с открытым стоком общего назначения;
- CNFy = 10: Двухтактный выход с альтернативной функцией;
- CNFy = 11: Выход с открытым стоком с альтернативной функцией.
С целью повышения помехоустойчивости все входные буферы содержат в своём составе триггеры Шмидта. Часть выводов STM32 , снабженных защитными диодами, соединёнными с общей шиной и шиной питания, помечены в datasheet как FT (5V tolerant) - совместимые с напряжением 5 вольт.
Защита битов конфигурации GPIO
Для защиты битов в регистрах конфигурации от несанкционированной записи в STM32
предусмотрен регистр блокировки настроек GPIOx_LCKR
GPIOx_LCKR
- регистр блокировки настроек вывода порта:
Для защиты настроек отдельного вывода порта необходимо установить соответствующий бит LCKy. После чего осуществить последовательную запись в разряд LCKK значений "1” - "0” - "1” и две операции чтения регистра LCKR , которые в случае успешной блокировки дадут для бита LCKK значения "0” и "1” . Защита настроечных битов сохранит своё действие до очередной перезагрузки микроконтроллера.
Файл определений для периферии микроконтроллеров STM32 stm32f10x.h определяет отдельные группы регистров, объединённые общим функциональным назначением (в том числе и GPIO ), как структуры языка Си, а сами регистры как элементы данной структуры. Например:
GPIOC->BSRR
– регистр BSRR установки/сброса порта GPIOC.
Воспользуемся определениями из файла stm32f10x.h для иллюстрации работы с регистрами ввода-вывода микроконтроллера STM32F100RB
установленного в стартовом наборе STM32VLDISCOVERY
:
#include "stm32F10x.h" u32 tmp; int main (void) { RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Разрешить тактирование PORTC. GPIOC->CRH |= GPIO_CRH_MODE8; // Вывод светодиода LED4 PC8 на выход. GPIOC->CRH &=~GPIO_CRH_CNF8; // Двухтактный выход на PC8. GPIOC->CRH |= GPIO_CRH_MODE9; // Вывод светодиода LED3 PC9 на выход. GPIOC->CRH &=~GPIO_CRH_CNF9; // Двухтактный выход на PC9. GPIOA->CRL&=~GPIO_CRL_MODE0; // Кнопка "USER" PA0 - на вход. // Заблокировать настройки выводов PC8, PC9. GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9| GPIO_LCKR_LCKK; GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9; GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9| GPIO_LCKR_LCKK; tmp=GPIOC->LCKR; tmp=GPIOC->LCKR; }
Запись и чтение GPIO
Для записи и чтения портов предназначены входной GPIOx_IDR и выходной GPIOx_ODR регистры данных.
Запись в выходной регистр ODR порта настроенного на вывод осуществляет установку выходных уровней всех разрядов порта в соответствии с записываемым значением. Если вывод настроен как вход с подтягивающими резисторами, состояние соответствующего бита регистра ODR активирует подтяжку вывода к шине питания (pull-up, ODR=1) или общей шине микроконтроллера (pull-down, ODR=0).
Чтение регистра IDR возвращает значение состояния выводов микроконтроллера настроенных как входы:
// Если кнопка нажата (PA0=1), установить биты порта C, иначе сбросить. if (GPIOA->IDR & GPIO_IDR_IDR0) GPIOC->ODR=0xFFFF; else GPIOC->ODR=0x0000;
Сброс и установка битов порта
Для атомарного сброса и установки битов GPIO в микроконтроллерах STM32 предназначен регистр GPIOx_BSRR . Традиционный для архитектуры ARM способ управления битами регистров не требующий применения операции типа "чтение-модификация-запись” позволяет устанавливать и сбрасывать биты порта простой записью единицы в биты установки BS (BitSet) и сброса BR (BitReset) регистра BSRR . При этом запись в регистр нулевых битов не оказывает влияния на состояние соответствующих выводов.
GPIOx_BSRR – регистр сброса и установки битов порта:
GPIOC->BSRR=GPIO_BSRR_BS8|GPIO_BSRR_BR9; // Зажечь LED4 (PC8), погасить LED3. GPIOC->BSRR=GPIO_BSRR_BS9|GPIO_BSRR_BR8; // Зажечь LED3 (PC9), погасить LED4.
Альтернативные функции GPIO
и их переназначение (remapping)
Практически все внешние цепи специального назначения STM32
(включая выводы для подключения кварцевых резонаторов, JTAG/SWD
и так далее) могут быть разрешены на соответствующих выводах микроконтроллера, либо отключены от них для возможности их использования в качестве выводов общего назначения. Выбор альтернативной функции вывода осуществляется при помощи регистров с префиксом "AFIO
”_.
Помимо этого регистры AFIO
_ позволяют выбирать несколько вариантов расположения специальных функций на выводах микроконтроллера. Это в частности относится к выводам коммуникационных интерфейсов, таймеров (регистры AFIO_MAPR
), выводам внешних прерываний (регистры AFIO_EXTICR
) и т. д.
Порты ввода/вывода GPIO в STM32 имеют по 16 линий, каждая из которых может быть настроена необходимым образом. Поддерживаются функции цифрового ввода, цифрового вывода, входа внешнего прерывания, а также функции ввода/вывода других модулей микроконтроллера. Программирование STM32 для работы с GPIO основано на использовании регистров конфигурации, чтения, записи, защиты конфигурации и регистра битового доступа.
Регистры конфигурации порта.
Port configuration register low (GPIOx_CRL) (x=A..G)
Port configuration register high (GPIOx_CRH) (x=A..G)
Для программирования режимов работы портов ввода/вывода STM32, используются два 32 разрядных регистра для каждого GPIO. Они позволяют произвольно настроить режим работы любой отдельной линии. Регистр GPIOx_CRL отвечает за линии с номерами от 0 до 7, GPIOx_CRH – за линии 8-15. Для каждой из них в регистре имеется два двухразрядных поля CNFy и MODEy. Первое определяет тип работы линии, второе – направление обмена по линии. все биты доступны для чтения/записи.
Регистр GPIOx_CRL
Бит регистра |
||||||||||||||||
Поле |
||||||||||||||||
Линия ввода/вывода |
||||||||||||||||
Бит регистра |
||||||||||||||||
Поле |
||||||||||||||||
Линия ввода/ вывода |
Регистр GPIOX_CRH
Бит регистра |
||||||||||||||||
Поле |
||||||||||||||||
Линия ввода/вывода |
||||||||||||||||
Бит регистра |
||||||||||||||||
Поле |
||||||||||||||||
Линия ввода/вывода |
Поле MODEy может принимать следующие значения:
- 00 – линия работает на ввод. Данное состояние устанавливается после сброса.
- 01 – линия работает на выход, с максимальной частотой переключения 10 МГц
- 10 – линия работает на выход, с максимальной частотой переключения 20 МГц
- 11 – линия работает на выход, с максимальной частотой переключения 50 МГц
Поле CNFy зависит от направления передачи. При работе на вход (MODEy=0) доступны следующие состояния:
- 00 – аналоговый вход.
- 01 – вход в третьем состоянии. (Устанавливается после сброса).
- 10 – вход с подтягивающим резистором
- 11 – зарезервировано для будущих применений.
При работе на выход (MODEy>0) поле CNFy может иметь следующие состояния:
- 00 – цифровой выход
- 01 – цифровой выход с открытым стоком
- 10 – цифровой выход, подключенный специализированным блокам
- 11 – цифровой выход, подключенный специализированным блокам с открытым стоком
Регистр защиты от изменения настроек
Port configuration lock register (GPIOx_LCKR) (x=A..G)
Поле |
||||||||||||||||
Поле |
Установить блокируемый бит в GPIOx_LCKRДля невозможности изменения настроек порта в микроконтроллерах STM32 используется регистр GPIOx_LCKR. Его младщие 15 бит отвечают за соответсвующие линии порта ввода/вывода. Бит 16, установленный в 1, разрешает блокировку изменения настроек. все биты доступны на чтение/запись. Для усложнения жизни пользователям ;-) , используется специальный алгоритм установки защиты. Если он применен, то следующее изменение конфигурации доступно только после сброса. Алгоритм установки защиты выглядит следующим образом:
- Установить бит 16 GPIOx_LCKR.
- Сбросить бит 16 GPIOx_LCKR.
- Установить бит 16 GPIOx_LCKR.
- Прочитать GPIOx_LCKR
- Повторно прочитать GPIOx_LCKR
Регистры установки состояния линий
В отличие от привычных 8-ми битных моделей, в STM32 имеется несколько регистров, отвечающих за состояние линий порта ввода вывода. Условно они разделены на две группы – регистры порта и регистры установки отдельных битов.
Выходной регистр порта ввода/вывода
Port output data register (GPIOx_ODR) (x=A..G)
Поле |
||||||||||||||||
Поле |
Данный регистр имеет разрядность 32, но используются только младшие 16 бит. Биты с 16 по 31 не используются. При записи в GPIOx_ODR какого-либо значения, это значение устанавливается на выходных линиях соответствующего порта. Биты регистра доступны только для чтения/записи.
Входной регистр
Port input data register (GPIOx_IDR) (x=A..G)
Бит | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Поле | Резерв | |||||||||||||||
Бит | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 1 |
Поле | IDR15 | IDR14 | IDR13 | IDR12 | IDR11 | IDR10 | IDR9 | IDR8 | IDR7 | IDR6 | IDR5 | IDR4 | IDR3 | IDR2 | IDR1 | IDR0 |
Аналогично регистру выхода, регистр входа имеет толь 16 младших действующих бит из 32. Чтение GPIOx_IDR возвращает значение состояния всех линий порта. Биты регистра доступны только для чтения.
Регистр битовых операций
Port bit set/reset register (GPIOx_BSRR) (x=A..G)
Бит | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Поле | BR15 | BR14 | BR13 | BR12 | BR11 | BR10 | BR9 | BR8 | BR7 | BR6 | BR5 | BR4 | BR3 | BR2 | BR1 | BR0 |
Бит | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 1 |
Поле | BS15 | BS14 | BS13 | BS12 | BS11 | BS10 | BS9 | BS8 | BS7 | BS6 | BS5 | BS4 | BS3 | BS2 | BS1 | BS0 |
Данный регистр позволяет обращаться к конкретной линии ввода вывода микроконтроллера STM32. Запись единицы в один из старших разрядов сбрасывает выход линии, а запись единицы в младшие разряды устанавливает высокий уровень сигнала на соответствующей линии. Запись в регистр производится в формате слова, при этом нулевые биты никакого действия не оказывают. Биты регистра доступны только для записи.
Регистр сброса
Port bit reset register (GPIOx_BRR) (x=A..G)
Бит | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Поле | Резерв | |||||||||||||||
Бит | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 1 |
Поле | BR15 | BR14 | BR13 | BR12 | BR11 | BR10 | BR9 | BR8 | BR7 | BR6 | BR5 | BR4 | BR3 | BR2 | BR1 | BR0 |
Данный регистр производит сброс высокого уровня линии, установленной в регистре GPIOx_ODR. Задействованы только младшие 16 бит, доступных только для записи.
Помигаем светодиодом!
Поскольку микроконтроллеры STM32 - настоящие 32-битные ARM-ядра, сделать это будет непросто. Здесь всё сильно отличается от привычных методов в PIC или AVR, где было достаточно одной строкой настроить порт на выход, а второй строкой - вывести в него значение - но тем интереснее и гибче.
Архитектура STM32
Подробно архитектура микроконтроллеров расписана в статье, однако напомню основные положения, интересные нам сейчас.
Ядро тактируется кварцем, обычно через ФАПЧ. Это - тактовая частота ядра , или SYSCLK . На плате STM32VLDiscovery установлен кварц на 8 МГц, а ФАПЧ в большинстве случаев настраивается как умножитель на 3 - т.е. SYSCLK на плате STM32VLDiscovery обычно равен 24 МГц.
От ядра отходит шина AHB , имеющая свою тактовую частоту - ей можно установить некий прескалер относительно SYSCLK, однако можно оставить его равным единице. Эта шина подобна шине между процессором и северным мостом компьютера - точно так же она служит для связи ARM ядра и процессора периферии, а также на ней висит память и конечно, контроллер DMA.
К шине AHB подключены две периферийных шины - APB1 и APB2 . Они равнозначны, просто обслуживают разные контроллеры интерфейсов. Частоты обоих шин APB1 и APB2 можно задавать собственными прескалерами относительно AHB, но их тоже можно оставить равными единице. По умолчанию после запуска микроконтроллера вся периферия на шинах APB1 и APB2 отключена в целях экономии энергии.
Интересующие нас контроллеры портов ввода-вывода висят на шине APB2.
Модель периферии в STM32
Вся периферия микроконтроллеров STM32 настраивается по стандартной процедуре.
- Включение тактирования соответствующего контроллера - буквально, подача на него тактового сигнала от шины APB;
- Настройки, специфичные для конкретной периферии - что-то записываем в управляющие регистры;
- Выбор источников прерываний - каждый периферийный блок может генерировать прерывания по разным поводам. Можно выбрать конкретные «поводы»;
- Назначение обработчика прерываний;
- Запуск контроллера.
Если прерывания не нужны - шаги 3 и 4 можно пропустить.
Вот, к примеру, инициализация таймера (указаны шаги из последовательности):
/* 1 */ RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; /* 2 */ TIM6->PSC = 24000; TIM6->ARR = 1000; /* 3 */ TIM6->DIER |= TIM_DIER_UIE; /* 4 */ NVIC_EnableIRQ(TIM6_DAC_IRQn); /* 5 */ TIM6->CR1 |= TIM_CR1_CEN;
Контроллер портов ввода-вывода
Наконец-то подобрались к основной теме статьи.
Так устроена одна нога ввода-вывода микроконтроллера STM32F100:
Выглядит сложнее, чем в PIC или AVR Но на самом деле, ничего страшного.
На входе стоят защитные диоды, не дающие опустить потенциал ножки ниже земли или поднять его выше напряжения питания. Следом установлены управляемые подтягивающие резисторы - по желанию ножку можно подтянуть к земле или к питанию. Однако нужно помнить что эти подтяжки довольно слабые.
Вход
Рассмотрим «вход». Сигнал напрямую идёт в линию «Analog», и если ножка настроена как вход АЦП или компаратора - и если эти блоки есть на этой ножке - сигнал напрямую попадает в них. Для работы с цифровыми сигналами установлен триггер Шмитта (это тот, который с гистерезисом), и его выход попадает в регистр-защёлку входных данных - вот теперь состояние ножки можно считать в программе, читая этот регистр (кстати, он называется IDR - input data register). Для обеспечения работы не-GPIO-периферии, висящей на этой ножке как на входе - сделан отвод под именем «Alternate function input». В качестве этой периферии может выступать UART/USART, SPI, USB да и очень многие другие контроллеры.
Важно понимать, что все эти отводы одновременно включены и работают, просто к ним может быть ничего не подключено.
Выход
Теперь «выход». Цифровые данные, записанные в порт как в выход, лежат в регистре ODR - output data register. Он доступен как на запись, так и на чтение. Читая из ODR, вы не читаете состояние ножки как входа! Вы читаете то, что сами в него записали.
Здесь же - выход от не-GPIO-периферии, под названием «Alternate function output», и попадаем в Output driver. Режим работы выхода с точки зрения схемотехники настраивается именно здесь - можно сделать пуш-пулл выход (линия жёстко притягивается к земле или питанию), выход с открытым коллектором (притягиваем линию к питанию, а землю обеспечивает что-то внешнее, висящее на контакте) или вовсе отключить выход. После драйвера в линию входит аналоговый выход от ЦАП, компаратора или ОУ, и попадаем снова в подтягивающие резисторы и диоды.
Драйвер цифрового выхода имеет также контроль крутизны, или скорости нарастания напряжения. Можно установить максимальную крутизну, и получить возможность дёргать ногой с частотой 50 МГц - но так мы получим и сильные электромагнитные помехи из-за резких звенящих фронтов. Можно установить минимальную крутизну, с максимальной частотой «всего» 2 МГц - но и значительно уменьшить радиопомехи.
На картинке можно заметить ещё один регистр, «Bit set/reset registers». Дело в том, что можно писать напрямую в регистр ODR, а можно использовать регистры BRR/BSRR. На самом деле, это очень крутая фича, о которой я расскажу дальше.
Возможности
Сейчас всё стало похоже на хаос - неясно, как управлять всеми этими возможностями. Однако нет, контроллер порта отслеживает возможные режимы работы выхода, и исключает неверные комбинации - например, он не даст одновременно работать в одну выходную линию и драйверу цифрового выхода, и аналоговому выходу. Зато наличие такого количества настроек даёт обширные возможности.
Например, в более старших сериях можно настроить выход с открытым коллектором, и включить подтяжку к земле. Получается именно то, что нужно для шины 1-Wire. Правда, в серии STM32F1xx такой возможности нет, и нужно ставить внешний резистор подтяжки.
Атомарные операции
В старых микроконтроллерах часто возникала ситуация - если мы хотим изменить какие-то биты в порту (а на самом деле просто включить или выключить ножку) - нам приходилось читать весь регистр порта, устанавливать/сбрасывать в нём нужные биты и записывать обратно. Всё было хорошо до того момента, когда эту операцию посередине не прерывало прерывание. Если обработчик этого прерывания тоже что-то делал с этим же портом - возникала крайне трудноуловимая ошибка. С этим боролись разными средствами, например глобально запрещали прерывания на время обработки порта - но согласитесь, это какой-то костыльный вариант.
В STM32 эта проблема решена аппаратным путём - у вас есть регистры установки и сброса битов (BSRR и BRR), и здесь убиты сразу три зайца:
- не нужно читать порт для работы с ним
- для воздействия на конкретные пины нужно работать с конкретными битами, а не пытаться изменять весь порт
- эти операции атомарны - они проходят за один цикл, и их невозможно прервать посередине.
Подробнее про «конкретные биты» - каждый такт APB2 читаются регистры BSRR и BRR, и сразу же их содержимое применяется на регистр ODR, а сами эти регистры очищаются.Таким образом, если нужно установить 3 и 5 биты в порте - пишем в BSRR слово 10100, и всё успешно устанавливается.
Блокирование конфигурации
При желании, можно заблокировать конфигурацию любого пина от дальнейших изменений - любая попытка записи в регистр конфигурации окончится неуспехом. Это подойдёт для ответственных применений, где случайное переключение к примеру, выхода из режима open drain в push-pull выжжет всё подключенное к этому пину, или сам пин. Для включения блокирования предназначен регистр LCKR, только он снабжён защитой от случайной непреднамеренной записи - чтобы изменения вступили в силу, нужно подать специальную последовательность в бит LCKK.
Управляющие регистры
Всё управление контроллером GPIO сосредоточено в 32-битных регистрах GPIOx_RRR, где x - номер порта, а RRR - название регистра.
Младший конфигурационный регистр GPIOx_CRL
Настраивает первые 8 ножек, с номерами 0..7. У каждой ножки два параметра, MODE и CNF.
MODE отвечает за режим вход/выход и скорость нарастания сигнала.
00 - вход (режим по умолчанию)
01 - выход со скоростью 10 МГц
10 - выход со скоростью 2 МГц
11 - выход со скоростью 50 МГц
CNF отвечает за конфигурацию пина.
- В режиме входа (MODE=00):
00 - аналоговый режим
01 - плавающий вход (дефолт)
10 - вход с подтяжкой к земле или питанию
11 - зарезервирован
- В режиме выхода (MODE=01, 10 или 11):
00 - выход GPIO Push-pull
01 - выход GPIO Open drain
10 - выход альтернативной функции Push-pull
11 - выход альтернативной функции Open drain
Старший конфигурационный регистр GPIOx_CRH
Настраивает вторые 8 ножек, с номерами 8..15. Всё аналогично GPIOx_CRL.
Регистр входных данных GPIOx_IDR
Каждый бит IDRy содержит в себе состояние соответствующей ножки ввода-вывода. Доступен только для чтения.
Регистр входных данных GPIOx_ODR
Каждый бит ODRy содержит в себе состояние соответствующей ножки ввода-вывода. Можно записывать данные и они появятся на выходе порта, можно читать данные - читая предыдущее записанное значение.
Регистр атомарной установки/сброса битов выходных данных GPIOx_BSRR
Старшие 16 бит - для сброса соответствующих пинов в 0. 0 - ничего не делает, 1 - сбрасывает соответствующий бит. Младшие 16 бит - для установки битов в 1. Точно так же, запись «0» ничего не делает, запись «1» устанавливает соответствующий бит в 1.
Регистр атомарного сброса битов выходных данных GPIOx_BRR
Младшие 16 бит - для сброса соответствующих пинов. 0 - ничего не делает, 1 - сбрасывает соответствующий бит.
Регистр только для записи - он сбрасывается в ноль на каждом такте APB2.
Регистр блокирования конфигурации GPIOx_LCKR
Каждый бит LCKy блокирует соответствующие биты MODE/CNF регистров CRL/CRH от изменения, таким образом конфигурацию пина невозможно будет изменить вплоть до перезагрузки. Для активации блокирования необходимо записать блокирующую последовательность в бит LCKK: 1, 0, 1, читаем 0, читаем 1. Чтение бита LCKK сообщает текущий статус блокировки: 0 - блокировки нет, 1 - есть.
Работа в разных режимах
Режим входа
- Отключается драйвер выхода
- Резисторы подтяжек включаются по вашим настройкам, одно из трёх состояний - «вход, подтянутый к земле», «вход, подтянутый к питанию», или «плавающий вход»
- Входной сигнал семплируется каждый такт шины APB2 и записывается в регистр IDR, и чтение этого регистра сообщает состояние ножки.
Режим выхода
- Драйвер выхода включен, и действует так:
В режиме «Push-Pull» работает как полумост, включая верхний транзистор в случае «1» и нижний в случае «0»,
В режиме «Open drain» включает нижний транзистор в случае «0», а в случае «1» оставляет линию неподключенной (т.е. в третьем состоянии).
- Входной триггер Шмитта включен
- Отключаются резисторы подтяжек
Режим альтернативной функции (не-GPIO-периферия)
- Выходной драйвер - в режиме Push-Pull (к примеру, так работает ножка TX модуля USART) или Open drain, в зависимости от требований контроллера
- Выходной драйвер управляется сигналами периферии, а не регистром ODR
- Входной триггер Шмитта включен
- Резисторы подтяжки отключены
- Выходной сигнал семплируется каждый такт шины APB2 и записывается в регистр IDR, и чтение этого регистра сообщает состояние ножки в режиме Open drain.
- Чтение регистра ODR сообщает последнее записанное состояние в режиме Push-Pull.
Аналоговый режим
- Выходной драйвер выключен
- Триггер Шмитта полностью отключается, чтобы не влиять на напряжение на входе
- Резисторы подтяжки отключены
- В регистре IDR - постоянно 0.
Вся внутренняя аналоговая периферия имеет высокий входной импеданс, поэтому и сама ножка по отношению к остальной схеме будет иметь высокий входной импеданс.
Наконец-то включаем светодиод
Теперь мы знаем всё, чтобы включить этот светодиод! Пойдём с самого начала.
Нужно включить тактирование GPIO порта. Поскольку мы используем светодиод на плате Discovery, выберем зелёный - он подключен к порту PC9. То есть, необходимо включить тактирование GPIOC.
Теперь говорим про Push-pull выход. Это соответствует 00 в регистре CNF.
Ну вот, честно говоря и всё. Напоследок - листинг мигающего светодиода
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
GPIOC->CRH &= !(GPIO_CRH_CNF9_0 | GPIO_CRH_CNF9_1);
GPIOC->CRH |= GPIO_CRH_MODE9_1;
uint32_t i, n=1000000;
while(1) {
GPIOC->BSRR |= GPIO_BSRR_BS9;
i=0; while(i++ И всё-таки ещё не всё. Ради упрощения всяческих настроек я делаю библиотеку itacone. На текущий момент в ней реализована работа с GPIO-пинами и пара функций общего применения - но работа продолжается. GPIO
(general-purpose input/output,Интерфейс ввода/ вывода общего назначения)-
Интерфейс связывающий микроконтроллер с внешними устройствами (Кнопками, светодиодами, датчиками и так далее). Как и любой другой микроконтроллер, микроконтроллер STM
32, имеет в своем составе интерфейс ввода/вывода. Данный интерфейс позволяет управлять внешними устройствами путем передачи сигналов низкого и высокого уровня через контакты GPIO
, а так же принимать данные с них, путем приема сигналов низкого или высокого уровня. Контакты GPIO
, группируются в порты(GPIOA
,GPIOB
,GPIOC
…). Каждый контакт может работать на прием или передачу данных. Рассмотрим режимы работы контакта GPIO
: Hi
—
Z
вход
- В таком состоянии сопротивление входа стремится к бесконечности, и он ведет себя как отключенный от схемы. Вход с подтяжкой к питанию
- Контакт работает на прием данных. Когда данных нет, контакт подтягивается к логической “1” (напряжению питания). Это делается, что бы избежать помех и искажения данных. Вход с подтяжкой к земле
- Контакт работает на прием данных. Когда данных нет, контакт подтягивается к логическому “0” (земле). Это делается, что бы избежать помех и искажения данных. Аналоговый вход
- Контакт работает в аналоговом режиме, что позволяет выводить на него сигнал ЦАП или считывать сигнал для АЦП. Выход с открытым коллектором
.
Двухтактный выход
- Контакт работает на передачу данных. Контакт может быть установлен в логическую “1”
либо в логический “
”,
соответствующим регистром.
Альтернативный режим с подтяжкой
.
Альтернативный режим с открытым коллектором
. Альтернативные режимы пока рассматривать не будем, равно как выход с открытым коллектором и аналоговый вход. Рассмотрим подробнее режимы работы “Вход с подтяжкой к питанию/земле” и “Двухтактный выход”. Как и любую другую периферию, перед использованием GPIO
необходимо настроить. Так как периферия микроконтроллеров STM
32 очень богата и разнообразна, разработчики потрудились облегчить жизнь пользователям и избавить их от ручной правки регистров, путем создания библиотек HAL
и SPL
.
Разумеется, никто не запрещает вам вручную править регистры. Хоть ручная правка регистров и сложна, однако позволяет лучше понять работу микроконтроллера и оптимизировать прошивку. Но мы не будет сильно углубляться в дебри даташитов и регистров. Возьмем библиотеку SPL
и с её помощью инициализируем порты ввода/вывода нашей платы STM
32 F
3 DISCOVERY
. Работать будем в среде Keil
uVision
5. Как создать свой первый проект в данной среде смотрите здесь: Добавим следующий код:
#include "stm32f30x_gpio.h"
#include "stm32f30x_rcc.h"
void InitGPIO(void)
{
GPIO_InitTypeDef PORTE;
GPIO_InitTypeDef PORTA;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
GPIO_StructInit (&PORTE);
PORTE.GPIO_Mode = GPIO_Mode_OUT;
PORTE.GPIO_OType = GPIO_OType_PP;
PORTE.GPIO_Pin = GPIO_Pin_10;
PORTE.GPIO_PuPd = GPIO_PuPd_DOWN;
PORTE.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_StructInit (&PORTA);
PORTA.GPIO_Mode = GPIO_Mode_IN;
PORTA.GPIO_Pin = GPIO_Pin_0;
PORTA.GPIO_PuPd = GPIO_PuPd_DOWN;
PORTA.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init (GPIOE,&PORTE);
GPIO_Init (GPIOA,&PORTA);
};
int main(void)
{
InitGPIO();
while (1)
{
if (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1)
{
GPIO_SetBits(GPIOE,GPIO_Pin_10);
}
else
{
GPIO_ResetBits(GPIOE,GPIO_Pin_10);
}
}
} #include "stm32f30x_gpio.h"
#include "stm32f30x_rcc.h"
void
InitGPIO
(void
)
GPIO_InitTypeDef
PORTE
;
GPIO_InitTypeDef
PORTA
;
RCC_AHBPeriphClockCmd
(RCC_AHBPeriph_GPIOE
,
ENABLE
)
;
RCC_AHBPeriphClockCmd
(RCC_AHBPeriph_GPIOA
,
ENABLE
)
;
GPIO_StructInit
(&
PORTE
)
;
PORTE
.
GPIO_Mode
=
GPIO_Mode_OUT
;
PORTE
.
GPIO_OType
=
GPIO_OType_PP
;
PORTE
.
GPIO_Pin
=
GPIO_Pin_10
;
PORTE
.
GPIO_PuPd
=
GPIO_PuPd_DOWN
;
PORTE
.
GPIO_Speed
=
GPIO_Speed_50MHz
;
GPIO_StructInit
(&
PORTA
)
;
PORTA
.
GPIO_Mode
=
GPIO_Mode_IN
;
PORTA
.
GPIO_Pin
=
GPIO_Pin_0
;
PORTA
.
GPIO_PuPd
=
GPIO_PuPd_DOWN
;
PORTA
.
GPIO_Speed
=
GPIO_Speed_50MHz
;
GPIO_Init
(GPIOE
,
&
PORTE
)
;
GPIO_Init
(GPIOA
,
&
PORTA
)
;
int
main
(void
)
InitGPIO
()
;
while
(1
)
if
(GPIO_ReadInputDataBit
(GPIOA
,
GPIO_Pin_0
)
==
1
)
GPIO_SetBits
(GPIOE
,
GPIO_Pin_10
)
;
else
GPIO_ResetBits
(GPIOE
,
GPIO_Pin_10
)
;
Итак, разберем подробнее, что же происходит в данном коде. Точка входа - функция main
, именно с неё начинается работа нашей прошивки. Сначала вызывается функция InitGPIO
,в которой мы настраиваем работу наших портов. Остановимся на ней подобробнее.
Вначале создаются две структуры GPIO_InitTypeDef инициализации портов GPIOE
,GPIOA
, названные мною как PORTA
,PORTE
. Данные структуры являются частью библиотеки SPL
, и содержат в себе параметры работы соответствующих портов. Затем, происходит включение тактирование портов GPIOA
,GPIOE
командами RCC_AHBPeriphClockCmd. Следует помнить что, изначально тактирование периферии контроллера отключено, в целях снижения энергопотребления. Данной командой мы включаем или выключаем тактирование периферии шины AHB
. Подробнее о тактировании поговорим в следующих уроках.
После этого мы заполняем поля структуры GPIO_InitTypeDef , для портов GPIOE
,GPIOA
. Предварительно структуру можно проинициализировать (записать в неё стандартные значения) командой GPIO_StructInit. Рассмотрим поля структуры GPIO_InitTypeDef:
Итак, поля заполнены, теперь инициализируем порты согласно тому, что прописано в соответствующей структуре командой GPIO_Init. На этом функция InitGPIO
заканчивается. После вызова функции InitGPIO
, наступает основной цикл программы while
. В нем мы считываем входящее значение 0 ножки порта GPIOA
(К которой подключена кнопка USER
платы STM
32 F
3 DISCOVERY
). Делаем мы это командой GPIO_ReadInputDataBit,
которая возвращает входное значение выбранной ножки порта. В соответствии с состоянием ножки 0 порта GPIOA
, выставляем значение 10 ножки порта GPIOE
командами GPIO_ResetBits и GPIO_S
etBits. Команда GPIO
_ResetBits
устанавливает логический ноль на выбранной ножке порта, а команда GPIO
_SetBits
устанавливает логическую единицу на выбранной нами ножке. Когда вы загрузите данную программу в плату STM
32 F
3 DISCOVERY
, не забудьте нажать кнопку RESET
, которая сбросит микроконтроллер. После этого нажимайте кнопку USER
и смотрите за результатом.
Любое копирование, воспроизведение, цитирование материала, или его частей разрешено только с письменного согласия администрации MKPROG
.RU
. Незаконное копирование, цитирование, воспроизведение преследуется по закону! При возникновении, некоторого события контроллер прерываний автоматически прерывает выполнение основной программы, и вызывает соответствующую функцию обработки прерываний. После выхода из функции обработчика прерываний программа продолжает выполнение с того места, где произошло прерывание. Все происходит автоматически (при правильной настройке NVIC, но об этом ниже). Из самого названия видно, что контроллер NVIC поддерживает вложенность прерываний и приоритеты. Каждому прерыванию при настройке NVIC присваивается свой приоритет. Если во время обработки низкоприоритетного прерывания возникает высокоприоритетное, то оно, в свою очередь, прервет обработчик низкоприоритетного прерывания. В стек перемещается регистр регистр статуса программы ( Program Status Register (PSR)
), счетчик программы (Program Counter (PC)
) и регистр связи (Link Register (LR)
). Описание регистров ядра приведено в Cortex-M4 Generic User Guide . Благодаря этому, запоминается состояние, в котором находилось ядро перед переходом в режим обработки прерываний. Также сохраняются регистры R0 - R3 и R12. Эти регистры используются в инструкциях для передачи параметров, поэтому, помещение в стек делает возможным их использование в функции обработки прерывания, а R12 часто выступает в роли рабочего регистра программы. По завершении обработки прерывания все действия выполнятся в обратном порядке: извлекается содержимое стека и, параллельно с этим, осуществляется выборка адреса возврата. С момента инициации прерывания до выполнения первой команды обработчика прерывний проходит 12 тактов, такое же время необходимо для возобновления основной программы после завершения обработки прерывания. 1. Приостановка низкоприоритетного прерывания
2. Непрерывная обработка прерываний
3. Запаздывание высокприоритетного прерывания
Значение приоритета прерывания задается в регистрах Interrupt Priority Registers
(см. Cortex-M4 Generic User Guide). При этом, часть бит отвечает за приоритет группы, в которой находится прерывание, а часть - за приоритет внутри группы. Как вы, наверно, заметили, в Cortex-M4 Generic User Guide сказано, что настройка приоритетов и группировки приоритетов зависят от конкретной реализации implementation defined
. Содержимое файла
// Enable the IAR extensions for this source file.
#pragma language=extended
#pragma segment="CSTACK"
// Forward declaration of the default fault handlers.
void ResetISR(void);
static void NmiSR(void);
static void FaultISR(void);
static void IntDefaultHandler(void);
// The entry point for the application startup code.
extern void __iar_program_start(void);
extern void EXTI_Line0_IntHandler(void);
extern void EXTI_Line6_IntHandler(void);
// A union that describes the entries of the vector table. The union is needed
// since the first entry is the stack pointer and the remainder are function
// pointers.
typedef union
{
void (*pfnHandler)(void);
void * ulPtr;
}
uVectorEntry;
// The vector table. Note that the proper constructs must be placed on this to
// ensure that it ends up at physical address 0x0000.0000.
__root const uVectorEntry __vector_table @ ".intvec" =
{
{ .ulPtr = __sfe("CSTACK") },
// The initial stack pointer
ResetISR, // The reset handler
NmiSR, // The NMI handler
FaultISR, // The hard fault handler
IntDefaultHandler, // MPU Fault Handler
IntDefaultHandler, // Bus Fault Handler
IntDefaultHandler, // Usage Fault Handler
IntDefaultHandler, // Reserved
IntDefaultHandler, // Reserved
IntDefaultHandler, // Reserved
IntDefaultHandler, // Reserved
IntDefaultHandler, // SVCall Handler
IntDefaultHandler, // Debug Monitor Handler
IntDefaultHandler, // Reserved
IntDefaultHandler, // PendSV Handler
IntDefaultHandler, // SysTick Handler
//External Interrupts
IntDefaultHandler, // Window WatchDog
IntDefaultHandler, // PVD through EXTI Line detection
IntDefaultHandler, // Tamper and TimeStamps through the EXTI line
IntDefaultHandler, // RTC Wakeup through the EXTI line
IntDefaultHandler, // FLASH
IntDefaultHandler, // RCC
EXTI_Line0_IntHandler, // EXTI Line0
IntDefaultHandler, // EXTI Line1
IntDefaultHandler, // EXTI Line2
IntDefaultHandler, // EXTI Line3
IntDefaultHandler, // EXTI Line4
IntDefaultHandler, // DMA1 Stream 0
IntDefaultHandler, // DMA1 Stream 1
IntDefaultHandler, // DMA1 Stream 2
IntDefaultHandler, // DMA1 Stream 3
IntDefaultHandler, // DMA1 Stream 4
IntDefaultHandler, // DMA1 Stream 5
IntDefaultHandler, // DMA1 Stream 6
IntDefaultHandler, // ADC1, ADC2 and ADC3s
IntDefaultHandler, // CAN1 TX
IntDefaultHandler, // CAN1 RX0
IntDefaultHandler, // CAN1 RX1
IntDefaultHandler, // CAN1 SCE
EXTI_Line6_IntHandler, // External Lines
IntDefaultHandler, // TIM1 Break and TIM9
IntDefaultHandler, // TIM1 Update and TIM10
IntDefaultHandler, // TIM1 Trigger and Commutation and TIM11
IntDefaultHandler, // TIM1 Capture Compare
IntDefaultHandler, // TIM2
IntDefaultHandler, // TIM3
IntDefaultHandler, // TIM4
IntDefaultHandler, // I2C1 Event
IntDefaultHandler, // I2C1 Error
IntDefaultHandler, // I2C2 Event
IntDefaultHandler, // I2C2 Error
IntDefaultHandler, // SPI1
IntDefaultHandler, // SPI2
IntDefaultHandler, // USART1
IntDefaultHandler, // USART2
IntDefaultHandler, // USART3
IntDefaultHandler, // External Lines
IntDefaultHandler, // RTC Alarm (A and B) through EXTI Line
IntDefaultHandler, // USB OTG FS Wakeup through EXTI line
IntDefaultHandler, // TIM8 Break and TIM12
IntDefaultHandler, // TIM8 Update and TIM13
IntDefaultHandler, // TIM8 Trigger and Commutation and TIM14
IntDefaultHandler, // TIM8 Capture Compare
IntDefaultHandler, // DMA1 Stream7
IntDefaultHandler, // FSMC
IntDefaultHandler, // SDIO
IntDefaultHandler, // TIM5
IntDefaultHandler, // SPI3
IntDefaultHandler, // UART4
IntDefaultHandler, // UART5
IntDefaultHandler, // TIM6 and DAC1&2 underrun errors
IntDefaultHandler, // TIM7
IntDefaultHandler, // DMA2 Stream 0
IntDefaultHandler, // DMA2 Stream 1
IntDefaultHandler, // DMA2 Stream 2
IntDefaultHandler, // DMA2 Stream 3
IntDefaultHandler, // DMA2 Stream 4
IntDefaultHandler, // Ethernet
IntDefaultHandler, // Ethernet Wakeup through EXTI line
IntDefaultHandler, // CAN2 TX
IntDefaultHandler, // CAN2 RX0
IntDefaultHandler, // CAN2 RX1
IntDefaultHandler, // CAN2 SCE
IntDefaultHandler, // USB OTG FS
IntDefaultHandler, // DMA2 Stream 5
IntDefaultHandler, // DMA2 Stream 6
IntDefaultHandler, // DMA2 Stream 7
IntDefaultHandler, // USART6
IntDefaultHandler, // I2C3 event
IntDefaultHandler, // I2C3 error
IntDefaultHandler, // USB OTG HS End Point 1 Out
IntDefaultHandler, // USB OTG HS End Point 1 In
IntDefaultHandler, // USB OTG HS Wakeup through EXTI
IntDefaultHandler, // USB OTG HS
IntDefaultHandler, // DCMI
IntDefaultHandler, // CRYP crypto
IntDefaultHandler, // Hash and Rng
IntDefaultHandler, // FPU
};
// This is the code that gets called when the processor first starts execution
// following a reset event. Only the absolutely necessary set is performed,
// after which the application supplied entry() routine is called. Any fancy
// actions (such as making decisions based on the reset cause register, and
// resetting the bits in that register) are left solely in the hands of the
// application.
void
ResetISR(void)
{
//
// Call the application"s entry point.
//
__iar_program_start();
}
// This is the code that gets called when the processor receives a NMI. This
// simply enters an infinite loop, preserving the system state for examination
// by a debugger.
static void
NmiSR(void)
{
//
// Enter an infinite loop.
//
while(1)
{
}
}
// This is the code that gets called when the processor receives a fault
// interrupt. This simply enters an infinite loop, preserving the system state
// for examination by a debugger.
static void
FaultISR(void)
{
//
// Enter an infinite loop.
//
while(1)
{
}
}
// This is the code that gets called when the processor receives an unexpected
// interrupt. This simply enters an infinite loop, preserving the system state
// for examination by a debugger.
static void
IntDefaultHandler(void)
{
//
// Go into an infinite loop.
//
while(1)
{
}
}
Использование Сама секция задается в начале ROM памяти. Адреса можно посмотреть (документ, в котором описана адресация флеш памяти STM32): Комбинация директивы IAR и спецфункции IAR: Саму таблицу заполняют адреса функций, реализующий вечный цикл. Исключение сделано только для интересующих нас функций: Листинг
//Definitions for SCB_AIRCR register
#define SCB_AIRCR (*(unsigned volatile long*)0xE000ED0C) //acces to SCB_AIRCR
#define SCB_AIRCR_GROUP22 0x05FA0500 //change priority data
//Definitions for RCC_AHB1_ENR register
#define RCC_AHB1_ENR (*(unsigned volatile long *)(0x40023830)) //acces to RCC_AHB1ENR reg
#define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield
#define RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield
#define RCC_AHB1_ENR_GPIOD 0x8 //GPIOD bitfield
//Definitions for RCC_APB2_ENR register
#define RCC_APB2_ENR (*(unsigned volatile long *)(0x40023844)) //acces to RCC_APB2ENR reg
#define RCC_APB2_ENR_SYSCFG 0x4000 //SYSCFG bitfield
//Definitions for GPIO MODE registers
#define GPIOA_MODER (*(unsigned volatile long*)(0x40020000)) //acces to GPIOA_MODER reg
#define GPIOC_MODER (*(unsigned volatile long*)(0x40020800)) //acces to GPIOC_MODER reg
#define GPIOD_MODER (*(unsigned volatile long*)(0x40020C00)) //acces to GPIOD_MODER reg
//GPIO ODR register definition
#define GPIOD_ODR (*(unsigned volatile long*)(0x40020C14)) //acces to GPIOD_MODER reg
#define GPIO_ODR_13PIN 0x2000
#define GPIO_ODR_14PIN 0x4000
//Bitfields definitions
#define GPIO_MODER_0BITS 0x3 //Pin 0 mode bits
#define GPIO_MODER_0IN 0x0 //Pin 0 input mode
#define GPIO_MODER_6BITS 0x300 //Pin 6 mode bits
#define GPIO_MODER_6IN 0x000 //Pin 6 input mode
#define GPIO_MODER_13BITS 0xC000000 //Pin 13 mode bits
#define GPIO_MODER_13OUT 0x4000000 //Pin 13 output mode
#define GPIO_MODER_14BITS 0x30000000 //Pin 14 mode bits
#define GPIO_MODER_14OUT 0x10000000 //Pin 14 output mode
//GPIOC_PUPDR register definition
#define GPIOC_PUPDR (*(unsigned volatile long*)(0x4002080C)) //acces to GPIOC_PUPDR reg
#define GPIOC_PUPDR_6BITS 0x3000 //PC6 bitfield
#define GPIOC_PUPDR_6PU 0x1000 //PC6 pull-up enable
//SYSCFG_EXTIx registers definitions
#define SYSCFG_EXTICR1 (*(unsigned volatile long*)0x40013808) //SYSCFG_EXTICR1 acces
#define SYSCFG_EXTICR1_0BITS 0xF //EXTI 0 bits
#define SYSCFG_EXTICR1_0PA 0x0 //EXTI 0 - port A
#define SYSCFG_EXTICR2 (*(unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces
#define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 bits
#define SYSCFG_EXTICR2_6PC 0x200 //EXTI 6 - port C
//EXTI definitions
#define EXTI_IMR (*(unsigned volatile long*)0x40013C00) //EXTI_IMR reg acces
#define EXTI_LINE0 0x1 //LINE 0 definition
#define EXTI_LINE6 0x40 //LINE 6 definition
#define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg acces
#define EXTI_FTSR (*(unsigned volatile long*)0x40013C0C) //EXTI_FTSR reg acces
#define EXTI_PR (*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces
//NVIC registers and bits definitions
#define NVIC_ISER0_REG (*(unsigned volatile long*)0xE000E100) //NVIC_ISER0 reg acces
#define NVIC_ISER0_6VECT 0x40 //vect 6 definition
#define NVIC_ISER0_23VECT 0x800000 //vect 30 definition
#define NVIC_IPR0_ADD (0xE000E400)
#define NVIC_IPR23_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 23))
#define NVIC_IPR6_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 6))
Обратите внимание на то, что значения спецрегистров МК объявлены как volatile
. Это необходимо, чтобы компилятор не пытался оптимизировать операции обращения к ним, поскольку это не просто участки памяти и их значения могут изменяться без участия ядра. Листинг
//Definitions for SCB_AIRCR register
#define SCB_AIRCR (*(unsigned volatile long*)0xE000ED0C) //acces to SCB_AIRCR
#define SCB_AIRCR_GROUP22 0x05FA0500 //change priority data
//Definitions for RCC_AHB1_ENR register
#define RCC_AHB1_ENR (*(unsigned volatile long *)(0x40023830)) //acces to RCC_AHB1ENR reg
#define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield
#define RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield
#define RCC_AHB1_ENR_GPIOD 0x8 //GPIOD bitfield
//Definitions for RCC_APB2_ENR register
#define RCC_APB2_ENR (*(unsigned volatile long *)(0x40023844)) //acces to RCC_APB2ENR reg
#define RCC_APB2_ENR_SYSCFG 0x4000 //SYSCFG bitfield
//Definitions for GPIO MODE registers
#define GPIOA_MODER (*(unsigned volatile long*)(0x40020000)) //acces to GPIOA_MODER reg
#define GPIOC_MODER (*(unsigned volatile long*)(0x40020800)) //acces to GPIOC_MODER reg
#define GPIOD_MODER (*(unsigned volatile long*)(0x40020C00)) //acces to GPIOD_MODER reg
//GPIO ODR register definition
#define GPIOD_ODR (*(unsigned volatile long*)(0x40020C14)) //acces to GPIOD_MODER reg
#define GPIO_ODR_13PIN 0x2000
#define GPIO_ODR_14PIN 0x4000
//Bitfields definitions
#define GPIO_MODER_0BITS 0x3 //Pin 0 mode bits
#define GPIO_MODER_0IN 0x0 //Pin 0 input mode
#define GPIO_MODER_6BITS 0x300 //Pin 6 mode bits
#define GPIO_MODER_6IN 0x000 //Pin 6 input mode
#define GPIO_MODER_13BITS 0xC000000 //Pin 13 mode bits
#define GPIO_MODER_13OUT 0x4000000 //Pin 13 output mode
#define GPIO_MODER_14BITS 0x30000000 //Pin 14 mode bits
#define GPIO_MODER_14OUT 0x10000000 //Pin 14 output mode
//GPIOC_PUPDR register definition
#define GPIOC_PUPDR (*(unsigned volatile long*)(0x4002080C)) //acces to GPIOC_PUPDR reg
#define GPIOC_PUPDR_6BITS 0x3000 //PC6 bitfield
#define GPIOC_PUPDR_6PU 0x1000 //PC6 pull-up enable
//SYSCFG_EXTIx registers definitions
#define SYSCFG_EXTICR1 (*(unsigned volatile long*)0x40013808) //SYSCFG_EXTICR1 acces
#define SYSCFG_EXTICR1_0BITS 0xF //EXTI 0 bits
#define SYSCFG_EXTICR1_0PA 0x0 //EXTI 0 - port A
#define SYSCFG_EXTICR2 (*(unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces
#define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 bits
#define SYSCFG_EXTICR2_6PC 0x200 //EXTI 6 - port C
//EXTI definitions
#define EXTI_IMR (*(unsigned volatile long*)0x40013C00) //EXTI_IMR reg acces
#define EXTI_LINE0 0x1 //LINE 0 definition
#define EXTI_LINE6 0x40 //LINE 6 definition
#define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg acces
#define EXTI_FTSR (*(unsigned volatile long*)0x40013C0C) //EXTI_FTSR reg acces
#define EXTI_PR (*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces
//NVIC registers and bits definitions
#define NVIC_ISER0_REG (*(unsigned volatile long*)0xE000E100) //NVIC_ISER0 reg acces
#define NVIC_ISER0_6VECT 0x40 //vect 6 definition
#define NVIC_ISER0_23VECT 0x800000 //vect 30 definition
#define NVIC_IPR0_ADD (0xE000E400)
#define NVIC_IPR23_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 23))
#define NVIC_IPR6_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 6))
void EXTI_Line0_IntHandler(void);
void EXTI_Line6_IntHandler(void);
void main()
{
//NVIC
SCB_AIRCR = SCB_AIRCR_GROUP22;
//Enable SYSCFG , GPIO port A,C and D clocking
RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD;
RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG;
//LED3 and LED5 initialization
GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT;
GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT;
//PA0 and PC6 pins initialization
GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN;
GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN;
//Enable PC7 pull-up
GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_6BITS)) | GPIOC_PUPDR_6PU;
//Set up EXTI
EXTI_RTSR |= EXTI_LINE0;
EXTI_FTSR |= EXTI_LINE6;
EXTI_IMR = EXTI_LINE0|EXTI_LINE6;
//EXTI to port connection
SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA;
SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC;
//Set interrupts priority
NVIC_IPR6_REG = 0xF0;
NVIC_IPR23_REG = 0x00;
//Enable interrupts
NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT;
while(1)
{
}
}
void EXTI_Line0_IntHandler(void)
{
//Clear interrupt
EXTI_PR = EXTI_LINE0;
//Turn on LED 3
GPIOD_ODR |= GPIO_ODR_13PIN;
}
void EXTI_Line6_IntHandler(void)
{
//Clear interrupt
EXTI_PR = EXTI_LINE6;
//Turn LED4
GPIOD_ODR |= GPIO_ODR_14PIN;
while(1);
}
Теги:
Библиотека itacone
Как это работает?
Данный пост не претендует на абсолютную полноту, я советую изучить раздел прерываний в Cortex™-M3 Technical Reference Manual . Поскольку эта часть ядра не претерпела изменений, ее описание дано в первой ревизии r1p1 на ядро Cortex-M3.Вход в прерывание и выход из него
При инициации прерывания NVIC переключает ядро в режим обработки прерывания. После перехода в режим обработки прерывания регистры ядра помещаются в стек. Непосредственно во время записи значения регистров в стек осуществляется выборка начального адреса функции обработки прерывания.Вложенность прерываний
Как было сказано выше NVIC поддерживает прерывания с различными приоритетами, которые могут прерывать друг друга. При этом, могут возникнуть различные ситуации, обработка которых по разному оптимизирована.
В этой ситуации, обработка низкоприоритетного прерывания прекращается. Следующие 12 циклов выполняется сохранение в стек нового набора данных и запускается обработка высокоприоритетного прерывания. После его обработки, содержимое стека автоматически извлекается и возобновляется обработка низкоприоритетного прерывания.
Больших отличий от прерывания основной программы не наблюдается.
Эта ситуация может возникнуть в двух случаях: если два прерывания имеют одинаковый приоритет и возникают одновременно, если низкоприоритетное прерывание возникает во время обработки высокоприоритетного.
В этом случае, промежуточные операции над стеком не производятся. Происходит только загрузка адреса обработчика низкоприоритетного прерывания и переход к его выполнению. Отказ от операций над стеком экономит 6 тактов. Переход к следующему прерыванию происходит не за 12 тактов, а всего за 6.
Ситуация возникает, если высокоприоритетное прерывание происходит во перехода к обработке низкоприоритетного (за те самые 12 тактов). В этом случае переход к высокоприоритетному прерыванию будет происходить не менее 6 тактов с момента его возникновения (время необходимое для загрузки адреса обработчика прерывания и перехода к нему). Возврат в низкоприоритетное уже описан выше.Приоритеты прерываний
Помимо простой установки приоритета прерываний, NVIC реализует возможность группировки приоритетов.
Прерывания в группе с более высоким приоритетом могут прерывать обработчики прерываний группы с более низким приоритетом. прерывания из одной группы, но с разным приоритетом внутри группы не могут прерывать друг друга. Приоритет внутри группы определяет только порядок вызова обработчика, когда были активизированы оба события.
Настройка распределение бит на приоритет группы или приоритет внутри группы осуществляется с помощью регистра Application Interrupt and Reset Control Register
(ВНИМАТЕЛЬНО!!! см. Cortex-M4 Generic User Guide).
А вот дальше не очень приятная вещь. В к МК STM32F407 про NVIC почти нет информации. Но есть ссылка на отдельный документ. Для того, чтобы разобраться с реализацией NVIC в STM32 придется прочитать еще один документ - . Вообще говоря, я советую внимательно изучить данный документ и по всем другим вопросам, в нем работа ядра расписана более подробно, чем в документации от ARM.
В нем, уже можно найти:A programmable priority level of 0-15 for each interrupt. A higher level corresponds to a
lower priority, so level 0 is the highest interrupt priority
Из возможных 8 бит приоритета используются только 4. Но этого вполне достаточно для большинства задач.Маскирование прерываний
Предположим, что у нас стоит задача запуска ракеты-носителя при нажатии на красную кнопку, но только при условии, что повернут ключ.
Нет совершенно ни какого смысла генерировать прерывание на поворот ключа. А вот прерывание на нажатие красной копки нам понадобится. Для того, чтобы включать/выключать различные вектора прерываний, существует маскирование прерываний.
Маскирование прерывания осуществляется с помощью регистров Interrupt Set-enable Registers
.
Если прерывание замаскировано, это не означает, что периферия не генерирует события! Просто NVIC не вызывает обработчик этого события.Таблица векторов прерываний
Все возможные прерывания, поддерживаемые NVIC, записываются в таблицу векторов прерываний. По сути своей, таблица векторов прерываний есть ни что иное, как список адресов функций обработчиков прерываний. Номер в списке соответствует номеру прерывания.Создаем таблицу векторов и располагаем ее в правильном месте
Для тог, чтобы таблица векторов с правильными адресами функций обработчиков прерываний располагались в начале флеш памяти МК, создадим и подключим к проекту файл startup.c.
@ ".intvec"
Располагает таблицу __vector_table в начале секции, объявленной в файле линкера. Сам файл можно посмотреть тут:
#pragma segment="CSTACK"
__sfe("CSTACK")
Записывает в начале флеша указатель на верхушку стека.
extern void EXTI_Line0_IntHandler(void);
extern void EXTI_Line6_IntHandler(void);
В функции, вызываемой при старте, просто производится переход к
extern void __iar_program_start(void);
Это функция - main(). Сам символ можно переопределить, если возникнет желание:Переходим к основному файлу
Для начала выпишем и переопределим все адреса и битовые поля, которые нам понадобятся.Настраиваем группирование приоритетов
В первую очередь стоит настроить группировку приоритетов прерываний: SCB_AIRCR = SCB_AIRCR_GROUP22;
.Данное действие должно выполняться только один раз. В сложных проектах, использующих сторонние библиотеки стоит проверять данный факт. Изменение разбиения приоритетов на группы может привести к некорректной работе прошивки.Включение тактирование используемой периферии
Напомню, что перед началом работы с периферийными блоками необходимо включить их тактирование:
//Enable SYSCFG , GPIO port A and D clocking
RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD;
RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG;
Работать сразу с SYSCFG нельзя, нужно подождать несколько тактов. Но мы и не будем. Займемся инициализацией GPIO.Инициализация GPIO
Светодиоды инициализируются так же как и в прошлый раз:
//LED3 and LED5 initialization
GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT;
GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT;
Кнопка PA0 и контакт PC7 инициализируются как входные:
//PA0 and PC6 pins initialization
GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN;
GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN;
Вот только для контакта PC6 необходимо включить подтяжку питания. Активация подтяжки производится с помощью регистра GPIOC_PUPDR:
//Enable PC6 pull-up
GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_7BITS)) | GPIOC_PUPDR_6PU;
Настройка EXTI
И так, на нужно настроить следующие параметры - включить прерывания для линий 0 и 6, для линии 0 прерывание по растущему фронту, для линии 6 - прерывание по падающему фронту:
//Set up EXTI
EXTI_RTSR |= EXTI_LINE0;
EXTI_FTSR |= EXTI_LINE6;
EXTI_IMR = EXTI_LINE0|EXTI_LINE6;
Осталось настроить пины каких портов подключены к линии EXTI (странное решение, например МК stellaris могут генерировать прерывание при любой комбинации пинов, у STM32 с этим сложнее):
//EXTI to port connection
SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA;
SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC;
Настройка NVIC
Осталось настроить приоритеты прерываний и маскировать их для инициации обработки. Обратите внимание, что регистры NVIC_IPR
доступны для побайтового обращения, что значительно упрощает доступ только к необходимым байтам приоритетов отдельных векторов прерываний. Достаточно только сделать сдвиг на величину номера вектора прерывания (см. листинг определений). Еще раз напомним, что EXTI Line 0 имеет 6 номер в таблице векторов, а EXTI line 5_9 - номер 23. У STM32 значение имеют только старшие 4 бита приоритета:
//Set interrupts priority
NVIC_IPR6_REG = 0xF0;
NVIC_IPR23_REG = 0x0;
Для демонстрации приоритеты установлены различными.
Теперь можно включить прерывания:
//Enable interrupts
NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT;
С этого момента нажатие на кнопку и закоротки PC6 и GND будет приводить к вызову функций обработчиков прерываний EXTI_Line0_IntHandler и EXTI_Line6_IntHandler соответственно.Обработка прерываний
В функциях обработки прерываний в первую очередь необходимо очистить прерывание, после этого можно зажечь светодиоды. Для демонстрации приоритетов прерываний в один из обработчиков добавлен вечный цикл. Если приоритет прерывания с вечным циклом ниже приоритета второго - то оно не сможет быть вызвано. Иначе, оно сможет прервать первое. Я предлагаю вам самим попробовать различные знчения приоритетов прерываний и наглядно увидеть к чему это приводит (ВНИМАНИЕ - не забудьте про группы прерываний!
).
void EXTI_Line0_IntHandler(void)
{
//Clear interrupt
EXTI_PR = EXTI_LINE0;
//Turn on LED 3
GPIOD_ODR |= GPIO_ODR_13PIN;
}
void EXTI_Line6_IntHandler(void)
{
//Clear interrupt
EXTI_PR = EXTI_LINE6;
//Turn LED4
GPIOD_ODR |= GPIO_ODR_14PIN;
while(1);
}
Вместо заключения
На всякий случай приведу полный листинг получившейся программы.
Для проверки влияния приоритетов прерываний и приоритетов групп прерываний попробуйте менять приоритеты и наблюдать, что будет происходить (два бита - приоритет внутри группы, 2 бита - приоритет группы).
Добавить метки