В уроке узнаем, что такое библиотека LL. Как ее использовать совместно с библиотекой HAL. Научимся управлять портами через функции LL.
Предыдущий урок Список уроков Следующий урок
Компания STMicroelectronics – производитель микроконтроллеров STM32 достойно поддерживает свою продукцию со стороны программного обеспечения. Один из ее программных продуктов – это пакет STMCube, значительно облегчающий труд разработчиков.
Сейчас в пакете нас интересуют:
- STM32CubeMX – графический редактор конфигурации микроконтроллера, генератор начального кода.
- Библиотека HAL – драйверы периферии высокого уровня абстракции.
- Библиотека LL – низкоуровневые драйверы.
С первыми двумя компонентами мы познакомились в предыдущих уроках. В этом будем разбираться с третьим.
Библиотека LL.
HAL-драйверы ориентированы не на периферийные устройства микроконтроллера, а на функциональные возможности этих устройств. К примеру, на одном и том же таймере могут быть реализованы: отсчет интервалов времени, циклическое прерывание, ШИМ, захват и т.п. Для всех этих функций существуют разные драйверы HAL. Более того, один драйвер часто использует несколько периферийных устройств. Например, вместе с тем же таймером HAL-драйвер может управлять и системой прерываний.
Как следствие, HAL-драйверами можно пользоваться, не вникая в детали работы периферийных устройств. Они значительно облегчают труд разработчика.
Драйверы LL обеспечивают элементарные операции с регистрами периферии микроконтроллера. Их функции в точности повторяют возможные действия с регистрами. По сути это те же операции с именами библиотеки CMSIS, только используется не прямой доступ к регистрам, а функции. И данные для конфигурации задаются не в числовой форме, а в виде символьных констант.
Конечно, это облегчает работу по конфигурации периферии. Нет необходимости разбираться с форматами регистров, думать, в каком разряде надо выставить 0 или 1. Но знать назначение каждого регистра и принцип действия периферийного устройства в целом надо досконально.
Зато, в отличие от HAL-драйверов, функции библиотеки LL имеют намного меньший размер кода, выполняются значительно быстрее.
- HAL- это высокоуровневые драйверы, ориентированные на функциональные возможности периферии. Они скрывают от программиста структуру периферийных устройств, значительно облегчают работу с ними. Также HAL-драйверы обеспечивают переносимость программного обеспечения на различные типы микроконтроллеров STM32.
- LL – это драйверы низкого уровня, ориентированные на регистры. Их функции точно отражают структуру периферийных устройств на уровне регистров. Использование LL-драйверов требует глубокого знания периферии.
HAL и LL уровни дополняют друг друга и могут использоваться совместно. Все зависит от задачи, цели, по которой происходит оптимизация программы.
Порты ввода-вывода один из немногих видов периферийных устройств, работа с которыми через LL-функции скорее проще, чем с помощью HAL-драйверов. Не надо создавать дополнительную структуру, заполнять ее, понятно, что делает каждая функция.
Да и вообще, неумение пользоваться LL-библиотекой обязательно ограничит ваши возможности, как программиста STM32. Давайте учиться пользоваться LL-функциями.
Реализуем ту же задачу, что и в двух предыдущих уроках. Нажали кнопку – светодиод светится, отпустили – светодиод погас.
Совместное использование библиотек HAL и LL.
Для совместного использования библиотек необходимо выполнить ряд формальных действий. Возможно, есть какой-то другой способ, но я это делаю так.
Давайте создадим очередной учебный проект Lesson9_1. В STM32CubeMX зададим конфигурацию только системы тактирования на максимальную частоту 72 мГц. Выводы конфигурировать не будем.
Если в менеджере проектов (Project Manager) мы нажмем закладку Advanced setting в левом поле, то увидим окно расширенных установок.
В верхнем поле указаны периферийные устройства, которые мы конфигурировали и напротив их выбрана библиотека HAL. Правой кнопкой мыши можно открыть выпадающее меню и установить библиотеку LL.
Если мы сделаем это, то код начальной конфигурации будет создан с использованием библиотеки LL. Конфигуратор установит для программы LL-драйверы.
Пользоваться HAL-драйверами мы не сможем. Поэтому, оставим в этих строках HAL и создадим проект.
Теперь нужно установить нужные нам LL-драйверы.
Сначала загрузим их по ссылке:
https://www.st.com/en/embedded-software/stm32cube-mcu-packages.html
Для нашего микроконтроллера STM32F103T8C6 выберем STM32CubeF1.
Нажмем Get Software и загрузим пакет STM32CubeF1.
Мы собираемся работать с портами ввода-вывода. Нам необходим драйвер stm32f1xx_ll_gpio.h.
Находим его в скачанном пакете и копируем в папку проекта Drivers\STM32F1xx_HAL_Driver\INC\.
Теперь подключим этот драйвер в заголовочном файле main.h.
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stm32f1xx_ll_gpio.h"
/* USER CODE END Includes */
Теперь мы можем в программе вызывать функции, LL GPIO драйвера.
Управление портами ввода-вывода.
Давайте разрабатывать программу.
Разрешим работу портов с помощью HAL-функций, так как мы сделали в предыдущем уроке.
/* USER CODE BEGIN SysInit */
__HAL_RCC_GPIOB_CLK_ENABLE(); // разрешение порта B
__HAL_RCC_GPIOC_CLK_ENABLE(); // разрешение порта C
Перед использованием библиотеки LL лучше просмотрите ее функции в справочнике.
Оставьте справочник открытым (он открывается в дополнительном окне) и заглядывайте в него в ходе урока.
Вывод PB12 нам надо установить в режим - вход с подтягивающим резистором, подключенным к шине питания.
Используем функцию LL_GPIO_SetPinMode(). Вариант подключения подтягивающего резистора выбираем функцией LL_GPIO_SetPinPull().
/* USER CODE BEGIN SysInit */
__HAL_RCC_GPIOB_CLK_ENABLE(); // разрешение порта B
__HAL_RCC_GPIOC_CLK_ENABLE(); // разрешение порта C
// инициализация портов
// конфигурация вывода PB12 на вход
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_12, LL_GPIO_MODE_INPUT);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_12, LL_GPIO_PULL_UP);
Не нахожу ничего, что надо пояснять.
Настраиваем вывод PB13 на активный выход.
// конфигурация вывода PB13 на выход
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_13, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_13, LL_GPIO_OUTPUT_PUSHPULL);
Конфигурация закончена. Получилось проще и понятнее, чем в предыдущем уроке с HAL-драйвером.
Пишем блок реализации алгоритма управления светодиодом. Размещаем его перед тегом:
/* USER CODE END WHILE */
Проверяем состояние кнопки. Для этого считываем состояние порта функцией LL_GPIO_ReadInputPort() и проверяем 12й бит.
if( (LL_GPIO_ReadInputPort(GPIOB) & (1 << 12)) != 0 ) {
// на выводе PB12 высокий уровень, кнопка отжата
}
else {
// на выводе PB12 низкий уровень, кнопка нажата
}
Можно использовать функцию не чтения всего порта, а проверки его битов LL_GPIO_IsInputPinSet(). Проверяем вывод LL_GPIO_PIN_12.
if( LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_12) != 0 ) {
// на выводе PB12 высокий уровень, кнопка отжата
}
else {
// на выводе PB12 низкий уровень, кнопка нажата
}
Для управления состоянием светодиода будем использовать функции LL_GPIO_SetOutputPin() и LL_GPIO_ResetOutputPin(). Они устанавливают и сбрасывают заданные выводы.
if( LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_12) != 0 ) {
// на выводе PB12 высокий уровень, кнопка отжата
LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_13); // установка
}
else {
// на выводе PB12 низкий уровень, кнопка нажата
LL_GPIO_ResetOutputPin(GPIOB, LL_GPIO_PIN_13); // сброс
}
Компилируем, загружаем в плату, проверяем.
Полностью проект можно загрузить по ссылке:
Думаю, вы согласитесь с моим утверждением в начале урока, что работать с портами ввода-вывода через LL-функции очень просто. Но более сложной периферией мы будем управлять с помощью библиотеки HAL.
В следующем уроке, скорее всего, займемся обработкой дребезга кнопки. Не хочется полностью повторять последовательность тем уроков Ардуино, но не могу представить ни одну программу без кнопки, переключателя, дискретного датчика и т.п.
Добрый день! Я наверно ещё не всё понимаю. Если порты можно настроить командами, то CubeMX нужен только для настройки тактирования?
Здравствуйте!
Все установки можно настроить и в CubeMX, и в библиотеке HAL, и в LL, и прямым обращениям к регистрам. Выбирайте сами.
Просто настройки тактирования редко меняются в ходе выполнения программы, и они довольно сложны. Их лучше выполнять в CubeMX.
Можно все настроить в CubeMX, а потом работать с переменными, им сформированными.
Спасибо, пока вопросов нет. Но чувствую, будут!
Очень актуально. Урок нужный как никогда. Имеет смысл представить в STmicro. — Потому что они создали гибрид Си СТМ и ассемблер НАЛ. Сравним традиции Microchip. Есть проекты — инженерные. На почту — если есть время. Благодарю!
Здравствуйте.
Вопрос: используя разные библиотеки после компиляции получается идентичный HEX-файл или они будут отличаться?
Если разный, то получается что выполнение одной и той же задачи можно сделать разными способами даже используя одни и те же схемы подключения и идентично высокие и низкие уровни (подключая разные библиотеки) и МК будет работать медленней или быстрее? Зависит от умения программиста?
Бывают ли идеальные коды? И где критерии идеальности?
Что легче для другого человека: оптимизировать код программы другого человека или создать её с нуля, будучи уверенным, что он создаст её оптимальней?
Здравствуйте!
Нет, HEX-коды будут разными.
Остальные вопросы больше философские.
Как правило, чем ниже уровень программирования, тем быстрее выполняются программы, но тем сложнее их разрабатывать. Самые быстрые программы на Ассемблере, потом на чистом C, дальше идет использование библиотек. Конечно квалификация программиста имеет значение.
Ничто не бывает идеальным. Правильнее ставить вопрос об оптимальности разработки. И тут все зависит от критерия оптимальности. Иногда важнее скорость разработки программы, иногда скорость выполнения или длинна кода. Критерием может выступать наглядность, понятность программы для других. Наверное, можно придумать другие критерии.
Инженер должен реализовать поставленную задачу в тех условиях, которые у него есть.
Начал применять библиотеку LL
Столкнулся с переносимостью кода портов ввода/вывода для микроконтроллеров STM32F407 и STM32F103.
При использовании LL_GPIO_ResetOutputPin имеет внутри разную реализацию
1) STM32F407
__STATIC_INLINE void LL_GPIO_ResetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BSRR, (PinMask <BRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
}
Так например при вызове LL_GPIO_ResetOutputPin(GPIOB, 0x0F << HD44780_DATA_SHIFT);
В STM32F103 уже работать так не будет.
Как бороться с этой проблемой?
Начал применять библиотеку LL
Столкнулся с переносимостью кода портов ввода/вывода для микроконтроллеров STM32F407 и STM32F103.
При использовании LL_GPIO_ResetOutputPin имеет внутри разную реализацию
1) STM32F407
__STATIC_INLINE void LL_GPIO_ResetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BSRR, (PinMask <BRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
}
Так например при вызове LL_GPIO_ResetOutputPin(GPIOB, 0x0F << HD44780_DATA_SHIFT);
В STM32F103 уже работать так не будет.
Как бороться с этой проблемой?
2) STM32F103
__STATIC_INLINE void LL_GPIO_ResetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
}
Почему то комментарий обрезался
И как на сайте редактировать комментарии и удалять?
И как на сайте редактировать комментарии и удалять?
Эдуард, спасибо за Ваш труд. В STM32 есть еще одна техника управления отдельными битами -«битовая сегментация».
Если написать определение:
#define PC8B (*((volatile unsigned long *) Адрес для РС8 ))
Устанавливать лог. уровни на ножках можно лаконичными выражениями, без битовых масок.
PC8B=1; // установить единицу на PC8
PC8B=0; // установить ноль на PC8
Хорошее описание техники и калькулятор адресов по ссылке:
http://wow-only.ru/2011-04-14-14-57-54/97-2013-05-27-14-18-47/319-bit-band-cortex-m3.html
«Не нахожу ничего, что надо пояснять.. Получилось проще и понятнее, чем в предыдущем уроке с HAL-драйвером.»
Это просто не то слово как проще! Две недели читаю, но так и не понял, как из библиотеки HAL формировать команды для портов, копирую как обезьяна :(((
Эволюционируй.
Р. Докинза почитай.
Здравствуйте!
Столкнулся с проблемой.
Успешно работаю с STM32F103, F407 + CMSIS. Все круто, всё работает.
Но вот попался новый проц, STM32F030F4, самый мелкий (по габаритам) у ST. А мой любимый стиль — минимализм, любимый проц — ATtiny10, в корпусе sot-23 :))) И по работе полно задач, не требующих загрузки Линукса и подключения к интернету. 🙂
Начинаю осваивать. IDE Атолик слышал краем уха про проц F030, но — у него напрочь нет библиотек для Кортекс-М0! Облом-с…
Куб IDE — запустился, создал проект под HAL, замигал лампочкой, передал в компорт «Hello,Russia!\r\n» Ура!!! Обрадовался, накидал в проект инициализацию нужной мне периферии… Обьем кода голой инициализации — 22кБ! При 16кБ флеша у F030… :((( Для процов повзрослее, с мегабайтами памяти, ну и фиг бы с ним…
Перепилил под LL. Стало полегче — всего 7кБ. Еще одной лампочкой мигнуть можна… Теперь вотъ курю регистры с битами, и вместо размышлений об алгоритме — удивляюсь, куда килобайты памяти деваются…
… а информации по особенностям LL-библиотеки практически нету!