Урок 8. Управление портами ввода-вывода через функции библиотеки HAL.

Уроки STM32

Научимся работать с портами с помощью библиотеки 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                             
}

Можно компилировать, загрузить программу в микроконтроллер и проверить.

Вот ссылка на мой проект полностью:

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Для обращения к кнопке, светодиоду мы использовали абсолютные имена – номера выводов, название портов. Это не совсем удобно.

 

Давайте создадим новый проект Lesson8_2. Настроим конфигурацию системы тактирования, назначим вывод PB12 на режим вход с подтягивающим к питанию резистором, определим вывод PB13, как активный вывод. И самое главное – присвоим им имена (User label) Button и Led.

Откроем новый проект. Заглянем в файл main.h.

Файл 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
}

Читаемость программы значительно улучшилась.

Вот такой вариант проекта.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Думаю, вы вспомнили Ардуино и с облегчением вздохнули. Все намного проще и понятнее, чем в предыдущем уроке.

 

В следующем уроке займемся работой с портами через LL-функции. Что-то среднее. Сложнее, чем в этом уроке, но проще, чем в предыдущем.

Предыдущий урок     Список уроков     Следующий урок

3

Автор публикации

не в сети 6 часов

Эдуард

285
Комментарии: 1945Публикации: 197Регистрация: 13-12-2015

23 комментария на «Урок 8. Управление портами ввода-вывода через функции библиотеки HAL.»

  1. Отличные уроки, а главное все понятно разжевано с примерами и картинками, спасибо за ваши труды.

    1
  2. Здравствуйте.

    опечатка в конце:

    // на выводе 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;

    как её понять: выход без подтяжки куда-либо?

    0
  3. Почему у Вас токоограничивающий резистор 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 Ом

    0
    • Здравствуйте!
      В принципе расчет правильный, только Uсветодиода не 3, 1,5 В. У красных даже меньше, у белых может доходить до 2 В, но никак не 3.
      Когда вы устанавливаете слишком большой ток, вступает внутреннее ограничение тока выхода микроконтроллера. Поэтому, начиная с определенного сопротивления резистора ток остается постоянным.

      0
  4. Эдуард, помогите плиз. Как в HAL читать данные не с одного пина, а двоичное число со всего порта A???
    прописал в main
    uint16_t Port = GPIOA->IDR; все равно не читает. сам код работает, а чтения порта А нет.

    0
  5. Ирония в том, что чтобы стать настоящим самостоятельным программистом, надо самому строчить по клавишам, набирая килотонны кода.
    А зарегистрировавшись за 40р в месяц и делая копи\паст — уж точно в голове останется мало знаний.
    Так что — к черту подписку за 40р! Буду строчить коды бесплатно!
    Спасибо автору.

    0
  6. Зачем такой громоздкий код на кнопку:
    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))

    0
  7. Добрый день.
    Можно ли в HAL как то создать функцию чтения Button_Pin, которая выполняла бы HAL_GPIO_ReadPin(Button_GPIO_Port, Button_Pin))?
    И тогда чтение состояния кнопки выглядело бы f=Button_Pin;
    Аналогично с записью: Led_Pin=1;
    При инициализации мы же привязали пин к порту, сказали, что он вход и т.д. А теперь в программе каждый раз нужно указывать опять порт, пин. Громоздко как то. Ну это я после Mbed OS если что.

    0
    • Здравствуйте!
      HAL — чужая библиотека. Как в ней создать функцию.
      Могут быть разные способы адресации выводов микроконтроллера. В системе STM32 выбран способ задания номера порта и вывода.

      0
    • Здравствуйте!
      Там светодиод, установленный на плате. Могли захотеть что-то проверить с помощью него. В той программе, которая получилась, конечно, эта установка лишняя.

      0
  8. Добрый день. Скажите, пожалуйста, что это за speed? И почему мы его ставим на medium? Любую скорость можно выбрать или нужно в зависимости от чего-то его настраиваить?

    0
    • Здравствуйте!
      От скорости зависит энергопотребление микроконтроллера и максимальная скорость изменения выходных сигналов. Если выходные сигналы медленные, допустима их задержка, то лучше выбирать минимальную частоту тактирования выходов.

      0
  9. Добрый вечер.
    Заказал такую плату, сижу изучаю 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 моргают…
    Спасибо!..

    0
    • Здравствуйте!
      Если вы установили на плату STM32 систему Ардуино, то и на первый, и на второй вопрос ответ да.
      Но я в уроках STM32 рассказываю о программировании STM32 без использования Ардуино.

      0
      • Спасибо, и? — (пред)последние вопросы:
        1)PinsOut[4] = {46, 45, 43, 42} — это, надеюсь, ножки на плате STM, а не на чипе!?
        2)Только на одном сайте нашел >< внятные и простые методы управления таймерами:
        Timer.pause/setPeriod/attachInt… — Для использования нужно #include некую библиотеку? — Кстати, тоже тема для статейки чайникам…
        Эдуард, обещаю потревожить Вас только после получения посылки из Китая!
        Всего наилучшего!..

        0
        • Здравствуйте!
          Да, это номера выводов. На счет таймеров не знаю. Я с STM32 работаю без Ардуино.
          Возможно функции работы с таймерами встроенные. Тогда библиотеку подключать не надо. Посмотрите на github https://github.com/stm32duino/Arduino_Core_STM32/blob/main/cores/arduino/HardwareTimer.h .
          Спрашивайте без стеснения. Что знаю — отвечу.

          0
  10. С Наступившим Новым годом Вас, Эдуард!
    Здоровья, благополучия, новых достижений!..
    Продолжаю ожидать посылку с микроконтроллером (наверное министр почты так ненавидит нас, москалей — если до Нижнего Расплюйска посылка идет 2 недели, судя по отзывам…) и грызть гранит…
    Во 2-м уроке ( http://mypractic.ru/urok-2-plata-stm32f103c8t6-zagruzka-programmy-vo-flash-pamyat-mikrokontrollera-cherez-sistemnyj-butloader.html ) заинтересовала Таблица энергопотребления.
    Снова появилось подтверждение, что должен быть какой-то метод управления тактовой частотой процессора. Не настолько подкован в RISC-ассемблере и тем паче в архитектуре, но может — достаточно всего перегрузить некий регистр?…..

    0
  11. Здравствуйте. переписываю ваши уроки под STM32F411, все работает, но при доступе непосредственно к портам кнопка установленная на плате работает сразу, а через библиотеку HAL c задержкой. причем пробовал менять частоту с 16 до 96 МГц задержка выключения одинаковая. Такое чувство, что где то Delay стоит.

    0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".