【问题标题】:Is there an easy way to convert a number to hexidecimal ASCII chars in C?有没有一种简单的方法可以将数字转换为 C 中的十六进制 ASCII 字符?
【发布时间】:2010-09-09 14:02:04
【问题描述】:

我正在为嵌入式设备编写 C 固件程序。我想通过串口发送一组十六进制字符值。有没有一种简单的方法可以将值转换为 ASCII 十六进制?

例如,如果数组包含 0xFF,我想发送 ASCII 字符串“FF”,或者对于 0x3B 的十六进制值,我想发送“3B”。

这通常是如何完成的?

我已经有了串行发送功能,所以我可以这样做......

char msg[] = "Send this message";
SendString(msg);

SendString 函数为传入数组中的每个元素调用此函数:

// This function sends out a single character over the UART
int SendU( int c)
{
    while(U1STAbits.UTXBF);
    U1TXREG = c;
    return c;
}

我正在寻找一个可以让我这样做的功能......

char HexArray[5] = {0x4D, 0xFF, 0xE3, 0xAA, 0xC4};
SendHexArray(HexArray);

//Output "4D, FF, E3, AA, C4"

【问题讨论】:

  • 你需要传递一个计数给SendHexArray()或者有一个终止符,否则函数无法知道数组有多长(或者,当然,数组总是 5 个元素长)。

标签: c embedded ascii hex


【解决方案1】:

使用sprintf 将数字写入字符串,然后使用现有的SendString 函数通过UART 发送该数字。当然,您可以一次执行一个数字:

char num_str[3];
sprintf( num_str, "%02X", 0xFF );
SendString(num_str);

%02Xformat string 用于 printf 系列函数,它表示用 0 填充元素直到宽度为 2,并将元素格式化为十六进制数。

02 部分确保当您想要打印 0x0F 时,您会在输出流中获得 0F 而不仅仅是 F。如果你使用小写的x,你会得到小写字符(例如ff而不是FF)。

【讨论】:

  • 你能解释一下 %02X 部分吗?
  • %02X 使用大写字母 (X) 以十六进制表示形式写入 int 值,至少使用 2 个空格并在左侧填充“0”。
  • 这很好,但是如果这是对任何一个庞大的 stdio 函数系列的第一次调用,请检查链接映射,看看是否还有空间用于 ROM 中的应用程序的其余部分。即使您现在正在使用它们,请检查您的链接地图,看看更换它们是否可以为您节省下个月的新功能空间。
  • 我同意RBerteig。根据您对嵌入式的含义, sprintf 就像拿核弹来破解坚果一样。 sprintf 可能会在可执行映像的大小上增加 10k 字节。
  • 可能,但可能不会。特别是如果您已经在使用标准格式的 I/O;您可能已经承受了开销的打击。更令人担忧的往往是格式化 I/O 操作所需的堆栈空间量。大约 4K 并不罕见。
【解决方案2】:

汇编语言时代 8 位 micro 的经典技巧是将 nybble 转换为两个段。从0到9的值和从10到15的值。然后简单的算术保存16字节的查找表。

void SendDigit(int c) {
    c &= 0x0f;
    c += (c <= 9) ? '0' : 'A'-10;
    SendU(c);
}

void SendArray(const unsigned char *msg, size_t len) {
    while (len--) {
        unsigned char c = *msg++;
        SendDigit(c>>4);
        SendDigit(c);
    }
}

有几个旁注是按顺序排列的。首先,这是有效的,因为数字和字母在 ASCII 中都是连续的。如果你不幸想要EBCDIC,这仍然有效,因为字母“A”到“F”在那里也是连续的(但字母表的其余部分被分成三个跨度)。

其次,我更改了SendArray() 的签名。我的版本小心地使缓冲区无符号,这在计划将字节提升为更大的整数类型以进行算术时通常更安全。如果它们被签名,那么像 nibble[(*msg)&gt;&gt;4] 这样的代码可能会尝试在数组中使用负索引,结果将毫无用处。

