Регистры gpio stm32. EasySTM32 - Порты микроконтроллера. Старший конфигурационный регистр GPIOx_CRH

13.04.2019

Мы рассмотрели работу с битовыми операциями и двоичными числами, тем самым заложив основу для рассмотрения новой темы. В этом уроке мы с Вами рассмотрим очередной вопрос: что такое регистры и как с ними работать ?

Память и регистры

Одним из самых важных навыков необходимых при работе с микроконтроллерами является умение взаимодействовать с регистрами. Давайте для себя разберемся, что же это такое ?

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

Каждый из регистров имеет свой порядковый номер – адрес. Адрес регистра обозначается 32-битным числом представленным в шестнадцатеричной системе счисления. Путём записи по адресу регистра определённой комбинации единиц и нулей, которые обычно представлены в шестнадцатеричном виде, осуществляется настройка и управление тем или иным узлом в МК. Вспомним, что в программе для работы с битовыми операциями, мы могли представить в виде шестнадцатеричного числа произвольный набор единиц и нулей. В целом стоит отметить, что существует два вида регистров: регистры общего назначения и специальные регистры. Первые расположены внутри ядра МК, а вторые являются частью RAM-памяти.

Так же стоит отметить, что Reference Manual , который мы скачивали в , это один большой справочник по регистрам, содержащимся в целевом микроконтроллере, а библиотека CMSIS позволяет нам оперировать символьными именами регистров вместо числовых адресов. Например, к регистру 0x40011018 мы можем обратиться просто, используя символьное имя GPIOC_BSSR . Конкретные примеры конфигурирования мы рассмотрим в ходе разбора нашей программы из первого занятия .

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

  1. Названия регистра и описания его назначения
  2. Адреса регистра или смещением относительно базового адреса
  3. Значения по умолчанию после сброса
  4. Типа доступа к ячейкам регистра (чтение, запись, чтение/запись)
  5. Значения и описания параметров записываемых битов
Давайте рассмотрим пример работы с регистрами в конкретной ситуации, чтобы получить общее представление о принципах настройки микроконтроллера.

Разбор кода из первого занятия

Итак, давайте вспомним задачу, которую мы решили на первом уроке используя готовый код примера: нам было необходимо написать программу, которая бы обеспечила попеременное включение двух светодиодов на плате 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, даже для такой простой задачи как включение и выключение светодиода нам необходимо предварительно ответить на ряд вопросов:
  1. Как настроить, нужные нам, пины порта GPIO для того чтобы можно было включить светодиод?
  2. Как включить и выключить светодиод?
Ответим на них по порядку.

Куда подключены наши светодиоды? К какому выводу микроконтроллера?

Для того, чтобы посмотреть где что находится на плате Discovery, а в частности, нужные нам светодиоды - нужно открыть Schematic-файл, либо тот который мы скачали с сайта ST , либо прямо из Keil:


Открыв Schematic мы увидим схему всего того, что есть на плате - схему ST-Link, обвязку всей периферии и многое другое. На текущий момент нас интересуют два светодиода, ищем их обозначение:


Как мы видим, наши светодиоды подключены к порту GPIOC на 8 и 9 пин.

Как включить тактирование на нужный порт GPIO?

В целом, любая работа с периферией в микроконтроллерах STM32 сводится к стандартной последовательности действий:
  1. Включение тактирования соответствующего периферийного модуля. Осуществляется это через регистр RCC путем подачи тактового сигнала напрямую с шины на которой находится данный модуль. По умолчанию тактирование всей периферии отключено для минимизации энергопотребления.
  2. Настройка через управляющие регистры, путем изменения параметров специфичных для конкретного периферийного устройства
  3. Непосредственный запуск и использование результатов работы модуля
То есть, для начала работы нам нужно запустить тактирование на порт GPIOC. Это делается напрямую через обращение к регистру RCC отвечающему за тактирование всего и вся и включению тактового сигнала с шины, к которой подключен наш порт GPIO.

