Преобразовать шестнадцатеричное число в десятичное из дополнения до 2 со знаком

Я считываю данные со счетчика по протоколу dlms и получаю оттуда шестнадцатеричные значения. Я хочу преобразовать эту шестнадцатеричную строку в десятичное значение со знаком. Например, ff9d означает значение -99 [ссылка].

Мой шестнадцатеричный код в десятичный:

unsigned int hexToDec(String hexString)
{
  unsigned int decValue = 0;
  int nextInt;

  for (int i = 0; i < hexString.length(); i++)
  {
    nextInt = int(hexString.charAt(i));
    if (nextInt >= 48 && nextInt <= 57) nextInt = map(nextInt, 48, 57, 0, 9);
    if (nextInt >= 65 && nextInt <= 70) nextInt = map(nextInt, 65, 70, 10, 15);
    if (nextInt >= 97 && nextInt <= 102) nextInt = map(nextInt, 97, 102, 10, 15);
    nextInt = constrain(nextInt, 0, 15);
    decValue = (decValue * 16) + nextInt;
  }
  return decValue;
}

Кто-нибудь может помочь мне, как это сделать в Arduino IDE?

, 👍4

Обсуждение

Вы пытались погуглить что-то вроде «C++ преобразовать шестнадцатеричную строку в int»?, @chrisl

Да, я преобразовал шестнадцатеричную строку в десятичную. Но не могу понять подписанную часть. Я могу поделиться этим кодом, если хотите?, @Prateek Goyal

Да, поделитесь кодом. Вы можете отредактировать свой вопрос, чтобы включить его., @chrisl

Я отредактировал свой вопрос @chrisl, @Prateek Goyal

Вы не хотите преобразовывать число в десятичное число: вы хотите преобразовать его в двоичное. int представлен в двоичном формате. Десятичный формат используется для представления целочисленных литералов в _исходном коде_, но компилятор преобразует их в двоичные, и во время выполнения каждое число является двоичным., @Edgar Bonet

У вас есть фрагмент кода, как это сделать в Arduino @EdgarBonet?, @Prateek Goyal


2 ответа


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

2

Например, ff9d означает значение -99

Написанная вами функция возвращает целое число без знака, которое, будучи без знака, не может быть отрицательным. Простое исправление - объявить его как

int hexToDec(String hexString)

Этого должно быть достаточно, чтобы заставить его работать должным образом. Обратите внимание, что местный переменная decValue по-прежнему должна быть беззнаковой, иначе вы получите подписанное переполнение, которое является неопределенным поведением (т. е. «запрещено») в С++. Возврат беззнакового числа из функции, которая должна вернуть число со знаком совершенно нормально: вы получаете неявное преобразование который гарантированно поступит правильно.

Теперь, если я могу предоставить еще несколько отзывов об этой функции:

  • Используйте, если можете, простые строки C (char *) вместо String объекты, так как последние не очень дружелюбны к памяти вашего Arduino.
  • Используйте целые числа фиксированной длины (uint16_t, если ваш протокол требует 16-разрядного целые числа) вместо универсального int, иначе ваш код не будет работать. если вы перейдете на 32-разрядную платформу, например Arduino на базе ARM.
  • Используйте символьные литералы ('0' вместо 48) вместо жесткого кодирования. Кодовые точки ASCII, так как это делает код более читабельным.
  • Используйте простое вычитание (nextInt - '0') вместо дорогая функция map().

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

int16_t hex2int(const char *hex)
{
    uint16_t value;  // без знака, чтобы избежать переполнения со знаком
    for (value = 0; *hex; hex++) {
        value <<= 4;
        if (*hex >= '0' && *hex <= '9')
            value |= *hex - '0';
        else if (*hex >= 'A' && *hex <= 'F')
            value |= *hex - 'A' + 10;
        else if (*hex >= 'a' && *hex <= 'f')
            value |= *hex - 'a' + 10;
        else
            break;  // остановимся на первой нешестнадцатеричной цифре
    }
    return value;
}

Пример использования:

Serial.println(hex2int("ff9d"));  // prints "-99"

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

Канонический способ восстановить целое число из массива байтов – это сборка байтов вместе с использованием побитовой логики и битовых сдвигов. В этот случай:

int16_t reactive_power = (uint16_t) data[dataSize - 5] << 8
                       | data[dataSize - 4];
,

Как мне передать ff9d как строку в функции hex2int?, @Prateek Goyal

@Prateek: посмотрите пример, который я добавил., @Edgar Bonet

Я делаю это: String reacP = String(data[dataSize - 5], HEX) + String(data[dataSize - 4], HEX); Serial.println (hexToInt (reacP)); ** но я получаю сообщение об ошибке: невозможно преобразовать «String» в «const char*» для аргумента «1» в «int16_t hexToInt (const char*)»**, @Prateek Goyal

@Prateek: Мне кажется, вы конвертируете двоичные данные в шестнадцатеричную строку только для того, чтобы преобразовать их обратно в двоичные. Пожалуйста, отредактируйте свой вопрос, указав, как вы получаете данные. Самое главное: каков тип данных data?, @Edgar Bonet

Этот тип данных data представляет собой unsigned char, как в void ReactivePower (unsigned char data[], int dataSize)., @Prateek Goyal

Я решил проблему, добавив .c_str() в шестнадцатеричное значение. Большое спасибо @edgarbonet еще раз!, @Prateek Goyal

