Проблемы с преобразованием uint32_t в char*

Я использую емкостный сенсорный датчик, который имеет 12 точек касания и сохраняет данные о состоянии в виде двоичного числа.

getting the touch state

Я хочу принять это состояние, добавить немного данных на передний план, а затем отправить их на сервер через websockets. Я использую библиотеку Arduino Websockets для своего клиента websocket.

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

sendBinary declaration

Итак, я написал функцию, которая создает новое 32-разрядное целое число без знака, добавляет мои метаданные, затем состояние касания, а затем пытается получить адрес целого числа полезной нагрузки и преобразовать его в символьный указатель:

void sendTouchStateToServer()
{
    Serial.println("sending state to server");

    uint32_t payload = touchControllerMessageType << 12;
    Serial.println(payload);
    payload |= currentTouchState;

    Serial.print("Payload contents: ");
    Serial.println(payload);
    Serial.print("Payload contents cast as character pointer: ");
    Serial.println((char *)&payload);
    Serial.print("Payload size: ");
    Serial.println(sizeof(payload));

    client.sendBinary((char *)&payload, sizeof(payload));
}

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

garbled output

Я также попытался создать payloadPointer и напрямую установить адрес целого числа в качестве адреса указателя, но это также не удалось.

direct pointer addressing (I think)

(Я должен воспользоваться этим моментом, чтобы сказать, что я все еще довольно новичок в подобных концепциях arduino cpp).

Каков правильный способ сделать это? Я читаю указатели и пытаюсь понять, как правильно это сделать, но не могу до конца разобраться. Может ли кто-нибудь объяснить, как правильно это сделать и почему? Я хочу, чтобы это сработало, но более того, я хочу понять, "почему и как" правильно.

Спасибо! :луки:

, 👍2

Обсуждение

Вы пытаетесь отправить {метаданные, полезную нагрузку} в виде строки символов? Преобразование полезной нагрузки путем ее приведения не делает того, чего вы хотите. Если это так, вам нужно создать строку в буфере (char[]), преобразовать или скопировать метаданные в этот буфер, преобразовать (не приводить) данные из целого числа в его строковое представление, добавить эту строку в буфер и, наконец, передать адрес буфера (которыйэто (char *), который вам нужен) для функции, ожидающей символов. Приведение int к a (char *) не изменяет его; он сообщает компилятору считать это указателем на символы, а это не так!, @JRobert

При отправке двоичных данных используйте Serial.write (), а не Serial.print(). Последнее относится только к данным в кодировке ASCII, @chrisl

