I2C часть 1 (перевод из книги Mastering STM32)

Давно хотел сделать перевод именно этой главы, еще со времени, когда сделал первый перевод главы по SPI (уже год прошел), и вот, это время пришло. 14 глава книги разделена условно на две части, соответственно, и у меня будет две статьи: первая часть будет введение в работу I2C, а вторая — практическая, по использованию HAL в работе I2C периферии.

14. I2C

В настоящее время даже самая простая печатная плата содержит две и более цифровых интегральных микросхем, в дополнение к основному микроконтроллеру, предназначенных для конкретных задач. АЦП и ЦАП, память EEPROM, датчики, логические порты ввода/вывода, часы реального времени RTC, радиотрансиверы и контроллеры ЖКИ дисплеев — это лишь небольшой список возможных интегральных микросхем, специализирующихся на выполнении только одной конкретной задачи. Дизайн современной цифровой электроники — это в основном о правильном выборе (и программировании) мощных, специфических и, чаще всего, дешевых интегральных микросхем, объединенных в конечную печатную плату.

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

Почти все микроконтроллеры STM32 предоставляют выделенную аппаратную периферию, способную взаимодействовать с использованием протоколов I2C и SPI. Эта глава является первой из двух, посвященных этой теме, и в ней кратко описывается протокол I2C и соответствующие API интерфейсы CubeHAL для программирования этого периферийного устройства. Если вам интересно узнать больше про I2C, то документ UM10204 от NXP предоставляет полную и самую актуальную спецификацию.

14.1 Введение в спецификацию I2C

Inter-Integrated Circuit (также известная как I2C) представляет собой аппаратную спецификацию и протокол, разработанный подразделением полупроводников Philips (ныне NXP Semiconductors) еще в 1982 году (NXP приобрела Freescale Semiconductor в 2015 году и обе компании в данный момент предоставляют микроконтроллеры на базе Cortex-M. Это означает, что в настоящее время NXP производит два различных семейства микроконтроллеров на базе Cortex-M: LPC от NXP и Kinetis от Freescale. Оба этих семейства являются прямыми конкурентами STM32 и не ясно, какое из них выживет после этого важного приобретения, т.к., по мнению автора, развивать оба нет смысла. Хотя LPC и Kinetis сравнимы с STM32, последний, вероятно, более распространен, особенно среди радиолюбителей и студентов. Примечание автора). Это multi-slave (I2C также может быть протоколом с несколькими master устройствами, что означает, что на одной и той же шине могут существовать два и более ведущих контроллера, но только один за раз может взять управление на себя и разрешать доступ к шине. На практике очень редко используется режим с несколькими master контроллерами и данная книга не охватывает этот режим. Примечание автора), полудуплексная, ориентированная на 8-битные шины, спецификация последовательного канала передачи данных, в которой используются только два провода для соединения заданного числа ведомых или slave устройств к ведущему или master контроллеру. До октября 2006 года разработка устройств на базе I2C подлежала уплате лицензионных отчислений Philips, но это ограничение было снято (Вам все еще необходимо платить отчисления NXP, если вы хотите получить официальный и лицензированный пул адресов I2C для ваших устройств, но я думаю, что это не относится к читателям этой книги. Примечание автора).

Рисунок 1. Графическое представление шины I2C

Два провода, образующие шину I2C, представляют собой двунаправленные линии с открытым стоком, называемые линией последовательной передачи данных (SDA) и последовательной линией синхронизации (SCL) соответственно. (см. Рисунок 1). Протокол I2C регламентирует, что обе этих линии должны быть подтянуты к питанию с помощью резисторов подтяжки. Номинал сопротивления этих резисторов напрямую связан с емкостью шины и скоростью передачи данных. Этот документ от Texas Instruments предоставляет необходимые формулы для вычисления номинала сопротивлений резисторов. Тем не менее, довольно часто используются резисторы со значением сопротивления близким к 4.7 кОм.

Современные микроконтроллеры, такие как STM32, позволяют конфигурировать GPIO как подтягивающую линию с открытым стоком, позволяя использовать внутренние подтягивающие резисторы. Часто в интернете можно прочитать, что вы можете использовать внутренние подтяжки для I2C, тем самым избегая использования внешних компонентов. Однако, во всех устройствах STM32 внутренние подтягивающие резисторы имеют значение близкое к 20 кОм, чтобы избежать нежелательных утечек тока. Такое большое значение сопротивления увеличивает время, необходимое шине для достижения высокого уровня, снижая скорость передачи данных. Если скорость не важна для вашего приложения, и если (очень важно) вы не используете длинные проводники между микроконтроллером и микросхемой (менее 2 см), то для многих приложений можно использовать внутренние подтягивающие резисторы. Однако, если на плате достаточно места для пары резисторов, настоятельно рекомендуется использовать внешние резисторы подтяжки.

