代码之家  ›  专栏  ›  技术社区  ›  Kent

用C++/Arduino将位打包到结构中

  •  26
  • Kent  · 技术社区  · 6 年前

    我有一个结构:

    typedef struct {
      uint8_t month;  //  1..12 [4 bits]
      uint8_t date;   //  1..31 [5 bits]
      uint8_t hour;   // 00..23 [5 bits]
      uint8_t minute; // 00..59 [6 bits]
      uint8_t second; // 00..59 [6 bits]
    } TimeStamp;
    

    但我想把它打包,这样它只消耗四个字节,而不是五个字节。

    有没有一种方法可以通过移位来创建更紧密的结构?

    这似乎不多,但它正在进入一个新的阶段 EEPROM 因此,在4KB的页面中,一个字节的存储量是额外的512字节(我也可以将剩余的六个字节用于其他内容)。

    4 回复  |  直到 5 年前
        1
  •  32
  •   Peter Mortensen icecrime    5 年前

    你在找位域。

    它们看起来像这样:

    typedef struct {
      uint32_t month  : 4;   // 1..12 [4 bits]
      uint32_t date   : 5;   // 1..31 [5 bits]
      uint32_t hour   : 5;   // 00..23 [5 bits]
      uint32_t minute : 6;   // 00..59 [6 bits]
      uint32_t second : 6;   // 00..59 [6 bits]
    } TimeStamp;
    

    根据编译器的不同,为了在没有填充的情况下放入四个字节,成员的大小必须为四个字节(即。 uint32_t )在这种情况下。否则,结构成员将被填充,使其不会在每个字节边界上溢出,如果使用 uint8_t .将此作为一般规则使用应有助于防止编译器差异。

    下面是一个MSDN链接,它深入到了位字段:

    C++ Bit Fields

        2
  •  15
  •   Cubic mgsloan    6 年前

    一般来说,位字段是一种“正确”的方式,但为什么不直接存储年初以来的秒数呢?4个字节足以轻松存储这些数据;事实上,4个字节足以存储1970年到2038年间的秒数。从中获取其他信息是一个简单的练习,只要你知道当前年份(只要你感兴趣的时间范围不超过70年,你可以将其与其他信息一起存储(即使这样,你也可以将时间戳分为68年范围,并为每个范围存储一个偏移量)。

        3
  •  13
  •   Serve Laurijssen    6 年前

    另一种解决方案是将值存储在一个32位变量中,并通过位移位检索单个项。

    uint32_t timestamp = xxxx;
    
    uint8_t month = timestamp & 0x0F;
    uint8_t date = (timestamp & 0x1F0) >> 4;
    uint8_t hour = (timestamp & 0x3E00) >> 9;
    uint8_t minute = (timestamp & 0xFC000) >> 14;
    uint8_t second = (timestamp & 0x3F00000) >> 20;
    
        4
  •  2
  •   supercat    6 年前

    如果你能处理两秒的精度,MS-DOS时间戳格式使用16位来保存日期(1980年为7位,月为4位,日为5位)和16位来保存时间(小时为5位,分钟为6位,秒为5位)。在Arduino这样的处理器上,可能可以编写跨越16位边界分割值的代码,但我认为如果可以避免这种分割(就像MS-DOS接受两秒精度所做的那样),代码将更加高效。

    否则,正如在另一个答案中所指出的,使用32位的秒数,因为一些基本时间通常比试图以“日历格式”跟踪事情更有效。如果你需要做的只是从一个日历格式的日期提前到下一个日历格式的日期,那么这样做的代码可能比在日历日期和线性日期之间转换的代码要简单,但是如果你需要做很多其他事情(甚至从一个日期向后退一步到前一个日期),那么在输入或显示日期时,最好将日期转换为线性格式,否则,只需处理线性秒数。

    如果你选择闰年的3月1日作为基准日期,那么使用线性秒数会更方便。然后,当日期超过1461时,将其从日期中减去,并在年份中加上4(16位比较和减法在Arduino上是有效的,即使在2040年,循环所需的时间也可能比单个16x16除法要短)。如果日期超过364,则减去365并增加年份,最多再尝试两次(如果第三次减去后的日期是365,则保留该日期)。

    需要注意确保所有角盒都能正常工作,但即使是在8位或16位微芯片上,转换也会出人意料地高效。