Программирование ARM STM32 HAL-функции I2C Thu, March 28 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

STM32 HAL-функции I2C Печать
Добавил(а) microsin   

В этом руководстве (перевод статьи [1]) мы рассмотрим встроенное периферийное устройство I2C микроконтроллера (MCU) STM32. Начнем с краткого введения в принцип работы шины Inter-Integrated Circuit (I2C), и подробно рассмотрим аппаратный модуль STM32 I2C, его функционал, режимы работы, опции и конфигурации. Будут рассмотрены сигналы прерывания, которые может генерировать аппаратура STM32 I2C hardware. В качестве примеров рассмотрим различные режимы приема и передачи данных I2C - polling (с опросом флагов), interrupt (получение статуса по прерываниям) - как устройства I2C master, так и устройства I2C slave. В заключение мы проверим доступные варианты примеров конфигураций I2C, которые можно получить в генераторе кода STM32CubeMX [2], рассмотрим способы работы с периферией I2C через библиотеки HAL API, предоставляемые компанией STMicroelectronics.

[Введение в коммуникации по шине I2C]

Шина I2C изначально была разработана компанией Philips Semiconductors (теперь NXP) в 1982 году. В документации для некоторых MCU наподобие Atmel AVR интерфейс I2C часто называют как TWI (аббревиатура от Two Wire Interface). I2C является синхронной, двунаправленной, полудуплексной шиной. Она позволяет работать в конфигурациях с несколькими главными устройствами на шине и одним или несколькими подчиненными устройствами (multi-master), несколькими подчиненными устройствами на шине и одним главным устройством (multi-slave). Однако чаще всего встречается конфигурация multi-slave, когда одно главное устройство (master) управляет одним или несколькими подчиненными (slave) устройствами. I2C широко используется в промышленной и бытовой аппаратуре для обмена данными между микросхемами на небольших расстояниях, когда они расположены на одной печатной плате или в одном корпусе электронного прибора.

Режимы и скорости I2C. Поначалу скорость I2C ограничивалась 100 килобит/сек (частота тактов SCL 100 кГц). Со временем в спецификацию было внесено несколько дополнений, так что теперь существует 5 категорий рабочих скоростей. Категории устройств Standard-mode, Fast-mode (Fm), Fast-mode Plus (Fm+) и High-speed mode (Hs-mode) обратно совместимы другом, т. е. более высокоскоростное устройство master имеет возможность переключиться в режим пониженной скорости для обеспечения совместимости с менее скоростными устройствами на шине. Устройства Ultra Fast-mode несовместимы с устройствами предыдущих версий, поскольку такая шина работает только в одном направлении передачи данных.

Шина I2C с двунаправленной передачей данных может работать в следующих режимах (полудуплекс):

Standard-Mode (Sm), скорость передачи до 100 килобит/сек
Fast-Mode (Fm), скорость передачи до 400 килобит/сек
Fast-Mode Plus (Fm+), скорость передачи до 1 Мегабит/сек
High-speed Mode (Hs-mode), скорость передачи до 3.4 Мегабит/сек

Однонаправленная шина I2C:

Ultra Fast-Mode (UFm), скорость передачи до 5 Мегабит/сек

Аппаратура STM32 штатно поддерживает режимы скоростей Sm и Fm. Программированием регистра делителя тактовой частоты можно добиться поддержки режима Fm+.

Физический слой I2C. Шина I2C использует драйвер с открытым стоком (открытый коллектор) для своих сигналов SDA и SCL. Это означает, что сигналы шины SDA и SCL должны быть подтянуты к + питания (Vcc) через внешние подтягивающие резисторы (pull-up). Типичный номинал для pull-up от 4.7 кОм до 10 кОм, но это может меняться в зависимости от скорости передачи и длины линий сигналов SDA и SCL. По этой причине состоянием ожидания шины (IDLE, никакие данные не передаются) считается лог. 1 на обоих сигналах SDA и SCL, когда все транзисторы драйвера закрыты. Если какой-либо из транзисторов драйвера откроется, то на соответствующем сигнале шины появится лог. 0.

I2C physical layer fig01

Рис. 1. Физическая организация шины I2С.

Чтобы записать на шину сигнал лог. 0, мы включаем выходной драйвер, переводя тем самым сигнальную линию в низкий уровень (LOW). Для записи лог. 1 выключаем выходной драйвер, и линия будет поднята на высокий уровень (HIGH) под действием внешних резисторов. Открытый сток шины I2C делает её двунаправленной, и протокол I2C обеспечивает разрешение коллизий устройств, когда несколько устройств пытаются получить одновременно доступ к шине. Любое из master-устройств на шине, которое первым откроет свой транзистор, выведя на шину лог. 0, выиграет тем самым арбитраж, и другое master-устройство приостановит свою работу в ожидании освобождения шины.

SDA и SCL, достоверность данных. Физически обе линии сигналов SDA и SCL являются двунаправленными. Когда шина свободна, на обоих этих сигналах присутствует уровень HIGH. Выходные каскады всех параллельно подключенных к шине устройств должны иметь выход с общим стоком, реализуя тем самым логическую функцию "проводное И" (wired-AND).