Внимание! Вопрос касательно системы тактирования, её настройки и использования мы подробно рассмотрим в отдельной статье.

Найти к какой шине подключен наш порт 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 бита:

Каким образом мы можем это сделать из программы? Всё достаточно просто. В данном случае у нас два варианта:

  1. Запись в регистр напрямую численного значения регистра напрямую через его адрес.
  2. Настройка с использованием библиотеки 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:

Порты ввода/вывода пожалуй важнейшая часть микроконтроллера, без неё всё остальное просто бессмысленно. Сколько бы не было у контроллера памяти, периферии, какой бы высокой не была тактовая частота - это всё не имеет значения если он не может взаимодействовать с внешним миром. А взаимодействие это осуществляется через эти самые порты ввода/вывода. Далее для краткости будем называть их просто портами. Порт это некоторый именованный набор из 16-ти (как правило) ног контроллера, каждая из которых может быть индивидуально настроена и использована. Количество портов может различаться, например в контроллере установленном в отладочной плате STM32vl Discovery имеются три порта A,B,C. Существует два основных режима работы ног контроллера: вход и выход. Когда нога контроллера настроена на выход - к ней можно прицепить любой потребитель: светодиод, пищалку, да и вообще что угодно. Нужно понимать что ноги у контроллера не потянут большую нагрузку. Максимальный ток который может пропустить через себя одна нога составляет ~20 мА. Если планируется подключать что-то с более высоким энергопотреблением то нужно делать это через транзисторный ключ. В противном случае нога порта (а то и весь порт, чем черт не шутит) сгорит и перестанет выполнять свои функции. Чтобы обезопасить ногу порта можно прицепить к ней резистор номиналом примерно 220 ом. Таким образом при напряжении питания 3.3 вольта даже при коротком замыкании ноги на землю ток не превысит критического значения. Второй режим работы ноги контроллера - это вход. Благодаря этому режиму мы можем считывать например состояние кнопок, проверяя есть ли на ноге напряжение или нет. Это вкратце, а сейчас рассмотрим подробнее как работать с портами. Рассматривать будем конечно же на практике, благо что аппаратная часть (светодиоды и кнопка) для наших экспериментов уже реализована на плате STM32vl Discovery. Если же платы нет, то можно подключить к контроллеру светодиоды и кнопку следующим образом:

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

  1. Включить тактирование порта
  2. Настроить две ножки как выходы
  3. Установить логическую единицу на 2-х выводах порта

Для начала создадим проект в CooCox"e точно так же как мы [делали ранее] . Запишем в файл main.c следующий код и будем разбираться:

#include int main(void) { RCC->APB2ENR |= RCC_APB2Periph_GPIOC; GPIOC->CRH |=0x33; GPIOC->CRH &= ~0xCC; GPIOC->ODR |= (GPIO_ODR_ODR9 | GPIO_ODR_ODR8); }

Всего-то четыре строчки кода, но сколько смысла:) Для начала разберемся что значит "Включить тактирование порта". В контроллере полно периферии: Таймеры, АЦП, USART и т.д. Порт ввода/вывода является такой же периферией. Когда периферия включена (подаются тактовые импульсы) - она потребляет ток. Нет тактирования - нет потребления. По умолчанию вообще весь этот зоопарк периферии вырублен. Итак нас интересует порт C, ведь именно на нем висят наши светодиоды. Для включения/выключения периферии есть два регистра RCC_APB1ENR и RCC_APB2ENR. Нам нужен последний, потому что через него мы можем управлять тактированием порта C. Устроен этот регистр так:

Как видно на картинке в нем есть бит IOPCEN. Установив его в единицу мы включим наш порт. Именно это и делает строчка кода

RCC->APB2ENR |= RCC_APB2Periph_GPIOC;

