C ++: быстрая конкатенация нерегулярных двоичных кодовых слов - PullRequest
1 голос
/ 05 апреля 2020

Мне нужно передавать последовательные данные из аппаратного модуля SPI. Этот модуль SPI принимает 16-разрядные слова и сначала передает их MSB.

Для подачи в модуль SPI я подготовил массив из 16-разрядных целых чисел.

Вот сложная часть: данные то, что я собираюсь передавать из модуля SPI, не состоит из слов шириной 16 бит. Вместо этого необходимо вывести 588 битов в 68 кодовых словах следующим образом:

Первое слово, codeIndex = 0, имеет ширину 24 бита. Каждое другое слово (codeIndex = x для всех нечетных чисел x) имеет ширину 3 бита. Все Остальные кодовые слова (codeIndex = x для всех ненулевых четных чисел) имеют ширину 14 битов

Интерфейс SPI должен последовательно выводить все эти кодовые слова без добавления или пропуска битов. Это означает, что первое 16-разрядное слово, которое я отправляю интерфейсу SPI, должно быть самыми значимыми 16 битами 24-битного codeIndex = 0, а следующее 16-битное слово будет оставшимися восемью битами codeIndex = 0, за которыми следует всеми тремя битами codeIndex = 1, за которыми следуют пять старших значащих битов codeIndex = 2, и следующее 16-битное слово, отправляемое на интерфейс SPI, завершит sh off codeIndex = 2 и т. д.

Я придумал подход для этого, который интенсивно использует двоичные операторы ИЛИ и побитовые сдвиги, но, похоже, должен быть более быстрый способ сделать это, манипулируя указателями или чем-то подобным.

Мой подход использует справочную таблицу. Каждая строка справочной таблицы определяет 16-битное слово с точки зрения количества нерегулярных кодовых слов в нем, каких кодовых слов в нем и побитовых смещений, которые должны быть применены к этим кодовым словам, прежде чем их будут OR в 16. слово. Поскольку 588 бит заполняют ровно 36,75 16-битных слов, я сделал эту таблицу длиной четыре итерации, чтобы идеально заполнить 147 16-битных слов. Из приведенного ниже кода видно, что я развернул несколько циклов в программе, чтобы попытаться ускорить его.

void fillBuffer(volatile uint16_t *buf) {
  #define THIS_CODE_INDEX *codeIndex
  #define THIS_CODE_OFFSET *codeOffset

  //is the codeword merging bits? 
  #define CODE_SELECT_ITERATION \
  if ( (THIS_CODE_INDEX & 1) == 1) { \
    codeWord = mBitPattern[frame.mBits[(THIS_CODE_INDEX-1)>>1]];\
  }\
  /*is it a normal data word?*/\
  else if (THIS_CODE_INDEX > 2) {\
    codeWord = efmCode[frame.data[(THIS_CODE_INDEX>>1)-1]];\
  }\
  /*is it the sync word?*/\
  else if (THIS_CODE_INDEX == 0) {\
    codeWord = 0b100000000001000000000010;\
  }\
  /*it must be the control bits*/\
  else {\
    if (frameIndex >= 2) { /*if this is the third frame or beyond,*/\
      codeWord = efmCode[frame.data[(THIS_CODE_INDEX>>1)-1]];\
    }\
    else if (frameIndex==0) {  /*if it's the first frame,*/\
      codeWord = 0b00100000000001; /*use s1*/\
    }\
    else {  /*if it's the second frame,*/\
      codeWord = 0b00000000010010; /*use s2*/\
    }\
  }\

  #define CODE_OUTPUT_ITERATION(n) \
  if (THIS_CODE_OFFSET < 0) buf[n] |= ((uint16_t) (codeWord >> (THIS_CODE_OFFSET*-1)));\
  buf[n] |= ((uint16_t) (codeWord << THIS_CODE_OFFSET));

  #define THIS_CODE_COUNT wordCount

  #define FRAME_ITERATION(n) \
    int8_t wordCount = decompTable[n][0];\
    buf[n]=0;\
    \
    /*for each codeword that makes up this 16 bit frame:*/\
    codeIndex = &decompTable[n][1];\
    codeOffset = &decompTable[n][2];\
    \
    {\
      CODE_SELECT_ITERATION\
      CODE_OUTPUT_ITERATION(n)\
      \
      if (THIS_CODE_COUNT > 1){\
        codeIndex+=2;\
        codeOffset+=2;\
        \
        CODE_SELECT_ITERATION\
        CODE_OUTPUT_ITERATION(n)\
        \
        if (THIS_CODE_COUNT > 2) {\
          codeIndex+=2;\
          codeOffset+=2;\
          \
          CODE_SELECT_ITERATION\
          CODE_OUTPUT_ITERATION(n)\
        }\
      }\
    }\



  uint32_t codeWord;

  const int8_t *codeIndex = &decompTable[0][1];
  const int8_t *codeOffset = &decompTable[0][2];


  for (uint8_t i =  0; i <  37; i++) {
    FRAME_ITERATION(i)
  }

  frameIndex++;

  for (uint8_t i = 37; i <  74; i++) {
    FRAME_ITERATION(i)
  }

  frameIndex++;

  for (uint8_t i = 74; i < 111; i++) {
    FRAME_ITERATION(i)
  }

  frameIndex++;

  for (uint8_t i =111; i < 147; i++) {
    FRAME_ITERATION(i)
  }
}