Примечание: если не используется опциональное растягивание импульса тактов slave-устройством (clock stretching) в конфигурации с одним master, то на шине I2C сигнал SCL не обязательно должен быть с открытым стоком, и все сигналы SCL у slave-устройств работают только как вход. В этом случае выход SCL устройства master может быть двухтактным, потому что его уровнем всегда управляет одно и только одно устройство - master. Сигнал SDA по-прежнему должен быть с открытым стоком как у устройства master, так и у всех устройств slave.

Из-за того, что к шине I2C могут быть подключены микросхемы, изготовленные по разным технологиям (CMOS, NMOS, TTL), уровни напряжений для лог. 0 (LOW) и лог. 1 (HIGH) не жестко фиксированы, однако привязаны к уровню напряжения питания VDD (все микросхемы, подключенные к шине I2C, должны иметь одинаковое напряжение питания). Пороговые уровни установлены на 30% и 70% от VDD. Таким образом, лог. 0 (LOW) должен обеспечиваться уровнем VIL = 0.3VDD, и лог. 1 (HIGH) уровнем VIH = 0.7VDD. Данные на SDA должны оставаться стабильными во время периода HIGH на сигнале тактов SCL. Изменение уровня HIGH или LOW сигнала данных должно происходить только тогда, когда сигнал SCL находится на уровне LOW. Для каждого передаваемого по шине бита SDA генерируется один период тактов SCL.

I2C send one bit fig02

Рис. 2. Передача бита по шине I2C.

Элементы транзакций I2C. Типовое сообщение I2C состоит из некоторого количества базовых элементов (сигналов), которые последовательно генерируются сигналами шины. Первый базовый элемент, который появляется на шине - сигнал старта (start condition, далее сокращенно START, или S). За сигналом START идет другой базовый элемент - адрес устройства (обычно 7-разрядный, но иногда 10 разрядный), затем идет один R/W-бит, который обозначает тип операции, которую master заказал на шине. Если бит R/W равен 0, то это означает операцию записи (write), а если равен 1, то операцию чтения (read). После этого, если slave-устройство с переданным адресом присутствует на шине и работает нормально, то оно подтверждает свое присутствие сигналом в бите ACK (Acknowledge), подтягивая шину SDA к лог. 0. Если же устройства с указанным адресом нет на шине, или оно не готово к обмену, шина SDA остается в лог. 1, что означает отрицательное подтверждение NACK (Negative Acknowledge).

Если была заказана операция записи, после этого master передает байт данных, за которым идет сигнал подтверждения от slave-устройства. Далее может быть передано несколько байт. По завершении передачи master может прервать обмен отправкой еще одного базового элемента - сигнала остановки (Stop Condition, далее сокращенно STOP, или P).

Master для инициации новой транзакции может выдать повторно сигнал старта - REPEATED START (далее по тексту сокращенно Sr), без выдачи сигнала STOP (P).

I2C protocol base elements typical sequence fig03

Рис. 3. Типовая последовательность базовых элементов протокола I2C.

Таким образом, различают следующие базовые элементы протокола I2C:

• Сигнал запуска транзакции, Start Condition (START, S)
• Сигнал завершения транзакции, Stop Condition (STOP, P)
• Сигнал перезапуска транзакции, Repeated Start (Restart) Condition (REPEATED START, Sr)
• Положительное подтверждение, Acknowledge (ACK, A)
• Отсутствие подтверждения, Not Acknowledge (NACK, ~A)
• Адрес и бит R/W
• Байт данных

Более подробно про работу шины I2C и её протокола можно почитать в различных публикациях, например в Википедии или статье [3].

[Аппаратура STM32 I2C]

Основные возможности STM32 I2C:

• Совместимость с конфигурацией Multimaster: один и тот же интерфейс может работать в режиме Master или Slave.
• Функции I2C Master: аппаратная генерация тактов, сигналов START и STOP.
• Функции I2C Slave: программируемый адрес I2C с одновременной поддержкой 2 адресов (с возможностью подтверждения двух slave-адресов), детектирования сигнала STOP.
• Генерация и детектирование 7-битной/10-битной адресации и общего вызова по шине (General Call).
• Поддержка скоростей обмена:
   – Standard Speed (до 100 кГц)
   – Fast Speed (до 400 кГц)
• Аналоговый фильтр помех (Analog noise filter).
• 2 вектора прерываний:
   – 1 прерывание для успешного обмена адресом / данными.
   – 1 прерывание для ситуации ошибки.
• Опциональное растягивание импульса тактов slave-устройством (clock stretching) для управления потоком.
• 1-байтный буфер с поддержкой DMA.
• Конфигурируемая функция PEC (packet error checking) для генерации или проверки.
• Совместимость с шиной SMBus 2.0.
• Совместимость с шиной PMBus.

I2C peripheral block diagram fig04

Рис. 4. Блок-схема аппаратуры STM32 I2C.

Как можно увидеть из схемы на рис. 4, здесь присутствует главный регистр сдвига данных, регистр буфера и некоторая логика управления, обслуживающая поведение машины состояний шагов транзакции I2C. Логика обслуживает детектирование совпадения адреса, генерацию сигнала SCL, фильтрацию сигналов от помех, проверку ошибок и т. д.

Выбор режима I2C. Интерфейс STM32 I2C может работать в одном из 4 режимов:

