我有一个结构:
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;
但我想打包它所以它只消耗4个字节而不是5个。
有没有办法将位移位以创建更紧密的结构?
它可能看起来不多,但是它进入了EEPROM,因此在4Kb页面中保存1个字节是额外的512个字节(我可以使用剩余的额外6个位用于其他内容)。
答案 0 :(得分:29)
您正在寻找的是位域。
他们看起来像这样:
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;
根据您的编译器,为了在没有填充的情况下适合4个字节,在这种情况下,成员的大小必须是4个字节(即uint32_t
)。否则,结构成员将填充到每个字节边界不溢出,如果使用uint8_t
,则会产生5字节的结构。将此作为一般规则应有助于防止编译器差异。
这是一个MSDN链接,深入到位域:
答案 1 :(得分:14)
Bitfields通常是一种“正确”的方法,但为什么不从年初开始存储秒数呢? 4个字节足以舒适地存储这些;事实上,4个字节足以存储1970年到2038年之间的秒数。只要您知道当前年份(您可以将其他信息与其他信息一起存储),从中获取其他信息就是一个简单的练习。作为您感兴趣的时间范围不到70年(甚至那时您可以将时间戳分组为68年范围并为每个范围存储偏移量。)
答案 2 :(得分:12)
另一个解决方案是将值存储在一个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;
答案 3 :(得分:2)
如果你可以处理两秒精度,MS-DOS时间戳格式使用16位来保存日期(1980年为7位,月为4,日为5)和16位时间(小时)五分钟,六分钟,五分钟秒。在像Arduino这样的处理器上,有可能编写在16位边界上分割值的代码,但我认为如果你可以避免这样的分裂代码会更有效(就像MS-DOS通过接受两秒钟那样)精度)。
否则,正如在另一个答案中所指出的那样,使用32位秒数,因为一些基准时间通常比尝试跟踪日历格式"中的事物更有效。如果您需要做的就是从一个日历格式日期前进到下一个日历格式日期,那么执行该操作的代码可能比在日历日期和线性日期之间转换的代码更简单,但是如果您需要执行其他任何操作(甚至从日期向后退一步)当你输入或显示日期时,你可能会更好地将日期转换为线性格式,或者只使用线性秒数。
如果您选择闰年3月1日的基线日期,则可以更方便地使用线性秒数。然后当日期超过1461时,从日期中减去该值并在年份中加4(16位比较和减法在Arduino上是有效的,甚至在2040年,循环可能仍然比单个16x16除法花费更少的时间)。如果日期超过364,则减去365并增加年份,并尝试最多两次[如果第三次减法后日期为365,则保留]。
需要注意确保所有极端情况都能正常工作,但即使是在8位或16位微处理器上,转换效率也会非常高。