@Prateek: это очень плохое решение. Смотрите измененный ответ., @Edgar Bonet

Ой, разве это так. Позвольте мне тогда проверить ваш исправленный ответ. Просто из любопытства, почему использование .c_str() - плохая идея?, @Prateek Goyal

@Prateek: Преобразование в строку в первую очередь - плохая идея. Представление чисел в виде строк цифр ASCII полезно для восприятия человеком, но бесполезно в вашей ситуации. Ваша проблема из тех, которые можно решить тривиально, переместив пару байтов. Вместо этого вы берете очень длинную, затратную по памяти и ресурсоемкую диверсию., @Edgar Bonet

Я понял ошибку @edgarbonet. Ваш метод сработал идеально. Спасибо друг!, @Prateek Goyal


1

Какой режим DLMS вы используете?

IEC 61107 или в настоящее время IEC 62056-21 — это международный стандарт компьютерного протокола для считывания показаний счетчиков коммунальных услуг. Он предназначен для работы через любые носители, включая Интернет. Счетчик отправляет данные ASCII (в режимах A..D) или HDLC (режим E) на ближайший переносной блок (HHU) через последовательный порт. МЭК 62056

В своем вопросе вы упомянули:

шестнадцатеричная строка

что означает строку шестнадцатеричных символов с использованием ASCII (режимы от A до D).

Однако в комментариях вы упомянули:

void ReactivePower(unsigned char data[], int dataSize) String reacP = String(data[dataSize - 5], HEX) + String(data[dataSize - 4], HEX);

что означает массив байтов, полученный из потока байтов с использованием HDLC (режим E).

ASCII (режимы от A до D)

Для ASCII (режимы от A до D) существует простое преобразование, чтобы получить цифровое значение из шестнадцатеричного символа.

              ASCII
 Hex   --------------------                            Digit
 char  Decimal  Hexadecimal    Op1    Low nibble  Op2  Base 10
-----  -------  -----------  -------  ----------  ---  -------
 '0'      48        30        & 0x0F       0              0
 '1'      49        31        & 0x0F       1              1
 '2'      50        32        & 0x0F       2              2
 '3'      51        33        & 0x0F       3              3
 '4'      52        34        & 0x0F       4              4
 '5'      53        35        & 0x0F       5              5
 '6'      54        36        & 0x0F       6              6
 '7'      55        37        & 0x0F       7              7
 '8'      56        38        & 0x0F       8              8
 '9'      57        39        & 0x0F       9              9
 'A'      65        41        & 0x0F       1       +9    10
 'B'      66        42        & 0x0F       2       +9    11
 'C'      67        43        & 0x0F       3       +9    12
 'D'      68        44        & 0x0F       4       +9    13
 'E'      69        45        & 0x0F       5       +9    14
 'F'      70        46        & 0x0F       6       +9    15
 'a'      97        61        & 0x0F       1       +9    10
 'b'      98        62        & 0x0F       2       +9    11
 'c'      99        63        & 0x0F       3       +9    12
 'd'     100        64        & 0x0F       4       +9    13
 'e'     101        65        & 0x0F       5       +9    14
 'f'     102        66        & 0x0F       6       +9    15
  • Для символов от '0' до '9' просто используйте И с 0x0F.
  • Для символов от "A" до "F" и от "a" до "f", AND с 0x0F и добавить 9.
char ch = '1';
byte b = 0;
if (ch >= '0' && ch <= '9')
{
  b = ch & 0x0F;
}
else if ((ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
{
  b = (ch & 0x0F) + 9;
}

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

void setup()
{
  pinMode(13, OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  char string[] = "A";
  //char string[] = "Вафля";
  //символьная строка[] = "ff9d";
  int16_t the_int = 0;
  int count = AsciiToInt16(string, the_int);

  Serial.print(string);
  Serial.print(",  ");
  if (count)
  {
    Serial.print("Ok,  ");
    Serial.print(count);
    Serial.print(",  ");
    Serial.println(the_int);
  }
  else
  {
    Serial.println("Bad");
  }
}

int AsciiToInt16(const char* ch, int16_t& value)
{
  int count = 0;
  byte nibble = 0;

  // Остановиться после 4-го полубайта или нулевого ограничителя.
  for(int i = 0; i < 4 && ch[i] != 0; i++)
  {
    if (AsciiToNibble(ch[i], nibble))
    {
      value <<= 4;
      value |= nibble;
      count++;
    }
    else
    {
      break;  // Остановиться после не шестнадцатеричной цифры.
    }
  }
  return count;
}

bool AsciiToNibble(const char ch, byte& b)
{
  if (ch >= '0' && ch <= '9')
  {
    b = ch & 0x0F;
    return true;
  }
  else if ((ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
  {
    b = (ch & 0x0F) + 9;
    return true;
  }
  return false;
}

Вывод

A,  Ok,  1,  10
Waffle,  Bad
ff9d,  Ok,  4,  -99

HDLC (режим E)

Для HDLC (режим E) просто выполните:

void ReactivePower(byte data[], int dataSize)
{
  uint16_t reacP = (data[dataSize - 5] << 8) | data[dataSize - 4];
  Serial.println(dataSize);
  Serial.println(reacP);
}
. . .
  byte data[] = { 1, 3, 5, 2, 4, 8 };
  ReactivePower(data, sizeof(data));

Вывод

6
773
,