STM32 с нуля. GPIO. Использование портов ввода-вывода.

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

Режимы работы портов ввода-вывода в STM32

Например, надо нам настроить ножку PB5. Идем в регистр GPIOB->CRL и выставляем соответствующие биты так как нам требуется (на картинке 32-х битный регистр CRL). Для PB5 это биты:

STM32. Настройка портов ввода-вывода

После 8-битных контроллеров все это может показаться сложным и нелогичным, но на самом деле реализовано все довольно изящно :). Посмотрим, что тут есть еще…

  • Выходной регистр 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 и находим там множество различных функций для работы с GPIO.

Рассмотрим функцию GPIO_Init() (код приводить не буду, все в файле библиотеки). Так вот, эта функция как раз и связывает нашу созданную структуру и конкретные регистры контроллера. То есть мы передаем в эту функцию переменную, в соответствии с которой выставляются нужные биты нужных регистров микроконтроллера. Все очень просто, но от этого не менее гениально! Поковыряйте еще файлы библиотеки. Там функции на любой случай есть 🙂 Кстати очень удобно – перед функцией идет описание переменных, которые она принимает и возвращает, а также описание собственно того, что эта функция призвана делать. Так что, разобраться несложно.

Отвлечемся ненадолго от портов ввода-вывода и обсудим один довольно тонкий момент. Чтобы использовать порты, либо любую другую периферию, обязательно надо включить тактирование. И порты, и периферия изначально отключены от тактирования, так что без этого действия ничего не заведется. Программа скомпилируется, но на деле работать ничего не будет. За тактирование в SPL отвечают файлы stm32f10x_rcc.c и stm32f10x_rcc.h. Не забывайте добавлять их в проект.

Давайте уже перейдем к программированию. Как это принято, заставим диод помигать 🙂 Чтобы получше разобраться с Standard Peripheral Library немножко усложним обычное мигание диодом – будем опрашивать кнопку, и если она нажата – диод загорается, иначе – гаснет. Запускаем Keil, создаем проект, добавляем все нужные файлы, не забываем про CMSIS. Из SPL для этого проекта нам понадобятся 4 файла, уже упомянутые выше. Создание нового проекта описано в предыдущей статье учебного курса и также там можно найти ссылки на библиотеки.

Итак, код:

/***************************************************************************************/
//Подключаем все нужные файлы
#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);
		}
	} 
} 


/***************************************************************************************/

Кстати, возможно кто-то обратит внимание на наличие скобок {  }, несмотря на всего лишь одну инструкцию  в теле if и else. А это уже привычка 🙂 Очень рекомендуется так писать, особенно при разработке крупных проектов. При дописывании/исправлении программы невнимательный программист может не обратить внимания на отсутствие скобок и дописать вторую инструкцию, которая, как вы понимаете, уже окажется все блока if или else. Та же тема с циклами. Когда над проектом работает много народу, нет никаких гарантий, что кто-нибудь не окажется невнимательным, так что, чтобы не тратить минуты/часы на последующие поиски ошибки, рекомендую ставить эти скобки всегда. Хотя может, кто-то и не согласится с такой логикой…

Нажимаем F7, компилируем, и вот наша первая программа для STM готова. Вроде бы код довольно подробно откомментирован, так что поясню только пару моментов.

Функция GPIO_StructInit(&port) – принимает в качестве аргумента адрес переменной port. Эта функция заполняет поля структуры, переданной ей в качестве аргумента значениями по умолчанию. Это делать не обязательно, но во избежание каких-нибудь непредсказуемых багов, лучше всегда вызывать эту функцию.

Еще две функции, которые мы использовали:

  • GPIO_SetBits(GPIOA, GPIO_Pin_0);
  • GPIO_ResetBits(GPIOA, GPIO_Pin_0);

Ну вы и так догадались для чего они 🙂

Итак, на этом сегодня заканчиваем, а в следующей статье познакомимся со средствами Keil’а для отладки наших программ!

Поделиться!

Подписаться
Уведомление о
guest
58 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
MRZ
MRZ
7 лет назад

Добрый день! Я вижу у вас в Keil комментарии на русском языке. У меня при переключении на русский Keil печатает какую-то абракадабру. Может где-то в настройках надо что-то задать, не подскажете? Я так и не нашел.

MRZ
MRZ
7 лет назад

Странно, но у меня вообще нет пункта “8051: Editor C Files”. Версия Keil 6.50.

Victor
Victor
6 лет назад

Добрый день!
Попробовал скомпилировать получил 44 варнинга и ошибку
stm32f10x_rcc.c(1451): warning: #223-D: function “assert_param” declared implicitly
а вот ошибка
.\p1.axf: Error: L6218E: Undefined symbol assert_param (referred from stm32f10x_gpio.o).
Подскажите, что не так.
Спасибо

Victor
Victor
Reply to  Victor
6 лет назад

Все таки скомпилировал…
благодаря
http://easyelectronics.ru/rabota-s-stm32f10x-standard-peripherals-library.html
пытаюсь запустить дебагер выдает сообщение
*** error 65: access violation at 0x40021000 : no ‘read’ permission

Victor
Victor
6 лет назад

да не заметил…
контролер stm32f107vc

Victor
Victor
Reply to  Aveal
6 лет назад

Да, действительно на 103-м работает…
Но мне надо 107…
на 103-х нет ethernet…

Victor
Victor
6 лет назад

В проекте меняю используемый чип с 103 на 107 – возникает ошибка при отладке… меняю назад на 103 – все хорошо… Как-то можно данную проблему победить?

LEV
LEV
6 лет назад

Подскажите пожалуйста как например вывод PB03 настроить как цифровой вход с подтяжкой на землю и/или с подтяжкой к питанию.

LEV
LEV
6 лет назад

А как это сделать не используя SPL, через CMSIS.

GPIOB->CRL &=~GPIO_CRH_MODE3_0;

GPIOB->CRL |= GPIO_CRH_CNF3_1;

LEV
LEV
6 лет назад

Я делаю вот так:
GPIOA->CRL &= ~GPIO_CRL_MODE5;
GPIOA->CRL |= GPIO_CRL_CNF5_1;
GPIOA->ODR = 0xFFFF; //в pull-up;

Замеряю напряжение на выводе PA05, оно равно 0 :(. Я так предпологаю что там должно быит напряжение питания?

LEV
LEV
6 лет назад

Теперь делаю так:

RCC->APB2ENR|= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
RCC->CFGR |= RCC_CFGR_MCO_SYSCLK;

GPIOA->CRH |= GPIO_CRH_MODE11_1; // Настраиваем на выход
GPIOA->CRH &= ~GPIO_CRH_CNF11; // Симметричный выход

GPIOB->CRL &= ~GPIO_CRL_MODE0; // Настраиваем на вход
GPIOB->CRL |= GPIO_CRL_CNF0_1; // С подтяжкой 0x44444448
GPIOB->ODR = 0xFFFF;

НО если вместо GPIO_CRL_CNF0_1 подставить 0x44444448 то все работает! Что это может быть? подскажите?

LEV
LEV
6 лет назад

Скидывать не буду, подсказали:)


После сброса конфиг выглядит так :
CNF 01 MODE 00 – Input float
Ты ставишь CNF0_1 получаешь :
CNF11 MODE 00
Что соответствует Reserved а не Input c pullup .”

Но все равно спс! 🙂

Павел
Павел
6 лет назад

Здравствуйте.Пытаюсь делать в точности так,как Вы описываете. Код с этой страницы. Компиляция проходит успешно. Запускаю симулятор – выдаётся ошибка ” *** error 65: access violation at 0x40021000 : no ‘read’ permission ” . Контроллер stm32f103cb
Подскажите, как справиться с этой проблемой.

Павел
Павел
6 лет назад

контроллер:
в меню options for Target `STM32` вкладка Device
указан STM32F103CB

Настройки отладчика:
в меню options for Target `STM32` вкладка Debug:

точка у Use Simulator
пустой квадрат у Limit Speed to Real-Time
галка у Load Application at Startup
галка у Run to main()
Initialization File – пустая строка
Restore Debug Session Setting – во всех квадратах галки
CPU DILL: SARMCM3.DLL
Pframetr: -REMAP
Dialog DLL: DCM.DLL
Parametr:-pCM3

Что не так?

Михаил
Михаил
6 лет назад

Схема подключения , похожа на http://parshev.files.wordpress.com/2011/11/button_bb1.png?w=500&h=416

Вот мой код
//
#include
#include “stm32f30x_gpio.c”
//

void initAll()
{
GPIO_InitTypeDef port;
//
RCC_AHBPeriphResetCmd(RCC_AHBPeriph_GPIOA, ENABLE);
//
GPIO_StructInit(&port);
//
port.GPIO_Mode=GPIO_Mode_IN;
port.GPIO_PuPd=GPIO_PuPd_DOWN;
port.GPIO_Pin=GPIO_Pin_3;
port.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&port);
//
port.GPIO_Mode=GPIO_Mode_OUT;
port.GPIO_OType=GPIO_OType_PP;
port.GPIO_PuPd=GPIO_PuPd_NOPULL;
port.GPIO_Pin=GPIO_Pin_2;
port.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&port);
}
int main()
{
uint8_t buttonState = 0;
initAll();
while(1)
{
buttonState=GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3);

if (buttonState == 1 )
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
}
}

Почему-то у меня моргают и диоды на плате…не понятно. Помогите разобраться

Михаил
Михаил
6 лет назад

Плата STM32F3Discovery. Диод(обычный красный) подключен одной ногой на GND платы, второй через резистор на PA3. Кнопка к PA2 и на +3В c платы – и ничего не светиться.
Подключаю кнопку к +5В и диод светиться но в месте с ним еще светятся голубые диоды на плате(бледно, но светятся). В чем может быть дело?
Делал то же самое но из примеров от STM с EXTI там все работает на +3В.

Михаил
Михаил
6 лет назад

Да да РА3 кнопка, ошибся. Где прочитать про то как тестировать. И из-за этого у меня не работает?

Михаил
Михаил
6 лет назад

Спасибо, невнимательно читал(
Хочу попробовать сделать все уроки по ардуино но на стм32.

Омар
6 лет назад

Здравствуйте. При подключении “GPIO_STM32F4xx.h” и “stm32f4xx_gpio.h” проект не компилится.
Там одинаковые переменные. Как быть?
C:\Keil_v5\ARM\PACK\Keil\STM32F4xx_DFP\1.0.8\RTE_Driver\GPIO_STM32F4xx.h(57): error: #40: expected an identifier GPIO_AF_TIM1 = 1,

Вот такого вида ошибки.

Александр
6 лет назад

Я было взялся за 5 версию, но, похоже, придётся поставить 4. Слишком уж много граблей:)

Михаил
Михаил
5 лет назад

Подскажите пожалуйста, пытаюсь сделать это задание. Сижу и пытаюсь понять где накосячил. Смысл какой – у меня процессор не 103 а 407 и пытаясь адаптировать под него эту мигалку, при компиляции получаю ошибку “MainCode\main.c(11): error: #20: identifier “GPIO_Mode_IPD” is undefined”. Залез в “stm32f4xx_gpio.h”, а там вообще не так много перечислений режимов работы. И тут возник вопрос – а может у меня процессор так не умеет или надо как-то по другому это делать. Среда разработки Keil 4.73, процессор выбран верно, SPL качал с сайта производителя(еле нашёл где это вообще скачивается).

Паша
Паша
5 лет назад

если настроить вывод как “Output push-pull”, куда будет подтяжка к “+” или к “-” ?

Паша
Паша
5 лет назад

Да, то что двухтактный я понимаю, извиняюсь я неполноценно задал вопрос:
допустим
gpio.GPIO_Mode = GPIO_Mode_Out_PP; //Выход Push-Pull
и дальше пошли настраивать остальную периферию.
Как будет вести себя вывод в это время ?
ведь в регистр мы еще не записали
ни GPIOC->BSRR=GPIO_BSRR_BS9; // “1”
ни GPIOC->BSRR=GPIO_BSRR_BR9; // “0”