• Slave transmitter (передача данных подчиненным устройством).
• Slave receiver (прием данных подчиненным устройством).
• Master transmitter (передача данных главным устройством).
• Master receiver (прием данных главным устройством).

По умолчанию периферия работает в режиме slave. Интерфейс автоматически переключится из режима slave в режим master после генерации сигнала START, и из режима master в режим slave, если произойдет потеря арбитража или будет сгенерирован сигнал STOP. Такое поведение аппаратуры обеспечивает совместимость multi-master. Далее мы создадим 4 рабочих примера, демонстрирующих работу периферии I2C в этих режимах.

STM32 I2C в режиме Slave. По умолчанию аппаратура I2C находится в режиме slave. Для переключения в режим master необходима генерация сигнала START. Для обеспечения корректных таймингов шины тактовая частота периферии должна быть запрограммирована в регистр I2C_CR2. Исходная входная частота периферии должна быть как минимум:

• 2 МГц для режима скорости Sm (режим стандартной скорости 100 кГц)
• 4 МГц для режима скорости Fm (режим повышенной скорости 400 кГц)

При обнаружении сигнала START от внешнего master принимаемый адрес с сигнала SDA последовательно поступает в регистр сдвига. Затем он сравнивается с запрограммированными (в регистрах (I2C_OAR1 и (I2C_OAR2) адресами. После получения адреса интерфейс slave-устройства с помощью регистра сдвига принимает байты через сигнал SDA в регистр I2C_DR. После каждого байта интерфейс генерирует импульс подтверждения, если установлен бит ACK.

Если установлен флаг RxNE, и данные не были прочитаны из регистра I2C_DR до окончания приема следующего байта, установится бит BTF и интерфейс будет ожидать, пока не будет очищен бит BTF путем чтения регистра I2C_SR1, за которым идет чтение регистра I2C_DR. Пока не будет очищен бит BTF, интерфейс slave будет растягивать сигнал SCL (подтягивая его к уровню LOW), давая тем самым устройству master сигнал неготовности к получению дальнейших данных. Дальнейшая передача данных будет невозможна, пока slave не освободит линию сигнала SCL. По этой причине следует быть осторожным с функцией clock stretching в устройствах slave.

После того, как передан последний байт данных, master генерирует сигнал STOP. Интерфейс определит этот сигнал, установит бит STOPF и сгенерирует прерывание, если установлен бит ITEVFEN. Бит STOPF очищается путем чтения регистра SR1, за которым идет запись регистра CR1.

STM32 I2C в режиме Master. В этом режиме интерфейс инициирует передачу данных и генерирует сигнал тактов SCL. Передача данных всегда начинается с сигнала START, и заканчивается сигналом STOP. Режим Master выбирается сразу, как только на шине генерируется сигнал START. Дальнейшая генерация последовательности необходимых базовых элементов протокола I2C происходит в режиме master.

Типовая последовательность действий программы для организации работы в режиме master:

• Программируется входная тактовая частота периферийного устройства I2C в регистре I2C_CR2, чтобы обеспечить корректные тайминги шины I2C.
• Конфигурируются регистры управления тактами.
• Конфигурируется регистр времени нарастания уровня.
• Программируется регистр I2C_CR1, чтобы разрешить работу периферийного устройства I2C.
• Устанавливается бит START в регистре I2C_CR1, чтобы сгенерировать сигнал START.

Исходная входная частота периферии должна быть как минимум (требования такие же, как и для режима slave):

• 2 МГц для режима скорости Sm (режим стандартной скорости 100 кГц)
• 4 МГц для режима скорости Fm (режим повышенной скорости 400 кГц)

STM32 I2C PEC. Аббревиатура PEC расшифровывается как Packet Error Checking, т. е. это функция проверки ошибок при передаче данных. В периферийном устройстве I2C реализован калькулятор PEC, чтобы улучшить надежность коммуникаций по шине I2C. PEC вычисляется с использованием полинома C(x) = x8 + x2 + x + 1 CRC-8 , последовательно на каждом бите. Если разрешить работу PEC, то появляется возможность автоматически проверять наличие ошибок в больших транзакциях данных, без каких-либо дополнительных накладных расходов по процессорному времени MCU (вычисление и проверка контрольной суммы PEC происходит аппаратно).

Однако следует учитывать, что если master в любой момент проиграет арбитраж, то это приведет к повреждению PEC. И в результате устройство вернется обратно в режим slave, будет ожидать, пока выигравший арбитраж master не завершит свое сообщение. Только после этого можно будет заново начать процесс передачи своих данных и заново начать подсчет PEC для них.

[Обработка ошибок STM32 I2C]

На шине могут произойти некоторые события ошибки, которые детектируются интерфейсом I2C, чтобы информировать программу о проблемах, возникающих на уровне аппаратуры. Программа просто может определить эти ситуации ошибки путем чтения соответствующих бит для каждого сигнала ошибки. Ситуации ошибки включают следующие случаи:

Bus Error (BERR). Эта ошибка произойдет, когда интерфейс I2C определить внешний (неожиданный в данный момент) сигнал STOP или START, когда происходит передача адреса или данных.

Acknowledge Failure (AF). Эта ошибка произойдет, когда интерфейс определит бит отсутствия подтверждения (non-acknowledge, NACK).

Arbitration Lost (ARLO). Эта ошибка произойдет, когда интерфейс определит потерю арбитража.

Overrun/Underrun Error (OVR). Событие переполнения на приеме (Overrun) может произойти в режиме slave, если запрещено растягивание тактов (clock stretching). В этом случае интерфейс принял данные, и данные в регистре DR не были прочитаны до приема интерфейсом следующего байта. Событие недогрузки (Underrun) может произойти в режиме slave, когда также запрещено растягивание тактов, и интерфейс I2C передает данные. В этом случае программа не обновила регистр DR следующим байтом до момента, когда поступили такты уже для следующего байта.

[Прерывания STM32 I2C]

Различные события прерывания I2C привязаны к одному и тому же вектору, так что обработчике прерывания (ISR) для I2C необходимо проанализировать флаги, чтобы определить событие (причину) прерывания. Эти события генерируют прерывание, если установлен соответствующий бит разрешения.

Таблица 1. Запросы на прерывание I2C.

Событие прерывания Флаг события Бит, разрешающий прерывание
Отправлен сигнал START (режим master) SB ITEVFEN
Отправлен адрес (режим master), или обнаружено совпадение адреса (режим slave) ADDR
Отправлен 10-битный заголовок (режим master) ADD10
Принят сигнал STOP (режим slave) STOPF
Завершена передача байта данных BTF
Буфер приема не пуст RxNE ITEVFEN и ITBUFEN
Буфер передачи пуст TxE

[Передача и прием STM32 I2C]

В этой секции мы рассмотрим возможные варианты обработки транзакций I2C в приложении firmware для STM32.

I2C Polling. Первый и самый простой способ делать чего-нибудь в программе при передаче данных - просто периодически, с нужной частотой опрашивать (poll) аппаратуру интерфейса, чтобы перейти к следующему шагу. Однако это самый неэффективный метод, потому что бесполезно тратится процессорное время MCU на выполнение циклов ожидания.

Все это одинаково относится как к передаче, так и к приему. Если выполняется передача, то программа ждет завершения передачи текущего байта, чтобы потом запустить передачу следующего байта. Если выполняется прием, то программа ждет приема байта, после его получения обрабатывает его (записывает в буфер памяти), снова ждет, и так далее.

I2C Interrupt. Для более эффективного использования процессорного времени MCU мы можем разрешить прерывания I2C. Тогда программа будет получать сигналы от нужных событий, и запускать ISR для их обработки. Это может происходить, когда данные были переданы или были приняты. Что экономит много ресурсов процессора, потому что программа в фоновом режиме может заниматься другими действиями, не тратя время на циклы опроса аппаратуры.

Однако и с прерываниями следует быть осторожными, когда необходимо реализовать приложение жесткого реального времени, когда необходимо быстро, в строго заданный интервал времени реагировать на критические внешние события - например, в нужное время обработать сигнал датчика столкновения и вовремя раскрыть подушку безопасности автомобиля. Мы не можем знать заранее, когда произойдет прерывание, во время выполнения какой задачи может начать выполняться код его ISR. При неправильном назначении приоритетов прерываний и небрежного планирования выполнения задач приложения может произойти некорректное поведение программы в реальном времени.

I2C с использованием DMA. Чтобы избежать событий недогрузки и переполнения для работы на максимальной скорости во время передачи для I2C необходимо вовремя предоставлять данные, и во время приема нужно вовремя считывать поступившие данные. Для упрощения перемещений данных аппаратура I2C поддерживает функцию DMA (Direct Memory Access, прямой доступ к памяти), реализующую простой протокол запрос/подтверждение.

Запросы DMA генерируются, когда при передаче регистр данных опустошается, и когда при приеме он заполняется. Использование DMA также освобождает процессорное время MCU, потому что перемещения данных между периферийным устройством и памятью происходят аппаратно. DMA считается самым эффективным методом обработки потока данных.

[Чтение/запись микросхемы памяти I2C]

В этой секции мы рассмотрим полезные функции HAL API для библиотеки драйвера I2C, которые позволяют читать и записывать микросхему памяти. Функции HAL_I2C_Mem_Write() и HAL_I2C_Mem_Read() реализуют чтение и запись микросхем памяти с блокированием выполнения.

Существуют и другие аналогичные версии функций, которые выполняют ту же самую работу без блокировки (с использованием прерываний или прерываний + DMA). Но для начала давайте разберем, как происходит процесс чтения/записи памяти устройства.

Нам, как разработчикам, обычно не нужно что-то делать на низком аппаратном уровне, когда необходимо реализовать какие-либо действия по обмену данными с внешними устройствами. Нужно просто иметь общее представление о том, как работает шина, и помнить о том, что низкоуровневые операции по реализацию протокола I2C может автоматически обрабатывать аппаратура MCU, нам всего лишь нужно её правильно запрограммировать путем записи и чтения необходимых регистров и флагов I2C. Для этой цели существуют готовые функции HAL (Hardware Abstraction Layer), которые упрощают манипуляции с регистрами I2C.

Базовый функционал по передаче данных через I2C в качестве устройства Master обрабатывает следующая HAL-функция HAL_I2C_Master_Transmit(). Она передает какие-либо данные через I2C в указанный адрес slave-устройство (если это устройство присутствует на шине) в блокирующем режиме.

В чем же разница между функциями I2C_Transmit и I2C_Mem_Write? Для ответа на этот вопрос рассмотрим пример взаимодействия с устройством MPU6050 IMU (расшифровывается как Inertial Measurement Unit, т. е. это микросхема инерциального гироскопа, или датчика положения в пространстве).

У IMU есть внутренний адрес устройства I2C, чтобы любой master шины мог по нему обратиться к модулю сенсора микросхемы MPU6050. Внутри MPU6050 есть регистры с внутренними уникальными адресами. Таким образом, для работы с сенсором IMU нам нужно установить (записать) некоторые значения в определенные регистры микросхемы MPU6050 (чтобы её сконфигурировать), и затем считывать значения из других регистров MPU6050, в которых содержатся данные IMU.

Итак, если у вас есть master STM32, и хотите получить показания MPU6050 IMU, то нужно будет выполнить следующие указанные в таблице действия.

Таблица 2. Последовательность базовых элементов протокола I2C для чтения одного байта.

Master S AD+W   RA   S AD+R     NACK P
Slave     ACK   ACK     ACK DATA    

Как можно видеть, устройство master (STM32 MCU) должно запустить транзакцию отправкой сигнала START (S). Затем master должен отправить адрес с признаком операции записи (AD+W, адрес здесь это адрес I2C самого модуля MPU6050). Затем master записывает внутренний регистр адреса микросхемы MPU6050 (RA), это тот регистр, который необходимо прочитать. Устройство slave (микросхема MPU6050) подтвердит эту команду (ACK), и в ответ на операцию чтения (повторная отправка S и адреса с признаком операции чтения AD+R) отправит данные, которые находятся в этом указанном регистре. В завершении транзакции master прервет коммуникацию отправкой сигналов NACK и STOP (P).

Аналогичная операция чтения происходит со многими другими микросхемами с интерфейсом I2C, отличаются только адрес I2C и адреса внутренних регистров или ячеек памяти.

Вы можете выполнить операции записи и чтения с помощью вызова базовых низкоуровневых функций HAL API (I2C_Transmit, I2C_Receive), или с помощью более высокоуровневых функций (Mem_Write, Mem_Read), разных версий, поддерживающих все режимы аппаратуры STM32 (blocking, interrupt, DMA).

Разберем пример конфигурирования микроконтроллера STM32F429ZITx (плата 32F429IDISCOVERY, или STM32F429I-DISC1 [4]) для подключения к микросхеме контроллера клавиатуры TCA8418 [5]. Будем использовать I2C1 и его выводы PB8 SCL, PB9 SDA, работу с I2C по прерываниям (без DMA).

Шаг 1. Запустите генератор кода STM32CubeMX, выберите микроконтроллер (кнопка ACCESS TO MCU SELECTOR).

I2C1 master STM32CubeMX example fig01

I2C1 master STM32CubeMX example fig02

Шаг 2. На закладке Pinout & Configuration разверните выпадающий список Connectivity, и выберите I2C1. В выпадающем списке I2C поменяйте выбор с Disable на I2C.

I2C1 master STM32CubeMX example fig03

Шаг 3. Теперь нужно выбрать ножки портов для аппаратуры интерфейса I2C1. Для ножек портов GPIO микроконтроллера STM32F429 можно выбрать альтернативные функции, соответствующие различным внутренним периферийным устройствам (подробнее см. [6]). Для выбора ножек мы воспользуемся удобным интерфейсом генератора кода STM32CubeMX.

По умолчанию генератор кода назначил для I2C1 ножки портов PB6 (сигнал SCL) и PB7 (сигнал SDA). Нам же нужны ножки PB8 для SCL и PB9 для SDA. Для этого в правой части окна STM32CubeMX, где представлен вид на корпус LQFP144 микроконтроллера, переведите вид на выводы PB7 и PB6 (приближать можно колесиком мыши с удержанием клавиши Ctrl, а перемещать картинку можно удерживая правую кнопку мыши). Кликните на вывод PB7, и выберите Reset_State. Конфигурация по умолчанию сбросится, и можно выбрать другие ножки порта.

Кликните на вывод порта PB8, и выберите для него альтернативную функцию I2C1_SCL. 

I2C1 master STM32CubeMX example fig04

Таким же способом выберите для PB9 альтернативную функцию I2C1_SDA. После этого на закладке настроек GPIO Settings появятся новые выбранные ножки для сигналов SCL и SDA.

I2C1 master STM32CubeMX example fig05

Шаг 4. Перейдите в раздел настроек контроллера прерываний (NVIC Settings). Поставьте галочки I2C1 event interrupt (обработка основных событий приема и передачи) и I2C1 error interrupt (обработка событий ошибки).

I2C1 master STM32CubeMX example fig06

Если Вы планируете использовать DMA, то на закладке DMA Settings можно добавить соответствующие запросы DMA.

Шаг 5. Теперь можно будет установить вариант скорости I2C (Standard Mode или Fast Mode) и тактовую частоту для режима master. Выберите закладку настроек Parameter Settings, установите режим Fast Mode, автоматически установится частота тактов I2C 400 кГц. Здесь можно сконфигурировать и другие параметры, такие как скважность тактов, коэффициент цифрового фильтра, запрет аналогового фильтра.

I2C1 master STM32CubeMX example fig07

Если для интерфейса I2C STM32 используется режим slave, то необходимо также установить Primary slave address и его длину. Также для slave-режима можно разрешить растягивание тактов и другие параметры.

Шаг 6. Теперь нужно настроить общую систему тактирования STM32. Перейдите в раздел настроек System Core, выберите RCC, и в выпадающем списке High Speed Clock (HSE) выберите Crystal/Ceramic Resonator.

I2C1 master STM32CubeMX example fig08

Перейдите на закладку Clock Configuration, переключите мультиплексор тактов в положение HSE, и выберите частоту кварцевого резонатора 8 МГц (такой кварц установлен на плате STM32F429 DISCOVERY). Остальные опции здесь можно оставить по умолчанию.

I2C1 master STM32CubeMX example fig09

Шаг 7. Перейдите на закладку Project Manager. В разделе настроек проекта (кнопка Project) выберите имя для проекта (поле ввода Project Name), место расположения папки проекта (Project Location), используемую среду разработки (Toolchain / IDE) и его минимальную версию (Min Version). Остальные параметры можно оставить без изменения.

I2C1 master STM32CubeMX example fig10

Сохраните проект выбором в меню File -> Save Project (Ctrl+S).

I2C1 master STM32CubeMX example fig11

Шаг 8. Кликните на кнопку GENERATE CODE (находится справа вверху). Если Вы раньше не генерировали проекты для этого семейства микроконтроллеров, то будет выведен запрос на подтверждение загрузки необходимого пакета библиотек. Подтвердите, начнется процесс загрузки и установки.

I2C1 master STM32CubeMX example fig12

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

I2C1 master STM32CubeMX example fig13

Примечание: во время установки может произойти ошибка "Target directory allready exists". Это старая болячка STM32CubeMX, решить эту проблему довольно легко, см. [8].

[Пример работы master I2C]

Пример инициализации I2C:

I2C_HandleTypeDef hi2c1;
 
void MX_I2C1_Init(void)
{
   hi2c1.Instance = I2C1;
   hi2c1.Init.ClockSpeed = 100000;
   hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
   hi2c1.Init.OwnAddress1 = 0;
   hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
   hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
   hi2c1.Init.OwnAddress2 = 0;
   hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
   hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
   if (HAL_I2C_Init(&hi2c1) != HAL_OK)
      HALerrorHandler(__FILE__, __LINE__);
   if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
      HALerrorHandler(__FILE__, __LINE__);
   if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK)
      HALerrorHandler(__FILE__, __LINE__);
}

Теперь предстоит запрограммировать регистры микросхемы контроллера TCA8418, чтобы она была сконфигурирована в соответствии с используемой матрицей клавиатуры.

Предположим, что необходимо сконфигурировать матрицу клавиатуры 5x8 (ROW0-ROW4, COL0-COL7):

TCA8418 schematic example

В этом случае для инициализации TCA8418 необходимо отправить следующий массив данных TCA8418cfg:

// Адреса регистров TCA8418:
#define TCA8418_REG_CFG          0x01
#define TCA8418_REG_INT_STAT     0x02
#define TCA8418_REG_KEY_EVENT_A  0x04
#define TCA8418_REG_KP_GPIO1     0x1D
#define TCA8418_REG_KP_GPIO2     0x1E
#define TCA8418_REG_KP_GPIO3     0x1F
 
static const uint8_t TCA8418cfg [] =
{
   TCA8418_REG_KP_GPIO1, 0x1F,   // Режим сканирования для ROW0-ROW4
   TCA8418_REG_KP_GPIO2, 0xFF,   // Режим сканирования для COL0-COL7
   // Основная конфигурация TCA8418:
   TCA8418_REG_CFG,      0xF1,   // 11110001
   // Расшифровка 0xF1 = 11110001b:
   //   AI=1: автоинкремент адреса разрешен
   //   GPI_E_CFG=1: события GPI не отслеживаются, когда клавиатура заблокирована
   //   OVR_FLOW_M=1: старые данные при переполнении удаляются
   //   INT_CFG=1: сигнал ~INT снимается через 50 мкс
   //   OVR_FLOW_IEN=0: ~INT при переполнении не генерируется
   //   K_LCK_IEN=0: ~INT при разблокировке клавиатуры не генерируется
   //   GPI_IEN=0: ~INT для сигналов GPI не генерируется
   //   KE_IEN: разрешение генерации ~INT для событий матрицы клавиатуры
   // Очистка флагов прерывания OVR_FLOW_INT, K_LCK_INT, GPI_INT, K_INT:
   TCA8418_REG_INT_STAT, 0x0F
};

Отправка данных конфигурации:

#define TCA8418_DEV_ADDR   (0x34 << 1)
 
HAL_StatusTypeDef halstatus;
 
for(uint8_t i = 0; i < 9; i += 2)
{
   // Передача пар байт "адрес регистра, значение регистра":
   halstatus = HAL_I2C_Master_Transmit(&hi2c1,
                                       TCA8418_DEV_ADDR,
                                       (uint8_t*)TCA8418cfg+i,
                                       2,
                                       I2Cx_TIMEOUT_MAX_KB);
   if (HAL_OK != halstatus)
   {
      // Тут нужно вставить обработку ошибки:
      ..
   }
}

Пример чтения кода нажатой клавиши, которое должно запускаться по сигналу ~INT:

uint8_t keycode = 0;
uint8_t data8;
 
// запрос 1 байта из FIFO:
data8 = TCA8418_REG_KEY_EVENT_A;
halstatus = HAL_I2C_Master_Transmit(&hi2c1,
                                    TCA8418_DEV_ADDR,
                                    &data8,
                                    1,
                                    I2Cx_TIMEOUT_MAX_KB);
if (HAL_OK != halstatus)
{
   // Обработка ошибки
   ..
}
halstatus = HAL_I2C_Master_Receive(&hi2c1,
                                   TCA8418_DEV_ADDR,
                                   &data8,
                                   1,
                                   I2Cx_TIMEOUT_MAX_KB);
if (HAL_OK != halstatus)
{
   // Обработка ошибки
   ..
}
 
// В переменной keycode находится код нажатой клавиши,
// в старшем бите keycode единица означает нажатие,
// ноль отпускание:
keycode = data8;

Ниже рассмотрено использование STM32 I2C1 в режиме подчиненного устройства, с обработкой событий I2C по прерываниям.

Настройка параметров аппаратуры I2C:

static void MX_I2C1_Init(void)
{
   hi2c1.Instance = I2C1;
   hi2c1.Init.ClockSpeed = 100000;
   hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
   // Адрес подчиненного устройства I2C:
   hi2c1.Init.OwnAddress1 = 16;
   hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
   hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
   hi2c1.Init.OwnAddress2 = 0;
   hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
   hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
   if (HAL_I2C_Init(&hi2c1) != HAL_OK)
      HALerrorHandler(__FILE__, __LINE__);
   if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
      HALerrorHandler(__FILE__, __LINE__);
   if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK)
      HALerrorHandler(__FILE__, __LINE__);
}