最后,我添加了一个长度参数。对于一般的二进制转储,您可能没有任何有意义的字节值可用作结束标记。为此,计数更有效。

编辑: 修复了一个错误:对于超过 10 位的数字,算术是 c + 'A' - 10,而不是 c + 'A'。感谢 Brooks Moses 抓住了这一点。

【讨论】:

  • 这是我选择的解决方案,假设没有 sprintf +1
  • 我实际上使用的是 16 位 micro(PIC24F 系列),这会改变您的决定吗?
  • 实际上,你需要的是'A' - 10,而不是'A'
  • @Brooks,这就是为什么测试用例很重要,尤其是对于琐碎的代码!
  • @Jordan S,这个或数字表查找都是不错的选择。假设您没有打错表格,查找更容易正确,并且可能对代码审查员(五年后可能是您)更清楚。否则,编译两者,并根据您所面临的限制对大小或执行时间进行基准测试。
【解决方案3】:

我将从数组查找开始。

char *asciihex[] = {
    "00", "01", "02", ..., "0F",
    "10", "11", "12", ..., "1F",
    ...,
    "F0", "F1", "F2", ..., "FF"
};

然后简单地查一下...

SendString(asciihex[val]);

编辑

结合 Dysaster 的 nibbles 理念:

void SendString(const char *msg) {
    static const char nibble[] = {'0', '1', '2', ..., 'F'};
    while (*msg) {
        /* cast to unsigned char before bit operations (thanks RBerteig) */
        SendU(nibble[(((unsigned char)*msg)&0xf0)>>4]); /* mask 4 top bits too, in case CHAR_BIT > 8 */
        SendU(nibble[((unsigned char)*msg)&0x0f]);
        msg++;
    }
}

【讨论】:

  • 我不知道为什么这被否决了。 +1。这将是我接受的答案。它非常直接且超级快。此外,该表可以在廉价闪存 ROM 的一个小的特殊分区中实现
  • 或者你可以通过啃咬来节省空间。为 0-9,a-f 创建一个数组,然后进行两次查找:(val>>4) 和 (val&0xf)。为您节省 200 多个字节。 :)
  • 也许... 是投反对票的原因。我喜欢小食的想法。
  • 使用 Dysaster 的 nibbles 方法,您甚至可以将其转换为 switch 语句。然后编译器将决定是否应该使用表查找、条件存储或条件跳转来实现。通常编译器比程序员的第一个优化方法更好。
  • 你可能想要使 nibble[] "const" 以便它可以被 ROM 化。我相信在当前的实现中,16 字节序列将从 ROM 复制到 C 运行时启动代码中的读/写(通常是 .data)空间。
【解决方案4】:

sprintf 会做到的。

sprintf (dest, "%X", src);

【讨论】:

  • %X 将输出可变数量的字符,因为它会抑制前导零。您希望 %02X 符合 OP 的规范。
  • @RBerteig,我在问题中看不到任何明确需要 2 字节字符串的内容。 (或 3 个带有空终止符。)
【解决方案5】:

如果你的编译器支持,你可以使用itoa。否则,我会在 Nathan's & Mark 的答案中使用 sprintf 。如果支持 itoa 并且性能存在问题,请尝试一些测试以确定哪个更快(过去的经验让我认为 itoa 会更快,但 YMMV)。

【讨论】:

  • 我想说“itoa 不会以 16 为基数输出,例如十六进制。”但我发现这些天确实如此……我一定是老了。我敢肯定它不习惯。
猜你喜欢
  • 2017-03-11
  • 2020-01-19
  • 1970-01-01
  • 2011-11-21
  • 2010-09-17
  • 2017-09-11
  • 2013-12-22
  • 1970-01-01
  • 2021-03-04
相关资源
最近更新 更多