После включения тактирования мы должны настроить некоторые (а именно 8-ю и 9-ю) ноги порта на выход. За конфигурирование вообще любого порта отвечают два регистра GPIOx_CRL и GPIOx_CRH где икс это буква порта (от A до G), в нашем случае это буква С. Оба регистра выполняют одну и ту же функцию, просто GPIOx_CRL отвечает за конфигурирование младшей половины порта (ножки с 0 по 7), а GPIOx_CRH старшей (ножки с 8 по 15). Наши светодиоды висят на ногах PC8 и PC9 а это значит что для настройки этих ног в режим выхода нам потребуется регистр GPIOC_CRH. Вот так он устроен:

Как видно из этой красивой и цветной картинки, на каждую ногу отводится по четыре бита. Причем биты объединены в две группы по два бита в каждой. первая группа - MODE. Собственно эти биты решают входм или выходом будет конкретная ножка порта, допустимы следующие комбинации:

Максимальная частота в моём понимании это насколько быстро нога может менять свое состояние, скорее всего частота влияет на энергопотребление. Теперь для рассмотрим следующую группу бит CNF. Если мы настроили ногу на выход (биты MODE отличны от нуля) то биты группы CNF могут принимать следующие значения:

Тут всё немного сложнее, во-первых разберёмся что подразумевается под обычным и альтернативный режимами. В обычном режиме вы можете распоряжаться ногой как вам угодно, например установить единицу или ноль при помощи своего кода. В альтернативном режиме вы передаёте эту ножку контроллера в распоряжение какой-либо периферии контроллера например UART"у, SPI, I2c и всему прочему что нуждается в ножках. Теперь разберемся чем отличается push-pull от открытого коллектора. В режиме push-pull нога всегда находится в одном из двух состояний: На ней всегда либо земля либо полное напряжение питания. В режиме открытого коллектора: Земля или ничего, нога просто как-бы зависает в воздухе ни к чему не подключенная внутри контроллера. Теперь рассмотрим что означают те же самый два бита если наш порт настроен на вход (биты MODE обнулены):

Аналоговый режим предназначен для работы АЦП, если мы хотим чтоб АЦП мог производить измерения используя эту ногу мы должны выбрать этот режим. Вход без подтяжки делает ногу входом с Hi-z состоянием, это означает что сопротивление входа велико и любая электрическая наводка (помеха) может вызвать появление на таком входе единицу или ноль, причем сделать это не предсказуемо. Во избежание этого нужно использовать подтяжку, она позволяет установить на входе какое либо устойчивое состояние которое не будет зависеть от помех. Подтяжка представляет собой резистор большого сопротивления подключенный одним концом к земле или к плюсу питания, а другим концом ко входу. Например если включена подтяжка к плюсу питания, то когда нога контроллера ни куда не припаяна на ней всегда логическая единица. Если мы припаяем кнопку между этой ножкой и землёй, то всякий раз при нажатии кнопки на ноге будет появляться логический ноль. Если бы подтяжка была выключена, то в момент нажатия кнопки на ноге так же появлялся бы ноль, но при отпущенной кнопке нога могла бы легко поймать любую наводку и вызвать появление логической единицы на ноге. В результате, микроконтроллер бы думал что кто-то хаотично жмет на кнопку. Мы рассмотрим все это на практике чуть позже, а сейчас вернемся к нашему регистру GPIOC_CRH. Итак мы планируем установить биты этого регистра (для двух ножек PC8 и PC9) следующим образом:


Исходя из вышесказанного, такая комбинация бит настроит обе ножки на выход с максимальной частотой 50 МГц в обычном режиме push-pull, что нам вполне подходит. Эта строчка устанавливает в единицы биты MODE:

GPIOC->CRH |=0x33;

А вот эта, обнуляет биты CNF:

GPIOC->CRH &= ~0xCC;

После выполнения этих двух строк, младшие 8 бит этого регистра будут такими как на рисунке выше: 00110011, при этом все остальные биты останутся в том состоянии в котором они и были, наш код их не затронет. В принципе, в данном случае не будет ничего страшного если вместо этих двух строк мы просто напишем:

GPIOC->CRH = 0x33; // 0x33 это и есть 00110011