Инициализация аппаратуры I2C и прерывания:

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
   GPIO_InitTypeDef GPIO_InitStruct = {0};
 
   // В качестве мастера I2C у нас будет работать ATmega328.
   __HAL_RCC_GPIOB_CLK_ENABLE();
 
   /** Конфигурация ножек I2C1:
   PB8     ------> I2C1_SCL
   PB9     ------> I2C1_SDA
    */
   GPIO_InitStruct.Pin = I2C1_SCL_Pin|I2C1_SDA_Pin;
   GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
   GPIO_InitStruct.Pull = GPIO_PULLUP;
   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
   GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
   /* Разрешение тактирования I2C: */
   __HAL_RCC_I2C1_CLK_ENABLE();
 
   /* Инициализация обработчика прерывания */
   HAL_NVIC_SetPriority(I2C1_EV_IRQn, KEYB_EV_INTERRUPT_PRIORITY, 0);
   HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
   HAL_NVIC_SetPriority(I2C1_ER_IRQn, KEYB_ER_INTERRUPT_PRIORITY, 0);
   HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
}

Обработчик прерывания событий I2C:

void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) 
{
   static BaseType_t xHigherPriorityTaskWoken;
   // По семафору keybsem разблокируется поток, который
   // осуществляет обмен с устройством master:
   xSemaphoreGiveFromISR(keybsem, &xHigherPriorityTaskWoken);
}