Обратите внимание, что микроконтроллеры семейства STM32F1 не обеспечивают возможность подтягивания линий SDA и SCL. Их GPIO должны быть сконфигурированы как с открытым стоком и для подтягивания линий I2C необходимы два внешних резистора.

Будучи протоколом, основанным на передаче данных только по двум проводам, должен быть способ адресации отдельного ведомого устройства на одной шине. По этой причине I2C определяет, что каждое ведомое устройство должно иметь уникальный адрес для шины, в которой оно находится (Это является одним из самых существенных ограничений протокола I2C. Фактически, производители микросхем редко выделяют достаточно контактов для настройки полного адреса ведомого устройства, если вам повезет, то будет выделено не более трех контактов, предоставляя только восемь вариантов адресов. При проектировании платы с несколькими устройствами I2C, обратите внимание на их адресацию, если возможно коллизии, то вам придется использовать два или более периферийных устройств I2C. Примечание автора). Адрес может иметь размерность 7 или 10 бит (последний вариант используется довольно редко).

Скорости шины I2C четко определены спецификацией протокола, однако не редко можно встретить микросхемы, способные устанавливать пользовательские значения скорости обмена данными. Обычные частоты шины I2C 100 кГц, также известный как стандартный режим (standard mode), и 400 кГц, известный как быстрый режим (fast mode). Последние версии стандарты позволяют работать на более высоких скоростях (1 МГц, известный как fast mode plus, 3.4 МГц или high speed mode, а также 5 МГц — ultra fast mode).

Протокол I2C является достаточно простым, так что микроконтроллер может «имитировать» выделенное периферийное устройство I2C: этот метод называется bit-banging и обычно используется в действительно недорогих 8-битных архитектурах, которые иногда не предоставляют выделенный интерфейс I2C для уменьшения количества выводов и/или стоимости микроконтроллера.

14.1.1 Протокол I2C

В протоколе I2C все транзакции всегда инициируются и завершаются ведущим устройством. Это одно из немногих правил этого коммуникационного протокола, которое необходимо учитывать при программировании (и особенно отладке) устройств I2C. Все сообщения, которыми обмениваются по шине I2C, делятся на два типа фреймов: адресный фрейм, где ведущий указывает, какому ведомому устройству отправляется сообщение, и один или несколько фреймов данных, которые являются 8-битными сообщениями, передаваемыми от ведущего к ведомому или наоборот. Данные помещаются в линию SDA после того, как в SCL установится низкий уровень, выборка происходит по высокому уровню на SCL. Время между фронтами синхронизации и считыванием/записью данных определяется устройствами в шине и может быть различно для различных микросхем.

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

Теперь попробуем проанализировать основные этапы коммуникации по I2C.

Рисунок 2. Структура базового I2C сообщения

14.1.1.1 Условия START и STOP

Все транзакции в шине начинаются с начального условия START и заканчиваются условием STOP (см. Рисунок 2). Переход от высокого логического уровня к низкому на линии SDA, в то время как на SCL высокий логический уровень, определяет условие START. Переход от низкого логического уровня к высокому на линии SDA, в то время как на SCL высокий логический уровень, определяет условие STOP.

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

14.1.1.2 Формат байта

Каждое слово данных, передаваемое по линии SDA, должно иметь размерность 8 бит и это также подразумевается для адресного фрейма, как мы увидим позднее. Количество байт, которые могут быть переданы за одну транзакцию, не ограничено. Каждый байт должен сопровождаться битом подтверждения Acknowledge (ACK). Первым передается MSB старший значащий бит (см. Рисунок 2). Если ведомое устройство не может получить или передать еще один полный байт данных до тех пор, пока не выполнит какую-либо другую функцию, например, обслуживание внутреннего прерывания, оно может удерживать линию SCL в низком логическом уровне, чтобы перевести ведущее устройство в шине в режим ожидания. Затем передача данных продолжается после того как ведомое устройство освобождает линию синхронизации SCL.

14.1.1.3 Фрейм адреса

Адресный фрейм всегда является первым в любой транзакции. Для 7-битного адреса сначала следует старший значащий бит адреса, после адреса идет бит R/W, указывающий, является ли это операцией чтения (1) или записи (0) (см. Рисунок 2).

Рисунок 3. Структура сообщения при использовании 10-битной адресации

