Создаем I2C Master Controller на Verilog. Логический уровень

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Создаем I2C Master Controller на Verilog. Логический уровень

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

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

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

image

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

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


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

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

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

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


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

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

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

image


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

image

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

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

image

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

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

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

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

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

image

При передаче информации от 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-устройства.  

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

image

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

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

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

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

image

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

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

image

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

image

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

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

image

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

image

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

image

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

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

image

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


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

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

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

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

image

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

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

image

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


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

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

image

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

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

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


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

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

image

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

  • Поддерживается Standart и Fast Mode (частота SCL — 100 и 400 кГц соответственно);
  • Память организована как два блока по 256х8 бит памяти;
  • Поддерживается больше чем миллион циклов записи\стирания (для экспериментов хватит
Источник: https://habr.com/ru/companies/timeweb/articles/753076/


Интересные статьи

Интересные статьи

Вы никогда не узнаете всего о руководстве, и это нормально! Этой зимой мы с группой друзей болтали о Dungeons & Dragons (пятой редакции). Некоторые из них раньше играли, другие были абсолютны...
Мы публикуем серию статей для подготовки к собеседованиям Java-разработчиков. Будем рассказывать о том, как разработчику успешно пройти собеседование и не поседеть во время чтения тонн мануалов. М...
В этой статье вы узнаете, как создать собственный GPS-трекер с помощью микроконтроллеров Pycom LoPy, а также научитесь настраивать одноканальный LoRa Nano-Gateway. Здесь я изложу клю...
Всем привет! Меня зовут Вадим, и я один из технических консультантов и, по совместительству, системный администратор "РосКомСвободы". Но данный пост будет не обо мне. Он будет историей о подо...
В древности один человек за всю свою жизнь мог увидеть не более 1000 людей, а общался лишь с десятком соплеменников. Сегодня же мы вынуждены держать в голове информацию о большом количестве з...