Код, который получает байт данных от устройства master:

halstatus = HAL_I2C_Slave_Receive_IT(&hi2c1, &data8, 1);

[Блокирующие HAL-функции для STM32 I2C]

"Блокирующими" называют такие функции, которые не возвращают управление из своего тела до тех пор, пока не будет выполнена их задача (например, пока не будет выполнена передача заказанного блока, или пока в процессе передачи не произойдет ошибка). Т. е. эти функции блокируют выполнение кода, из которого они были вызваны. Использование таких функций вполне допустимо в простых приложениях, когда не нужно параллельно выполнять другие задачи, либо в многопоточных приложениях с вытеснением (на основе RTOS), когда в случае блокировки потока на функции другие потоки могут нормально выполняться. Ниже приведен список блокирующих функций.

Передача master:

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

Прием master:

HAL_StatusTypeDef HAL_I2C_Master_Receive (I2C_HandleTypeDef* hi2c,
                                          uint16_t DevAddress,
                                          uint8_t* pData,
                                          uint16_t Size,
                                          uint32_t Timeout);

Передача slave:

HAL_StatusTypeDef HAL_I2C_Slave_Transmit (I2C_HandleTypeDef* hi2c,
                                          uint8_t* pData,
                                          uint16_t Size,
                                          uint32_t Timeout);

