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

Делаю я это для того, чтобы изучить то, как функционирует этот интерфейс на всех уровнях и чтобы заложить основу для разработки I2C Master Controller на Verilog, с помощью которого будет будет организован обмен данными с дисплеем SSD1306 и Zynq.

Всем, кому интересно — приглашаю ознакомиться с материалом под катом! =)


Дисклеймер. Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте. Я не являюсь профессиональным разработчиком под ПЛИС на языке Verilog и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…

❯ Общая логика действий


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

Коротко напомню ключевые основы: 

  • В I2C имеется четкое разделение ролей: устройства могут выполнять роль ведущего т.е. Master и подчиненного устройства т.е. Slave; 
  • Обмен данными происходит сеансами; 
  • Master полностью управляет сеансом: инициирует сеанс обмена данными, управляет передачей, подавая тактовые импульсы на линию SCL, и завершает сеанс.

❯ Как выставляются и читаются биты при обмене данными


Как я уже писал выше — протокол обеспечивает передачу данных с помощью специально организованных сеансов. Каждый такой сеанс начинается с подачи Master-ом стартового сигнала (иногда называемого стартовым битом или стартовым условием или командой). 

Стартовый сигнал в I2C — это изменение логического уровня на линии SDA с логической единицы на ноль. Данная манипуляция сигналом воспринимается как START команда только при условии линия SCL выставлена в единицу. Такой переход воспринимается всеми устройствами, подключенными к шине, как признак начала процедуры обмена данными. После этого сигнала шина считается занятой, что очень важно обозначить в случае если на шине несколько Master-устройств. 

Временная диаграмма сигнала START выглядит вот так:



Передача данных заканчивается сигналом завершения, или командой STOP, которая по своей сути является обратной по отношению к команде START:


То есть сигнал завершения сеанса — это когда Master изменяет уровень на линии SDA с нуля на единицу при наличии высокого уровня на линии SCL. После этого сигнала шина считается свободной.

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



Рассмотрим секцию сеанса подписанную как DATA. Выставление и считывание каждого бита данных идет по тактовому импульсу и благодаря этому передача данных в I2C совершенно не зависит от временных интервалов, а выставление и считывание данных определяется только изменением тактовых сигналов.

Определим точные интервалы времени, когда происходит выставление данных, а когда происходит считывание. 

Когда линия SCL в нуле — выставляется бит на SDA т. е. Master или Slave прижимают к земле SDA-линию, если нужно выставить значение бита “0” или ничего не делают, если нужно выставить значение бита “1” на линию. 

Когда сигнал на линии SCL установлен в высокий — происходит считывание бита. В этот момент времени, в т. ч. на отрезках изменения фронтов на SCL — сигнал данных должен оставаться неизменным.

Для простоты понимания я сделал диаграмму:


При передаче информации от Master к Slave, ведущий генерирует такты на SCL и выдает биты на SDA. В то же самое время — Slave считывает данные, когда SCL выставляется в высокий уровень. 

При передаче информации от Slave к Master, ведущий генерирует такты на SCL и фиксирует, что там выставляет Slave на линии SDA т. е. считывает данные. Пользуясь моментами когда SCL выставлен в низкий уровень Slave выставляет на SDA бит, который Master считывает когда он поднимет SCL обратно. 

Другими словами — изменение данных на SDA может быть только при низком уровне на SCL и считывание данных с SDA производится только когда SLC имеет высокое значение. Но если вдруг случается так, что SDA изменяется при SCL равном высокому значению — то это уже воспринимается устройствами на шине как служебные команды START или STOP. Таким образом отличаются служебные команды от происходящего на шине во время обмена. 

❯ Логический уровень. Пакеты


Итак, разобравшись как передаются (читаются\записываются) отдельные биты на шине — самая пора разобраться с тем, как выстраивание этих битов в определенные последовательности позволяет производить обмен данными. 

Весь поток данных на шине можно разделить на условные пакеты. Каждый пакет состоит из 9 бит, 8 из которых несут в себе полезную информацию, а 1 бит используется для подтверждения приема или сигнализации о том, что пакет не принят. Важно еще сказать, что количество пакетов, передаваемых за один сеанс связи неограниченно. 

