Найти тему

Чем отличаются библиотеки HAL vs LL для STM32 на примере I2C

Оглавление

STM32CubeMx предлагает две библиотеки для упрощения работы разработчикам:

  • HAL
  • LL
В STM32CubeMx эти настройки можно выбирать на вкладке ProjectManager/Advanced Settings
В STM32CubeMx эти настройки можно выбирать на вкладке ProjectManager/Advanced Settings

Чтобы понять разницу между двумя подходами, необходимо взглянуть н пример отправки сообщений по интерфейсу I2C

HAL

//глобальные переменные
I2C_HandleTypeDef I2cHandle;
uint8_t aTxBuffer[] = "что-то очень важное"
//функция передачи данных
HAL_I2C_Master_Transmit(&I2cHandle, (uint16_t)I2C_ADDRESS, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 10000);

Короткая функция, которая скрывает от разработчика все подробности. Можно также выбрать версии, использующие прерывания и DMA.

LL

LL_I2C_AcknowledgeNextData(I2C1, LL_I2C_ACK);
LL_I2C_GenerateStartCondition(I2C1);
while(!LL_I2C_IsActiveFlag_SB(I2C1));
LL_I2C_TransmitData8(I2C1, SLAVE_OWN_ADDRESS | I2C_REQUEST_WRITE);
while(!LL_I2C_IsActiveFlag_ADDR(I2C1));
while(LL_I2C_IsActiveFlag_TXE(I2C1));
LL_I2C_TransmitData8(I2C1, data);
LL_I2C_GenerateStopCondition(I2C1);

Предоставлен полный контроль над интерфейсом, скрыта только непосредственная работа с регистрами.

Вывод

Библиотека HAL в этом отношении берет на себя основную работу по защите пользователя от каких бы то ни было внезапных происшествий, но плата за это -- скорость. Впрочем для быстрого прототипирования то, что нужно.

В то время как библиотека LL даёт куда больший контроль над МК, что не всегда будет плюсом, особенно в прототипировании. В обучении она показывает себя с наилучшей стороны.

Приложения (в которых приведен код функций):

HAL подробно

Hardware Abstract Layer был создан, чтобы упростить жизнь пользователю, одновременно дав ему максимально широкие возможности. Приведу для наглядности код одной из функций HAL.

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{

Переменная начала тиков для будущих операций

uint32_t tickstart = HAL_GetTick();

Если интерфейс находится в состоянии готовности, то начинаем основные операции. В противном случае, заканчиваем функцию в состоянии "HAL_BUSY".

if (hi2c->State == HAL_I2C_STATE_READY)
{
/* Wait until BUSY flag is reset */

Оживаем сброса флага интерфейса I2C_FLAG_BUSY, здесь нам впервые понадобится зарезервированная переменная, которая используется для выхода из бесконечного цикла -- иначе говоря, проверять не больше определенного количества раз. Если не дождались, то заканчиваем функцию с ошибкой.

if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)
{
return HAL_BUSY;
}

Блокируем интерфейс от нежелательных воздействий в процессе нашей работы с ним.

__HAL_LOCK(hi2c);

Проверяем включен ли интерфейс и включаем его, в противном случае.

if ((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)
{
__HAL_I2C_ENABLE(hi2c);
}

/* Disable Pos */

Отключаем флаг Pos, используемый для принятия данных.

CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_POS);

Устанавливаем стартовые параметры интерфейса.

hi2c->State = HAL_I2C_STATE_BUSY_TX;
hi2c->Mode = HAL_I2C_MODE_MASTER;
hi2c->ErrorCode = HAL_I2C_ERROR_NONE;

Нам надо передать данные из буфера pData размером Size.

/* Prepare transfer parameters */
hi2c->pBuffPtr = pData;
hi2c->XferCount = Size;
hi2c->XferSize = hi2c->XferCount;
hi2c->XferOptions = I2C_NO_OPTION_FRAME;

Отправляем адрес получателя Slave Address. Если не дождались, то заканчиваем функцию с ошибкой.

if (I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK)
{
return HAL_ERROR;
}

Сбрасываем флаг адреса.

__HAL_I2C_CLEAR_ADDRFLAG(hi2c);

Отправляем данные в заданном количестве

while (hi2c->XferSize > 0U)
{

Ждем флаг освобождения регистра отправки данных

if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
{
if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{
/* Generate Stop */

Если получили код ошибки, то останавливаем процесс передачи данных.

SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
}
return HAL_ERROR;
}

В противном случае, передаем последовательно данные.

hi2c->Instance->DR = *hi2c->pBuffPtr;
hi2c->pBuffPtr++;

Обновляем счетчики.

hi2c->XferCount--;
hi2c->XferSize--;

Если установлен флаг окончания передачи данных, то осуществить снова передачу байта.

if ((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))
{
hi2c->Instance->DR = *hi2c->pBuffPtr;
hi2c->pBuffPtr++;
hi2c->XferCount--;
hi2c->XferSize--;
}

Проверяем, что флаг окончания не возвращает ошибку.

/* Wait until BTF flag is set */
if (I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
{
if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{

Останавливаем процедуру, в случае ошибки.

SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
}
return HAL_ERROR;
}
}

В противном случае, корректно завершаем функцию.

SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
hi2c->State = HAL_I2C_STATE_READY;
hi2c->Mode = HAL_I2C_MODE_NONE;
__HAL_UNLOCK(hi2c);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}