Шина I2C. Подробности программной реализации
Освойте протокол I2C.
Дополнительная информация
Шина I2C. Подробности аппаратной реализации
Типовая транзакция
Устройства, связанные через I2C, должны поддерживать определенную последовательность событий. Каждое событие соответствует определенному способу управления линиями тактовой синхронизации (SCK) и данных (SDA); как обсуждалось в статьях, приведенных в списке «Вспомогательная информация», эти два сигнала являются единственным средством, с помощью которого устройства на шине могут обмениваться информацией. Мы будем рассматривать одну информационную последовательность как «транзакцию»; это слово более уместно, чем «передача», поскольку каждая транзакция включает в себя как переданные данные, так и полученные данные, хотя в некоторых случаях единственными полученными данными являются бит подтверждения (ACK) или не-подтверждения (NACK), детектируемые ведущим устройством. Следующая временная диаграмма показывает типовую транзакцию I2C.
Обратите внимание на следующее:
- Пунктирная линия, соответствующая длительности логической единицы в тактовом сигнале, напоминает нам, что логическая единица (и для SCL, и для SDA) является «рецессивным» состоянием – другими словам, сигнал доходит до высокого логического уровня с помощью подтягивающего резистора. «Доминантное» состояние – это логический ноль, потому что сигнал будет на низком логическом уровне только тогда, когда устройство действительно приводит его к состоянию логического нуля.
- Транзакция начинается со «стартового бита». Каждая I2C транзакция должна начинаться со стартового бита, который определятся как спадающий фронт на линии SDA, в то время как линия SCL находится в состоянии логической единицы.
- Транзакция заканчивается «стоповым битом», определяемым как нарастающий фронт на линии SDA, в то время как линия SCL находится в состоянии логической единицы. Транзакции I2C должны заканчиваться стоповым битом; однако, как будет рассказано позже, на шине могут появиться несколько стартовых битов до того, как будет сгенерирован стоповый бит.
- Данные действительны, когда на линии синхронизации установлена логическая единица, и изменяют состояние, когда на линии синхронизации установлен логический ноль; цифровые системы связи обычно ориентируются на изменения состояния на линиях, поэтому на практике данные считываются по нарастающему фрону на линии синхронизации и обновляются по спадающему фронту на линии синхронизации.
- Обмен информацией происходит по одному байту за раз, начиная со старшего значащего бита; и за каждым байтом следует ACK или NACK.
- Вы можете ожидать, что ACK будет обозначаться логической единицей, а NACK – логическим нулем, но в данном случае это не так. ACK соответствует логическому нулю, а NACK – логической единице. Это необходимо, потому что логическая единица является рецессивным состоянием – если ведомое устройство не работает, то сигнал, соответственно, будет поднят до NACK. Аналогично, ACK (указывается доминантным логическим нулем) может быть передан только в том случае, если устройство работает и готово продолжить транзакцию.
Следующий список описывает последовательность событий в вышеуказанной транзакции:
- Ведущее устройство генерирует стартовый бит, чтобы начать транзакцию.
- Ведущее устройство передает 7-битный адрес, соответствующий ведомому устройству, с которым оно хочет установить соединение.
- Последним битом в первом однобайтовом сегменте является индикатор чтения/записи. Мастер устанавливает этот бит в логическую единицу, если он хочет считывать данные с ведомого устройства, или в логический ноль, если хочет записать данные в ведомое устройство.
- Следующий байт – это первый байт данных. Он приходит либо от ведущего, либо от ведомого устройства, в зависимости от состояния бита чтения/записи. Как обычно, у нас есть 8 бит данных, начинающихся со старшего значащего бита.
- За байтом данных следует ACK или NACK, сгенерированный ведущим устройством, если это транзакция чтения, или ведомым устройством, если это транзакция записи. ACK и NACK могут означать разные вещи в зависимости от прошивки или низкоуровневой аппаратной схемы взаимодействующих устройств. Например, мастер может использовать NACK, чтобы сказать: «это последний байт данных», или если ведомое устройство знает, сколько данных должно быть отправлено, оно может использовать ACK для подтверждения того, что данные были успешно получены.
- Транзакция завершается стоповым битом, сгенерированным ведущим устройством.
Сколько байт?
Каждая транзакция начинается одинаково: стартовый бит, адрес, чтение/запись, ACK/NACK. После этого любое количество байт данных может быть отправлено от мастера к ведомому устройству или от ведомого к мастеру, причем после каждого байта следует ACK или NACK. NACK может быть полезен как способ сказать: «прекрати отправку данных!». Например, мастер может захотеть получать непрерывный поток данных от ведомого устройства (например, датчик температуры); за каждым байтом следует ACK, и если мастеру необходимо обратить внимание на что-то еще, он может послать ведомому устройству NACK и начать новую транзакцию, когда будет снова готов.
Старт без стопа
Протокол I2C допускает нечто, называемое условием «повторного старта». Это происходит, когда мастер инициирует транзакцию со стартовым битом, а затем инициирует новую транзакцию через другой стартовый бит без промежуточного стопового бита следующим образом:
Эта функция может использоваться всякий раз, когда одному ведущему устройству необходимо выполнить две или более отдельных транзакции. Однако есть ситуация, когда условие повторного старта особенно удобно.
Допусти, у вас есть ведомое устройство, которое хранит информацию в банке регистров. Вы хотите запросить данные из регистра с адресом 160, 0xA0 в шестнадцатеричном формате. Протокол I2C не позволяет мастеру отправлять данные и получать данные в одной транзакции. Следовательно, вы должны выполнить транзакцию записи, чтобы указать адрес регистра, а затем отдельную транзакцию чтения для извлечения данных. Хотя этот подход может привести к проблемам, поскольку мастер освобождает шину в конце первой транзакции, и, таким образом, другой мастер может занять шину и не дать первому мастеру получить нужные ему данных. Кроме того, второй мастер может взаимодействовать с тем же ведомым устройством и задать другой адрес регистра... Если первый мастер затем займет шину и прочитает данные без повторного указания адреса регистра, он будет считывать неправильные данные! Если второй мастер затем попытается выполнить транзакцию чтения в своей процедуре «запись и затем чтение», то это также закончится чтением неверных данных! Этого системного сбоя стоит ожидать, но, к счастью, условие повторного старта может предотвратить этот беспорядок, инициировав вторую транзакцию (чтение) без освобождения шины.
Когда ведущие устройства не могут уживаться вместе
Часть того, что делает I2C настолько универсальной, – это поддержка нескольких ведущих устройств. Но, как показывает предыдущий раздел, ведущие устройства не всегда хорошо работают вместе. Логика I2C устройства должна быть в состоянии определить, свободна ли шина; если шину занял другой мастер, то устройство до запуска своей собственной транзакции ждет, пока не завершится текущая транзакция. Но что происходит, когда два (или более) мастера пытаются инициировать транзакцию одновременно? I2C обеспечивает эффективное и удивительно простое решение этой неприятной, если бы она случилась, проблемы. Этот процесс называется «арбитраж», и он полагается на гибкость схемы шины I2C с открытым стоком: если один мастер пытается привести сигнал к логической единице, а другой мастер пытаются привести сигнал к логическому нулю, то «выиграет» мастер с логическим нулем, и, кроме того, «проигравший» может обнаружить, что фактическое состояние на выходе отличается от состояния, которое он хотел установить:
Эта схема показывает основу арбитража I2C; процесс происходит следующим образом:
- Оба мастера генерируют стартовые биты и осуществляют свои передачи.
- Если мастера выбирают на линии одни и те же логические уровни, ничего не происходит.
- Как только мастера пытаются установить на линии разные логические уровни, мастер, установивший на линии логический ноль, объявляется победителем; а проигравший обнаруживает несоответствие логических уровней и отказывается от своей передачи.
Потратьте минутку, чтобы оценить простоту и эффективность этого механизма:
- Победитель продолжает свою передачу без перерыва – нет поврежденных данных, нет конфликта устройств, нет необходимости в перезапуске транзакции.
- Теоретически проигравший мог контролировать адрес ведомого устройства в ходе процесса арбитража и фактически принимать правильные данные, если так оказалось, что он и является этим ведомым устройством.
- Если конкурирующие мастера запрашивают данные от одного и того же ведомого устройства, процесс арбитража не требует необязательного прерывания транзакции – не будет обнаружено ошибки, и ведомое устройство выведет свои данные на шину, чтобы их могли получить несколько мастеров.
Заключение
В данной статье рассмотрены важные детали I2C, которые влияют на разработку программного или низкоуровнего аппаратного обеспечения. Если ваш микроконтроллер включает аппаратные модули I2C или SMBus, то некоторые детали реализации будут обрабатываться автоматически. Хотя это и удобно, но не оправдывает безграмотность потому, что вам всё равно нужно знать хотя бы немного (и, возможно, немного больше) о том, как на самом деле работает I2C. Кроме того, если вы когда-нибудь окажетесь на необитаемом острове без аппаратных модулей I2C, представленная здесь информация поможет вам на пути к разработке чисто программных I2C функций (с так называемой побитовой обработкой).