Показаны сообщения с ярлыком BCD. Показать все сообщения
Показаны сообщения с ярлыком BCD. Показать все сообщения

среда, 24 августа 2022 г.

Представление системного времени

При вычислении интервалов времении, в POSIX и С11 используется структура timespec (секунды, наносекунды), получаем такую проверку.

ts.tv_nsec+=timeout;
if (ts.tv_nsec>=1000000000){
  ts.tv_nsec  = ts.tv_nsec % 1000000000;
  ts.tv_sec  += ts.tv_nsec / 1000000000;
}

В этом методе есть недостаток. Мы не можем применять такой подход для контроллеров. На архитектуре типа Cortex-M23 нет деления. Эмуляция деления будет вычисляться удивительно неэффективно. Надо исключить операцию деления и операцию взятия остатка от деления.
Идеально было бы исключить и проверку, чтобы код стал линейным.

Я перебирал варианты, как это вообще можно сделать. На ум приходят разные трюки с операциями: сложение с насыщением, с проверкой переполнения, сложение с переносом. Деление на константу можно заменить на умножение и сдвиг, но это опять не сильно эффективная операция.

Есть способ проведения вычислений с двоично-десятичными числами (Binary-coded decimal, BCD), когда при сложении выполняется коррекция операции сложения - добавление константы, чтобы заработать перенос между разрядами десятичного числа. В случае двоично-десятичных чисел к десятичному разряду добавляется дополнение (число 6), если разряд больше 9. Число 6 получается, как инверсия (0x9^0xF) или в нашей математике (16-10). Аналогично мы расчитываем дополнение для миллиарда в двоичном представлении ((1<<30) - 1000000000) или (0x3B9ACA00^0xFFFFFFFF).

Получился такой код, он предполагает использование 64 битного упакованного времени, при этом в малдших 32 битах числа расположены наносекунды, а в старших секунды.

// коррекция сложения интервалов времени
if ((uint32_t)t64 > 999999999UL) t64 += (uint32_t)~999999999UL; 

Полусумматор и входящий перенос

А был ли перенос между разрядами? Сумматор (операция сложения двоичных чисел) состоит из цепочки полусумматоров, в которой каждый последующий разряд использует перенос с предыдущего разряда. Нужно выделить входящий перенос каждого разряда. Рассмотрим как работает аппаратная функция переноса разрядов в полусумматоре.


ci = carry_in -- входящий перенос: 0 или 1
s  = a^b^ci    -- результат сумма разрядов
сo = ci? (a|b) : (a&b); -- исходящий перенос

Нас интересуют входящие переносы (сi) по маске двоично-десятичного числа 0x11111110 при сложении с дополнением (a+b+D). Сложение с дополнением формирует правильную цепочку переносов между десятичными разрядами. При этом коррекция результата сложения требуется, если между десятичными разрядами был перенос.

s1 = a+b; -- формирует правильные результаты в каждом разряде, если переноса не было,
s2 = a+b+D; -- формирует правильную цепочку переносов между разрядами.
s = co? s2: s1; -- коррекция результата сложения

Расчет BСD времени в упакованном формате
struct _BCD_Time {
  unsigned int susec:8;   //сотые
  unsigned int   sec:8;   //секунды
  unsigned int  mins:8;   //минуты
  unsigned int  hour:8;   //часы
}; 

#define BCD_TIME_CORRECT 0x66A6A666UL 
#define BCD_TIME_CARRYIN 0x11111110UL
time_add(a, b) {
  s1 = a + b;
  s2 = a + b + BCD_TIME_CORRECT;  // коррекция BCD для вычисления переносов
  m  =(a ^ b ^ s2) & BCD_TIME_CARRYIN; // из переносов делаем маску
  m  = m - (m>>4); // маска для выделения десятичных разрядов
  return s1 + (m & BCD_TIME_CORRECT); // коррекция сложения
}

Вот в таком виде можно применять к вычислению времени и временных интервалов независимо от архитектуры ядра процессора.