Jump to content

    
Sign in to follow this  
Arlleex

Софт-реализация I2C-мастера: учесть все и не сойти с ума

Recommended Posts

Приветствую!

А почему бы, собственно, и да.

Короче, есть в каком-то обозримом будущем вероятность того, что ногодрыгом придется поднимать I2C-мастера. Глянул что вообще имеется из готового - расстроился. Часто вижу какие-то нелепые задержки на NOP-ах процессора и другой костыльный бред:smile: А даже если и так - все слишком упрощено и работает только у того, кто сие изобрел. Чуть шаг влево - например, слейв требует обязательного СТОП-СТАРТ- вместо РЕСТАРТ-условия - все, не работает. Мастер - штука довольно простая (хотя и со своими тараканами), поэтому моя идея заключается в следующем. Прикладное ПО оперирует функциями I2CSend() и I2CRecv(). Ему по-барабану на внутреннее устройство этих функций. Но в реализации софт-I2C эти функции как конструктор собирают нужную транзакцию из более мелких "простейших" операций на шине. Сами операции потактно формируются либо в прерывании таймера, либо в каком-то периодическом процессе. Предлагаю их рассмотреть. Цифрами 1, 2 ... 5 обозначаю "тики" (минимальные интервалы) таймера, штрих-линиями - границу операции, точками - проверку на обязательное соответствие логических уровней на SCL/SDA уровням, показанным на рисунке.

1. СТАРТ-условие

image.png.68c2abf9178dfad6a97a5bb4397e240e.png

СТАРТ-условие всегда должно начинаться с проверки, что SCL/SDA в лог. 1, иначе это будет что угодно, но не СТАРТ-условие.

2. РЕСТАРТ-условие

image.png.91ef41f646091b0159981f01aa76652a.png

РЕСТАРТ-условие всегда должно начинаться с проверки, что SCL в лог. 0, иначе это будет что угодно, но не РЕСТАРТ-условие.
В этом случае переключаем SDA в лог. 1 вне зависимости от того, какой уровень был на ней до этого. Как видно, здесь требуется уже 4 такта.

3. БИТ

image.png.1b3504089210f87af4d1afc5dccb2673.png

Это интервал передачи/приема бита. Этот же паттерн отражает и ACK/NACK.
Вначале проверяется, что SCL в лог. 0. Потом выставляем данные и "клокаем".

4. СТОП-условие

image.png.ff4ab4e4c4726ecf0d02986e0225e00a.png

СТОП-условие всегда должно начинаться с проверки, что SCL в лог. 0.
Если это так, SDA опускается в лог. 0, а затем формируется СТОП-условие.

5. ЗАЩИТНОЕ СТОП-условие

image.png.b405d740228c8355ad3b9f77aa74fb12.png

В отличие от простого СТОП-условия, формирует СТОП-условие из любого состояния SDA/SCL.
ЗАЩИТНОЕ СТОП-условие предназначено для вывода некоторых слейвов из коматозного состояния.
Хотя спецификация говорит о минимум 9 циклах на SCL, однажды мы выяснили, что лучше подавать СТОП-условия.
Обычные СТОП-ы "как есть" сформировать не получится друг за другом, поэтому нужна диаграмма именно ЗАЩИТНОГО СТОП-а.

Любое несоответствие на диаграммах выше (например, SCL в лог. 0 в начале СТАРТ-услвоия) - ошибка транзакции.

Пример диаграммы передачи

image.thumb.png.6496e1a0c5aba2377999004bf9fd9e7e.png 

Как видно, СТАРТ можно сформировать не из любого места, а только после СТОП-а или ЗАЩИТНОГО СТОП-а.
БИТ можно сформировать только между СТАРТ-ом и СТОП-ом или ЗАЩИТНЫМ СТОП-ом. ЗАЩИТНЫЕ СТОП-ы можно формировать где угодно.

Собственно, прикладное ПО может сформировать последовательность из таких отрезков, чтобы получить нужную транзакцию. Однако, некоторые операции можно сократить, например, передачу/прием байта не нужно разбивать на 8 (+1 ACK) отдельных подкоманд формирования битов; простейшую транзакцию СТАРТ-передача-СТОП тоже желательно обозначить одной операцией. Т.е. не до маразма доходить. Прикладное ПО, когда хочет совершить транзакцию на шине, добавляет в очередь транзакций специального вида дескрипторы (и данные на передачу при необходимости) и запускает таймер. Таймерное прерывание потихоньку выгребает "заявки" и рисует диаграммы, шевеля ногами SCL/SDA. С приемом разберемся чуть позже, а пока рассмотрим структуру дескриптора (и данных, если прикладной код хочет что-то записать в слейв)
 image.thumb.png.be59018df77f8e46f6b61c75183d1ed6.png

