пакетные биты в структуре c++/arduino

У меня есть структура:

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, поэтому 1 байт сохраняется дополнительно 512 байт на странице 4 Кб (и я могу использовать эти дополнительные 6 бит, оставшиеся для чего-то еще).

Ответ 1

То, что вы ищете, это битподы.

Они выглядят так:

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) в этом случае. В противном случае члены структуры будут заполнены, чтобы не переполняться на каждой границе байта, что приведет к созданию структуры из 5 байтов при использовании uint8_t. Использование этого как общего правила должно помочь предотвратить несоответствия компилятора.

Здесь ссылка MSDN, которая немного углубляется в битовые поля:

C++ Бит-поля

Ответ 2

Битфы - один из "правильных" способов сделать это в целом, но почему бы просто не хранить секунды с начала года? 4 байта достаточно, чтобы удобно хранить их; на самом деле, достаточно 4 байтов для хранения секунд между 1970 и 2038 годами. Получение другой информации из нее - это простое упражнение, если вы знаете текущий год (который вы можете хранить вместе с остальной информацией как долго поскольку диапазон времени, в который вы заинтересованы, занимает менее 70 лет (и даже тогда вы можете просто группировать временные метки в диапазоны на 68 лет и хранить смещение для каждого диапазона).

Ответ 3

Другим решением является сохранение значений в одной 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

Если вы можете справиться с двухсекундной точностью, формат временной отметки MS-DOS использовал 16 бит для хранения даты (год-1980 как 7 бит, месяц как 4, день 5) и 16 бит за время (час пять, минут шесть, секунд пять). На процессоре, таком как Arduino, может быть возможно написать код, который разделяет значения по 16-разрядной границе, но я думаю, что код будет более эффективным, если вы сможете избежать такого разделения (как MS-DOS, приняв двухсекундную точность).

В противном случае, как было отмечено в другом ответе, использование 32-битного количества секунд, так как некоторое базовое время часто будет более эффективным, чем попытка отслеживать вещи в "формате календаря". Если все, что вам нужно сделать, это перейти от одной даты в формате календаря к следующему, код для этого может быть проще, чем код для преобразования между датами календаря и линейными датами, но если вам нужно сделать что-нибудь еще (даже шаг назад от даты к предыдущей), вам, вероятно, будет лучше конвертировать даты в/из линейного формата, когда они будут введены или отображены, и в противном случае просто работать с линейными числами секунд.

Работа с линейными номерами секунд может быть более удобной, если вы выберете в качестве базовой даты 1 марта високосного года. Затем, в то время как дата превышает 1461, вычтите из даты и добавьте 4 к году (16-битное сравнение и вычитание эффективны на Arduino, и даже в 2040 году цикл может по-прежнему занимать меньше времени, чем одно разделение 16x16). Если дата превышает 364, вычтите 365 и увеличьте год, и попробуйте сделать это до двух раз [если дата равна 365 после третьего вычитания, оставьте ее].

Некоторая осторожность необходима, чтобы гарантировать, что все угловые случаи работают правильно, но даже на небольшом 8-битном или 16-битном микро, конверсии могут быть на удивление эффективными.