Прием slave:

HAL_StatusTypeDef HAL_I2C_Slave_Receive (I2C_HandleTypeDef* hi2c,
                                         uint8_t* pData,
                                         uint16_t Size,
                                         uint32_t Timeout);

Запись в устройство памяти:

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_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);

Все эти функции возвращают значение из перечисления HAL_StatusTypeDef (в случае успеха будет возвращено HAL_OK, иначе один из кодов ошибки). В таблице ниже показано назначение параметров функций.

Таблица 3. Параметры HAL-функций STM32 I2C.

Параметр Назначение
I2C_HandleTypeDef* hi2c Указатель на дескриптор выбранного аппаратного устройства I2C.
uint16_t DevAddress Адрес I2C подчиненного устройства на шине.
uint8_t* pData Указатель на буфер данных в памяти.
uint16_t Size Размер буфера данных (количество байт).
uint32_t Timeout Таймаут ожидания завершения операции в миллисекундах.
uint16_t MemAddress Адрес внутри устройства памяти.
uint16_t MemAddSize Размер адреса устройства памяти.
uint32_t Trials Количество проверок готовности.

[HAL-функции прерываний для STM32 I2C]

Следующие функции могут работать в неблокирующем режиме - они возвращают управление сразу после вызова, и выполняют всю работу по обслуживанию транзакции I2C, используя для этого обработчик прерывания (ISR). Достоинство такого принципа работы в том, что во время передачи данных I2C процессор не простаивает на циклах ожидания и может выполнять другую работу.