Bus Operation ID - это некий идентификатор, по которому можно сгруппировать части одной транзакции. Например, для EEPROM же нужно сначала записать внутренний адрес ячейки памяти, и лишь потом делать транзакции чтения. Общая транзакция разбивается на 2 - первая СТАРТ + запись адреса ведомого + запись внутреннего адреса, вторая - РЕСТАРТ + запись адреса ведомого + БИТ-ы чтения (с выдачей ACK-ов) + СТОП на конце. Технически для EEPROM это одна операция, но для нас это будет 2, но под одним Bus Operation ID.

Rx/Tx Byte Quantity - если это транзакция передачи (см. позже описание Bus Operation Control), то в этой же очереди следом будут идти байты, которые нужно отправить ведомому. Если это прием - данных нет, и принимаемые данные будут помещаться в очередь приема (со своими статусными сообщениями в дескрипторе) - мол, успешно или нет.

Bus Operation Control - самое интересное в дескрипторе. Бит 0 указывает, что за транзакция совершается (прием или передача). В битах 2:1 указывается, нужно ли формировать СТАРТ- или РЕСТАРТ- перед "телом" транзакции. Кроме того, можно здесь указать, что шину нужно "прочистить" (подав 10 ЗАЩИТНЫХ СТОП-ов), и лишь потом выдать СТАРТ-условие. Биты 4:3 указывают, нужно ли в конце "тела" транзакции делать СТОП-условие или его разновидности. Бит 5 нужен только для транзакции приема, он указывает, нужно ли отвечать NACK-ом на последнем принимаемом байте, или ACK-ом (обычно, все-таки, должен быть NACK, но пусть будет универсально). В битах 7:6 отражается необходимость "рапортовать" о результатах транзакции (в очередь приемных сообщений) - например, при передаче сообщать, что слейв не может сожрать какой-то байт, или не получилось СТАРТ-условие сформировать и т.д. Можно настроить, что рапортация будет только при ошибках, или наоборот (только когда транзакция завершена успешно) - это удобно для реализации функции поиска девайсов на шине с заранее не известными физическими адресами. Бит 8 позволяет "прочистить" очередь запросов для транзакций с одинковыми ID по списку (если EEPROM не ответила ACK-ом на свой адрес - зачем ей передавать данные?). Бит 9 позволяет автомат "попросить" сформировать последовательность "прочистки" шины в случае, если транзакция по какой-то причине запорота.

В итоге - формируем нужную последовательность, записываем в очередь передачи - она отрабатывается и рапортует в очередь приема.

Что не учел? Мультимастер?

Share this post


Link to post
Share on other sites
1 hour ago, Arlleex said:

нелепые задержки на NOP-ах процессора и другой костыльный бред:smile:

Нелепо прерываться с частотой 100+кГц, а отнюдь не nop'ы в задержках иметь.

 

1 hour ago, Arlleex said:

Что не учел?

Clock stretching?

Share this post


Link to post
Share on other sites
3 часа назад, aaarrr сказал:

Нелепо прерываться с частотой 100+кГц, а отнюдь не nop'ы в задержках иметь.

ИМХО, нужно всегда отдавать себе отчет, что если хочется полноценные I2C-скорости иметь, то нужно как-то на аппаратный I2C сильнее засматриваться... Но когда такой возможности нет, с более медленными скоростями смириться еще можно. С другой стороны, можно подумать еще о связке TIM + DMA -> GPIO, если такое можно провернуть в используемом МК. Или другие механизмы ногодрыга активно использовать - в LPC43xx (вроде) есть некий SGPIO, который, насколько я знаю, много чего может наногодрыжить. А на NOP-ах тоже далеко не уедешь - 100кбит/с при тактовой CPU в 32МГц (у меня сейчас так) - это уже 320 тактов в пустую. Мне скорость не особо важна, главное, что обмены со слейвом - штука регулярная.

Цитата

Clock stretching?

Точно, спасибо:good3: Я подумаю, как.
 

2 часа назад, jcxz сказал:

Вам бы статьи на вики о работе интерфейсов писать...

Я обычно для своих каких-то программных механизмов всегда рисую похожие картинки, так потом проще вспомнить основную суть, не открывая исходников:smile:
 

3 часа назад, gerber сказал:

Софт-реализация I2C Master - это халява, по сравнению с софт-реализацией I2C Slave

Слейв даже не хочу думать пока что... Нет надобности.

Share this post


Link to post
Share on other sites
4 часа назад, Arlleex сказал:

В отличие от простого СТОП-условия, формирует СТОП-условие из любого состояния SDA/SCL.
ЗАЩИТНОЕ СТОП-условие предназначено для вывода некоторых слейвов из коматозного состояния.
Хотя спецификация говорит о минимум 9 циклах на SCL, однажды мы выяснили, что лучше подавать СТОП-условия.
Обычные СТОП-ы "как есть" сформировать не получится друг за другом, поэтому нужна диаграмма именно ЗАЩИТНОГО СТОП-а.

9 циклов SCL полезнее в тех случаях, когда STOP используется для запуска какого-то внутреннего процесса в ведомой микросхеме I2C. Например STOP запускает запись сектора в I2C-флешь. Поэтому в таких случаях, возможно, лучше использовать 9 тактов SCL, а не STOP.

Share this post


Link to post
Share on other sites
2 минуты назад, controller_m30 сказал:

9 циклов SCL полезнее в тех случаях, когда STOP используется для запуска...

Сама транзакция будет "собираться" по кирпичикам так, как указано в спецификации на конкретную микросхему слейва. 9 циклов SCL - это "не о том" песня. Это когда между мастером и слейвом произошел рассинхрон и шина "залипла" в силу смещенного положения ACK. В микросхемах с внутренним автоинкрементом адреса чтения/записи такое "проклокивание" может не прокатить. И лучше подавать СТОП-ы (вместо 9 клоков 9 СТОП-ов).

Share this post


Link to post
Share on other sites

Наверное Защитный Стоп полезен - я просто не знаю, к каким микросхемам он может быть применён (не всё можно знать). Но мне интересно уточнить для стандартной флешь, например 24C32, что Вы планируете применять - Защитный Стоп, или 9 тактов SCL при SDA=1?

Share this post


Link to post
Share on other sites

Тогда я позволю себе уточнить для случая 24C32, что в ситуации, если ведомая микросхема перед сбросом МК что-то передавала на шину, то сигнал Защитный Стоп (повторенный 9 раз) будет ею интерпретирован как ACK от МК, и она вместо того чтобы освободить шину, продолжит передавать биты дальше.

 

Ну а в ситуации, если перед сбросом МК шла запись в буфер флешь, то STOP запустит запись содержимого буфера в память (в неизвестные адреса, кстати), и при этом некоторое время флешь не будет подтверждать обращения к ней. Поэтому остальные сигналы Защитного Стоп (кроме первого) скорее всего пропадут втуне.

Share this post


Link to post
Share on other sites
1 hour ago, Arlleex said:

А на NOP-ах тоже далеко не уедешь

Если строить универсальное решение, лучше предусмотреть разные источники времени. Где-то нужна регулярность, где-то скорость.

Share this post


Link to post
Share on other sites
17 минут назад, controller_m30 сказал:

Ну а в ситуации, если перед сбросом МК шла запись в буфер флешь, то STOP запустит запись содержимого буфера в память (в неизвестные адреса, кстати)

Почему "в неизвестные"? Во всех протоколах флешь-памяти какие я видел, адрес всегда передавался ДО данных. А значит к тому моменту как появилось что писать, адрес (правильный) у чипа уже имеется.

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

Share this post


Link to post
Share on other sites
24 минуты назад, jcxz сказал:

Почему "в неизвестные"? Во всех протоколах флешь-памяти какие я видел, адрес всегда передавался ДО данных. А значит к тому моменту как появилось что писать, адрес (правильный) у чипа уже имеется.

Ведомая микросхема конечно знает куда переписать содержимое своего буфера. А вот знает-ли об этом программа МК?

Если произошла перезагрузка МК и он пытается инициализировать шину I2C, "забыв" что в буфере 24C32 лежат готовые к записи данные (отправляй только STOP и понеслась!). Тогда запустив серию из 9 "Защитных Стоп" - МК запустит запись неизвестных ему данных по неизвестному ему (МК) адресу.

Share this post


Link to post
Share on other sites
1 час назад, controller_m30 сказал:

МК запустит запись неизвестных ему данных по неизвестному ему (МК) адресу.

И что? А если МК успеет запустить запись данных (закончит I2C-транзакцию) и тут же перезагрузится - что от этого изменится? Вы этого не боитесь?

Будет то же самое.

Share this post


Link to post
Share on other sites

В случае 24C32 запись может производиться пакетами по 64 байта (размер буфера флешь). Если МК планировал записать 64 байта, а успел записать только 3 (такое количество для примера), после чего перезагрузился - тогда Защитный Стоп запустит запись такого, возможно некорректного, блока данных. Полагаю что любая запись должна производиться только под "осознанным" контролем программы МК. А случайно инициировать запись во флешь, в надежде что если там что-то и запишется, то это 100% корректные данные - это легкомыслие. 

 

В целом я поддерживаю идею Arlleex по созданию конечного программного автомата I2C_Master. И уточняю момент с 9 тактами SCL или 9 тактами Guard STOP - только с целью доведения этой идеи до блеска :)

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this