В данной статье рассмотрим функции HAL для работы с I2C в STM32. Так же разберем небольшой пример для лучшего понимания работы функций. В статье, все примеры представлены для режима «Ведущий». Это значит, что инициатором обмена данными является «Мастер».
Общее описание
Для каждой серии микроконтроллеров STMicroelectronics предлагает встроенный пакет библиотек, которые включают в себя драйвер аппаратного абстрагированного уровня (HAL). Этот драйвер предоставляет разработчикам набор API-интерфейсов. Что позволяет абстрагироваться от сложности микроконтроллера и его периферийных устройств. Интерфейс I 2 C является одним из таких периферийных устройств. Драйвер HAL поддерживает три модели программирования для своих функций обработки данных: опрос, прерывание и DMA. Функции опроса работают в режиме блокировки. Это режим, когда управление основной программе передается после завершения работы функции. Для исключения зависания, такие функции в качестве параметра принимают время ожидания, после которого будет принудительный выход из функции. Функции прерывания и DMA работают в неблокирующем режиме. В этом режиме наша программа будет работать не ожидая завершения окончания передачи по I2C. Для таких функций необходимо реализовать функцию Callback. Сюда передается управление. после завершения работы функции в неблокирующем режиме.
При работе в качестве ведущего I 2 C в режиме блокировки доступны четыре функции API для связи с ведомым устройством:
HAL_I2C_Master_Transmit()
HAL_I2C_Master_Receive()
HAL_I2C_Mem_Write()
HAL_I2C_Mem_Read()
HAL_I2C_Master_Transmit
IT()HAL_I2C_Master_Receive
IT()HAL_I2C_Master_Transmit
DMA()HAL_I2C_Master_Receive
DMA()
Для неблокирующей функциональности режимы прерывания и DMA имеют эквивалентные функции.
HAL I2C: передача данных
Отправка данных с ведущего устройства на ведомое с помощью функций HAL достаточно проста. Можно использовать либо функции HAL_I2C_Master_Transmit()
, либо HAL_I2C_Mem_Write()
.
HAL_I2C_Master_Transmit()
Вид прототипа для этой функции API показан ниже. Первый параметр — это просто структура конфигурации, которая содержит настройки параметров I2C. Второй параметр — это адрес ведомого устройства (который необходимо сдвинуть влево на единицу). Третий и четвертый параметры — это указатель на буфер данных и объем данных из буфера, которые должны быть отправлены на ведомое устройство соответственно. Последний параметр — это время ожидания в миллисекундах. Обратите внимание, что пользователь может предоставить HAL_MAX_DELAY
в качестве аргумента отключение тайм-аута и бесконечную блокировку до тех пор, пока функция не выполнит свою работу.
ВНИМАНИЕ! В функции адрес необходимо сдвинуть на единицу, так как первый бит указывает, что мы собираемся делать, читать данные или записывать.
ВНИМАНИЕ! Задержку HAL_MAX_DELAY
использовать не желательно, так как если вы укажите например неверный адрес или устройство будет не доступно, то ваша программа зависнет на функции передачи.
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Последовательность I 2 C, полученная в результате вызова функции, находится ниже по тексту. Заштрихованные области показывают, где сигнал управляется ведомым устройством. Во время передачи ведомое устройство подтверждает свой собственный адрес (плюс бит записи) и любые последующие байты данных. Напомним, что количество отправляемых байтов данных определяется аргументом, Size
предоставленным вызову функции.
В качестве примера использования этой функции рассмотрим приведенный ниже код, в котором содержимое буфера отправляется на ведомое устройство с адресом 0x40. На рисунке ниже можно увидеть форму сигнала во время передачи I 2 C. Эту форму можно получить с помощью логического анализатора.
uint8_t dataBuffer[10] = {0x03, 0x01}; HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);
HAL_I2C_Mem_Write()
Эта функция чаще всего используется для записи данных в память. Примером может быть энергонезависимая память stm32 eeprom i2c. Или же для конфигурации датчиков. Прототип этой функции выглядит следующим образом.
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Функция отличается от HAL_I2C_Master_Transmit двумя дополнительными параметрами. Параметр MemAddress
— начальный адрес памяти в ведомом устройстве, куда будет записано содержимое буфера. Параметр MemAddSize
— размер адреса внутренней памяти. Принимает одно из двух значений I2C_MEMADD_SIZE_8BIT
или I2C_MEMADD_SIZE_16BIT
. Последовательность I 2 C теперь будет выглядеть, как показано ниже:
Как видим, все идентично функции HAL_I2C_Master_Transmit()
. Отличие только в передаче аргумента MemAddress
. Он отправляется после адреса подчиненного устройства. В следующем примере HAL_I2C_Mem_Write()
используется для записи значения 0x01 в регистр с адресом памяти 0x03.
uint8_t dataBuffer[10] = {0x01}; HAL_I2C_Mem_Write(&hi2c1, (0x40 << 1), 0x03, I2C_MEMADD_SIZE_8BIT, dataBuffer, 1, HAL_MAX_DELAY);
HAL I2C: прием данных
HAL_I2C_Master_Receive()
Эта функция API используется для простого запроса данных с ведомого устройства. Обратите внимание, что в приведенном ниже прототипе его параметры идентичны параметрам HAL_I2C_Master_Transmit()
. Однако в этом случае буфер данных используется для хранения входящих данных, а Size
параметр указывает, сколько байтов данных необходимо получить перед отправкой NAck.
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Диаграмма последовательности, описывающая работу этой функции, представлена ниже. Обратите внимание, что она принципиально отличается от любой из рассмотренных выше функций передачи данных. Для приема данных нам необходимо настроить бит направления в адресе на прием. для этого нужно сдвинуть адрес влево на 1 и установить единицу в первый бит адреса. Мастер сообщает подчиненному устройству прекратить отправку данных, отправив Nack, за которым следует условие Stop.
В приведенном ниже примере кода показано, как мастер запрашивает три байта данных у ведомого устройства с адресом 0x40. Мы можем видеть, наблюдая за захватом логического анализатора, что после завершения операции буфер данных будет содержать значения {0x00, 0x68, 0xF0}.
HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1)|0x01, dataBuffer, 3, HAL_MAX_DELAY);
HAL_I2C_Mem_Read()
Эта функция API используется для запроса данных от ведомого устройства с определенного адреса памяти . Опять же, рассмотрим датчик I 2 C, в котором измерения хранятся в определенном регистре ведомого устройства, а ведущее устройство должно считывать данные из этого регистра. Как показано ниже, прототип функции содержит те же аргументы, что и HAL_I2C_Mem_Write()
функция.
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Основное отличие данной функции от большинства, это повторный запуск в режиме блокировки. Функция сначала выполняет операцию передачи I 2 C. Сообщая ведомому устройству, из какого адреса памяти следует получать данные. Затем делает повторный запуск для приема данных. Полная последовательность I 2 C показана на рисунке ниже.
Следующий пример кода получает два байта данных от ведомого устройства с адресом 0x40, начиная с адреса памяти 0x01. Обратите внимание на захват логического анализатора, что буфер будет содержать {0x68, 0x90}, когда операция завершится.
HAL_I2C_Mem_Read(&hi2c1, (0x40 << 1), 0x01, I2C_MEMADD_SIZE_8BIT, dataBuffer, 2, HAL_MAX_DELAY);
HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1)&0x01, dataBuffer, 3, HAL_MAX_DELAY);
Опечатка в (0x40 << 1)&0x01? В результате будет 00000000, а не 10000001.
Спасибо за обнаруженную ошибку. Правильно будет применить логическую операцию «ИЛИ» (0x40 << 1)|0x01 . В статье поправим!