Конечный Aвтомат Аппаратного I2C-Трансивера

Пролог

Накануне я столкнулся с ситуацией, когда берешь uHAL драйвер от вендора микроконтроллера, собираешь его как в примерах и код не работает. Никогда такого не было, и вот опять…

На этот раз осечка в драйвере I2C трансивера. При попытке прочитать 16-битный регистр родным драйвером I2C шина за стороне slave просто берёт и зависает. Микроконтроллер показывает постоянный bus busy флаг, re-init I2C трансивера ничего не меняет. Выход один, перезагрузка по питанию всей PCB. Только это позволяет вновь хоть как-то достучаться до регистров ASIC (а).

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

Надежный I2C нужен всегда и везде. Вот лишь малый список ASIC (ов), которые управляются по I2C: at24cxx, lis3dh, bh1750, bmp180, nau8814, sa51034, ds3231, fda801, ltr390, max9860, si4703, ssd1306, wm8731 и прочие.

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

Постановка задачи

Написать на Си драйвер I2C в котором будут следующие функции:

  1. Проверка присутствия микросхемы по её 7-ми битному I2C адресу

bool i2c_check_addr(uint8_t num, 
                    uint8_t chip_addr);
  1. Чтение 16-битного регистра по адресу ячейки внутри ASIC (a)

bool i2c_read_word(uint8_t num, 
                   uint8_t chip_addr, 
                   uint8_t reg_addr, 
                   uint16_t* const word);
  1. Запись 16-битного слова в ASIC по его адресу на шине I2C

bool i2c_write_word(uint8_t num, 
                    uint8_t chip_addr, 
                    uint16_t word);

и тому подобное

Что надо из оборудования?

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

#

Оборудование

1

Логический анализатор Saleae Logic Pro 16 для чтения сырых сигналов на цифровой шине I2C

2

Электронная плата с микроконтроллером, который поддерживает аппаратный I2C трансивер. В моем случае это электронная плата с MCU AT32F435ZM

3

Какой-нибудь ASIC c поддержкой I2C трансивера. В моём случае это аудиокодек NAU8814

Что надо из софтвера?

Софтвер

Назначение

1

GCC ToolChain

Для сборки прошивки

2

Процессор электронных таблиц

Для составления документации по конечному автомату

3

Векторный графический редактор. Например inkscape

Чтобы нарисовать граф конечного автомата

4

Поддержка в прошивке UART-CLI

Для управления прошивкой и отладке кода.

В прошивке должен быть реализован интерфейс командной строки (TUI). Чтобы была возможность в run time запускать на исполнение произвольном порядке команды драйвера I2C трансивера, запускать модульные тесты ассоциированные с драйвером I2C и управлять уровнями логирования, чтобы смотреть и анализировать log (и) трассировки ветвей алгоритмов внутри микроконтроллера.

Теоретический минимум

I2C — это последовательный, синхронный, цифровой, полудуплексный, двухпроводной , много-мастерный физический интерфейс с топологией общая шина. Плюс протокол канального уровня модели OSI-7. На I2C чем-то похож интерфейс MDIO, SWD. Там тоже два провода и один-тактирование. По моим наблюдениям в каждом микроконтроллере присутствует аппаратный I2C трансивер. Да не один.

Несколько фактов про I2C:

1--Биты передаются старшим битом вперед (MSB).

2--Байты передаются в формате big-endian

3--В шине I2C есть мастер и ведомый устройства. Большинство I2C ASICов это slave устройства.

4--I2C интерфейс схемотехнически это общий коллектор. Это значит, что провода должны быть подтянуты к питанию. А узлы просто по очереди прижимают шину к земле. Получается кто угодно на шине может взять и прижать шину к GND. В таком случае обмен данными будет невозможен. Ноль это доминантный бит. Такое можно встретить также в интерфейсах LIN и 1-Wire.

5--В 99,99% случаев I2C применяется для обмена данными между микросхемами в пределах одной электронной платы PCB. То есть это интерфейс для обмена данными между микросхемами. Нынче почти все микросхемы это программно-управляемые устройства, которые работают согласно своей конфигурации в ячейках RAM памяти.

6--I2C это полудуплексный интерфейс. То есть в один момент времени на шине может происходить либо передача, либо приём битов, но никак не одновременно.

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

8--Бит данных захватывается по положительному перепаду на проводе SCL. То есть в момент перехода от 0V в 3.3V.

9--I2C последовательный интерфейс. Это значит что биты передаются один за другим. Как в UART, SPI, CAN, SWD, 1-wire.

10--I2C это синхронный интерфейс. Значит, что есть тактирование как в SPI, MDIO и SWD.

11--I2C это двухпроводной интерфейс. Прям как CAN, UART.

12--I2C это цифровая общая шина.

На низком уровне I2C оперирует сигналами. Вот полный реестр сигналов I2C.

71a59a6a0c7027dc22f6becb2417bd30.png

Терминология

Слово (Word)- целое неотрицательное 16-битное число. От нуля до 0xFFFF=65536

Вот шпаргалка по I2C

dbd6b7b1801f02c01c229186caa9cbe6.png

Вот типичная структура пакета для чтения 16-битного слова по I2C.

a94035d5f4900f2512ee8dee7d35af03.png

Реализация

Конечно-автоматная методика разработки ПО хороша тем, что она универсальна и очень хорошо формализована. Есть учебники по этой теме, которые уходят корнями в 195х-е года. Любая разработка FSM на состоит из восьми шагов.

номер

фаза

1

Перечислить состояния конечного автомата

2

Перечислить входы конечного автомата

3

Перечислить выходы конечного автомата

4

Построить таблицу переходов конечного автомата

5

