我可以将unit16_t转换为“uint12_t”吗?

Can I convert an unit16_t to an "uint12_t"?

提问人:98coolmen 提问时间:8/31/2023 更新时间:8/31/2023 访问量:137

问:

我遇到了一个问题,我需要将两个uint16_t数字压缩成一个 2 字节的空间。 我知道我丢失了信息,但高于 12 位的数字与我无关。 前 12 位应为一个数字,后 12 位应为中数。 CAN报文需要输出。 有没有人知道如何在 C++ 中解决这个问题?

这是我第一次尝试,但它导致了两个字节大小的数字,正如预期的那样。

  byte OUT[2];

  union OUT_byte {
    byte bufferOUT[2];
    uint16_t OUT_array_int;
  } OUTb;

  OUTb.OUT_array_int = (double)(round(INPUT)); // casting from an double originaly

  for (int i = 0; i < sizeof(OUTb.bufferOUT); i++) {
    OUT[i] = OUTb.bufferOUT[i];
  }

这给了我两个字节和一个包含我的信息的数组。 现在我需要两个数字压缩到 12 位,3 个字节。

C++ 转换 整数 CAN 总线

评论

4赞 Some programmer dude 8/31/2023
你们有什么编译器和硬件支持 12 位整数?我怀疑你有这个。相反,读取完整的 16 位并使用掩码(按位和 )提取所需的 12 位。&
1赞 Some programmer dude 8/31/2023
第二点,应该怎么做?该函数返回一个值,因此无需将其转换为 。如果没有其他证据,我怀疑它是浮点格式,所以将其舍入为浮点不会那么好。此外,在 C++ 中,您应该考虑任何需要进行 C 样式强制转换(就像您在那里所做的那样)作为您可能做错了什么的指标。(double)(round(INPUT))std::rounddoubledoubleINPUT
1赞 Yksisarvinen 8/31/2023
另一方面,像你一样通过联合类型双关语是 C++ 中的未定义行为。您只能访问上次写入的工会成员,而不能访问其他成员。
2赞 François Andrieux 8/31/2023
将两个 16 位整数截断为每个 12 位,仍然需要存储 24 位信息。您希望将其存储在 2 个字节中,这通常只有 16 位存储。您的平台是否具有至少 12 位宽的字节?
1赞 Toby Speight 8/31/2023
@François这正是它让我感到惊讶的原因。

答:

0赞 Mestkon 8/31/2023 #1

您必须执行一些位操作才能将 2 个值打包成 24 位数据。std::uint16_t

struct input
{
    std::uint16_t a, b;
}

struct output
{
    std::uint8_t array[3];
}

output pack2into3(input vals)
{
    // Assume bit 0 is the most significant bit
    std::uint8_t first = (vals.a >> 4) & 0xFF; // Shift down 4 and mask in the lowest 8 bits of a (bit 4-12).
    std::uint8_t second_high = vals.a & 0x0F; // Mask in the lowest 4 bits of a (bit 12-16).
    std::uint8_t second low = (vals.b >> 8) & 0x0F; // Shift down 8 and mask the lowest 4 bits of b (bit 4-8).
    std::uint8_t second = (second_high << 4) | second_low; // Or together the bits from a and b which makes up the second octet.
    std::uint8_t third = vals.b & 0xFF; // Mask in the lowest 8 bits of b (bit 8-16).
    return { { first, second, third } };
}

评论

1赞 Lundin 8/31/2023
int从不用于嵌入式系统,也从未用于任何处理按位算术的系统。始终使用无符号类型,并且强烈建议使用类型。stdint.h
0赞 Mestkon 8/31/2023
@Lundin 这是正确的,但更容易输入:)int
1赞 Lundin 8/31/2023
编程就是输入大量文本。对此有疑问的人应该考虑不同的职业。就我个人而言,我可以设法在一秒钟左右的时间内输入uint32_t......对我来说够快了,有什么急的。或者,如果您真的不喜欢输入大量毫无意义的文本,也许可以放弃并在文件顶部使用......std::uint8_tusing std::uint8_t
2赞 Lundin 8/31/2023 #2

这通常是通过使用足够大的 8 位倍数的类型来解决的。因此,在您的情况下,您将使用并忽略 4 个顶部位。uint16_t

但是,如果您确实需要一个由多个 12 位相邻块组成的位字段,则可以改用数组。示例(假设大端序):uint8_t

uint8_t can_data[8] =
{
    (data0 & 0xFF0) >> 4, 
    (data0 & 0x00F) << 4 | (data1 & 0xF00) >> 8, 
    (data1 & 0x0FF), 
    ...
};

那是:

  • data0 位 11-4 到第 1 字节位 7-0
  • data0 位 3-0 到第 2 字节位 7-4
  • data1 位 11-8 到第 2 字节位 3-0
  • 数据 1 位 7-0 到第 3 字节位 7-0

...等等。正如你所知道的,这是一个非常麻烦的格式。这是一种空间优化,以牺牲可读性和性能为代价。因此,除非万不得已,否则我们通常会避免使用这种格式。实际上,我自己在现实世界中使用非常相似的东西,当时我设计了一个包含多个 12 位模拟值的紧凑型无线协议。

在CAN的世界里,CANopen决定将所有模拟值设为16位,尽管实际上没有多少设备使用这种高分辨率。不过,它使他们免于像上面的位包装练习那样大惊小怪,因此这可能是对行业标准的正确呼吁。

评论

0赞 Mestkon 8/31/2023
这是一个比我的更深入的答案。好答案。
0赞 98coolmen 9/1/2023
谢谢。可悲的是,我需要编程的CAN报文布局需要两个12位整数,所以你帮了我很多。但仍然存在一些问题:它还没有 100% 起作用。为什么代码的 3.line 中有一个位掩码?移动数据时,4 位无论如何都会丢弃其余部分,为什么位掩码比uint16_t本身长,或者我错过了什么?
0赞 98coolmen 9/1/2023
@Lundin第 4 行中的 data0 也不应该向左移动 12 位吗?
0赞 Lundin 9/1/2023
@98coolmen 位掩码是 16 位中的 12 位,用于选择行所针对的 12 位中的哪一部分。通过使用 12 位位掩码,我们可以获得更多自记录代码(否则可以用很多不同的方式编写)。每行的移位是将数据移动到 8 位字节内的正确位置,仅此而已。
0赞 98coolmen 9/1/2023
@Lundin啊,我想我现在明白了。但遗憾的是仍然没有运气。信息仍然是错误的。这里可能是 dbc 文件中的相关部分。data0 : 28|12@1+ (0.5,-400) [-400|1647.5] "mbar" Vector__XXX data1 : 16|12@1+ (0.025,0) [0|102.375] "%" Vector__XXX