Разберем, как всё это работает на практике. Представим ситуацию, что Master захотел “переговорить” с Slave устройством и была послана START-команда на шину. Что же происходит дальше? Все устройства замирают в ожидании своего “имени” и слушают с кем должен состояться разговор. Посылается первый пакет, в котором Master рассказывает всем устройствам на шине с кем конкретно он хочет “переговорить”, и что он собирается делать — читать или записывать данные, ожидая подтверждения от Slave-устройства.  

Общая структура первого пакета выглядит следующим образом:



Адрес устройства чаще всего состоит из семи бит, но есть еще 10-битная система, которую мы рассматривать не будем. 

Восьмой бит — определяет направление передачи данных, если будет происходить чтение — выставляем “1”, если запись — “0”.

После озвучивания своих намерений т. е. кому передать или от кого собираемся принять данные — все Slave-устройства сверяют переданный адрес с тем который был передан. И Slave, который понял, что хотят поговорить именно с ним — выставляет бит ACK, притягивая его к земле, если он готов к этому. Если ACK не выставляется, т.е. остается на высоком уровне — то получается, что либо устройства нет на шине, либо устройство неисправно, либо занято.

Рассмотрим, что происходит на шине, при отправке первого пакета если мы хотим прочитать байт у устройства с адресом 1010100 (0xA8) и устройство нам подтвердило готовность передать данные:


Пунктирной желтой линией указано место где на себя берёт управление шиной Slave-устройство.

В случае если производится запись, например в устройство с адресом 1010100 (0xA8) и устройство подтверждает готовность принять данные:


И аналогичный случай, если Slave устройство не подтвердило готовность “сотрудничать”, т. е. Master отпустил линию, а Slave не прижал её в ноль. Синим пунктиром отметил время, когда ожидалось прижатие линии SDA к нулю: 


Но допустим на первый пакет устройство выдало свое подтверждение, например, на запись в Slave. Что происходит дальше? Правильно, посылается следующий пакет, но он уже немного длиннее чем стартовый т. к. обычно состоит из двух посылаемых байт. 

В случае если происходит запись в Slave — будет следующий пакет:


Поясню, что тут происходит. Мы поочередно посылаем бит за битом и на каждую порцию ожидаем подтверждение от Slave. После окончания передачи — Master просто завершает передачу передавая STOP команду. На временной диаграмме это выглядит вот так:


А если происходит чтение из Slave — будет другой пакет:


Но здесь есть существенное отличие. Подтверждение формирует не Slave, а Master т. к. он является получателем и при приеме последней порции данных от Slave-устройства — нужно дать ему понять, что данные больше передаваться не будут и отослать NACK. Если тут будет отослан ACK — то после STOP не будет отпущена линия и сессия не будет завершена. 

На временной диаграмме это будет выглядеть вот так:


❯ Логический уровень. Сигнал RESTART


Но существует еще одна интересная конструкция. Называется она повторный старт или просто RESTART, или ещё применяется обозначение Sr. По сути это сигнал, идентичный сигналу START который обозначает то, что обмен данными будет продолжен с переобъявлением условия, например чтобы не было необходимости освобождать шину при смене направления обмена, или при смене адреса. Этот сигнал не является требованием стандарта, а скорее является способом организации обмена данными с конкретно взятыми устройствами. 

Словом, такой сигнал используется в нескольких ситуациях:

  • Вместо сочетания сигналов STOP+START, чтобы не освобождать шину данных, что особенно важно когда используется несколько Master-устройств на шине;
  • Для быстрого переключения между устройствами с которыми ведется текущий сеанс связи;
  • Для смены направления передачи данных при обмене на шине;
  • Для выполнения операций, которые прямо прописаны в условиях использования тех или иных Slave-устройств.

На временной диаграмме это будет выглядеть так, как операция START которая была вставлена сразу после сигнала ACK:


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

Например, при чтении данных из EEPROM 24C02WP по произвольному адресу (т.н. Random Address Read) — должна быть сгенерирована следующая последовательность, среди которых тот самый повторный START: 


