Шестнадцатеричное/Байтовое реверсирование и преобразование

programming rfid bit byte-order

Я работаю над системой RFID с использованием MFRC522 с этой библиотекой : https://github.com/miguelbalboa/rfid Вот код, который у меня есть прямо сейчас:

int A=(mfrc522.uid.uidByte[0]);
int B=(mfrc522.uid.uidByte[1]);
int C=(mfrc522.uid.uidByte[2]);
int D=(mfrc522.uid.uidByte[3]);

Serial.println("UID is %x %x %x %x", A, B, C, D);

Это приводит к следующему результату:

UID is 35 C5 5A 59

Моя проблема в том, как мне сначала изменить этот UID : с 35 C5 5A 59 на 59 5A C5 35 - и во-вторых, преобразуйте и выведите соответствующее десятичное значение этого перевернутого UID. В этом случае это будет 1499120949.

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

, 👍1

Обсуждение

Уже есть ответ на Arduino.cc http://forum.arduino.cc/index.php?topic=561413.0, @Delta_G


4 ответа


0

Ваш UID имеет 4 байта, что соответствует 32 битам. Чтобы получить десятичное значение из этих байтов, вы должны объединить их в одну переменную. Для этого вы можете использовать длинную строку без знака. Чтобы собрать байты вместе, вы можете сдвинуть значения в границы байтов:

unsigned long dec_value = (D << 24) | (C << 16) | (B << 8) | A;

<< сдвигает биты байта на указанное количество битов влево; Побитовое ИЛИ | объединяет теперь не перекрывающиеся байты в одно значение. Я изменил порядок байтов в этом коде, как вы писали, что это необходимо.

Чтобы преобразовать эту длинную строку без знака в строку, вы можете использовать функцию ltoa():

char buf[50];
ltoa(dec_value, buf, 10);  // 10 - это базовое значение, а не размер- найдите ltoa для avr

Если вы хотите использовать строковый объект (чего следует избегать из-за фрагментации кучи), вы можете построить строку непосредственно из длинного значения:

String my_UID = String(dec_value);
,

Фантастика! Я попытался распечатать результат только для проверки с помощью Serial.println("%s\n", my_UID); но я получаю ошибку: нет соответствующей функции для вызова 'HardwareSerial::println(const char [4], String&)', @AlfroJang80

Serial.println() не работает как sprintf(), поэтому вы не можете использовать этот синтаксис там. Но вы должны быть в состоянии получить правильный результат, просто написав Serial.println(my_UID);. На самом деле нет необходимости снова форматировать строку (также потому, что println уже добавляет разрыв строки в конце, так что вы можете избавиться от лишнего "\n")., @chrisl

Ах, удалось заставить его напечатать, но я получаю 4294952245 в качестве десятичного ответа. Онлайн-конвертер сказал, что 595AC535 = 1499120949. Я тестирую его с помощью фиксированных шестнадцатеричных байтов - https://pastebin.com/i2NPjv60, @AlfroJang80

Вам нужно использовать "unsigned long" вместо "int" для ваших A,B,C и D - в противном случае смещение переполняется., @Majenko