Связано: [Как преобразовать uint32_t в тип char*] (https://stackoverflow.com/q/20222391/55075)., @kenorb


2 ответа


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

2

Ваш код с помощью sendBinary, вероятно, в порядке, если с другой стороны вы также используете функцию, которая ожидает получения ровно 32 бит двоичных данных (в формате little-endian).

Попытка напечатать (char*)&some_32bit_int, с другой стороны, не принесет ничего полезного.

Короткая версия такова:

  • если вы хотите отправлять (или получать) двоичные данные, используйте функции, созданные для двоичных данных - обычно они принимают указатель на char (или uint8_t или что-то в этом роде) и длину.
  • если вы хотите отправлять (или получать) строки, используйте функции, предназначенные для строк. Иногда они принимают Stringили std::string или, к сожалению, возможно, char * для строк C.

Никогда не смешивайте то и другое.

Функции, которые ожидают строку (C), ожидают, что она на самом деле будет строкой C, то есть последовательностью символов, заканчивающихся нулевым байтом (0x00). Ожидается, что символы будут в основном символами для печати в формате ASCII. Если вы дадите им просто необработанные данные, такие как адрес памяти int, вы не получите то, что хотите.

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

Если вы хотите отформатировать свои данные в строку для отправки (используя строковый / текстовый протокол), то для преобразования используйте такие функции, как sprintf. (Или используйте библиотеку, которая работает в формате JSON, если ваш приемник является веб-сайтом - это довольно удобно.)

например ,

char buffer[32];
sprintf(buffer, "{data:%d}", payload);

Затем отправьте буфер через функцию, которая ожидает строку C.


Подумайте об этом:

uint32_t payload = 0x00323130;

Первая строка инициализирует 32-битный int определенным значением, 0x00323130 в шестнадцатеричном формате. Теперь давайте предположим, что полезная нагрузка была сохранена в памяти по адресу 0x0100. Память после этого назначения будет выглядеть следующим образом:

Addr.  Val
0x0100 0x30  // наш тот же номер 0x00323130, сохраненный в формате little-endian
0x0101 0x31
0x0102 0x32
0x0103 0x00

Когда вы делаете:

client.sendBinary(&payload, 4);

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

Теперь, если вы сделаете:

Serial.println((char *) &payload);

Serial.println - это перегруженная функция. Когда вы присваиваете ему char*, он ожидает C-строку, которая представляет собой последовательность символов, заканчивающихся "нулевым байтом", то есть байтовым значением, равным нулю. Затем Serial.println просмотрит первый байт, на который указывает аргумент. Если оно равно нулю, оно останавливается. В противном случае он выводит этот символ в последовательную строку и переходит к следующему символу. Повторяйте до тех пор, пока не будет найден нулевой байт.

В специально созданном здесь случае с этим конкретным значением полезной нагрузкиSerial.println получит адрес 0x0100 и:

  • Посмотрите на значение 0x0100, получите 0x30, убедитесь, что оно не равно нулю, и передайте его в последовательную строку. Последовательный монитор получит 0x30 и отобразит это. По счастливой случайности это код символа ASCII для цифры "0".
  • Посмотрите на значение 0x0101, получите 0x31, убедитесь, что оно не равно нулю, и передайте его в последовательную строку. Последовательный монитор получит 0x31 и отобразит это. По счастливой случайности это код символа ASCII для цифры "1".
  • Посмотрите на значение 0x0102, получите 0x32, ... последовательный монитор отображает "2".
  • Награбьте значение в 0x0103, получите 0x00. Это нулевой байт, поэтому он останавливается на этом и отправляет последовательность перевода строки в последовательный.

Таким образом, в этом сфабрикованном сценарии вывод на последовательный монитор будет "123".

Попробуйте сделать это с полезной нагрузкой = 0x00616263; - последовательный монитор отобразит "cba".

Короче говоря, Serial.println будет выводить на последовательный сервер именно те байты, которые он находит в памяти, пока не встретит нулевой байт. Последовательный монитор попытается отобразить эти байты. Но, если вы не обработали эти значения очень тщательно, все, что вы получите от этого, это мусор - относительно небольшое количество 8-битных значений сопоставляется с печатными символами, и даже когда они это сделают, они не будут "выглядеть" как необработанные данные, которые у вас были.

Если ваш номер начинается с нулевого байта в его двоичном представлении с малым порядковым номером, Serial.println ничего не напечатает - например, если бы вы дали ему пустую строку.

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

,

Отличный, обстоятельный ответ. (Проголосовало), @Duncan C

Хороший ответ. Я тоже проголосовал., @Erdem

Вау, этот ответ был фантастическим! Спасибо, что нашли время написать это. В этом есть большой смысл. Я собираюсь провести некоторое исследование c ++ за пределами моего arduino, чтобы я мог провести некоторую проверку памяти (у меня нет инструментов для отладки JTAG). Еще раз спасибо за ответ!, @Chris Schmitz


2

Отправка с помощью client.sendBinary((char *)&payload, sizeof(payload)); в порядке.

Ваши попытки напечатать двоичные данные неверны.

Print() из 4 байтов массива uint32_t в виде символа напечатает 4 символа, коды ASCII которых находятся в этих 4 байтах, а затем продолжит печатать символы из памяти после переменной, пока не будет считан байт с 0. Некоторые символы могут быть недоступными для печати управляющими символами терминала.

Serial.write((char *)&payload, sizeof(payload)); напечатает 4 символа, коды ASCII которых находятся в этих 4 байтах.

Самый простой способ визуализировать вашу двоичную полезную нагрузку - Serial.println(полезная нагрузка, BIN);. Это выведет значение uint32_t в двоичном формате.

,

Потрясающе. Спасибо за ответ. Полезно знать о двоичном форматировании: clap:, @Chris Schmitz