❯ Логический уровень. Clock stretching


Рассмотрев способ организации передачи данных и способы их считывания получается, что периферийные устройства просто передают данные на шину или читают данные с шины с той скоростью, с которой их просит делать Master. Но бывают ситуации когда Master тактирует шину SCL очень быстро и Slave не успевает сделать всё, что от него требуется. Это бывает в случаях когда текущая операция еще не успела завершиться или например АЦП не успел произвести преобразование. В этом случае Slave придержит линию SCL на низком уровне и с этим Master сделать ничего не может т.к. если вы помните — тут работает схема “монтажного И” и Slave может выиграть для себя некоторое количество времени для завершения текущей операции. В этом случае Master должен понять, что происходит и дать Slave спокойно завершить операцию и дождаться когда SCL-линия снова поднимется. Самое главное на что стоит обращать внимание — это как часто такие девайсы “замедляют” линию и сколько их таких на шине, чтобы это в целом не сказалось на производительности других устройств или какой-либо функциональности.

Выглядит эта манипуляция следующим образом:


Важно понимать разницу между тем, когда Slave не успевает принимать или посылать данные с состоянием пост-обработки хранимых данных во входных буферах микросхемы, т.к. чаще всего, для ускорения обмена данные сначала помещаются в кэш, а уже потом куда-либо записываются. В первом случае (когда идет обмен данными) — будет производиться растягивание обмена и длины тактовых импульсов, а во втором (когда сеанс уже завершен, но нужно разобраться с данными) — он не будет просто отвечать на обращение Master-а к нему и всё. 

При проектировании конечного автомата нужно будет держать в голове, что на эту ситуацию тоже нужно будет правильно среагировать. 

❯ Реальный пример. EEPROM Microchip 24LC04


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

На отладочной плате, на которой я буду делать первоначальный прототип конечного автомата, есть I2C EEPROM от Microchip 24LC04:


И первый шаг с которого хотелось бы начать — изучение документации, что там приготовил нам производитель в части каких-либо характеристик и возможных особенностей (с точки зрения интерфейса I2C конечно же, остальные особенности не будем разбирать):

  • Поддерживается Standart и Fast Mode (частота SCL — 100 и 400 кГц соответственно);
  • Память организована как два блока по 256х8 бит памяти;
  • Поддерживается больше чем миллион циклов записи\стирания (для экспериментов хватит ?);
  • В анализ таймингов не будем ударяться, будем считать, что с этой точки зрения у нас всё хорошо;
  • Пины A0, A1, A2 не используются в качестве устанавливающих последние биты адреса и просто подключены к земле (почему — ниже);
  • Чтобы осуществить запись — нужно WP-пин притянуть к GND.

Разберем пример общего описания принципов обмена данными из документации. В ней приведена следующая диаграмма с этапами, которые подписаны латинскими буквами:


Этап А. Bus Not Busy. Это как раз тот этап, на котором шина свободна для передачи данных, обе линии подтянуты к высокому уровню.

Этап B. Start Data Transfer. На этом этапе Master притягивает линии SDA к “земле” (происходит high-to-low transition линии SDA), при чем это происходит когда SCL-линия находится на высоком уровне.

Этап C. Stop Data Transfer. На этом этапе Master завершает текущий сеанс связи, отпускает линию SDA к высокому уровню (low-to-high transition линии SDA), опять же при условии, что SCL-линия находится на высоком уровне.

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

Этап, не указанный отдельно, между двумя этапами D — это этап когда позволяется произвести смену значения линии данных и выставить новое значение.

❯ EEPROM. Контрольный пакет


Далее давайте посмотрим пример процедуры формирования первого пакета, в котором будем передавать адрес подчиненного устройства и направление передачи данных. У данной EEPROM весьма интересный способ использовать первый пакет:


Сначала идет команда START, после используется одинаковое для всех EEPROM этой серии кодовое слово 1010. Следующие два бита — вообще ни на что не влияют и что бы там ни стояло, всегда будет дан ACK. А вот третий бит B0 — определяет к какой из двух доступных страниц памяти будет осуществляться доступ. Следующий бит определяет направление передачи — запись или чтение. И последним битом подчиненный забирает управление шиной данных и притягивает ее к земле если распознал, что это обращаются к нему. 


