Научимся работать с портами с помощью библиотеки HAL.
Предыдущий урок Список уроков Следующий урок
Кто дочитал предыдущий урок до конца и не свихнулся, наверняка оценит, как просто работать с портами через функции библиотеки HAL.
В прошлом уроке мы обращались к регистрам непосредственно через имена библиотеки CMSIS. Приходилось разбираться с форматами регистров конфигурации, вычислять данные для загрузки в них, искать в заголовочном файле имена регистров и переопределений.
А представьте, что нашу последнюю программу необходимо переписать на другой микроконтроллер семейства STM32. Придется все делать заново. Опять открывать техническую документацию, проверять имена и форматы, корректировать программу, исправлять ошибки.
Все эти проблемы решает библиотека HAL. В ней мы работаем не со сложными регистрами конфигурации, а с теми объектами, которые нам необходимы и, главное, понятны. В данном уроке - с портами GPIO и выводами портов. Управлять портами становится не сложнее, чем в системе Ардуино. Только режимов больше.
Небольшое отступление.
Параллельно урокам STM32 я буду выкладывать на сайт строгую справочную информацию на русском языке. Прежде всего, это описание функций библиотеки HAL и библиотеки LL (о ней в следующем уроке).
Я буду все время ссылаться на эти документы. Описание любой функции, которую мы изучаем в уроке, лучше дополнительно посмотреть в справочнике, в формализованном виде. Тем более, что я обязательно сопровождаю строгую информацию примерами.
Кроме ссылок в уроках, справочники по функциям HAL и LL можно открыть в верхнем меню сайта в выпадающем списке STM32.
Информации на русском языке по этим библиотекам очень мало.
Постановка задачи.
Вернемся к задаче из предыдущего урока. Надо:
- Установить конфигурацию выводов:
- PB12 – Вход с подтягивающим на шину питания резистором. К нему у нас подключена кнопка.
- PB13 – Активный выход. Мы подключили к нему светодиод.
- PC13 – Активный выход. К нему подключен светодиод платы.
- Реализовать алгоритм управления светодиодом от кнопки:
- кнопка нажата – светодиод светится;
- кнопка отжата – светодиод погашен.
Давайте с помощью STM32CubeMX создадим проект Lesson8_1, в котором настроим только систему тактирования. Конфигурацию портов создавать нее будем. Сделаем это с помощью функций библиотеки HAL. Будем заполнять файл main.c постепенно.
Конфигурация портов с помощью библиотеки HAL.
Команды инициализации будем вставлять в блок
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
файла main.c.
Если мы не разрешили тактирование портов в STM32CubeMX, то надо это сделать функцией __HAL_RCC_GPIOx_CLK_ENABLE().
Не будем вдаваться в подробности, просто выполним команды разрешения портов B и C.
__HAL_RCC_GPIOB_CLK_ENABLE(); // разрешение порта B
__HAL_RCC_GPIOC_CLK_ENABLE(); // разрешение порта C
Заходим в справочник по функциям HAL для управления портами и видим на первом месте функцию инициализации HAL_GPIO_Init.
void HAL_GPIO_Init (GPIO_TypeDef * GPIOx, GPIO_InitTypeDef * GPIO_Init)
Функция имеет 2 аргумента:
- GPIOx – это имя порта, с которым мы работаем. Указывается в общепринятом виде: GPIOA, GPIOB, GPIOC и т.д.
- GPIO_Init – указатель на структуру параметров инициализации.
Структура описывается так:
typedef struct
{
uint32_t Pin; // номер вывода
uint32_t Mode; // режим
uint32_t Pull; // режим подтягивающего резистора
uint32_t Speed; // скорость переключение выходного сигнала
} GPIO_InitTypeDef;
Подробно назначение элементов структуры и варианты значений для них можно посмотреть в справочнике.
Для инициализации порта нам необходимо объявить структуру типа GPIO_InitTypeDef.
GPIO_InitTypeDef GPIO_InitStruct = {0};
Задаем нужные элементы структуры для вывода PB12.
/* конфигурация вывода PB12 на вход с подтягивающим резистором */
GPIO_InitStruct.Pin = GPIO_PIN_12; // номер вывода
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // режим вход
GPIO_InitStruct.Pull = GPIO_PULLUP; // резистор к шине питания
Вызываем функцию HAL_GPIO_Init для порта B.
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
Делаем тоже самое для выводов PB13 и PC13.
/* конфигурация вывода PB13 на активный выход */
GPIO_InitStruct.Pin = GPIO_PIN_13; // номер вывода
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // режим выход
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; // средняя скорость выхода
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* конфигурация вывода PC13 на активный выход */
GPIO_InitStruct.Pin = GPIO_PIN_13; // номер вывода
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // режим выход
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; // средняя скорость выхода
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
Установка конфигурации наших трех выходов закончена.
Дальше нам необходимо считать состояние вывода PB12. К нему мы подключили кнопку.
Сделать это можно функцией HAL_GPIO_ReadPin.
Полный формат: GPIO_PinState HAL_GPIO_ReadPin (GPIO_TypeDef * GPIOx, uint16_t GPIO_Pin).
У функции 2 аргумента:
- GPIOx – выбор порта (GPIOA, GPIOB, GPIOC … ).
- Pin – номер вывода (GPIO_PIN_0 … GPIO_PIN_15).
Функция возвращает состояние вывода, которое может принимать 2 значения:
GPIO_PIN_SET – высокий уровень;
GPIO_PIN_RESET – низкий уровень.
Блок проверки состояния кнопки будет выглядеть так.
if( HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_SET ) {
// на выводе PB12 высокий уровень, кнопка отжата
}
else {
// на выводе PB12 низкий уровень, кнопка нажата
}
Вставляем его в программу, но уже в цикл while(1) перед завершением блока
/* USER CODE END WHILE */
Теперь надо в зависимости от положения кнопки установить вывод PB13 (светодиод) в нужное состояние.
Для установки и сброса выводов портов есть функция HAL_GPIO_WritePin.
Формат: void HAL_GPIO_WritePin (GPIO_TypeDef * GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState).
Аргументы:
- GPIOx – выбор порта (GPIOA, GPIOB, GPIOC … ).
- Pin – номер вывода (GPIO_PIN_0 … GPIO_PIN_15).
- PinState – состояние вывода:
- GPIO_PIN_SET – высокий уровень;
- GPIO_PIN_RESET - низкий уровень.
Чтобы зажечь светодиод надо вызвать функцию:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // сброс вывода PB13
Погасить светодиод можно функцией:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); // установка вывода PB13
Вставляем эти команды в ветки оператора условного перехода.
if( HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_SET ) {
// на выводе PB12 высокий уровень, кнопка отжата
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); // установка вывода PB13
}
else {
// на выводе PB12 низкий уровень, кнопка нажата
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // сброс вывода PB13
}
Можно компилировать, загрузить программу в микроконтроллер и проверить.
Вот ссылка на мой проект полностью:
Для обращения к кнопке, светодиоду мы использовали абсолютные имена – номера выводов, название портов. Это не совсем удобно.
Давайте создадим новый проект Lesson8_2. Настроим конфигурацию системы тактирования, назначим вывод PB12 на режим вход с подтягивающим к питанию резистором, определим вывод PB13, как активный вывод. И самое главное – присвоим им имена (User label) Button и Led.
Откроем новый проект. Заглянем в файл main.h.
Там вот такой блок.
/* Private defines -----------------------------------------------------------*/
#define Button_Pin GPIO_PIN_12
#define Button_GPIO_Port GPIOB
#define Led_Pin GPIO_PIN_13
#define Led_GPIO_Port GPIOB
STM32CubeMX сделал для кнопки 2 переназначения основных аргументов HAL-функций управления портами :
- К имени вывода (Button) он добавил _Pin и присвоил стандартное название номера вывода GPIO_PIN_12.
- К имени вывода (Button) он добавил _GPIO_Port и присвоил стандартное название порта GPIOB.
И такую же операцию конфигуратор совершил над именем светодиода (Led).
Теперь в функциях в качестве параметров мы можем использовать символьные имена, часть которых мы задали на этапе конфигурации с помощью STM32CubeMX.
С учетом этого наша конструкция в цикле while() будет выглядеть так:
if( HAL_GPIO_ReadPin(Button_GPIO_Port, Button_Pin) == GPIO_PIN_SET ) {
// на выводе PB12 высокий уровень, кнопка отжата
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, GPIO_PIN_SET); // установка вывода PB13
}
else {
// на выводе PB12 низкий уровень, кнопка отжата
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, GPIO_PIN_RESET); // сброс вывода PB13
}
Читаемость программы значительно улучшилась.
Вот такой вариант проекта.
Думаю, вы вспомнили Ардуино и с облегчением вздохнули. Все намного проще и понятнее, чем в предыдущем уроке.
В следующем уроке займемся работой с портами через LL-функции. Что-то среднее. Сложнее, чем в этом уроке, но проще, чем в предыдущем.
Отличные уроки, а главное все понятно разжевано с примерами и картинками, спасибо за ваши труды.
Спасибо.
Здравствуйте.
опечатка в конце:
// на выводе PB12 низкий уровень, кнопка отжата
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, GPIO_PIN_RESET); // сброс вывода PB13
кнопка отжата — кнопка нажата
——————————
а зачем мы это пишем в
/* USER CODE BEGIN SysInit */
если мы сконфигурировали проект lesson6_1 в STM32CubeMX эти настройки выводов порта (кроме С порта).
и в main.c нам STM32CubeMX сделал функцию static void MX_GPIO_Init(void);
в которой всё нам прописал, что описано в этом уроке:
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, GPIO_PIN_SET);
/*Configure GPIO pin : Button_Pin */
GPIO_InitStruct.Pin = Button_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(Button_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : Led_Pin */
GPIO_InitStruct.Pin = Led_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(Led_GPIO_Port, &GPIO_InitStruct);
}
Только у меня еще написал строчку в конфигурации пина светодиода:
GPIO_InitStruct.Pull = GPIO_NOPULL;
как её понять: выход без подтяжки куда-либо?
Почему у Вас токоограничивающий резистор 560 Ом?
Я светодиод брал как и у Вас в следующем уроке 5мм диаметром.
У себя нашел резисторы 510 Ом и 680 Ом — светит ярко светодиод на обоих. Потом поставил на 22 Ом (на 15 Ом не было) — так же светит ярко — вообще никакой разницы не видно.
Это правильный расчет резистора(?):
R = Uгасящее / Iсветодиода
Uгасящее = Uпитания – Uсветодиода
Uпитания = 3,3 В
Uсветодиода = 3 В
Iсветодиода = 20 мА = 0.02 А
R =(3,3-3)/0.02= 15 Ом
Здравствуйте!
В принципе расчет правильный, только Uсветодиода не 3, 1,5 В. У красных даже меньше, у белых может доходить до 2 В, но никак не 3.
Когда вы устанавливаете слишком большой ток, вступает внутреннее ограничение тока выхода микроконтроллера. Поэтому, начиная с определенного сопротивления резистора ток остается постоянным.
Эдуард, помогите плиз. Как в HAL читать данные не с одного пина, а двоичное число со всего порта A???
прописал в main
uint16_t Port = GPIOA->IDR; все равно не читает. сам код работает, а чтения порта А нет.
Ирония в том, что чтобы стать настоящим самостоятельным программистом, надо самому строчить по клавишам, набирая килотонны кода.
А зарегистрировавшись за 40р в месяц и делая копи\паст — уж точно в голове останется мало знаний.
Так что — к черту подписку за 40р! Буду строчить коды бесплатно!
Спасибо автору.
А я оплатил годовую подписку. Не потому, что хочу что-то копировать, а потому, что автор достоин вознаграждения за такой прекрасный курс плюс массу справочной информации.
😉
Спасибо.
Зачем такой громоздкий код на кнопку:
if( HAL_GPIO_ReadPin(Button_GPIO_Port, Button_Pin) == GPIO_PIN_SET ) {
// на выводе PB12 высокий уровень, кнопка отжата
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, GPIO_PIN_SET); // установка вывода PB13
}
else {
// на выводе PB12 низкий уровень, кнопка отжата
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, GPIO_PIN_RESET); // сброс вывода PB13
}
когда это можно записать одной строкой:
HAL_GPIO_WritePin(Led_GPIO_Port, Led_Pin, HAL_GPIO_ReadPin(Button_GPIO_Port, Button_Pin))
Добрый день.
Можно ли в HAL как то создать функцию чтения Button_Pin, которая выполняла бы HAL_GPIO_ReadPin(Button_GPIO_Port, Button_Pin))?
И тогда чтение состояния кнопки выглядело бы f=Button_Pin;
Аналогично с записью: Led_Pin=1;
При инициализации мы же привязали пин к порту, сказали, что он вход и т.д. А теперь в программе каждый раз нужно указывать опять порт, пин. Громоздко как то. Ну это я после Mbed OS если что.
Здравствуйте!
HAL — чужая библиотека. Как в ней создать функцию.
Могут быть разные способы адресации выводов микроконтроллера. В системе STM32 выбран способ задания номера порта и вывода.
Эдуард, привет, а для чего мы объявляли порт PC13?
Здравствуйте!
Там светодиод, установленный на плате. Могли захотеть что-то проверить с помощью него. В той программе, которая получилась, конечно, эта установка лишняя.
Понял, спасибо.
Добрый день. Скажите, пожалуйста, что это за speed? И почему мы его ставим на medium? Любую скорость можно выбрать или нужно в зависимости от чего-то его настраиваить?
Здравствуйте!
От скорости зависит энергопотребление микроконтроллера и максимальная скорость изменения выходных сигналов. Если выходные сигналы медленные, допустима их задержка, то лучше выбирать минимальную частоту тактирования выходов.
Добрый вечер.
Заказал такую плату, сижу изучаю 2 недели — голова пухнет. Почти 40 y.ego занимался 580-м, сейчас всё круче. Вопросы:
1)а для 32-го работают команды PinMode, Dig..Read/Write и т.п. о которых Вы рассказывали в лекциях про Uno?
2)можно ли создать массив с описанием контактов типа int PinsOut [4] = {46, 45, 43, 42} ;,
а потом обращаться PinMode(PinsOut[0],OUTPUT) ; и т.д.?
Сутки искал — нигде не нашёл подобных примеров, все только PC13 моргают…
Спасибо!..
Здравствуйте!
Если вы установили на плату STM32 систему Ардуино, то и на первый, и на второй вопрос ответ да.
Но я в уроках STM32 рассказываю о программировании STM32 без использования Ардуино.
Спасибо, и? — (пред)последние вопросы:
1)PinsOut[4] = {46, 45, 43, 42} — это, надеюсь, ножки на плате STM, а не на чипе!?
2)Только на одном сайте нашел >< внятные и простые методы управления таймерами:
Timer.pause/setPeriod/attachInt… — Для использования нужно #include некую библиотеку? — Кстати, тоже тема для статейки чайникам…
Эдуард, обещаю потревожить Вас только после получения посылки из Китая!
Всего наилучшего!..
Здравствуйте!
Да, это номера выводов. На счет таймеров не знаю. Я с STM32 работаю без Ардуино.
Возможно функции работы с таймерами встроенные. Тогда библиотеку подключать не надо. Посмотрите на github https://github.com/stm32duino/Arduino_Core_STM32/blob/main/cores/arduino/HardwareTimer.h .
Спрашивайте без стеснения. Что знаю — отвечу.
С Наступившим Новым годом Вас, Эдуард!
Здоровья, благополучия, новых достижений!..
Продолжаю ожидать посылку с микроконтроллером (наверное министр почты так ненавидит нас, москалей — если до Нижнего Расплюйска посылка идет 2 недели, судя по отзывам…) и грызть гранит…
Во 2-м уроке ( http://mypractic.ru/urok-2-plata-stm32f103c8t6-zagruzka-programmy-vo-flash-pamyat-mikrokontrollera-cherez-sistemnyj-butloader.html ) заинтересовала Таблица энергопотребления.
Снова появилось подтверждение, что должен быть какой-то метод управления тактовой частотой процессора. Не настолько подкован в RISC-ассемблере и тем паче в архитектуре, но может — достаточно всего перегрузить некий регистр?…..
Здравствуйте. переписываю ваши уроки под STM32F411, все работает, но при доступе непосредственно к портам кнопка установленная на плате работает сразу, а через библиотеку HAL c задержкой. причем пробовал менять частоту с 16 до 96 МГц задержка выключения одинаковая. Такое чувство, что где то Delay стоит.