Как человек меняет порядок старших 8 битов значения «int» и оставляет старшие 8 битов одинаковыми? - PullRequest
2 голосов
/ 30 апреля 2019

Мое приложение требует, чтобы я сохранял значение в 16-битном счетчике, но из-за проблем с печатной платой требуется, чтобы младшие 8 бит счетчика были перевернуты (с 01001110 по 01110010).Код пишется на C (GCC), а регистр счетчика имеет тип int (16 бит).Мое приложение использует 8-битный микроконтроллер Atmel ATtiny.Я понимаю, что если я объявлю регистр счетчика типом int, компилятор выделит 2 области памяти.Должен ли я просто извлечь младший байт с помощью маски, затем переставить биты, а затем вставить их обратно с чем-то вроде;

counter = counter & 0x00       clear lower byte value
counter = counter + (register with the reversed 8 bits)   

// Then, Replace lower byte value with new value

Должно ли это работать?Спасибо

Ответы [ 4 ]

5 голосов
/ 01 мая 2019

Ваше слово описание процесса является правильным, но иллюстрация вашего псевдокода неточная и неполная.

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

// Copy counter LSB
uint8_t lsb = (uint8_t)(counter & 0xFFu) ;

// Clear counter LSB
counter &= 0xff00u ;

// Reverse LSB bits and mask into counter LSB
for( uint8_t mask = 0x80u;  
     mask != 0; 
     lsb >>= 1, mask >>= 1 )
{
    counter |= ((lsb & 0x01u) != 0) ? mask : 0 ;
}

Вам также следует использовать типы stdint.h uint16_t и uint8_t дляЭта операция, вместо того, чтобы полагаться на int, имеет какой-либо определенный размер - она ​​сделает код более переносимым и тестируемым в системе, где int не является 16-битным.И, как правило, при выполнении побитовых операций следует использовать типы без знака.

Несколько более быстрый метод, хотя, возможно, требующий немного больше места в ПЗУ, - это использование справочной таблицы.256-байтовая таблица поиска довольно громоздка для генерации и на ATtiny довольно непомерно с точки зрения использования памяти.Скорее, это можно сделать почти так же эффективно, используя 16-байтовый поиск следующим образом:

// Copy counter LSB
uint8_t lsb = (uint8_t)(counter & 0xFFu) ;

// Clear counter LSB
counter &= 0xff00u ;

static const uint8_t lookup[] = { 0x0, 0x8, 0x4, 0xC, 
                                  0x2, 0xA, 0x6, 0xE, 
                                  0x1, 0x9, 0x5, 0xD, 
                                  0x3, 0xB, 0x7, 0xF } ;

counter |= lookup[lsb & 0xf] << 4 | lookup[lsb >> 4] ;

Вы можете даже упаковать таблицу поиска и использовать всего 8 байтов (0x80, 0xC4 и т. Д.):

static const uint8_t lookup[] = { 0x80, 0xC4, 
                                  0xA2, 0xE6,  
                                  0x91, 0xD5,  
                                  0xB3, 0xF7 } ;

uint8_t msnib = ( lsb & 0x01 ) ? lookup[(lsb & 0xf) >> 1] >> 4 : 
                                 lookup[(lsb & 0xf) >> 1] & 0xf ;

uint8_t lsnib = ( lsb & 0x10 ) ? lookup[(lsb & 0xf0) >> 5] >> 4 : 
                                 lookup[(lsb & 0xf0) >> 5] & 0xf ;
counter |= (lsnib | msnib << 4) ;

Но уменьшение размера справочной таблицы вряд ли будет оправдано увеличением размера кода для результирующей дополнительной битовой манипуляции - и это немного «слишком умно» - потребовалось некоторое время, чтобы его получитьright!

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


Сравнительный анализ

Я тестировал каждую реализацию на https://godbolt.org/, установленном на AVR GCC 4.6.4, с использованием трех различных настроек оптимизации.Счетчик команд исключает добавленный код входа / выхода функции, чтобы сделать его компилируемым, и представляет только инструкции, сгенерированные из исходного кода в этом ответе.

|          |  Instruction Count |               |
|Algorithm | No Opt | -O3 | -Os | + Data (bytes)|
|----------|:------:|:---:|:---:|:-------------:|
| Loop     |   38   |  88 |  23 |       0       |
| LookUp16 |   59   |  38 |  37 |      16       |
| LookUp8  |  137   |  65 |  62 |       8       |

Тест мало говорит о времени выполнения, но, если размер кода важен, алгоритм цикла с оптимизацией пространства (-Os), вероятно, является лучшим выбором.

Без сомнения, справочная таблица работает быстрее, независимо от уровня оптимизации, и 16-байтовая справочная таблица с любой из этих оптимизаций может быть разумным балансом.Для -O3 он в целом меньше и быстрее, чем развернутый цикл из 88 команд.Он также имеет явное преимущество в том, что размер кода гораздо менее изменчив с настройками оптимизации, которые могут свести к минимуму неожиданности при переключении между сборками отладки и выпуска.

8-байтовый поиск имеет мало достоинств, возможно, иного, чем то, что он довольноинтересно.

1 голос
/ 03 мая 2019

Вот мой подход:

uint16_t Flipper(uint8_t hi, uint8_t reversed_lo)
{
    uint8_t lo=0;
    if (reversed_lo & 0x01) lo |= 0x80;
    if (reversed_lo & 0x02) lo |= 0x40;
    if (reversed_lo & 0x04) lo |= 0x20;
    if (reversed_lo & 0x08) lo |= 0x10;
    if (reversed_lo & 0x10) lo |= 0x08;
    if (reversed_lo & 0x20) lo |= 0x04;
    if (reversed_lo & 0x40) lo |= 0x02;
    if (reversed_lo & 0x80) lo |= 0x01;
    return (hi<<8) | lo;
}

Мой компилятор выдает 25 инструкций стоимостью 50 байт для этой функции:

;reversed_lo sits in R22
;hi          sits in R21
;lo          goes to R18
000007DF 60.fd                SBRC R22,0        Skip if bit in register cleared 
000007E0 02.c0                RJMP PC+0x0003        Relative jump 
000007E1 20.e0                LDI R18,0x00      Load immediate 
000007E2 01.c0                RJMP PC+0x0002        Relative jump 
000007E3 20.e8                LDI R18,0x80      Load immediate 
000007E4 61.fd                SBRC R22,1        Skip if bit in register cleared 
000007E5 20.64                ORI R18,0x40      Logical OR with immediate 
000007E6 62.fd                SBRC R22,2        Skip if bit in register cleared 
000007E7 20.62                ORI R18,0x20      Logical OR with immediate 
000007E8 63.fd                SBRC R22,3        Skip if bit in register cleared 
000007E9 20.61                ORI R18,0x10      Logical OR with immediate 
000007EA 64.fd                SBRC R22,4        Skip if bit in register cleared 
000007EB 28.60                ORI R18,0x08      Logical OR with immediate 
000007EC 65.fd                SBRC R22,5        Skip if bit in register cleared 
000007ED 24.60                ORI R18,0x04      Logical OR with immediate 
000007EE 66.fd                SBRC R22,6        Skip if bit in register cleared 
000007EF 22.60                ORI R18,0x02      Logical OR with immediate 
000007F0 66.23                TST R22       Test for Zero or Minus 
000007F1 0c.f4                BRGE PC+0x02      Branch if greater or equal, signed 
000007F2 21.60                ORI R18,0x01      Logical OR with immediate 
000007F3 30.e0                LDI R19,0x00      Load immediate 
000007F4 a9.01                MOVW R20,R18      Copy register pair 
000007F5 58.2b                OR R21,R24        Logical OR 
000007F6 ca.01                MOVW R24,R20      Copy register pair 
000007F7 08.95                RET       Subroutine return 
1 голос
/ 30 апреля 2019

У вас есть опечатка:

counter = counter & 0x00       clear lower byte value

должно быть

counter = counter & 0xFF00;

или

counter &= 0xFF00;

очистить младший байт. Вы можете повернуть биты, повернув бит по очереди к другой переменной. Если время имеет решающее значение, вам нужно сделать это в сборке, так как C не имеет оператора поворота, и объект должен быть смоделирован, например,

new_byte = 0;

if (orig_byte & 0x80)
    new_byte |= 0x01;

if (orig_byte & 0x40)
    new_byte |= 0x02;
...

и т.д.. вероятно, один из самых быстрых способов в C. или если вы можете сэкономить 256 байт флэш-памяти, просто используйте таблицу, например,

__flash unsigned char rotated_bytes[] = { 0x00, 0x80, 0x40, 0xC0, 0x20, ... };

new_byte = rotated_byte[orig_byte];

(замените __flash расширенным ключевым словом вашего компилятора, чтобы обозначить «память программы»)

0 голосов
/ 03 мая 2019

Простой способ инвертировать биты в байте - использовать объединения и битовые поля, как показано ниже.

> struct ST_BYTE {
    unsigned char b0    :   1;
    unsigned char b1    :   1;
    unsigned char b2    :   1;
    unsigned char b3    :   1;
    unsigned char b4    :   1;
    unsigned char b5    :   1;
    unsigned char b6    :   1;
    unsigned char b7    :   1;

} ;

union  U_BYTE
{
    struct ST_BYTE byteflag;
    unsigned char charflag;
};
unsigned char Reverse_Bits_in_Byte(U_BYTE local_byte)
{
    U_BYTE Byte_Var2;

    Byte_Var2.byteflag.b0 =local_byte.byteflag.b7;
    Byte_Var2.byteflag.b1 =local_byte.byteflag.b6;
    Byte_Var2.byteflag.b2 =local_byte.byteflag.b5;
    Byte_Var2.byteflag.b3 =local_byte.byteflag.b4;
    Byte_Var2.byteflag.b4 =local_byte.byteflag.b3;
    Byte_Var2.byteflag.b5 =local_byte.byteflag.b2;
    Byte_Var2.byteflag.b6 =local_byte.byteflag.b1;
    Byte_Var2.byteflag.b7 =local_byte.byteflag.b0;

    return (Byte_Var2.charflag);

}
void main()
{

    int i;
    for(i=0;i<8;i++)
    {
        Byte_Var1.charflag = pow(2,i);
        printf("\nBefore Reverse %02X\n", Byte_Var1.charflag);
        Byte_Var1.charflag = Reverse_Bits_in_Byte(Byte_Var1);
        printf("\nAfter Reverse %02X\n", Byte_Var1.charflag);
    }


}

Обратите внимание, что, хотя это просто, не рекомендуется по своим собственным причинам .

Выбор программиста - принять его или нет.

...