Мы рассмотрели работу с битовыми операциями и двоичными числами, тем самым заложив основу для рассмотрения новой темы. В этом уроке мы с Вами рассмотрим очередной вопрос: что такое регистры и как с ними работать ?
Память и регистры
Одним из самых важных навыков необходимых при работе с микроконтроллерами является умение взаимодействовать с регистрами. Давайте для себя разберемся, что же это такое ?В целом, регистр - это особый вид памяти внутри микроконтроллера, который используется для управления процессором и периферийными устройствами. Каждый регистр в архитектуре ARM представляет собой ячейку памяти и имеет длину в 32 бита, где каждый бит можно представить в виде крошечного выключателя с помощью которого осуществляется управление тем или иным параметром микроконтроллера.
Каждый из регистров имеет свой порядковый номер – адрес. Адрес регистра обозначается 32-битным числом представленным в шестнадцатеричной системе счисления. Путём записи по адресу регистра определённой комбинации единиц и нулей, которые обычно представлены в шестнадцатеричном виде, осуществляется настройка и управление тем или иным узлом в МК. Вспомним, что в программе для работы с битовыми операциями, мы могли представить в виде шестнадцатеричного числа произвольный набор единиц и нулей. В целом стоит отметить, что существует два вида регистров: регистры общего назначения и специальные регистры. Первые расположены внутри ядра МК, а вторые являются частью RAM-памяти.
Так же стоит отметить, что Reference Manual , который мы скачивали в первом уроке , это один большой справочник по регистрам, содержащимся в целевом микроконтроллере, а библиотека CMSIS позволяет нам оперировать символьными именами регистров вместо числовых адресов. Например, к регистру 0x40011018 мы можем обратиться просто, используя символьное имя GPIOC_BSSR . Конкретные примеры конфигурирования мы рассмотрим в ходе разбора нашей программы из .
Итак, обычно структура регистра описывается в виде небольшой таблицы с указанием:
- Названия регистра и описания его назначения
- Адреса регистра или смещением относительно базового адреса
- Значения по умолчанию после сброса
- Типа доступа к ячейкам регистра (чтение, запись, чтение/запись)
- Значения и описания параметров записываемых битов
Разбор кода из первого занятия
Итак, давайте вспомним задачу, которую мы решили на используя готовый код примера: нам было необходимо написать программу, которая бы обеспечила попеременное включение двух светодиодов на плате Discovery (возможно и не двух, если у вас другая версия платы Discovery) с временным интервалом.Давайте еще разок взглянем на код программы, которую мы использовали для того, чтобы заставить наш МК дрыгать двумя ногами на которых расположены наши светодиоды:
Код main.c
/* Заголовочный файл для нашего семейства микроконтроллеров*/
#include "stm32f0xx.h"
/* Тело основной программы */
int main(void)
{
/* Включаем тактирование на порту GPIO */
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
/* Настраиваем режим работы портов PC8 и PC9 в Output*/
GPIOC ->MODER = 0x50000;
/* Настраиваем Output type в режим Push-Pull */
GPIOC->OTYPER = 0;
/* Настраиваем скорость работы порта в Low */
GPIOC->OSPEEDR = 0;
while(1)
{
/* Зажигаем светодиод PC8, гасим PC9 */
GPIOC->ODR = 0x100;
for (int i=0; i<500000; i++){} // Искусственная задержка
/* Зажигаем светодиод PC9, гасим PC8 */
GPIOC->ODR = 0x200;
for (int i=0; i<500000; i++){} // Искусственная задержка
}
}
Первым делом, при работе с STM32, даже для такой простой задачи как включение и выключение светодиода нам необходимо предварительно ответить на ряд вопросов:
- Как настроить, нужные нам, пины порта GPIO для того чтобы можно было включить светодиод?
- Как включить и выключить светодиод?
Куда подключены наши светодиоды? К какому выводу микроконтроллера?
Для того, чтобы посмотреть где что находится на плате Discovery, а в частности, нужные нам светодиоды - нужно открыть Schematic-файл, либо тот который мы скачали с сайта ST , либо прямо из Keil:Открыв Schematic мы увидим схему всего того, что есть на плате - схему ST-Link, обвязку всей периферии и многое другое. На текущий момент нас интересуют два светодиода, ищем их обозначение:
Как мы видим, наши светодиоды подключены к порту GPIOC на 8 и 9 пин.
Как включить тактирование на нужный порт GPIO?
В целом, любая работа с периферией в микроконтроллерах STM32 сводится к стандартной последовательности действий:- Включение тактирования соответствующего периферийного модуля. Осуществляется это через регистр RCC путем подачи тактового сигнала напрямую с шины на которой находится данный модуль. По умолчанию тактирование всей периферии отключено для минимизации энергопотребления.
- Настройка через управляющие регистры, путем изменения параметров специфичных для конкретного периферийного устройства
- Непосредственный запуск и использование результатов работы модуля
Внимание! Вопрос касательно системы тактирования, её настройки и использования мы подробно рассмотрим в отдельной статье.
Найти к какой шине подключен наш порт GPIOC можно найти в Datasheet"е на наш МК в разделе Memory Mapping в Таблице 16. STM32F051xx peripheral register boundary addresses.
Как вы уже успели заметить, необходимая нам шина именуется как AHB2. Для того чтобы подробнее ознакомиться с регистром, в котором включается тактирование на нужный нам порт GPIO на шине AHB, надо перейти в соответствующий раздел в Reference Manual. По названию регистров мы можем определить тот, который нужен нам:
Переходим в этот пункт, и мы видим наш 32-битный регистр, его адрес смещения, значение по умолчанию, способ доступа к регистру и перечисление того, за что отвечает каждый бит в регистре.
Смотрим на таблицу и видим нечто напоминающее опции включения тактирования на портах GPIO. Переходим к описанию и находим нужную нам опцию:
Соответственно если мы установим 19 бит в значение «1» то это обеспечит включение тактирования на порт I/O C – то есть на наш GPIOC. К тому же - нам нужно включить отдельно один бит из группы, не затрагивая остальные т.к. мы не должны мешать и изменять без надобности другие настройки.
Основываясь на материалах прошлого урока, мы знаем что для того чтобы выставить определенный бит нужно используя логическую операцию «ИЛИ» сложить текущее значение регистра с маской которая содержит те биты которые необходимо включить. Например, сложим значение регистра RCC->AHBENR по умолчанию, т.е. 0x14 и число 0x80000 тем самым включим тактирование GPIOC путем установки 19 бита:
Каким образом мы можем это сделать из программы? Всё достаточно просто. В данном случае у нас два варианта:
- Запись в регистр напрямую численного значения регистра напрямую через его адрес.
- Настройка с использованием библиотеки CMSIS
То есть, мы могли бы обращаться к адресам регистров напрямую по адресу и написать так:
IO uint32_t * register_address = (uint32_t *) 0x40021014U; // Адрес нашего регистра в памяти
*(__IO uint32_t *)register_address |= 0x80000; // Включаем 19 бит с нашим параметром
Второй вариант мне кажется наиболее привлекательным, т.к. библиотека CMSIS организована таким способом, что регистру можно обращаться, используя только его название. Препроцессор в ходе обработки текста программы перед компиляцией подставит все цифровые значения адреса регистра автоматически. Давайте разберем этот вопрос чуть подробнее.
Предлагаю открыть наш проект, который мы сделали в первом занятии, или скачайте предварительно подготовленый отсюда и удалите все содержимое программы оставив только подключенный заголовочный файл, функцию main() и инструкцию для включения тактирования (она нам понадобится для подробного разбора кода).
Наш код будет выглядеть следующим образом:
/* Заголовочный файл для нашего семейства микроконтроллеров*/
#include "stm32f0xx.h"
/* Тело основной программы */
int main(void)
{
/* Включаем тактирование на порту GPIO */
RCC->AHBENR|=RCC_AHBENR_GPIOCEN;
}
Давайте для ознакомления копнём вглубь библиотеки CMSIS.
Для того, чтобы быстро перейти к месту где объявлена та или иная константа или переменная в Keil реализована удобная функция. Кликаем правой кнопкой по необходимой нам константе, например, на RCC:
И мы переносимся в глубины библиотеки CMSIS, в которой увидим, что все регистры доступные для управления программным способом имеют вид TypeDef-структур, в том числе и наш RCC:
Провалившись подобным образом в RCC_TypeDef мы увидим структуру в которой описаны все поля нашего регистра:
Соответственно, мы можем спокойно обращаться к нужному нам регистру записью вида PERIPH_MODULE->REGISTER и присваивать ему определенное значение.
Помимо мнемонического обозначения регистров есть так же обозначения конкретных битов. Если мы провалимся к объявлению параметра RCC_AHBENR_GPIOCEN из нашей программы, то так же увидим объявление всех параметров:
Таким образом, используя библиотеку CMSIS у нас получается лаконичная читаемая запись нужного нам параметра в регистр, через установку которого мы запускаем тактирование на нужный нам порт:
/* Включаем тактирование на порту GPIO */
RCC->AHBENR|=RCC_AHBENR_GPIOCEN;
В качестве задания:
определите используя возможности Keil, каким образом получился адрес регистра RCC->AHBENR как 0x40021014.
Как настроить нужные нам пины GPIO для того чтобы можно было включить светодиод?
Итак, мы знаем что нужные нам светодиоды подключены к порту GPIOC к пинам PC8 и PC9. Нам нужно настроить их в такой режим, чтобы загорался светодиод. Хотелось бы сразу же сделать оговорку, что порты GPIO мы рассмотрим подробнее в другой статье и тут мы сконцентрируемся именно на работе с регистрами.Первым делом нам нужно перевести режим работы пинов PC8 и PC9 в режим Output. Остальные параметры порта можно оставить по умолчанию. Переходим в Reference Manual в раздел 9. General-purpose I/Os (GPIO) и открываем пункт отвечающий за режим работы пинов порта GPIO и видим что за этот параметр отвечает регистр MODER:
Судя по описанию, для установки пинов PC8 и PC9 в режим Output мы должны записать 01 в соответствующие поля регистра GPIOC.
Это можно сделать через прямую установку с помощью числовых значений:
Или через использование определений из библиотеки:
/* Включаем тактирование на порту GPIO */
GPIOC->MODER |= GPIO_MODER_MODER8_0 | GPIO_MODER_MODER9_0;
После данной инструкции наши пины PC8 и PC9 перейдут в режим Output.
Как включить светодиод?
Если мы обратим внимание на список доступных регистров для управления портом GPIO то можем увидеть регистр ODR:Каждый из соответствующих битов отвечает за один из пинов порта. Его структуру вы можете увидеть ниже:
Для того, чтобы обеспечить попеременную смену состояний светодиодов надо с определенным временным интервалом включать/выключать 8 и 9 биты. То есть попеременно присваивать регистру значение 0x100 и 0x200.
Сделать это мы можем через прямое присвоение значений регистру:
GPIOC->ODR = 0x100; // Зажигаем PC8, гасим PC9
GPIOC->ODR = 0x200; // Зажигаем PC9, гасим PC8
Можем через использование определений из библиотеки:
GPIOC->ODR = GPIO_ODR_8; // Зажигаем PC8, гасим PC9
GPIOC->ODR = GPIO_ODR_9; // Зажигаем PC9, гасим PC8
Но так как микроконтроллер работает очень быстро - мы не будем замечать смены состояний светодиодов и визуально будет казаться что они оба горят постоянно. Для того чтобы они действительно моргали попеременно мы внесем искусственную задержку в виде цикла который займет МК бесполезными вычислениями на некоторое время. Получится следующий код:
/* Зажигаем светодиод PC8, гасим PC9 */
GPIOC->ODR = GPIO_ODR_8;
for (int i=0; i<500000; i++){} // Искусственная задержка
/* Зажигаем светодиод PC9, гасим PC8 */
GPIOC->ODR = GPIO_ODR_9;
for (int i=0; i<500000; i++){} // Искусственная задержка
На этом первоначальное знакомство с регистрами и методами работы с ними мы можем закончить.
Проверка результатов работы нашего кода
Небольшое приятное дополнение в конце статьи: в Keil имеется отличный Debug-инструмент с помощью которого мы можем пошагово выполнить нашу программу и просмотреть текущее состояние любого периферийного блока. Для этого после загрузки прошивки после компиляции мы можем нажать кнопку Start Debug Session:Основные регистры порта ввода/вывода микроконтроллера STM32
Под портом понимается определенный именованный набор ног микроконтроллера. В STM микроконтроллерах они именуются как GPIOA,GPIOB,GPIOC и т.д. Порты ввода/вывода в микроконтроллерах STM32 имеют, как правило, по 16 линий (ног). Под линией понимается та или иная ножка микроконтроллера. Каждая линия порта может быть сконфигурирована определенным образом и выполнять следующие функции:
- цифрового ввода;
- цифрового вывода;
- входа внешнего прерывания;
- функцию ввода/вывода других модулей микроконтроллера.
Сконфигурировать порт на нужный режим работы можно использую регистры микроконтроллера. К этим регистрам можно обращаться напрямую, а также использовать специальные методы из библиотеки периферий.
Давайте рассмотрим основные регистры необходимые для работы с портами ввода/вывода.
Регистры отвечающие за конфигурацию порта
Прежде чем начать работу с портом вывода его нужно сконфигурировать под ваши нужды.
За настройку или конфигурацию порта отвечают регистры конфигурации. В микроконтроллерах семейства STM32F302xx, STM32F303xx и STM32F313xx это следующие регистры:
- GPIOx_MODER;
- GPIOx_OTYPER;
- GPIOx_OSPEEDR;
- GPIOx_PUPDR.
Регистр GPIOx_MODER (где x=A...F)
Это 32-х битный регистр отвечает за режим работы линии. Для конфигурации опеределенной линии требует 2 бита. Возможны следующие комбинации:
- 00 - линия настроена на вход;
- 01 - на выход;
- 10- режим альтернативной функции;
- 11 - аналоговый режим.
Регистр GPIOx_TYPER (где x=A...F)
Данный регистр используеться для настройки типа работы линии использует в работе 16 бит остальные 16 зарезервированны. Принемает следующие значения:
- 0 - пуш пульнай режим;
- 1 - открытый сток
Регистр GPIOx_PUPDR (где x=A...F)
Данные регистр отвечает за подтяжку. Принемает следующие значения:
- 00 - без подтяжки
- 01 - подтяжка к плюсу питания
- 10 - подтяжка к земле
Регистр GPIOx_SPEEDR (где x=A...F)
Регистр настройки скорости работы линии.
- 00 - 2 МГц;
- 01 - 10 МГц;
- 10 - 50 МГц.
Регистр вывода (выходной регистр) GPIOx_ODR (где x=A…F) – output data register
Данный регистр используется для вывода данных в порт. При записи определенных значений в данный регистр на линии (ножке) устанавливается аналогичное значение. Так как у нас 16 линий а регистр имеет 32 бита, то используются только первые (младшие) 16 бит.
Регистр ввода (регистр состояния порта или входных данных) GPIOx_IDR (где x=A…F) – input data register
Данный регистр доступен только для чтения. Своего рода индикатор состояния порта. Аналог PINxв микроконтроллерах серии AVR.
Регистр побитовой установки выходного порта GPIOx_BSRR
Данный регистр побитно устанавливает состояние ножки в порте вывода.
Подробнее о всех регистрах конкретного микроконтроллера можно узнать в referens manual.pdf который можно скачать с официального сайт www.st.com
Настройка порта микроконтроллера при помощи библиотеки
Также порт можно настроить при помощи специальной библиотеки, в которой находятся разные методы для работы с регистрами ввода/вывода, а также объявлены специальные переменные. Данная библиотека освобождает программиста от необходимости «вручную» вычислять какое значение нужно записать в тот или иной регистр.
Рассмотрим пример кода включающий светодиод
#include "stm32f30x.h" // Device header #include "stm32f30x_rcc.h" #include "stm32f30x_gpio.h" void init_led(void) { RCC_APB2PeriphClockCmd (GPIOE,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; //Задаем максимальную скорость GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Режим выход GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //Указываем ножку к которой присоединен светодиод GPIO_Init(GPIOE,GPIO_InitStructure); // Инициальзация структуры } int main(void) { init_led(); GPIO_SetBits(GPIOE,GPIO_Pin_8); //Кстанавливаем высокое состояние на ножке while(1) { _NOP(); } }
В целях экономии электроэнергии, вся периферия у микроконтроллеров отключена. Для того чтобы «активировать» ту или иную периферию необходимо для начала подать на него тактовые сигналы.
Мы работаем с портом GPIOE поэтому нам необходимо включить тактирование при помощи метода
RCC_APB2PeriphClockCmd (uint32_t RCC_APB2Periph, FunctionalState NewState);
Который принимает два значение первый это собственно сам порт который нам необходимо включить, а второй это состояние данного порта, включен или выключен.
Недавно коллега меня подсадил на идею создания умного дома, я даже успел заказать себе десятки разных датчиков. Встал вопрос о выборе Микроконтроллера (далее МК) или платы. После некоторых поисков нашёл несколько вариантов. Среди них были и 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! И, в первую очередь, давайте посмотрим в каких режимах могут работать порты ввода-вывода в STM32F10x. А режимов этих существует море, а именно:
- Input floating
- Input pull-up
- Input-pull-down
- Analog
- Output open-drain
- Output push-pull
- Alternate function push-pull
- Alternate function open-drain
А если по-нашему, то при работе на вход:
- Вход – Hi-Z
- Вход – подтяжка вверх
- Вход – подтяжка вниз
- Вход – аналоговый
При работе порта на выход имеем следующие варианты:
- Выход – с открытым коллектором
- Выход – двухтактный
- Альтернативные функции – выход типа «с открытым коллектором»
- Альтернативные функции – двухтактный выход
Вот кстати документация на STM32F103CB –
В даташите есть внушительная таблица, в которой показано, какие альтернативные функции имеются у конкретного вывода.
Вот, например, выводы PA9, PA10:
В столбце Default видим, какие функции будут выполнять эти пины при их настройке для работы в режиме Alternative function. То есть, настроив эти пины соответствующим образом они из просто PA9 и PA10 превратятся в Rx и Tx для USART1. А для чего же тогда столбец Remap ? А это не что иное, как очень полезная функция ремаппинга портов. Благодаря ремапу, Tx USARTA ’а , например, может переместится с пина PA9 на PB6. Довольно часто эта функция оказывается чертовски полезной.
Ну с режимами вроде бы все более-менее понятно, пришло время окинуть взором регистры, которыми управляются порты ввода-вывода.
Раз уж только что обсудили в каких режимах могут существовать выводы STM32F10x, сразу же давайте прошарим как же их можно собственно перевести в нужный режим. А для этого выделены аж два регистра – CRL и CRH. В первом конфигурируются выводы от 0 до 7, во втором, соответственно от 8 до 15. Регистры, как вы помните, 32-разрядные. То есть на 8 выводов приходтся 32 бита – получается 4 бита на одну ножку. Открываем даташит и видим:
Например, надо нам настроить ножку PB5. Идем в регистр GPIOB->CRL и выставляем сответствующие биты так как нам требуется (на картинке 32-х битный регистр CRL). Для PB5 это биты:
После восьмибиток может показаться все достаточно сложным и каким то корявым, но на самом деле реализовано все довольно изящно =). Посмотрим, что тут есть еще.
Выходной регистр GPIOx_ODR – напоминает регистр PORTx в AVR. Все что попадает в этот регистр сразу же попадает во внешний мир. Регистр 32-разрядный, а ножек всего 16. Как думаете, для чего используются оставшиеся 16? Все очень просто, биты регистра с 15 по 31 не используются вовсе)
Входной регистр GPIOx_IDR – аналог PINx в AVR. Структура его похожа на упомянутую структуру ODR. Все, что появляется на входе микроконтроллера, сразу же оказывается во входном регистре IDR.
Еще два полезных регистра GPIOx_BSSR и GPIOx_BRR. Они позволяют менять значения битов в регистре ODR напрямую, без использования привычных бит-масок. То есть, хочу я, например, выставить в единицу пятый бит ODR. Записываю единичку в пятый бит GPIOx_BSSR, и все, цель достигнута. Вдруг захотелось сбросить пятый бит ODR – единицу в 5 бит GPIOx_BRR и готово.
Итак, основные регистры рассмотрели, но, на самом-то деле, мы в наших примерах будем делать все иначе, используя Standard Peripheral Library. Так что лезем ковырять библиотеку. За GPIO в SPL отвечают файлы stm32f10x_gpio.h и stm32f10x_gpio.c . Открываем их оба и видим очень много непонятных цифр-букв-значков итд.
На самом деле, все очень просто и понятно. За конфигурацию портов отвечает структура GPIO_InitTypeDef .
typedef struct { uint16_t GPIO_Pin; // Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ GPIOSpeed_TypeDef GPIO_Speed; // Specifies the speed for the selected pins. This parameter can be a value of @ref GPIOSpeed_TypeDef */ GPIOMode_TypeDef GPIO_Mode; // Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIOMode_TypeDef */ } GPIO_InitTypeDef; |
Видим, что структура имеет три поля: GPIO_PIN, GPIO_Speed и GPIO_Mode . Нетрудно догадаться, что первая отвечает за номер ножки порта, которую мы хотим настроить, вторая – за скорость работы порта, ну и третья, собственно, за режим работы. Таким образом, для настройки вывода нам всего лишь нужно объявить переменную типа структуры и заполнить ее поля нужными значениями. Все возможные значения полей тут же – в stm32f10x_gpio.h . Например,
typedef enum { GPIO_Mode_AIN = 0x0 , GPIO_Mode_IN_FLOATING = 0x04 , GPIO_Mode_IPD = 0x28 , GPIO_Mode_IPU = 0x48 , GPIO_Mode_Out_OD = 0x14 , GPIO_Mode_Out_PP = 0x10 , GPIO_Mode_AF_OD = 0x1C , GPIO_Mode_AF_PP = 0x18 } GPIOMode_TypeDef; |
Все значения уже рассчитаны создателями SPL, так что для настройки какого-нибудь вывода для работы в режиме Output push-pull надо всего лишь в соответствующей структуре задать поле: GPIO_Mode = GPIO_Mode_Out_PP.
Ну вот, структура объявлена, поля заполнены как надо, что же дальше? Ведь мы всего лишь создали переменную. Причем тут регистры, микроконтроллеры и вообще электроника? Лезем в файл stm32f10x_gpio.c и находим там тучу различных функций для работы с STM32 GPIO. Рассмотрим функцию GPIO_Init() (код приводить не буду, все в файле библиотеки). Так вот, эта функция как раз и связывает нашу созданную структуру и конкретные регистры контроллера. То есть мы передаем в эту функцию переменную, в соответствии с которой выставляются нужные биты нужных регистров микроконтроллера. Все очень просто, но от этого не менее гениально. Поковыряйте еще файлы библиотеки. Там функции на любой случай есть) Кстати очень удобно – перед функцией идет описание переменных, которые она принимает и возвращает, а также описание собственно того, что эта функция призвана делать. Так что, разобраться несложно, но надо немного дружить с английским. Хотя без этого никуда;)
Отвлечемся ненадолго от портов ввода-вывода и обсудим один довольно тонкий момент. Чтобы использовать порты, либо любую другую периферию, ОБЯЗАТЕЛЬНО надо включить тактирование. И порты, и периферия изначально отключены от тактирования, так что без этого действия ничего не заведется. Программа скомпилируется, но на деле работать ничего не будет. За тактирование в SPL отвечают файлы stm32f10x_rcc.c и stm32f10x_rcc.h . Не забывайте добавлять их в проект.
Давайте уже перейдем к программированию. Как это принято, заставим диодик помигать) Чтобы получше разобраться с Standard Peripheral Library немножко усложним обычное мигание диодом – будем опрашивать кнопку, и если она нажата – диод загорается, иначе – гаснет. Запускаем Keil, создаем проект, добавляем все нужные файлы, не забываем про CMSIS. Из SPL для этого проекта нам понадобятся 4 файла, уже упомянутые выше. Создание нового проекта описано в предыдущей статье учебного курса. Также там можно найти ссылки на библиотеки)
Итак, код:
/****************************gpio.c*********************************/ //Подключаем все нужные файлы #include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" //Тут будет вся инициализация всей использующейся периферии void initAll() { //Объявляем переменную port типа GPIO_InitTypeDef GPIO_InitTypeDef port; //Это функция из файла stm32f10x_rcc.c, включает тактирование на GPIOA //GPIOA сидит на шине APB2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE) ; //Про эту функцию напишу чуть ниже GPIO_StructInit(& port) ; //Заполняем поля структуры нужными значениями //Первый вывод – вход для обработки нажатия кнопки – PA1 port.GPIO_Mode = GPIO_Mode_IPD; port.GPIO_Pin = GPIO_Pin_1; port.GPIO_Speed = GPIO_Speed_2MHz; //А про эту функцию мы уже говорили //Отметим только что один из параметров – указатель(!) на //нашу структуру GPIO_Init(GPIOA, & port) ; //Настраиваем вывод, на котором будет висеть диодик – PA0 port.GPIO_Mode = GPIO_Mode_Out_PP; port.GPIO_Pin = GPIO_Pin_0; port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, & port) ; } /*******************************************************************/ int main() { //Объявляем переменную для хранения состояния кнопки uint8_t buttonState = 0 ; initAll() ; while (1 ) { //С помощью функции из SPL считываем из внешнего мира //состояние кнопки buttonState = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) ; if (buttonState == 1 ) { GPIO_SetBits(GPIOA, GPIO_Pin_0) ; } else { GPIO_ResetBits(GPIOA, GPIO_Pin_0) ; } } } /****************************End of file****************************/ |
Кстати, возможно кто-то обратит внимание на наличие скобок { }, несмотря на всего лишь одну инструкцию в теле if и else . А это уже привычка) Очень рекомендуется так писать, особенно при разработке крупных проектов. При дописывании/исправлении программы невнимательный программист может не обратить внимания на отсутствие скобок и дописать вторую инструкцию, которая, как вы понимаете, уже окажется все блока if или else . Та же тема с циклами. Когда над проектом работает много народу, нет никаких гарантий, что кто-нибудь не окажется невнимательным, так что, чтобы не тратить минуты/часы на последующие поиски косяка, рекомендую ставить эти скобки всегда) Хотя может, кто-то и не согласится с такой логикой.
Нажимаем F7, компилируем, и вот наша первая программа для STM готова. Вроде бы код довольно подробно откомментирован, так что поясню только пару моментов.
Функция GPIO_StructInit(&port) – принимает в качестве аргумента адрес переменной port .
Эта функция заполняет поля структуры, переданной ей в качестве аргумента дефолтными значениями. Это делать не обязательно, но во избежание каких-нибудь непредсказуемых косяков, лучше всегда вызывать эту функцию.
Еще две функции, которые мы использовали:
- GPIO_SetBits(GPIOA, GPIO_Pin_0);
- GPIO_ResetBits(GPIOA, GPIO_Pin_0);
Ну вы и так догадались для чего они 😉
Итак мы закончили рассматривать STM32 порты ввода-вывода. В следующей статье познакомимся со средствами Keil’а для отладки.
Порты ввода/вывода 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 бит, доступных только для записи.