Но нужно понимать что во все остальные биты (кроме первых восьми) запишутся нули, и это повлияет на конфигурацию остальных пинов (они все станут аналоговыми входами). Теперь когда обе ножки сконфигурированы можно попробовать зажечь светодиоды. За вывод данных в порт C отвечает регистр GPIOC_ODR, записывая в определённый бит единицу, мы получаем логическую единицу на соответствующей ножке порта. Поскольку в порте С 16 ножек, а регистр 32-х битный, то используются только первые 16 бит. Светодиоды подключены к пинам PC8 и PC9, поэтому мы должны установить восьмой и девятый биты. Для этого служит строчка:

GPIOC->ODR |= (GPIO_ODR_ODR9 | GPIO_ODR_ODR8);

Очень надеюсь, что читатели знакомы с битовой арифметикой в Си:) ибо без неё может быть сложновато. Ну собственно все, после компиляции и загрузки программы в контроллер - на платке загорятся два светодиода: синий и зелёный. Использование регистра GPIOC_ODR - это не единственный способ изменить состояние порта С. Существует еще один регистр позволяющий сделать это - GPIOC_BSRR. Этот регистр позволят атомарно устанавливает состояние какой-либо ножки. Ведь в примере выше мы делали следующим образом:

1) Считывали текущее состояние регистра GPIOC_ODR в некоторую временную переменную

2) устанавливали в ней нужные биты (8-й и 9-й)

3) записывали то что получилось обратно в регистр GPIOC_ODR.

Чтение->модификация->запись это довольно долгая процедура, иногда надо делать это очень быстро. Вот тут то и выходит на сцену регистр GPIOC_BSRR. Посмотрим как он устроен:

На каждую ножку порта выделяется по два бита: BRXX и BSXX. Далее всё просто: записывая единицу в бит BSXX мы устанавливаем на соответствующей ножке логическую единицу. Записывая единицу в бит BRXX мы сбрасываем в ноль соответствующую ножку. Запись нулей в любой из битов не приводит ни к чему. Если мы заменим последнюю строчку программы на:

GPIOC->BSRR=(GPIO_BSRR_BS8|GPIO_BSRR_BS9);

то получим тот же результат, но работает оно быстрей:) Ну а для того чтоб сбросить в ноль определённые биты порта С необходим так же записать две единицы но уже в биты BR8 и BR9:

GPIOC->BSRR=(GPIO_BSRR_BR8|GPIO_BSRR_BR9);

Но и это еще не всё :) Так же изменить состояние порта можно при помощи регистра GPIOC_BRR, установив в единицу какой либо из первых 16-бит, мы сбросим в ноль соответствующие ножки порта. Зачем он нужен - не понятно, ведь есть же регистр GPIOC_BSRR который может делать тоже самое да и еще плюс устанавливать логическую единицу на ноге (а не только сбрасывать в ноль как GPIOC_BRR). С выводом данных в порт теперь точно всё. Настало время что-то из порта прочитать. Ну а читать мы будет состояние кнопки, которая у нас подключена к ноге PA0. Когда кнопка не нажата - на ножке PA0 присутствует логический ноль за счёт резистора номиналом 10 кОм который подтягивает этот вывод к земле. После замыкания контактов кнопки, слабенькая подтяжка к земле будет подавлена напряжением питания и на входе появится логическая единица. Сейчас мы попробуем написать программу которая читает состояние ножки PA0 и в зависимости от наличия логической единицы, зажигает или гасит светодиоды. Управлять состоянием светодиодов мы уже научились из предыдущего примера, осталось только разобраться как читать что-то из порта. Для чтения из порта А используется регистр GPIOA_IDR. В его внутреннем устройстве нет ничего особо сложного и поэтому я не буду рисовать тут картинку, а объясню всё парой слов: Первые 16 бит регистра соответствуют 16 ногам порта. Что приходит в порт - то и попадает в этот регистр. Если на всех ногах порта А будут присутствовать логические единицы, то и из регистра GPIOA_IDR мы прочитаем 0xFFFF. Естественно, не надо забывать настраивать порт как вход, хотя по умолчанию он настроен именно так как нам надо, просто сделаем это для понимания сути дела. После этого в бесконечном цикле мы считываем регистр GPIOA_IDR, зануляем в все биты кроме нулевого (кнопка ведь висит на PA 0 ) и сравниваем результат с единицей. Если результат равен единице значит кто-то удерживает нажатой кнопку (и надо зажечь светодиоды), в противном случае (если 0) кнопка отпущена и светодиоды надо погасить. Может возникнуть здравый вопрос: Зачем занулять все остальные биты кроме нулевого? А дело тут вот в чем, все остальные ноги порта (как и PA0) так же настроены на вход без подтяжки. Это означает что в любой момент времени там может быть вообще всё что угодно, всё зависит от количества вокруг контроллера наводок и помех. Следовательно при нажатой кнопке из регистра GPIOA_IDR может прочитаться не только 0000 0000 0000 0001 но и например 0000 0000 01 01 0001 а следовательно сравнивать такое число с единицей нельзя, однако после зануления остальных битов вполне можно. Посмотрим на код реализующий всё сказанное выше:

#include int main(void) { //Включим тактирование порта С (со светодиодами) и порта А (с кнопкой) RCC->APB2ENR |= (RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA); //Настроим ножки со светодиодами как выходы GPIOC->CRH |=0x33; GPIOC->CRH &= ~0xCC; //Настроим ногу PA0 как вход без подтяжки (подтягивающий резистор уже есть на плате) GPIOA->CRL |= 0x04; GPIOA->CRL &= ~0x11; while(1) { //Бесконечный цикл if ((GPIOA->IDR & 0x01)==0x01) { //Кнопка нажата? GPIOC->BSRR=(GPIO_BSRR_BS8|GPIO_BSRR_BS9); //Зажигаем светодиоды } else { GPIOC->BSRR=(GPIO_BSRR_BR8|GPIO_BSRR_BR9); //Гасим светодиоды } } }

Код не особо сложный, но если вдруг появились вопросы, то они принимаются в комментариях. Напоследок хотелось бы в двух словах рассказать о еще одном регистре с непонятной областью практического применения - GPIOx_LCKR. Он служит для блокировки настроек порта. Это означает что настроив какую либо ножку порта на выход и установив соответствующий бит блокировки в этом регистре, мы не сможем сделать её входом (только после сброса контроллера).


Как видно из рисунка, кроме битов блокировки для каждой ноги порта, тут есть еще бит LCKK. Он используется когда мы хотим установить какой-либо бит блокировки. Алгоритм работы с этим регистром следующий:

  1. Устанавливаем нужные биты блокировки
  2. Записываем в LCKK единицу
  3. Записываем в LCKK ноль
  4. Записываем в LCKK единицу
  5. Читаем из LCKK ноль
  6. Читаем из LCKK единицу (опционально, только для того, чтоб убедиться что блокировка сработала)

Доброго времени суток! Сегодня мы займемся изучением 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 может иметь следующие состояния:

Регистр защиты от изменения настроек

Port configuration lock register (GPIOx_LCKR) (x=A..G)

Поле

Поле

Установить блокируемый бит в GPIOx_LCKRДля невозможности изменения настроек порта в микроконтроллерах STM32 используется регистр GPIOx_LCKR. Его младщие 15 бит отвечают за соответсвующие линии порта ввода/вывода. Бит 16, установленный в 1, разрешает блокировку изменения настроек. все биты доступны на чтение/запись. Для усложнения жизни пользователям ;-) , используется специальный алгоритм установки защиты. Если он применен, то следующее изменение конфигурации доступно только после сброса. Алгоритм установки защиты выглядит следующим образом:

  1. Установить бит 16 GPIOx_LCKR.
  2. Сбросить бит 16 GPIOx_LCKR.
  3. Установить бит 16 GPIOx_LCKR.
  4. Прочитать GPIOx_LCKR
  5. Повторно прочитать 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 состоит из 16 выводов, а каждый вывод может быть сконфигурирован одним из 8 способов.