Надеюсь, это не так уж и грязно.

Кажется, это так тип проблемы возник бы где-то, прежде чем я дошел до этого, хотя. Есть ли более быстрый способ выполнить этот расчет?

1 Ответ

1 голос
/ 05 апреля 2020

Вы спрашиваете о скорости выполнения (не о скорости программирования). Я предполагаю, что это 16-битный контроллер / процессор?

У нас есть 4 * 68 кодовых слов из памяти, которые мы должны преобразовать в 147 16-битных слов для SPI.

Самый быстрый подход выглядит следующим образом:

  • чтение кодового слова
  • опция: сдвиг кодового слова влево, ИЛИ
  • опция: запись результата, увеличение указателя, SET 0
  • опц .: сдвигать кодовое слово вправо, SET

По сравнению с вашим текущим решением, поскольку вы уже развернули циклы, я бы не считывал decompTables из памяти, а жестко кодировал их в программу. Вы можете создать один макрос, который добавляет одно кодовое слово и дает все соответствующие значения и действия в качестве имен и параметров макросов. Затем вызовите этот макрос 272 раза. Например:

#define ADDCW16_SHL(shl) \
   cw = *ptr_cw; \
   ptr_cw++; \
   temp |= cw << shl;

#define ADDCW16_WRITE() \
   cw = *ptr_cw; \
   ptr_cw++; \
   temp |= cw;
   *ptr_spi = temp; \
   ptr_spi++; \
   temp = 0;

#define ADDCW16_SHR_WRITE_SHL(shr, shl) \
   cw = *ptr_cw; \
   ptr_cw++; \
   temp |= cw >> shr;
   *ptr_spi = temp; \
   ptr_spi++; \
   temp = cw << shl;

uint16_t cw;
uint16_t temp;
ptr_cw = &codewords[0];
ptr_spi = &spibuf[0];

ADDCW16_WRITE() // first 16 bits of 24 bit codeword 1
ADDCW16_SHL(8) // second 8 bits of 24 bit codeword 1
ADDCW16_SHL(5) // 3 bit codeword 2
ADDCW16_SHR_WRITE_SHL(9, 7) // 14 bit codeword 3 (split 5 bit / 9 bit)
ADDCW16_SHL(4) // 3 bit codeword 4
ADDCW16_SHR_WRITE_SHL(10, 6) // 14 bit codeword 5 (split 4 bit / 10 bit)
// ...

Вы бы разбили первое 24-битное кодовое слово на два кодовых слова (два вызова ADDCW16). Другие кодовые слова имеют ровно один макрос-вызов на кодовое слово. Вызовы ADDCW16 могут быть сгенерированы небольшой компьютерной программой.

В качестве альтернативы вы можете использовать библиотеку заголовков буста (это только заголовки, и вы просто используете эту небольшую часть буста). Он обеспечивает циклы и арифметические операции c внутри препроцессора (с использованием существующего препроцессора и очень умных манипуляций с символами).

...