Передача master:

HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT (I2C_HandleTypeDef* hi2c,
                                              uint16_t DevAddress,
                                              uint8_t* pData,
                                              uint16_t Size);

После вызова функции HAL_I2C_Master_Transmit_IT периферийное устройство I2C начнет передавать все байты данных в буфере pData один за другим, пока не будет передано Size байт из буфера. Когда передача завершится, будет запущена функция обратного вызова HAL_I2C_MasterTxCpltCallback (см. ниже). Если нужно выполнить какие-либо действия по завершению транзакции, то для этого можно вставить в тело функции HAL_I2C_MasterTxCpltCallback любой необходимый код. Функция HAL_I2C_MasterTxCpltCallback определена в модуле stm32f4xx_hal_i2c.c библиотеки с модификатором __weak [7], и поэтому должна быть при необходимости переопределена в любом из модулей пользователя (например в main.c):

void HAL_I2C_MasterTxCpltCallback (I2C_HandleTypeDef * hi2c)
{
   // Передача I2C завершена. Сюда можно вставить какой-нибудь код,
   // например обновление данных буфера и повторный запуск транзакции.
   ..
}

Прием master:

HAL_StatusTypeDef HAL_I2C_Master_Receive_IT (I2C_HandleTypeDef* hi2c,
                                             uint16_t DevAddress,
                                             uint8_t* pData,
                                             uint16_t Size);

