Лучший способ отправки команд I2C между Arduino и ESP32

Я сделал плату с помощью Arduino nano и комплекта разработчика ESP32. Я подключил их по I2C с адаптацией напряжения. Я использую arduino IDE и связанную с ней библиотеку Wire.h.

Я отправляю сообщения в виде символов ascii или исходных байтов с ESP32 (ведущий) на Arduino (ведомый), и все работает нормально.

Я должен принять решение сейчас. Мне нужно реализовать простой протокол для отправки команды на Arduino, чтобы попросить его выполнить некоторые задачи или попросить его отправить некоторые данные в esp32 (например, скорость двигателя или состояние некоторых датчиков и т. д. вкл.).

Что бы я хотел спросить, так это ваше предложение, должен ли простой протокол управляться как собственные байты (естественный способ работы I2C) или как строка (управляемая с помощью массива). Я пытаюсь объяснить.

В случае байтов я мог бы отправить, например, FF 0E14 01, чтобы попросить Arduino запустить двигатель = FF со скоростью = 0E14 и направлением = 01. Таким образом, конкретная команда закодирована в первом байте.

Чего мне не хватает, так это необходимости или отсутствия разделителя, такого как "<" и «>», как в случае строки. В случае обмена нативными байтами значение "<" "занято" целым числом и не может быть распознано синтаксическим анализатором. Итак, какой разделитель я мог бы использовать?

С другой стороны, в случае строк мне не нравится необходимость преобразовывать число "213" или "1529" в относительное целое число. В случае предыдущей команды я мог отправить строку

""

имея в виду первый символ как команду и для этой конкретной команды «S» (например, «S»), анализируя следующие символы до «,» и конвертируя его в целое число и последний символ как направление (как байт или как char тест такой же).

Я не знаю... какой способ вы предлагаете и в случае нативных байтов какие разделители?

Спасибо.

, 👍1

Обсуждение

Это довольно основано на мнении. Мне кажется, вы уже разобрались в плюсах и минусах i2c. Я лично не рекомендую использовать строковый протокол на i2c и придерживаться общего протокола i2c. В этом случае вам не нужны стартовые/стоповые байты., @Sim Son


2 ответа


Лучший ответ:

0

Во-первых, использование протокола на основе байтового значения является стандартом для I2C. Нет реальной причины переходить к данным, закодированным ASCII (причина для этого с последовательным (UART) обычно заключается в том, что сообщение может быть прочитано человеком на ПК. Обычно это не относится к I2C, поскольку только компьютеры, такие как Raspberry У Pi даже есть интерфейсы I2C).

О разделителях: они действительно нужны только для асинхронной передачи данных (как UART) (для кадрирования полного пакета/сообщения) или, возможно, если у вас есть неизвестный размер передачи. И то, и другое не относится к I2C (это синхронный интерфейс, который уже реализует передачу пакетов/сообщений). Вы можете посмотреть техническое описание любого датчика I2C. Стандартный протокол связи представляет собой команду, отправляемую мастером, а затем отправку/прием определенного количества байтов по определенному протоколу, который определяет, какие байты содержат какую информацию. Здесь вам не нужен разделитель, потому что вы уже знаете, в каком формате будут отправлены данные.


Давайте возьмем пример из вашего вопроса: вы хотите, чтобы Arduino сообщал о скорости вашего двигателя ESP. Вы реализуете первый байт каждой основной передачи записи как команду, например, десятичное значение 10 как команду для сообщения скорости двигателя. Затем вы завершаете передачу мастер-записи и запрашиваете 1 байт у Arduino (поскольку вы уже знаете, что скорость двигателя имеет 1 байт). Через команду Arduino знает, что он должен отправить байт скорости двигателя, а ESP знает, что полученный байт будет 1 байтом скорости двигателя.

Теперь мы определяем команду 11 для сообщения скорости двигателя и 3-байтового значения датчика. ESP отправляет 11, а затем запрашивает 4 байта, так как знает, что сообщение будет иметь длину 4 байта. Arduino отправляет данные, и ESP знает, что первый байт будет скоростью двигателя, а следующие 3 байта — значением датчика. Разделитель не нужен, так как структура ваших данных уже определена реализацией вашего протокола. Маркеры начала или конца для сообщения не нужны, так как I2C уже работает с отдельными сообщениями/пакетами данных (передача начинается с условия START на шине и заканчивается условием STOP).


Это решение используется для большинства устройств I2C, поскольку его проще реализовать на низком уровне. На первый взгляд нам, людям, кажется, что проще использовать данные, закодированные в ASCII и, следовательно, удобочитаемые для человека. Но, как вы уже упоминали, это усложняет работу микроконтроллеров (написание парсеров для данных ASCII сложнее, чем использование двоичных данных по фиксированному протоколу). Имейте в виду, что I2C означает «межинтегральная схема», то есть связь между интегральными схемами, а не связь между интегральной схемой и человеком.

Но, в конце концов, вам решать, какой протокол вы хотите использовать.


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

Например, на Arduino (сторона получателя): первым состоянием может быть состояние команды, когда Arduino ожидает передачи I2C. Если передача была получена, она будет считывать свой первый байт. В зависимости от этого байта он перейдет в другое состояние, например, в состояние «Отправить скорость двигателя». Там он подготовит все для отправки скорости двигателя мастеру, если мастер запросит данные от Arduino.

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

,

Я ценю все детали Крисл. Я не могу проголосовать за ваш ответ, так как у меня пока низкая репутация. Я поступлю так, как вы предложили., @daigs

Единственная проблема связана с положением синтаксического анализатора или конечного автомата. Поскольку функция "void receiveEvent(int howMany)" вызывается как процедура ISR после прерывания, она должна быть как можно меньше. Поэтому я не должен писать парсер внутри него. Как справиться с этой проблемой? Другой вопрос: могу ли я использовать количество байтов, чтобы понять правильную длину входящего текущего пакета байтов? Спасибо., @daigs

Если обработка сообщения будет слишком долгой для ISR, обычным способом является буферизация сообщения в дополнительном буфере и установка флага внутри ISR. Этот флаг опрашивается внутри loop(), и обработка выполняется там, когда флаг установлен., @chrisl

Да, параметр «howMany» — это количество байтов данных при передаче, например, количество полученных байтов., @chrisl

Вы можете принять мой ответ, если считаете его правильным. Для этого не нужна репутация. И вы единственный, кто может принять ответ, раз вы задали этот вопрос., @chrisl


-1

Прежде чем указывать на ваш вопрос; Сначала я начну рекомендовать избегать использования i2C для таких вещей и рекомендовать UART (последовательный), который намного лучше для связи между несколькими микроконтроллерами.

UART) Serial Дает вам стек (память полученных данных), что означает, что вы получаете данные при обработке других функций и воздействуете на них в собственное время Arduino. UART более чем достаточно быстр для таких вещей и позволяет работать на больших расстояниях, чем несколько сантиметров, он более устойчив к помехам
UART (последовательный) использует меньше портов, и большинство портов можно преобразовать в программные последовательные
Вы найдете много библиотек, которые занимаются обменом данными, даже шифрованием через Serial
Если вы используете write(), а не print, вам не нужно преобразовывать данные.
если вы используете правильный ввод на вашем arduino, вы может даже читать данные, поступающие с вашего ПК, и многое другое

В любом случае, на ваш вопрос. Большинство сообщений данных, как правило, имеют начальный байт, а конечный байт для упаковки кармана среди обычных - это 00-Start и 0xFF в качестве конца.
Теперь предположим, что у нас есть только один подчиненный и одиночный уровень приоритета и т. д., нам не нужно быть слишком умным для заголовка пакета... затем я бы определил процесс, поскольку он позволит вам выбрать функцию, которая затем может получать следующие данные в качестве переменных.
Эта опция означает, что вам придется нумеровать операции с 1, а не с 0
Основное преимущество заключается в том, что, поскольку разные функции могут принимать разное количество переменных (например, одна указывает только на on off, а другая указывает на векторное расстояние), вы не будете должны определиться с длиной пакета, а просто иметь дело с любым количеством переменных (в разумных пределах)

,

Поскольку вы не можете использовать все числа от 0 до 255 на uart (по крайней мере, вам нужен стоп-байт), вам всегда придется преобразовывать данные в представление массива символов. Кроме того, асинхронность не всегда является преимуществом, поскольку она делает необходимым синтаксический анализатор., @Sim Son

Я не получаю много ваших очков против I2C. Библиотеки I2C имеют «стек» (который на самом деле является буфером FIFO, а не стеком), как и библиотека «Serial». I2C использует 2 контакта, как и UART. Библиотеки для шифрования обычно обрабатывают сами данные; для них не имеет значения, через какой интерфейс передаются данные. write() отправляет двоичные данные для обоих интерфейсов; здесь нет разницы. Я не понимаю, зачем вам нужны начальный и конечный байты для передачи I2C (поскольку у него уже есть условия START и END; он уже отправлен в пакете). Серийный подходит для связи между двумя микроконтроллерами. Больше довольно сложно, @chrisl

Также я не понимаю всего после «Теперь предположим, что». Я просто не понимаю, что вы хотите там сказать. Объясните, пожалуйста, в чем суть?, @chrisl