Шестнадцатеричное/Байтовое реверсирование и преобразование
Я работаю над системой 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.
@AlfroJang80, 👍1
Обсуждение4 ответа
Ваш 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
Стандартный способ преобразования отдельных байтов в число-использовать битовые
сдвиги и побитовое ИЛИ, как показано в ответе Крисла.
Однако при этом нужно быть осторожным, чтобы не переполнить битовые сдвиги. На
ардуино на основе 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” – массив символов, заканчивающийся нулем, при необходимости может быть преобразован в строковый объект, но я советую вам не делать этого , если это вообще возможно. Строковыми объектами удобнее манипулировать, но они абсолютно не подходят для работы с памятью, что может быстро стать проблемой на большинстве ардуино.
Чтобы добавить немного ко всей шестнадцатеричной и десятичной истории: Это всего лишь представления числовых значений, которые на самом деле не волнуют ваш компьютер/микропроцессор.
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
Я думаю, это делает то, что вам нужно. У вас уже есть данные в массиве, поэтому, чтобы изменить их, вы проходите список в обратном порядке и собираете число. Чтобы вместить 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
- rfid_default_keys проверить с помощью RC522
- Отправка данных из ESP8266 в PHP
- Как сделать динамическое меню с подменю ( Arduino mega или Arduino Uno)
- Вывести 2 числа, хранящиеся в 24-битном формате, в десятичном формате.
- Цикл чтения RFID-карты кажется остановленным
- Сформировать сигнал из массива битов
- Я пытаюсь совместить автоматический дверной замок с RFID MFRC522 с программой IR REMOTE.
- Ошибка прерывания проекта Arduino?
Уже есть ответ на Arduino.cc http://forum.arduino.cc/index.php?topic=561413.0, @Delta_G