После вызова функции HAL_I2C_Master_Receive_IT периферийное устройство I2C начнет принимать поступающие от slave-устройство байты данных, и записывать их один за другим в буфер pData, пока не будет принято Size байт. Когда все данные были приняты, будет вызвана callback-функция HAL_I2C_MasterRxCpltCallback. Если нужно выполнить какие-либо действия по завершению транзакции, то для этого можно вставить в тело функции HAL_I2C_MasterRxCpltCallback любой необходимый код. Функция HAL_I2C_MasterRxCpltCallback определена в модуле stm32f4xx_hal_i2c.c библиотеки с модификатором __weak [7], и поэтому должна быть при необходимости переопределена в любом из модулей пользователя (например в main.c):

void HAL_I2C_MasterRxCpltCallback (I2C_HandleTypeDef * hi2c)
{
   // Прием I2C завершен. Сюда можно вставить какой-нибудь код,
   // например обработку принятых данных.
   ..
}

Подобные API-функции реализованы также и для режима slave-устройства I2C.

[HAL-функции для STM32 I2C с использованием DMA]

Следующие функции также не блокирующие, и кроме прерываний они используют функцию прямого доступа к памяти (Direct Memory Access, DMA), дополнительно разгружающую процессор от операций перемещения данных из периферийного устройства в память (или в обратном направлении).

Передача master:

HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA (I2C_HandleTypeDef* hi2c,
                                               uint16_t DevAddress,
                                               uint8_t* pData,
                                               uint16_t Size);

Прием master:

HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA (I2C_HandleTypeDef* hi2c,
                                              uint16_t DevAddress,
                                              uint8_t* pData,
                                              uint16_t Size);

Эти функции работают аналогично своим *_IT аналогам, описанным выше. После вызова функции *_DMA периферийное устройство I2C начнет работу по выполнению заказанной транзакции. Перемещение данных между буфером и периферийным устройством I2C будет выполняться аппаратно, с помощью DMA. Когда транзакция завершится, будет запущена функция обратного вызова HAL_I2C_MasterTxCpltCallback (для передачи) или HAL_I2C_MasterRxCpltCallback (для приема), см. описание выше.

Подобные API-функции DMA реализованы также и для режима slave-устройства I2C.

[Проверка устройства STM32 I2C]

Эта функция может использоваться для проверки, присутствует ли на шине slave-устройство с адресом DevAddress, и находится ли оно в работоспособном для обмена состоянии:

HAL_StatusTypeDef HAL_I2C_IsDeviceReady (I2C_HandleTypeDef* hi2c,
                                         uint16_t DevAddress,
                                         uint32_t Trials,
                                         uint32_t Timeout);

[Ссылки]

1. STM32 I2C HAL Code Examples Slave & Master DMA Interrupt site:deepbluembedded.com.
2. STM32CubeMX site:st.com.
3. AT24C64: Serial EEPROM с интерфейсом I2C (TWI).
4. STM32F429 Discovery.
5. TCA8418: контроллер матрицы клавиатуры.
6. STM32F429: GPIO и альтернативные функции.
7. Что такое weak-функция?
8Ошибка установки пакета в STM32 CubeMX.

 

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


Защитный код
Обновить

Top of Page