Ниже изображена структура порта ввода-вывода.


Для того чтобы порт заработал его надо подключить к шине APB2 , установив соответствующий бит IOPxEN , в регистре разрешения тактирования периферийных блоков RCC_APB2ENR .
RCC->APB2ENR |= RCC_APB2ENR_IOPxEN; // Разрешить тактирование PORTx.
После включения все выводы находятся в состоянии плавающего входа , он же высокоимпедансный вход , он же Hi-Z , он же третье состояние .
  • Выходной драйвер выключен
  • Триггер Шмитта отключён
  • Подтягивающие резисторы отключены
  • В регистре IDR всегда “0”

В режиме входа

  • Выходной драйвер выключен
  • В зависимости от настройки, включаются резисторы подтяжки
  • Каждый такт шины APB2 данные с входа поступают в регистр IDR, считав этот регистр можно узнать состояние ножки

В режиме выхода

  • В режиме Open Drain при записи “0” открывается нижний транзистор, при записи “1” линия остаётся не подключённой
  • В режиме Push Pull при записи “1” открывается верхний транзистор, при записи “0” - нижний
  • Входной Триггер Шмитта включён
  • Резисторы подтяжки отключены

В режиме альтернативной функции

  • Драйвер включается в режиме Push Pull или Open Drain, в зависимости от конфигурации
  • Выходной драйвер управляется сигналами периферии, а не регистром ODR
  • Входной триггер Шмитта включён
  • Резисторы подтяжки отключены
  • По каждому такту шины APB2 данные c выхода передаются в регистр IDR, оттуда же их можно считать в режиме Open Drain
  • Чтение регистра ODR возвращает последнее записанное значение в режиме Push Pull

Из таблицы видно, что возможны два варианта конфигурации, в режиме альтернативной функции: Push Pull и Open Drain . Например, мы хотим, настроить в режим альтернативной функции ножку, отвечающую за приём данных по USART. Для этого в Reference Manual RM0008, начиная с 161 страницы, идут таблицы, в которых можно посмотреть как cконфигурировать вывод, для разной периферии.


Нам подойдет Input floating или Input pull-up .

Конфигурация выводов задаётся в регистрах GPIOx_CRL , GPIOx_CRH , в этих регистрах для конфигурации каждого вывода отведено 4 бита, MODE и CNF . В GPIOx_CRL конфигурируются выводы с 0 по 7, а в GPIOx_CRH с 8 по 15.



Если MODE = 00 , то вывод настроен на вход, конфигурация входа в таком случае задаётся в регистрах CNF . Если MODE не равен 00, в таком случае вывод настроен как выход, а значение MODE задаёт максимальную частоту, с которой может он переключаться.
//Полагаем что выводы после сброса в режиме плавающего входа //разрешаем тактирование порта A RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //вход с подтяжкой к + GPIOA->CRL &= ~GPIO_CRL_CNF0; GPIOA->CRL |= GPIO_CRL_CNF0_1; GPIOA->ODR |= GPIO_ODR_ODR0; //вход с подтяжкой к - GPIOA->CRL &= ~GPIO_CRL_CNF1; GPIOA->CRL |= GPIO_CRL_CNF1_1; GPIOA->ODR &= ~GPIO_ODR_ODR1; //аналоговый режим GPIOA->CRL &= ~GPIO_CRL_CNF2; //выход с открытым стоком 2MHz GPIOA->CRL &= ~GPIO_CRL_CNF3; GPIOA->CRL |= GPIO_CRL_CNF3_0; GPIOA->CRL |= GPIO_CRL_MODE3_1; //двухтактный выход 10MHz GPIOA->CRL &= ~GPIO_CRL_CNF4; GPIOA->CRL |= GPIO_CRL_MODE4_0; //альтернативная ф-ция, двухтактный выход, 50 MHz GPIOA->CRL &= ~GPIO_CRL_CNF5; GPIOA->CRL |= GPIO_CRL_CNF5_1; GPIOA->CRL |= GPIO_CRL_MODE5; //альтернативная ф-ция, выход с открытым стоком, 50 MHz GPIOA->CRL |= GPIO_CRL_CNF6; GPIOA->CRL |= GPIO_CRL_MODE6;
Считать состояние входа можно с помощью Port input data register или коротко GPIOx_IDR , где x – название порта, может быть от A до G. Считать состояние любого вывода можно из 16 младших бит, старшие 16 бит не используются.