В 10-битной системе адресации (см. Рисунок 3) для передачи адреса ведомого устройства требуется два фрейма. Первый будет состоять из 1111 0XXD, где ХХ — два бита MSB 10-битного адреса ведомого устройства, а D — бит R/W, который описан выше. Бит ACK первого фрейма будет установлен всеми ведомыми устройствами, в адреса которых совпадают два старших значащих бита. Как и при обычной передаче немедленно начинается другая передача и она содержит биты [7:0] адреса. На этом этапе адресуемый ведомый должен ответить битом ACK. Если этого не происходит, то режим сбоя такой же, как и в 7-битной системе.

14.1.1.4 Acknowledge (ACK) и Not Acknowledge (NACK)

ACK имеет место быть после каждого байта. Этот бит позволяет приемнику сигнализировать передатчику (обратите внимание, что здесь мы в общем говорим о приемнике и передатчике, потому что бит ACK/NACK может быть установлен как ведущим, так и ведомым. Примечание автора), что байт был успешно принят и может быть отправлен следующий. Ведущий контроллер генерирует все тактовые импульсы по линии SCL, включая девятый импульс ACK.

Сигнал ACK формируется следующим образом: передатчика освобождает линию SDA во время тактового импульса подтверждения, таким образом приемник может притянуть линию SDA к низкому уровню в течение периода тактового импульса. Когда SDA остается все также в высоком логическом уровне в течение девятого тактового импульса, это распознается как сигнал Not Acknowledge (NACK). Далее ведущий может сгенерировать либо условие STOP для отмены передачи, либо условие RESTART для начала новой передачи. Есть пять условий, которые могут привести к генерации NACK:

  1. На шине не обнаружено устройства с переданным адресом, соответственно ответить некому.
  2. Приемник не может принимать или передавать, потому что он выполняет некоторую функцию в реальном времени и не готов начинать связь с ведущим устройством.
  3. Во время передачи приемник получил данные или команды, которые он не понимает.
  4. Во время передачи приемник не может получать больше байт данных.
  5. Приемник ведущего устройства должен просигнализировать об окончании передачи ведомому передатчику.

Эффективность бита ACK/NACK обусловлена природой открытого стока протокола I2C. Открытый сток означает, что как ведущий, так и ведомый, участвующие в транзакции, могут понизить уровень на сигнальной линии, но не могут установить на ней высокий логический уровень. Если один из них отпускает линию, то она автоматически переходит в высокий уровень, благодаря соответствующему резистору. Также это гарантирует, что не может возникнуть конфликт шины, когда одно устройство пытается установить высокий уровень, а другое низкий, исключая возможность повреждения драйверов или чрезмерного рассеивания мощности.

14.1.1.5 Фреймы данных

После того, как фрейм адреса отправлен, начинается отправка данных. Ведущий будет просто продолжать генерировать тактовые импульсы на линии SCL с регулярным интервалом, а данные будут помещаться в линию SDA ведущим или ведомым устройством в зависимости от того, указывает ли бит R/W на операцию записи или чтения. Обычно первый байт или два первых байта содержат адрес регистра ведомого устройства для записи или чтения. Например, для EEPROM памяти первые два байта, следующие за фреймом адреса указывают на адрес ячейки памяти, участвующей в транзакции.

В зависимости от бита R/W, байты данных заполняются ведущим (если бит R/W установлен в 0) или ведомым (если бит R/W установлен в 1). Количество фреймов данных является произвольным и большинство ведомых устройств автоматически увеличивают внутренний регистр, отвечающий за последующие операции чтения или записи из следующего регистра в линии. Этот режим также называется последовательным или пакетным режимом (см. Рисунок 4) и это способ повысить скорость передачи данных.

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

14.1.1.6 Комбинированные транзакции

Протокол I2C по существу имеет простой шаблон связи:

  • ведущий отправляет на шину адрес ведомого устройства, участвующего в транзакции;
  • бит R/W, который является младшим значащим битом адреса, устанавливает направление потока данных (от ведущего к ведомому — W, или от ведомого к ведущему — R);
  • отправляется некоторое количество байт, каждый из которых чередуется с битом ACK пока не произойдет условие STOP.

