STM32. 3. Порты ввода-вывода (GPIO)

С использованием Standard Peripheral Library

STM32. 2. Первый проект

Ранее мы уже научились создавать проект и смогли помигать светодиодом, на чём и остановились. Теперь рассмотрим, как именно мы смогли им управлять и как можно управлять не только им, а любой дискретной нагрузкой или датчиком. А точнее: как управлять портами ввода-вывода на STM32.

Порты ввода вывода у ARM 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_1GPIO_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.

Схема подключения элементов к STM32

Схема подключения элементов

Для этого сначала подадим тактирование на порт 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).

STM32. 4. Последовательный порт (UART)

21 комментарий »

Alex_EXE | 11.08.2013 | STM32 |

21 комментарий на « STM32. 3. Порты ввода-вывода (GPIO)»

  1. Сергей пишет 12.10.2013 в 19:31 #

    В каком файле находится описание структуры GPIO_InitTypeDef GPIO_InitStructure; ?

  2. Сергей пишет 12.10.2013 в 23:09 #

    как целиком порт настроить на вход/выход , и как на целом порте установить лог.уровень ?

  3. Сергей пишет 13.10.2013 в 02:02 #

    и как сделать что бы русские комментарии работали?
    замена шрифтов и выбор другой кодировки не помогло
    µVision V4.72.10.0

  4. Alex_EXE пишет 16.10.2013 в 00:50 #

    1:В файлах GPIO библиотеки — stm32f10x_gpio.h
    2:Используйте — GPIO_Pin_All
    3:Это сложно, ответ давал — смотрите в комментариях к статье — подключение дисплея от nokia1100 к STM32

  5. Сергей пишет 17.10.2013 в 23:56 #

    UTF кодировку ставил не помогло, может нужно сам проект настраивать, к примеру я скачал ваш проект
    «STM32. 4. Последовательный порт (UART)», открыл его и в нем все комментарии отображаются корректно.

  6. Alex_EXE пишет 18.10.2013 в 00:11 #

    По началу кирилице у меня то же не было, на каким-то образом она появилась. Как позже выяснил, что возможно из-за работы с проектом из нескольких редакторов Keil, Notepad++, Word (во время написания материалов). Когда попробовал повторить, то получилось только с принудительным перекодированием файла в UTF через программу Shtirlitz (или всё же Notepad++, уже не помню).
    Итог — Keil не дружит с нашим алфавитом, а что бы подружить — приходиться поплясать с бубном.
    Ещё, как вариант за основу своих файлов взять уже настроенные файлы, где всё отображается корректно.

  7. Den пишет 02.07.2014 в 00:16 #

    Часа два пытался зажечь светодиод, потом час- чтоб работал от кнопки. Только с Вашей статьей стала понятней инициализация, и работа портов — спасибо!

  8. Sergey пишет 06.10.2014 в 15:29 #

    Строка 29. Нету сравнения.
    29. if (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) !=0 )

  9. Alex_EXE пишет 14.10.2014 в 23:18 #

    По умолчанию запись
    if(A) — проверка, что A не нулевое
    if(!A) — проверка, что A равно 0
    так что добавлять A!=0 не обязательно.
    Бывает при написании кода, что бы он корректно отображался на движке сайта, добавляю дополнительные пробелы, а то бывает, что он начинает в коде видеть свои спец символы. Но в любом случае в прикрепленном к статье файле код не подкорректированный.

  10. Арчил пишет 06.01.2015 в 23:28 #

    «…поэтому можем сконфигурировать, как вход с открытым коллектором (GPIO_Mode_IN_FLOATING)…» — в моем понимании, это все равно, что сказать — земля кружится вокруг луны. Я не критикую, наверняка это мой пробел в знаниях (я инженер электронщик, контроллеры только осваиваю). Просто не понятно

  11. Alex_EXE пишет 08.01.2015 в 05:52 #

    Исправил ошибку.

  12. Иван пишет 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 и ничего не происходит

  13. Alex_EXE пишет 09.02.2015 в 02:10 #

    С 407 и с IAR пока не работал, как заметил у каждой среды есть свои особенности.
    Тут могу посоветовать 2 варианта: использовать настройки по умолчанию — т.е раскомментировать SystemInit(); и возможно нужен будет кварц; ещё раз проверить всю инициализацию и проверить по схеме тактирования из даташита — поступает ли тактирование на все нужные блоки. Ещё можно поискать готовый пример.

  14. Andrei пишет 13.02.2015 в 20:14 #

    Подскажите, а как реализовать чтение из usart строки? Например ввожу в консоли AT+PIN01 и включается светодиод на PA1.

  15. Igor пишет 14.07.2015 в 21:15 #

    GPIO_Mode_Out_PP — выход с подтяжкой.
    Насколько я помню это немного не то. А означает, что
    пин конфигурируется как выход с Push-Pull, т.е. может принимать только два значения(логический ноль или единицу)

  16. idxi пишет 29.11.2016 в 02:26 #

    Хорошая материал! Легко, Понятно, доступно…
    Респект!

  17. snz.nn пишет 23.01.2017 в 21:52 #

    Спасибо за статью! Удалось оживить контроллер 😀

  18. Alex71 пишет 23.11.2017 в 18:09 #

    Спасибо, всё очень доступно, респект.

  19. Алексей пишет 01.04.2018 в 17:36 #

    Не работает. Светодиоды молчат, как рыба об лёд…

  20. Андрей пишет 28.08.2020 в 21:33 #

    Жаль, все это давно устарело… У F303 регистры по другому обзываются.

  21. Alex_EXE пишет 28.08.2020 в 21:48 #

    Отличия и схожесть есть у всех семейств контроллеров F0,1,2,3,4. Главное понять принципы.
    И контроллер для проекта обычно подбирается не по принципу самый крутой и дорогой, а по необходимому функционалу, цене и наличию, так что и с F1 ещё можно повстречаться.

Комментарии RSS

Оставьте отзыв