STM32. 3. Порты ввода-вывода (GPIO)
С использованием Standard Peripheral Library
Ранее мы уже научились создавать проект и смогли помигать светодиодом, на чём и остановились. Теперь рассмотрим, как именно мы смогли им управлять и как можно управлять не только им, а любой дискретной нагрузкой или датчиком. А точнее: как управлять портами ввода-вывода на STM32.
Порты ввода вывода у STM32
Для начала рассмотрим ранее представленный код:
#include "stm32f10x.h" GPIO_InitTypeDef GPIO_InitStructure; void init() // инициализация { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; // использовать выводы PC8 и PC9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // на выход GPIO_Init(GPIOC, &GPIO_InitStructure); } void delay(unsigned long p) // задержка { unsigned long i; for(i=0;i<p ;i++); } int main(void) { init(); GPIO_SetBits(GPIOC, GPIO_Pin_9); // включить зеленый светодиод, подключенный к PC9 while(1) // в бесконечном цикле крутиться программа { GPIO_SetBits(GPIOC, GPIO_Pin_8); // включить синий светодиод, подключенный к Led PC8 delay(2000000); GPIO_ResetBits(GPIOC, GPIO_Pin_8);// погасить синий светодиод, подключенный к Led PC8 delay(2000000); } }
Его можно, по функциям, разделить на 3 части: инициализация (в нашем случае порта ввода-вывода), задержка и главная функция, в которой мы вызвали инициализацию и в бесконечном цикле включали и выключали светодиод с заданной задержкой.
Начнём с самого простого и отвлечённого – с задержки delay(unsigned long p). В ней нет ничего сверх выдающегося: гоняем пустой цикл заданное количество раз, что в пустую расходует такты контроллера, т.е. занимает его пустой работой на некоторое время, т.е. организует задержку на заданное «время». «Время» условное, не в секундах и микросекундах, его рассмотрим позже, а в условных единицах.
Инициализация:
GPIO_InitTypeDef GPIO_InitStructure; void init() // инициализация { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; // использовать выводы PC8 и PC9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // на выход GPIO_Init(GPIOC, &GPIO_InitStructure); }
С использованием CMSIS и Standard Peripheral Library выглядит таким образом. Для использования второй библиотеки нужно добавить флаг USE_STDPERIPH_DRIVER в настройках компилятора.
По умолчанию вся периферия у контроллера выключена и нужно включить всё, что нам необходимо. Включение портов ввода вывода происходит в 3 этапа:
1 этап — Включаем тактирование порта.
Как было сказано ранее, вся периферия контроллера отключена. Первым делом нужно на модуль подать тактирующий сигнал. Для этого к проекту нужно подключить библиотеку RCC – контроллер сброса и тактирования, которая содержится в Standard Peripheral Library. Для этого из папки src нужно добавить в проект файл stm32f10x_rcc.c. Файл stm32f10x_rcc.h уже подключен в stm32f10x_conf.h, который, в свою очередь, подключен в файле stm32f10x.h, который мы подключили к проекту.
Для подачи тактового сигнала для портов ввода-вывода в библиотеке RCC отвечает функция:
RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
Первое значение – это название модуля, порты ввода-вывода имеют следующие имена:
RCC_APB2Periph_GPIOA
RCC_APB2Periph_GPIOB
RCC_APB2Periph_GPIOC
RCC_APB2Periph_GPIOD
RCC_APB2Periph_GPIOE
RCC_APB2Periph_GPIOF
RCC_APB2Periph_GPIOG
Для портов A, B, C, D, E, F и G соответственно. У STM32F100RB присутствуют первые три порта полностью и часть порта D.
Второе значение ENABLE и DESABLE, т.е. включить или выключить тактирование.
Всё вместе это имеет следующий вид:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
2 этап — Задаём настройки порта.
Для начала объявляем структуру, которая будет содержать настройки для порта ввода-вывода:
GPIO_InitTypeDef GPIO_InitStructure;
Сама структура выглядит следующим образом:
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 – режим работы порта.
Пины выглядят следующим образом:
GPIO_Pin_0 , GPIO_Pin_1 … GPIO_Pin_15 , GPIO_Pin_ALL .
Частот 3:
GPIO_Speed_2MHz
GPIO_Speed_10MHz
GPIO_Speed_50MHz
Естественно: чем больше частота, тем он сможет быстрее переключаться.
Режимов 8:
GPIO_Mode_AIN — аналоговый вход.
GPIO_Mode_IN_FLOATING — вход без подтяжки.
GPIO_Mode_IPD — вход с подтяжкой к земле.
GPIO_Mode_IPU — вход с подтяжкой к питанию.
GPIO_Mode_Out_OD — выход с открытым коллектором.
GPIO_Mode_Out_PP — выход с подтяжкой.
GPIO_Mode_AF_OD — альтернативный выход с открытым коллектором.
GPIO_Mode_AF_PP — альтернативный выход с подтяжкой.
Настройки для выше приведенного примера, когда к двум выходам PC8 и PC9 подключены светодиоды, выглядит следующим образом:
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
Пины порта C 8 и 9 настроены на выход с подтяжкой и работают на минимальной частоте в 2МГц.
3 этап — Передаём настройки порту.
После подачи тактовых импульсов на порт, заполнения структуры с настройками нужных пинов порта, передаём настройки (заполненную структуру) порту.
GPIO_Init(GPIOC, &GPIO_InitStructure);
Порты с GPIOA по GPIOG. Структуру рассмотрели ранее.
Действия с портом.
В распоряжении Standard Peripheral Library есть следующие варианты развития событий:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
— установить бит или биты (включить пин или пины) на заданном порте
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
— сбросить бит или биты (выключить пин или пины) на заданном порте
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
— в режиме выходного порта получить значения бита (узнать состояние пина) на выбранном порте
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
— в режиме выходного порта получить значения битов (узнать состояние пинов) на выбранном порте
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
— в режиме входного порта получить значения бита (узнать состояние пина) на выбранном порте
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
— в режиме входного порта получить значения битов (узнать состояние пинов) на выбранном порте
Где:
GPIOx – название порта (с GPIOA по GPIOG)
GPIO_Pin – устанавливаемый или запрашиваемый бит или биты (вывод или выводы)
Функции чтения возвращают значение установленного бита или порта целиком.
Пример.
С тем, как настроить порт разобрались, теперь подправим ранее созданный проект. Добавим в проект кнопку. Ту кнопку, что установлена на Discovery и подключена к PA0.
Схема подключения элементов
Для этого сначала подадим тактирование на порт A:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
Далее, воспользовавшись ранее объявленной структурой запишем в неё настройки для PA0:
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
Задача простая, поэтому 2МГц хватит. PA0 – это первый, а точнее на компьютерном языке нулевой вывод – GPIO_Pin_0. Вывод будем использовать как вход; на плате установлен подтягивающий резистор к земле, поэтому можем сконфигурировать, как вход без подтяжки (GPIO_Mode_IN_FLOATING) или продублировать подтяжку к земле (GPIO_Mode_IPD), вдруг забыли резистор впаять 🙂 .
Под конец передадим наши настройки порту:
GPIO_Init(GPIOA, &GPIO_InitStructure);
В качестве действия – при нажатой кнопки будет загораться зеленый светодиод (GPIO_Pin_9), иначе гаснуть.
if(GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0)) { GPIO_SetBits(GPIOC, GPIO_Pin_9); } else { GPIO_ResetBits(GPIOC, GPIO_Pin_9); }
Выше представленный код с дополнением приобретет следующий вид:
#include "stm32f10x.h" GPIO_InitTypeDef GPIO_InitStructure; void init() // инициализация { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; // использовать выводы PC8 и PC9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // на выход GPIO_Init(GPIOC, &GPIO_InitStructure); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // использовать вывод PA0 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // на вход GPIO_Init(GPIOA, &GPIO_InitStructure); } void delay(unsigned long p) // задержка { while(p>0){p--;} } int main(void) { init(); // инициализация while(1) // в бесконечном цикле крутиться программа { if(GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0))// Если нажата кнопка PA0 { GPIO_SetBits(GPIOC, GPIO_Pin_9); // включить зеленый светодиод, подключенный к Led PC9 } else { GPIO_ResetBits(GPIOC, GPIO_Pin_9);// погасить зеленый светодиод, подключенный к Led PC9 } GPIO_SetBits(GPIOC, GPIO_Pin_8); // включить синий светодиод, подключенный к Led PC8 delay(2000000); GPIO_ResetBits(GPIOC, GPIO_Pin_8); // погасить синий светодиод, подключенный к Led PC8 delay(2000000); } }
Для облегчения и упрощения кода переключение производиться только в момент смены состояния синего светодиода (PC8).
Alex_EXE | 11.08.2013 | STM32 |
Сергей пишет 12.10.2013 в 19:31 #
В каком файле находится описание структуры GPIO_InitTypeDef GPIO_InitStructure; ?
Сергей пишет 12.10.2013 в 23:09 #
как целиком порт настроить на вход/выход , и как на целом порте установить лог.уровень ?
Сергей пишет 13.10.2013 в 02:02 #
и как сделать что бы русские комментарии работали?
замена шрифтов и выбор другой кодировки не помогло
µVision V4.72.10.0
Alex_EXE пишет 16.10.2013 в 00:50 #
1:В файлах GPIO библиотеки — stm32f10x_gpio.h
2:Используйте — GPIO_Pin_All
3:Это сложно, ответ давал — смотрите в комментариях к статье — подключение дисплея от nokia1100 к STM32
Сергей пишет 17.10.2013 в 23:56 #
UTF кодировку ставил не помогло, может нужно сам проект настраивать, к примеру я скачал ваш проект
«STM32. 4. Последовательный порт (UART)», открыл его и в нем все комментарии отображаются корректно.
Alex_EXE пишет 18.10.2013 в 00:11 #
По началу кирилице у меня то же не было, на каким-то образом она появилась. Как позже выяснил, что возможно из-за работы с проектом из нескольких редакторов Keil, Notepad++, Word (во время написания материалов). Когда попробовал повторить, то получилось только с принудительным перекодированием файла в UTF через программу Shtirlitz (или всё же Notepad++, уже не помню).
Итог — Keil не дружит с нашим алфавитом, а что бы подружить — приходиться поплясать с бубном.
Ещё, как вариант за основу своих файлов взять уже настроенные файлы, где всё отображается корректно.
Den пишет 02.07.2014 в 00:16 #
Часа два пытался зажечь светодиод, потом час- чтоб работал от кнопки. Только с Вашей статьей стала понятней инициализация, и работа портов — спасибо!
Sergey пишет 06.10.2014 в 15:29 #
Строка 29. Нету сравнения.
29. if (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) !=0 )
Alex_EXE пишет 14.10.2014 в 23:18 #
По умолчанию запись
if(A) — проверка, что A не нулевое
if(!A) — проверка, что A равно 0
так что добавлять A!=0 не обязательно.
Бывает при написании кода, что бы он корректно отображался на движке сайта, добавляю дополнительные пробелы, а то бывает, что он начинает в коде видеть свои спец символы. Но в любом случае в прикрепленном к статье файле код не подкорректированный.
Арчил пишет 06.01.2015 в 23:28 #
«…поэтому можем сконфигурировать, как вход с открытым коллектором (GPIO_Mode_IN_FLOATING)…» — в моем понимании, это все равно, что сказать — земля кружится вокруг луны. Я не критикую, наверняка это мой пробел в знаниях (я инженер электронщик, контроллеры только осваиваю). Просто не понятно
Alex_EXE пишет 08.01.2015 в 05:52 #
Исправил ошибку.
Иван пишет 30.01.2015 в 19:37 #
Здравствуйте, пытаюсь в IAR выдать 1 на 12 ногу F407///
int main()
{ //SystemInit();
//RCC_AHB1PeriphClockCmd(RCC_Ahb1Periph_GPIOD, Enable);
//RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
GPIOD->MODER |= GPIO_MODER_MODER12_0; //output
GPIOD->OTYPER &= ~GPIO_OTYPER_OT_12; //Output push-pull
GPIOD->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12_1; //10 MHz
GPIOD->PUPDR &=~ GPIO_PUPDR_PUPDR12; //No pull-up, pull-down
//GPIOD->ODR=0xFFFF;
//GPIOD->BSRRL = GPIO_BSRR_BS_12;
В коментариях варианты компиляции, никаких ошибок все хорошо, заливаю hex и ничего не происходит
Alex_EXE пишет 09.02.2015 в 02:10 #
С 407 и с IAR пока не работал, как заметил у каждой среды есть свои особенности.
Тут могу посоветовать 2 варианта: использовать настройки по умолчанию — т.е раскомментировать SystemInit(); и возможно нужен будет кварц; ещё раз проверить всю инициализацию и проверить по схеме тактирования из даташита — поступает ли тактирование на все нужные блоки. Ещё можно поискать готовый пример.
Andrei пишет 13.02.2015 в 20:14 #
Подскажите, а как реализовать чтение из usart строки? Например ввожу в консоли AT+PIN01 и включается светодиод на PA1.
Igor пишет 14.07.2015 в 21:15 #
GPIO_Mode_Out_PP — выход с подтяжкой.
Насколько я помню это немного не то. А означает, что
пин конфигурируется как выход с Push-Pull, т.е. может принимать только два значения(логический ноль или единицу)
idxi пишет 29.11.2016 в 02:26 #
Хорошая материал! Легко, Понятно, доступно…
Респект!
snz.nn пишет 23.01.2017 в 21:52 #
Спасибо за статью! Удалось оживить контроллер 😀
Alex71 пишет 23.11.2017 в 18:09 #
Спасибо, всё очень доступно, респект.
Алексей пишет 01.04.2018 в 17:36 #
Не работает. Светодиоды молчат, как рыба об лёд…
Андрей пишет 28.08.2020 в 21:33 #
Жаль, все это давно устарело… У F303 регистры по другому обзываются.
Alex_EXE пишет 28.08.2020 в 21:48 #
Отличия и схожесть есть у всех семейств контроллеров F0,1,2,3,4. Главное понять принципы.
И контроллер для проекта обычно подбирается не по принципу самый крутой и дорогой, а по необходимому функционалу, цене и наличию, так что и с F1 ещё можно повстречаться.