//проверяем значение нулевого вывода порта А if (GPIOА->IDR & GPIO_IDR_IDR0)
Если порт настроен на выход, управлять его состоянием можно с помощью регистра Port output data register или GPIOx_ODR . Значение, которое мы запишем в этот регистр, появится на соответствующих выводах порта. Для установки состояния порта, выделены 16 младших бит, старшие 16 бит не используются.


//если вывод в режиме входа то активируется подтяжка к питанию GPIOA->ODR |= GPIO_ODR_ODR0; //или к земле GPIOA->ODR &= ~GPIO_ODR_ODR0; //если в режиме выхода, то на нём установится соответствующий лог.уровень //например так можно установить все выходы порта в 1 GPIOA->ODR = 0xFFFF;
В STM32 возможно атомарно управлять отдельными битами порта с помощью регистров GPIOx_BSRR (Port Bit Set/Reset Register) и GPIOx_BRR (Port Bit Reset Register).
Для установки отдельного бита порта вручную, надо считать значение порта, изменить нужный бит с помощью маски и результат вернуть обратно в GPIOx_ODR . Так как действий целых три, то возникшее между ними прерывание, может подпортить данные. С помощью описанных выше регистров, это делается в одно действие.
Для сброса бита надо в нулевой бит GPIOx_BRR записать единичку, при этом в нулевой бит GPIOx_ODR запишется 0, для этой операции выделены младшие 16 бит, старшие 16 бит не используются.


//сбросить нулевой бит порта А GPIOA->BRR = GPIO_BRR_BR0;
С GPIOx_BSRR всё чуть интереснее, младшие 16 бит отвечают за установку 1, старшие 16 бит за сброс в 0. Чтобы установить 1 в нулевой бит, надо в нулевой бит GPIOx_BSRR записать 1. Чтобы установить 0 в нулевой бит, надо в 16 бит установить 1.


//сбросить нулевой бит GPIOA->BSRR = GPIO_BSRR_BR0; //установить нулевой бит GPIOA->BSRR = GPIO_BSRR_BS0;
У STM32 есть возможность защитить конфигурацию порта от изменения, для этого выделен регистр GPIOx_LCKR . Младшие 16 бит используются для выбора вывода, который хотим заблокировать (выбор бита осуществляется установкой единицы), затем специальной последовательностью записей в 16 бит(LCKK ) осуществляется блокировка.


Последовательность следующая: записать в LCKK 1 , записать 0 ,записать 1, затем из регистра LCKR считать 0, считать 1. Последняя считанная единица говорит о том, что вывод заблокирован. Разблокировка вывода произойдёт только после перезагрузки контроллера.
#include "stm32f10x.h" uint32_t temp; int main(void) { //разрешаем тактирование порта RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //настраиваем как двухтактный выход GPIOA->CRL &= ~GPIO_CRL_CNF0; //с максимальной частотой 50MHz GPIOA->CRL |= GPIO_CRL_MODE0; //выбираем вывод который хотим залочить GPIOA->LCKR |= GPIO_LCKR_LCK0; //записываем 1 GPIOA->LCKR |= GPIO_LCKR_LCKK; //записываем 0 GPIOA->LCKR &= ~GPIO_LCKR_LCKK; //записываем 1 GPIOA->LCKR |= GPIO_LCKR_LCKK; //считываем 2 раза temp = GPIOA->LCKR; temp = GPIOA->LCKR; }
Для получения более подробной информации можно обратиться Reference Manual RM0008 , к разделу General-purpose and alternate-function I/Os (GPIOs and AFIOs) .
Похожие статьи