Alex
Alex
4 лет назад

Здравствуйте.
У меня такой вопрос:
У меня есть 8 бит данных.
У регистра ODR свободны первые 16 разрядов.
Могу ли я записать 8 бит данных в регистр ODR в ячейки с 8-ой по 15-ую, и как это сделать, не используя побитовую запись через BSRR?

Например, если записывать так:
uint8_t word = 3;
GPIOA->ODR = word;
То запись будет произведена в первые 8 разрядов ODR. А мне нужны с 8-го по 15-ый.

Андрей
Андрей
4 лет назад

Здравствуйте!
При компиляции программы, возникают следующие ошибки.
Контроллер в проекте выбрал такой же как у Вас.
Почему может возникать эта ошибка? Пути к файлам указаны верно. Тестовая программа компилировалась.
В этой же как будто не виден файл “stm32f10x_gpio.c”, но *.h файл подключен.
test.axf: Error: L6218E: Undefined symbol GPIO_Init (referred from test.o).
test.axf: Error: L6218E: Undefined symbol GPIO_ReadInputDataBit (referred from test.o).
test.axf: Error: L6218E: Undefined symbol GPIO_ResetBits (referred from test.o).
test.axf: Error: L6218E: Undefined symbol GPIO_SetBits (referred from test.o).
test.axf: Error: L6218E: Undefined symbol GPIO_StructInit (referred from test.o).
Target not created

Андрей
Андрей
Reply to  Андрей
4 лет назад

Эх, удалить уже нельзя, нашел свой косяк. Подключить то – подключил, а в проект добавить забыл.

Rammus
Rammus
4 лет назад

Здравствуйте. Вы пишите в уроке
“//Отметим только что один из параметров – указатель(!) на
//нашу структуру
GPIO_Init(GPIOA, &port);”
Но разве “&” это указатель, а не адрес?

Rammus
Rammus
Reply to  Aveal
4 лет назад

А, я понял, спасибо за ответ)

ФФФ
ФФФ
3 лет назад

Доброго времени суток!
Вопрос у меня такой, надеюсь поймете меня. может не совсем в тему. Вобщем у меня кнопка(физическая в приборе привязана к ножке микроконтроллера. когда я на нее нажимаю, прибор должен включиться. Т.е я должен опрашивать ножку контроллера, не нажата ли кнопка. если кнопка нажата, то я должен подать логическую единицу на другую ногу мк и зафиксировать что прибор сейчас включен. т.к при повторном нажатии на кнопку я его должен отключить. можете подсказать? как это реализовать программно.

Денис
Денис
2 лет назад

Добрый день! Не могу найти таблицу №18 в документации

Николай Д.
Николай Д.
2 лет назад

Добрый день.
Спасибо за уроки, у меня stm32f103c8 как изменить Ваш код , что-бы зажечь светодиод на РС13 (установленный на плате)?
/****************************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_GPIOC, ENABLE);

//А про эту функцию мы уже говорили
//Отметим только что один из параметров – указатель(!) на
//нашу структуру
GPIO_Init(GPIOC, &port);
//Настраиваем вывод, на котором будет висеть диодик – PA0
port.GPIO_Mode = GPIO_Mode_Out_PP;
port.GPIO_Pin = GPIO_Pin_13;
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &port);
}
/*******************************************************************/
int main()
{
//Объявляем переменную для хранения состояния кнопки

initAll();

while(1)
{

GPIO_SetBits(GPIOC, GPIO_Pin_13);

//GPIO_ResetBits(GPIOC, GPIO_Pin_13);//

}
}

/****************************End of file****************************/

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Июнь 2020
Пн Вт Ср Чт Пт Сб Вс
« Май    
1234567
891011121314
15161718192021
22232425262728
2930  

© 2013-2020 MicroTechnics.ru