Данная коммуникационная схема имеет небольшую ловушку: если мы хотим задать что-то конкретное для ведомого устройства, нам необходимо использовать две отдельные транзакции. Давайте рассмотрим это на примере. Предположим, что у нас есть I2C EEPROM память. Обычно устройства такого вида имеют несколько адресуемых областей памяти (EEPROM 64 кбит адресуется в диапазоне от 0 до 0х1FFF. Данные значения основаны на том факте, что 64 кбит равны 65536 битам, одна ячейка памяти имеет размерность 8 бит и следовательно 65536/8 = 8196 = 0х2000. Поскольку ячейки памяти начинаются с 0, то последняя имеет адрес 0х1FFF). Чтобы извлечь содержимое ячейки памяти, ведущее устройство должно выполнить следующие шаги:

  • запустить транзакцию в режиме записи (последний бит адреса ведомого устройства устанавливается в 0), отправив адрес ведомого устройства в шину I2C, чтобы EEPROM начал выборку сообщений из шины;
  • отправить два байта, представляющих область памяти, которую мы хотим прочитать;
  • завершить транзакцию отправив условие STOP;
  • начать новую транзакцию в режиме чтения (последний бит адреса ведомого устройства устанавливается в 1), отправив адрес ведомого устройства в шину I2C;
  • прочитать n байтов (обычно один, если читаем память в случайном режиме и больше одного, если читаем в последовательном режиме), отправленных ведомым устройством и затем завершить транзакцию условием STOP.
Рисунок 5. Структура комбинированной транзакции

Для поддержки этой коммуникационной схемы протокол I2C определяет комбинированные транзакции, в которых направление потока данных инвертируется (обычно от ведомого к ведущему или наоборот) после того, как передано определенное количество байт. На рисунке 5 схематически представлен этот способ связи с ведомыми устройствами. Ведущий начинает отправку адреса ведомого устройства в режиме записи (обратите внимание на W, выделенную красным цветом на рисунке), а затем отправляет адреса регистров, которые мы хотим прочитать. Затем отправляется новое условие START без завершения транзакции: это дополнительное условие также называется условием повторного START (или RESTART). Ведущий снова отправляет адрес ведомого устройства, но на этот раз в режиме чтения (обратите внимание на R, выделенную красным). Теперь ведомое устройство передает содержимое запрашиваемых регистров и ведущий подтверждает каждый отправленный байт. Ведущий завершает транзакцию, выдавая NACK (это важно и об этом будет далее) и условие STOP.

14.1.1.7 Удержание тактовых импульсов

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

В этом случае некоторые ведомые устройства будут выполнять то, что называют удержание тактовых импульсов. При этом ведомое устройство приостанавливает транзакцию, удерживая линию SCL в низком логическом уровне. Транзакция не может быть продолжена до тех пор, пока линия снова не будет свободной. Данная функция не является обязательной и большинство ведомых устройств не включают драйвер для работы с линией SCL, поэтому они не могут удержать тактирование (это связано в основном с упрощением аппаратной реализации I2C интерфейса). Как мы узнаем немного позже микроконтроллер STM32, сконфигурированный в режиме slave, может дополнительно реализовывать режим удержания тактовых импульсов.

14.1.2 Наличие периферийных устройств I2C в микроконтроллерах STM32

В зависимости от семейства и типа корпуса микроконтроллеры STM32 могут предложить до четырех независимых периферийных модулей I2C интерфейса. В таблице 1 показана доступность данного интерфейса в микроконтроллерах STM32, оснащающих все 16 плат Nucleo, которые мы рассматриваем в этой книге.

Таблица 1. Эффективная доступность периферийных устройств I2C в микроконтроллерах, оснащающих 16 плат Nucleo

Для каждого периферийного устройства I2C и данного микроконтроллера STM32 в таблице 1 показаны выводы, соответствующие линиям SDA и SCL. Кроме того, более темные ряды показывают альтернативные выводы, которые можно использовать при разводке печатной платы. Например, для микроконтроллера STM32F401RE, мы видим, что периферийное устройство I2C1 сопоставлено с PB7 и PB6, но PB9 и PB8 также могут использоваться в качестве альтернативных выводов. Обратите внимание, что I2C1 использует одни и те же выводы GPIO во всех микроконтроллерах STM32 в корпусах LQFP-64. Это первостепенный параметр совместимости выводов при использовании различных микроконтроллеров STM32.

Теперь мы готовы рассмотреть, как использовать API CubeHAL для программирования этого периферийного устройства.


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

6 Ответов в “I2C часть 1 (перевод из книги Mastering STM32)

  1. Спасибо за ваш труд.
    Заметил неточность во втором абзаце пункта .
    «В зависимости от бита R/W, байты данных заполняются ведущим (если бит R/W установлен в 1) или ведомым (если бит R/W установлен в 0).»
    В этом предложении 1 и 0 нужно поменять местами.

    1. Здравствуйте, спасибо, действительно так, опечатка в оригинале.

  2. Речь идёт о пункте 14.1.1.5 Фреймы данных.

  3. AS-i может понимать СТМ-ку? у нас на работе есть проблема со снабжением слейвов и датчиков…. приходится колхозить

Комментарии отключены.