Проблемы с преобразованием uint32_t в char*
Я использую емкостный сенсорный датчик, который имеет 12 точек касания и сохраняет данные о состоянии в виде двоичного числа.
Я хочу принять это состояние, добавить немного данных на передний план, а затем отправить их на сервер через websockets. Я использую библиотеку Arduino Websockets для своего клиента websocket.
В библиотеке есть метод sendBinary, который позволяет вам указать символьный указатель для данных.
Итак, я написал функцию, которая создает новое 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));
}
Проблема в том, что, хотя целочисленная версия полезной нагрузки выглядит нормально, когда я пытаюсь преобразовать ее в символьный указатель, содержимое оказывается неправильным.
Я также попытался создать payloadPointer
и напрямую установить адрес целого числа в качестве адреса указателя, но это также не удалось.
(Я должен воспользоваться этим моментом, чтобы сказать, что я все еще довольно новичок в подобных концепциях arduino cpp).
Каков правильный способ сделать это? Я читаю указатели и пытаюсь понять, как правильно это сделать, но не могу до конца разобраться. Может ли кто-нибудь объяснить, как правильно это сделать и почему? Я хочу, чтобы это сработало, но более того, я хочу понять, "почему и как" правильно.
Спасибо! :луки:
@Chris Schmitz, 👍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
Отправка с помощью 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
- Почему считается плохой практикой использовать ключевое слово "new" в Arduino?
- Работает с gcc, но не с Arduino. ошибка: taking address of temporary array
- ArduinoJSON v6 – передача буфера как параметра функции
- Существует ли какой-либо рабочий аналог пары std::function и std::bind в Arduino?
- Вычислить SHA256 строки и вывести в строку
- Преобразование беззнакового целого числа в указатель const char
- Разница между (*(volatile unsigned int *) и (volatile unsigned int)?
- Передача массивов, глобальных массивов внутри функций, указателей и объявление размеров массивов.
Вы пытаетесь отправить {метаданные, полезную нагрузку} в виде строки символов? Преобразование полезной нагрузки путем ее приведения не делает того, чего вы хотите. Если это так, вам нужно создать строку в буфере (
char[]
), преобразовать или скопировать метаданные в этот буфер, преобразовать (не приводить) данные из целого числа в его строковое представление, добавить эту строку в буфер и, наконец, передать адрес буфера (которыйэто (char *), который вам нужен) для функции, ожидающей символов. Приведение int к a (char *) не изменяет его; он сообщает компилятору считать это указателем на символы, а это не так!, @JRobertПри отправке двоичных данных используйте
Serial.write ()
, а неSerial.print()
. Последнее относится только к данным в кодировке ASCII, @chrislСвязано: [Как преобразовать uint32_t в тип char*] (https://stackoverflow.com/q/20222391/55075)., @kenorb