Построить таблицу выходов конечного автомата

6

Нарисовать граф переходов конечного автомата

7

Написать код

8

прогнать модульные тесты

В прочем, обо всем по-порядку…

Фаза 1: Перечислить состояния

aa5eaefb34561c45e85c4e4653e04bba.png

Фаза 2: Перечислить входные воздействия

Для нашего конечного автомата входами будут биты в статусных регистрах микроконтроллера. Как правило там такой список битов.

d4e7d427317e9006a5c1c9deb8ff4a06.png

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

Фаза 3: Перечислить выходы конечного автомата

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

9bd540ea4c193acf934495c41818cedd.png

Фаза 4 и 5: Построить таблицы переходов.

Таблицы переходов это двухмерные матрицы которые показывают в какое состояние надо переходить в зависимости от нынешнего состояния и входного сигнала. Для данного конечного автомата таблица будет весьма громоздкая, поэтому я составлю её в Google Spreadsheets.

фрагмент таблицы переходов

фрагмент таблицы переходов

Фаза 6: Изобразить граф конечного автомата

В самом упрощенном виде граф конечного автомата может выглядеть так.

граф конечного автомата I2C мастера

граф конечного автомата I2C мастера

Я нарисовал его в программе inkscape.

Фаза 7: Написать код

В целом, обладая перечисленной документацией код уже может написать абсолютно любой программист-микроконтроллеров. Исходники это не код на Си. Исходники — это документация: таблицы, формулы и схемы.

Достоинства реализации драйвера I2C на основе конечного автомата:

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

2++ Разработка через FSM хороша тем, что алгоритм сам составляется по мере отладки. Даже если изначально решение абсолютно неочевидное. При использовании методологии конечных автоматов алгоритм уточняется по мере получения красных модульных тестов.

3++ Обрабатываются все варианты использования алгоритма, как штатные, так и не штатные ситуации. FSM алгоритмы самые надежные. Безотказные, как автомат Калашникова.

4++ Для всех комбинаций возможных входных воздействий и нынешних состояний есть алгоритм для работы. Нет никаких исключительных ситуаций.

5++ Когда драйвер I2C реализован в виде конечного автомата то ему даже прерывания не нужны. Всё можно реализовать в одном супер цикле с выходом по таймауту.

6++ Код написанный на конечно автоматной методологии очень легко отлаживать. Достаточно только включить повыше уровень логирования и проанализировать цветной лог. Даже нет нужды в пошаговой отладке как таковой.

7++ Код написанный по FSM очень легко масштабировать. Достаточно добавить новые состояния и появится новый функционал.

Отладка автомата I2C

Отлаживать код — это то же, что работать детективом. Тут надо тщательнейшим образом анализировать логи в UART-CLI. Как водится, первым делом просканируем шину.

  1. Сканирование шины I2C это фундаментальная операция. Она показывает что плата пропаяна, что есть питание на микросхемах, что есть подтягивающие резисторы. Сканирование это smoke тест. Опа! Видим знакомый символ 0×1a. Это как раз NAU8814. Второе число это то же самое что 0×1a. Дело в том, что адрес семи битный, а алгоритм перебирает 8-битные числа. Вот старший бит и отбрасывается.

8b2db88907920f8a0c8f5625fb3de255.JPG

  1. Чтение слова. Это осциллограмма для чтения 16 бит регистра по I2C.

cf494ebaf8a80ecc7a362d9095f45d2e.JPG

Лог операции чтения слова:

  1. Запись 9-битного регистра по 7-ми битному адресу. По сути запись 16 бит слова по I2C.

8880a8194867fb4156600e5845d4c828.JPG

Лог записи регистра в виде отправки слова по шине I2C

66f34a237d5caa1c6604f0a849bfdec4.png

Все операции работают как часы. Модульные тесты проходят.

Пару слов про опыт работы с I2C интерфейс

Как можно заметить, драйвер интерфейса I2C это весьма сложная вещь. Надо следить за тем, чтобы шина не зависла. Работать с I2C надо предельно акурктно. Программировать I2C трансивер — это как ходить по канату. Шаг влево или шаг вправо и происходит bus error или bus busy. И привет… I2C — самый ненадёжный проводной интерфейс!

I2C slave микросхемы имеют свойство спонтанно зависать прижимая к земле провод тактирования SCL!

В таких случаях мастер ничего не может сделать на своей стороне, чтобы хоть как-то распетлять ситуацию. Происходить окончательная потеря link (а)…

В связи с этим, в особенно ответственных электронных устройствах (automotive, aerospace, military области) я бы рекомендовал следующие схемотехнические меры:

1- Вообще не использовать I2C. Как правило, каждый I2C совместимый чип помимо I2C поддерживает ещё и SPI режим. Примерами таких ASICов являются LIS3DH и NAU8814. В таких случаях надо отдавать предпочтение именно SPI интерфейсу. Легче будет поддерживать этот прибор. В SPI в принципе нечему ломаться.

2- На чип I2C ставить аналоговый ключ для электронного пере сброса электропитания на этом чипе по GPIO от MCU. Тогда в случае зависания можно будет пере инициализировать и хоть как-то дальше работать.

Итоги

Удалось написать работающий драйвер I2C трансивера в виде конечного автомата на девяти состояниях.

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

Словарь

Акроним

Расшифровка

PCB

Printed circuit board

ASIC

Application-specific integrated circuit

I2C

Inter-Integrated Circuit

FSM

Finite-state machine

Ссылки

Конечные автоматы — это настолько универсальный шаблон проектирования программного обеспечения, что коллекция его применения в моём опыте насчитывает уже более 9-ти случаев. FSM — золотой молоток!

© Habrahabr.ru