`dec_value “ - это плохо выбранное имя переменной, так как оно подразумевает, что в нем есть что-то” десятичное". Это не десятичное число: это просто число, внутренне хранящееся в двоичном виде, точно так же, как любая другая числовая переменная хранится в двоичном виде., @Edgar Bonet


1

Стандартный способ преобразования отдельных байтов в число-использовать битовые сдвиги и побитовое ИЛИ, как показано в ответе Крисла. Однако при этом нужно быть осторожным, чтобы не переполнить битовые сдвиги. На ардуино на основе AVR, int имеет длину 16 бит. Смещение его на 24 позиции влево гарантированно приведет к переполнению, что в C++ приводит к неопределенному поведению.

Решение, как объяснено в комментарии Майенко, состоит в том, чтобы привести к окончательному типу перед переходом, например:

uint32_t uid = (uint32_t) mfrc522.uid.uidByte[0] << 0
             | (uint32_t) mfrc522.uid.uidByte[1] << 8
             | (uint32_t) mfrc522.uid.uidByte[2] << 16
             | (uint32_t) mfrc522.uid.uidByte[3] << 24;

Обратите внимание, что я избавился от бесполезных промежуточных переменных A через D.

Как только у вас будет числовое значение, вы можете попытаться использовать его без явного преобразования в строку. Вместо этого вы полагаетесь на методы print() или println (), чтобы неявно выполнять преобразование только при необходимости, как в

Serial.print("UID = ");
Serial.println(uid);

или даже при сетевом подключении:

client.print(uid);

Это сэкономит вам память, необходимую для хранения строки. Если вам действительно нужно сохранить преобразованную строку в памяти, что может произойти, если вам нужно указать длину содержимого перед отправкой данных, вы можете сделать это с помощью функции ultoa() из avr-libc:

char uid_str[11];  // max = 10 цифр + конечное значение NUL
ultoa(uid, uid_str, 10);  // преобразовать в базу 10

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

Эта “строка C” – массив символов, заканчивающийся нулем, при необходимости может быть преобразован в строковый объект, но я советую вам не делать этого , если это вообще возможно. Строковыми объектами удобнее манипулировать, но они абсолютно не подходят для работы с памятью, что может быстро стать проблемой на большинстве ардуино.

,

0

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

int x = 0
int y = 0b0
int z = 0x0

все они имеют одинаковое значение и хранятся внутри в двоичном виде, как и любое другое число.

Вы специально сказали функции println отображать указанные числа в шестнадцатеричном представлении со спецификатором формата %x Serial.println("UID-это %x %x %x %x", A, B, C, D); использование %d или %i(для целого числа) выведет десятичные значения для каждого числа.

Преобразование массива байтов в более крупный тип данных

Если вы действительно хотите, чтобы они были отменены, вы можете просто привести переменную массива (которая является не чем иным, как указателем) к указателю uint32_t:

uint32_t dec_value = *(uint32_t*)mfrc522.uid.uidByte

Обратите внимание на *s Сначала мы приводим указатель к указателю другого типа (uint32_t*) , а затем обращаемся к его значению, добавляя другое*, которое затем сохраняется в dec_value, равное 1499120949 сейчас.

Это работает, потому что в C/++ структуры данных в памяти гарантированно располагаются в указанном вами порядке. Таким образом, вы, по сути, приказываете компьютеру "видеть" эти самые 4 байта (32 бита) как одно значение, а не как независимые байты.

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

Вот функция, которая решает эту проблему (аналогично другим ответам)

uint32_t bytearray_to_uint32(unsigned char *buffer)
{
    uint32_t value = 0;

    value |= (uint32_t)buffer[0] << 24;    // cast to target type before 
    value |= (uint32_t)buffer[1] << 16;    // shifting to avoid overflow
    value |= (uint32_t)buffer[2] << 8;     // no parentheses needed due to operator precedence
    value |= buffer[3];

    return value;
}

Применение этой функции в вашем случае приведет к 902126169 (0x35c55a59) вместо желаемого 1499120949 (0x595ac535).

Также проверьте ntohl, когда вы имеете дело с конечностью, которая все еще озадачивает вас.

,

1. Как уже говорилось в комментарии Майенко к ответу Крисла и повторено в моем собственном ответе, буфер[0] << 24 переполнится на большинстве ардуино. 2. "ntohl ()" не будет доступен на большинстве (возможно, на всех) ардуино., @Edgar Bonet

Я ссылался на ntohl только в образовательных целях. Сдвиг 8-битного значения на > 8 не приведет к переполнению, если цель не слишком мала (WinAVR). Опять же, функция, использующая сдвиг, включена только в образовательных целях, так как она не подходит для применения OP в данной форме., @mystery

Это может быть только мое личное мнение, но я не думаю, что “образовательные цели” оправдывают неопределенное поведение., @Edgar Bonet

Поведение зависит от целевой архитектуры и используемого компилятора. Любой приличный человек либо справится, либо выбросит вместо этого "shift-count-overflow"., @mystery


0

Я думаю, это делает то, что вам нужно. У вас уже есть данные в массиве, поэтому, чтобы изменить их, вы проходите список в обратном порядке и собираете число. Чтобы вместить 4 байта в 32 бита, вам нужно умножить каждый байт на (256 * его положение), что и делает сдвиг влево<<. Таким образом, помещая код в небольшой цикл, вы получаете компактный код, который должен выполнять эту работу.

int A=(mfrc522.uid.uidByte[0]);
int B=(mfrc522.uid.uidByte[1]);
int C=(mfrc522.uid.uidByte[2]);
int D=(mfrc522.uid.uidByte[3]);

Serial.println("UID is %x %x %x %x", A, B, C, D);
// ^ Your code
uint32_t reversed = 0;
for (int x = 3; x > -1; --x) // go backwards through the array
{  // Take the existing value and multiple by 256 (left shift by 8) then add the latest byte on.
   reversed = (reversed << 8) + mfrc522.uid.uidByte[x];
}
String decValue = String (reversed);  // Covert the number to a string
,