Таким образом, если подставить значения — получается следующее:

  • Чтобы произвести запись в первую страницу EEPROM нужно отправить на шину START + 1010XX00 и дождаться ACK;
  • Чтобы произвести чтение с первой страницы EEPROM нужно отправить на шину START + 1010XX01 и дождаться ACK;
  • Чтобы произвести запись во вторую страницу EEPROM нужно отправить на шину  START + 1010XX10 и дождаться ACK;
  • Чтобы произвести чтение со второй страницы EEPROM нужно отправить на шину  START + 1010XX11 и дождаться ACK.

❯ EEPROM. Запись данных


Если говорить непосредственно самих операциях, давайте посмотрим на операции записи и чтения. Вернемся к диаграммам из даташита и посмотрим, на последовательность действий при записи в ячейку или ячейки памяти:


Для записи надо в следующей посылке передать 8 бит адреса ячейки, которые во внутренней логике EEPROM зададут значение указателя текущего адреса. После этого нужно дождаться ACK и за ним так же 8 бит данных. Далее дожидаемся подтверждения и отправляем команду STOP.

Есть один приятный нюанс. Для того, чтобы сократить количество передаваемых данных и количество служебных данных — можно передать последовательно до 16 байт данных поочередно друг за другом, дожидаясь лишь подтверждения ACK. Только если передать больше 16 байт — произойдет перезапись первых байт из последовательности — это так, на всякий случай.


❯ EEPROM. Чтение данных



Теперь перейдем к процедуре чтения. Она немного сложнее чем процедура записи. Там помимо того, что нужно изменить бит R/W на 0, нужно еще взять во внимание, что чтение может производиться тремя разными операциями: 

  • Считывание данных с текущего адреса, на котором зафиксирован указатель;
  • Случайное считывание, т. е. по адресу который будет указан в посылке;
  • И последовательное считывание.

Первый случай выглядит следующим образом:


То есть мы не передаем тут никаких данных, помимо контрольного пакета и получаем данные с текущего указателя. Совсем по другому обстоят дела при чтении рандомной ячейки из EEPROM и там необходима некоторая хитрость. Попробуйте подумать, посмотреть внимательно на биты R/W и понять в чем прикол ?:


Если особенно внимательно посмотреть — мы сначала половиной команды устанавливаем указатель адреса в конкретном положении, а потом командой RESTART инициируем выдачу нам значения текущей ячейки, адрес которой находится в указателе. Вот так, оригинально и просто.

Последовательное чтение организуется на старте точно так же, как и чтение рандомной ячейки, за тем лишь исключением, что как только EEPROM отдал в шину первый байт данных, Master генерирует подтверждение ACK, вместо того, чтобы завершить передачу NACK, как при случайном считывании. Поэтому EEPROM на следующих тактовых импульсах будет выдавать данные из следующей ячейки, и нужно лишь Master-ом подтверждать получение:


А когда прочитаем все, что нужно — посылаем NACK и посылаем STOP-команду. Всё просто.

❯ EEPROM. Сеансы


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

Так вот. Благодаря такой возможности Мастер может не закрыв первый сеанс — уйти на время сделать дела в другом сеансе с другим устройством, или с этим же, но сменив направление передачи данных. 

Но в то же время такое халатное отношение к управлению сеансами может плодить реальные проблемы. Например, если мы имеем на шине 2 микросхемы EEPROM и на одной микросхеме произвели запись и открыли новый сеанс записи через RESTART на второй EEPROM — то реальная запись произойдет только после закрытия любых сеансов, то есть через посылку STOP-команды. Поэтому важно после записи контролировать, произошел ли реальный акт записи данных или нет.

❯ Итог. Общая картина


Теперь, рассмотрев бОльшую часть логики работы протокола можно перейти к описанию общей картины по алгоритму работы протокола, чтобы полностью иметь перед глазами. Немного пофантазировав и порисовав в Draw IO мысли на счет I2C — получилось следующее:


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

До встречи в следующих статьях ?



Возможно, захочется почитать и это: