Отправка многобайтовых данных по I2C между разными процессорами

esp8266 attiny byte-order

Я пытался считать данные с ATTINY84 через ESP8266. Оба программируются через Arduino IDE, хотя, поскольку PlatformIO является прокси для этого, я не уверен, какая версия ядра ATTINY используется.

У меня есть оба устройства, подключенные к шине I2C, с Arduino в качестве ведомого устройства. ESP отправляет код, запрашивающий данные определенного типа, затем вызывает Wire.requestFrom() для получения 8 байтов данных. Меня беспокоит как моя реализация, так и потенциальные проблемы с порядком байтов (даже если окажется, что это не проблема на этих двух конкретных устройствах, я хотел бы знать свои будущие варианты).

Код ATTINY — это сложная функция, которая вызывается при наличии запроса (и правильный код опции, но это, вероятно, не очень важно). Он записывает восемь байтов с помощью Wire.write после их распаковки из соответствующих массивов. (У меня есть много данных АЦП, которые технически не должны помещаться в ОЗУ.) Поскольку это функция обратного вызова или, возможно, прерывание, должен ли я беспокоиться о повреждении данных, если код слишком медленный для записи значений на шину? Я должен представить, что он пытается читать их, даже когда пишет. Я также могу предоставить фрагменты кода, если это поможет.


Я также не уверен, сталкиваюсь ли я с проблемами порядка байтов при использовании битового сдвига для захвата 2 и 8 битов из возвращаемого значения АЦП. Будет ли это всегда давать мне правильные значения, или компилятор С++ (как я предполагаю, это не так) просто отказывается обрабатывать различия между порядком данных? В частности, если я сдвину uint16_t (или любой другой нетривиальный тип данных) вправо на 8 бит и маскирую нижние 8 с помощью &0xFF, всегда я получу второе наименьшее значимый (в данном частном случае старший) байт, или компилятор может выдавать альтернативные выходные данные на основе порядка байтов?

Есть ли у Arduino эквивалент сетевого порядка байтов (или Wire делает это автоматически для переопределения функций), с помощью которого я могу обойти эту потенциальную проблему?

, 👍1


1 ответ


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

1

Кажется, в том, что вы задаете, содержится несколько вопросов. Вот я пытаюсь ответить конкретно на этот:

если я сдвину uint16_t (или любой другой нетривиальный тип данных) вправо на 8 бит, и замаскируйте нижние 8 с помощью &0xFF, всегда ли я получаю второй младший значащий (в данном конкретном случае старший) байт, или может компилятор производит альтернативные выходные данные на основе порядка байтов?

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

Теперь я расширю этот ответ, показав различные способы может преобразовать uint16_t в последовательность байтов, и последствия в порядке байтов.

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

uint16_t data;

в это:

uint8_t bytes[2];

Вы можете сделать это таким образом, чтобы сначала получить младший значащий байт:

bytes[0] = data >> 0;  // младший значащий байт
bytes[1] = data >> 8;  // старший байт

Или вы можете вместо этого выполнять задания наоборот (data >> 8 переходит в bytes[0]...), если вы хотите, чтобы MSB был первым. Обратите внимание, что нет смысла маскировать, так как присваивание делает это неявно.

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

bytes[0] = ((uint8_t *) &data)[0];  // первый байт
bytes[1] = ((uint8_t *) &data)[1];  // последний байт

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

union {
    uint16_t data;
    uint8_t bytes[2];
} x = { .data = some_16_bit_value };
do_something_with(x.bytes[0]);  // используем первый байт
do_something_with(x.bytes[1]);  // используем второй байт

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

,

Это отвечает большей части того, что я хотел, хотя часть о приведении указателей не дает результатов, которые я надеялся услышать. Что произойдет, если я отправлю uint32_t, например, через Wire.write() (или аналогичную функцию) напрямую? Это отправляет в собственном порядке байтов или Wire обрабатывает это автоматически? Я хотел бы избежать смещения битов для некоторых из моих более сложных сообщений, если это возможно., @RDragonrydr

У меня было три подвопроса, которые, я признаю, не помогают делу, но они также в некоторой степени связаны. Во-первых, это то, что на самом деле делает сдвиг битов, во-вторых, если библиотека Wire обрабатывает несоответствия порядка следования байтов самостоятельно, а в-третьих (что немного менее связано) точно, как/когда данные записываются или запрашиваются, когда мастер запрашивает данные - есть ли вероятность того, что ведомое устройство не будет готово вовремя или будет писать слишком медленно (это может привести к тому, что какой-то более поздний раздел сообщения будет неправильным, в то время как первый был правильным)., @RDragonrydr

@RDragonrydr: Wire.write() не обрабатывает порядок следования байтов. Он отправляет либо один байт ("Wire.write(value)" отправляет только младший бит значения "value"), либо массив байтов, когда вызывается как "Wire.write(const uint8_t *buffer, size_t length)"., @Edgar Bonet