Обсуждаем контроллеры компании Atmel.
Ответить

Как разбить двухбайтную переменную на 2 байта в СИ?

Пн дек 06, 2021 20:33:43

Требуется разбить двухбайтную переменную (uint16_t) на старший байт и младший. Как это сделать в СИ?

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Пн дек 06, 2021 20:39:07

a0 = (ui16 >> 0) & 0x00ff;
a1 = (ui16 >> 8) & 0x00ff;

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Пн дек 06, 2021 21:05:09

Код:
typedef union
   {
   uint8_t Byte[2];
   uint16_t i;
   float f;
   } TValue;
TValue Temp;
Temp.Byte[0]=0xFF;//uint8_t
Temp.Byte[1]=0xFF;//uint8_t
Temp.i=0xFFFF//uint16_t
Temp.float=1.0;//float

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Пн дек 06, 2021 21:10:46

Код:
union BytByte {
struct {
uint8_t l;
uint8_t h;
} bit;
uint16_t byte;
};

union BytByte myByte;

myByte.byte = 0x1235;

a0 = myByte.bit.l;
a1 = myByte.bit.h;

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Пн дек 06, 2021 23:30:34

Можно еще проще, т.к. всегда "конечность" архитектуры известра:
Код:
uint16_t data[N];
uint8_t *dptr = (uint8_t*) data;
for(int i = 2*N; i > 0; --i)
  do_something_with(*dptr++);

Если остроконечная, то сначала будет L, потом H; если тупоконечная - наоборот.
Если порядок байт нужно поменять, делаем так:
Код:
uint16_t data[N];
uint8_t *dptr = (uint8_t*) data;
for(int i = 0; i < N; ++i){
  do_something_with(dptr[1]);
  do_something_with(dptr[0]);
  dptr += 2;
}

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 01:06:05

Код:
uint16_t in_val = 0xBBAA;

unsigned char byte_1 = (uint8_t) (in_val & 0x00ff);
unsigned char byte_2 = (uint8_t) ((in_val & 0xff00) >> 8);

printf("b1: 0x%X, b2: 0x%X\n", byte_1, byte_2);

Код:
Вывод:
        b1: 0xAA, b2: 0xBB

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 10:11:30

Код:
uint16_t in_val = 0xBBAA;

unsigned char byte_1 = (uint8_t) (in_val & 0x00ff);
unsigned char byte_2 = (uint8_t) ((in_val & 0xff00) >> 8);

printf("b1: 0x%X, b2: 0x%X\n", byte_1, byte_2);

Зачем "& 0x00ff" и "& 0xff00" ?
В первом случае, при присвоении к 8-ми битной переменной, старший байт сам улетит.
Во втором случае, он улетит при сдвиге вправо.

Лишние букафки в исходнике.

Добавлено after 8 minutes 21 second:
Код:
uint16_t  uint16_val = 0x0123;

uint8_t v1 = uint16_val;
uint8_t v2 = uint16_val >> 8;

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 14:09:34

И конечно эффективность кода


Добавлено after 3 hours 54 minutes 29 seconds:
Что-то все так промолчали?

Давайте усложним, выдернем не только байты, но и полубайты, и что характерно одной конструкцией

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 14:49:53

В последнем проекте у меня много вот такого "ужаса", потому что оперативку и EEPROM нужно было экономить. Особенно EEPROM.


Битовые поля в union.

Короче, есть два варианта:
Код:
// Номер раз
uint16_t foo = 0xAA55;

uint8_t low = foo & 0x00FF; // low = 0x55
uint8_t high = (foo & 0xFF00) >> 8; // high = 0xAA    


Код:
// Вариант номер двас
typedef union _FooType
{
    struct
    
{
        uint8_t low;
        uint8_t high;
    };
    uint16_t foo;
}
FooType;

FooType foo;
foo.foo = 0xAA55;

//foo.low = 0x55;
//foo.high = 0xAA;


Суть второго варианта в том, что структура упакует свои поля в одно 16-битное слово, которое уже отражается в поле foo объединения FooType.

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 15:01:14

Код:
uint16_t in_val = 0xBBAA;

unsigned char byte_1 = (uint8_t) (in_val & 0x00ff);
unsigned char byte_2 = (uint8_t) ((in_val & 0xff00) >> 8);

printf("b1: 0x%X, b2: 0x%X\n", byte_1, byte_2);

Зачем "& 0x00ff" и "& 0xff00" ?
В первом случае, при присвоении к 8-ми битной переменной, старший байт сам улетит.
Во втором случае, он улетит при сдвиге вправо.

Лишние букафки в исходнике.

Добавлено after 8 minutes 21 second:
Код:
uint16_t  uint16_val = 0x0123;

uint8_t v1 = uint16_val;
uint8_t v2 = uint16_val >> 8;


"0xff00" в образовательных целях, да это пример аналогичный

Код:
uint8_t v1 = (uint8_t) uint16_val; // усекаем явно - чтоб компилятор не ругался
uint8_t v2 = (uint8_t) (uint16_val >> 8);
 
Причём машинный код генерируется (gcc) идентичный.

ПС. Код (усечение + битовый сдвиг) на одну (union) или две (struct *) машинных команд меньше. (без оптимизации)

Код:
typedef union 
{
    struct  
    
{
        uint8_t b1;
        uint8_t b2;
    };
    uint16_t dwaBytes;
}
 DwaBytes_t;

DwaBytes_t dwaByte = { .dwaBytes = 0xBBAA };
 

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 20:29:07

А что будет если от переменной unsigned равной нулю, отнять какое-то число? Например, 25? Unsigned переменная ведь не может иметь знака -...
В онлайн симуляторе СИ биты инвертируются и переменная приобретает неприлично большое значение. В CVAVR будет такое же поведение?

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 20:48:54

Вы как-то все радикально к этому по дошли, давайте что нибудь по проще, к примеру так
Код:
uint16_t temp;
uint8_t data[2];

// туда
temp = *((uint16_t*)&data[0]);

// обратно
*((uint16_t*)&data[0]) = temp;

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 21:44:18

Dimon456, зачем ты завел две переменные для одних и тех же данных? Вот так же:
Код:
uint16_t temp;
uint8_t *data = (uint8_t*) &temp;

data[0] = 0xad; data[1] = 0xde; printf("16bit: 0x%x\n", temp);  // Little-endian 8 -> 16bit

temp = 0xaabb; printf("lo: 0x%s, hi: 0x%x\n", data[0], data[1]); // 16bit -> little-endian 8

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 22:17:52

DX168B, структуры не требовательны к ресурсам? AVR'у будет не тяжело? :)

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Вт дек 07, 2021 23:39:52

А что будет если от переменной unsigned равной нулю, отнять какое-то число? Например, 25? Unsigned переменная ведь не может иметь знака -...

Си не контролирует выходы за пределы диапазона.
И все переполнения и прочее - на совести программиста.
Пусть будет unsigned char n = 0
тогда
n -= 25;
приведет к тому, что из 0 вычтется 25.
Результат будет -25.
-25 в знаковом представлении записывается как 0xE7.
Этот результат и запишется в нашу переменную n.
Но поскольку она беззнаковая - значение будет интерпретироваться как 231.
Вас же не удивит факт, если к байтовой переменной, в которой записано 240 (0xF0) прибавить 20 (0x14), то получится не 260, а 4 (0x04) ?
0xF0 + 0x14 = 0x104. Но 1 теряется, ибо переменная байтовая. Остается 0x04.
То же самое при вычитании работает.

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Ср дек 08, 2021 09:14:00

DX168B, структуры не требовательны к ресурсам? AVR'у будет не тяжело? :)

Нет. В Си структуры превращаются в обычные массивы. Структуры с битовыми полями у меня не потребляли больше обычных операций с масками и сдвигами.

Пример из реального проекта:
Код:
//********************************************
// Ужасная структура

// Outputs config
#pragma pack(1)
typedef struct COutsCfg
{
    union
    
{
        struct
        
{
            uint16_t out_0_cfg : 2;
            uint16_t out_1_cfg : 2;
            uint16_t out_2_cfg : 2;
            uint16_t out_3_cfg : 2;
            uint16_t out_4_cfg : 2;
            uint16_t out_5_cfg : 2;
            uint16_t out_6_cfg : 2;
            uint16_t out_7_cfg : 2;
        };
        uint16_t config16;
    };
    uint8_t beforeResetTimeout;
    uint8_t resetTimeout;
    uint8_t afterResetTimeout;
}
OutsCfg;
#pragma pack()

//********************************************
// Код 

// Reset outputs config
        OutsCfg outCfg;
        outCfg.config16 = OUTPUTS_DEFAULT_CONFIG;
        outCfg.beforeResetTimeout = OUTPUTS_TIMEOUT_BEFORE_RESET;
        outCfg.resetTimeout = OUTPUTS_TIMEOUT_RESET;
        outCfg.afterResetTimeout = OUTPUTS_TIMEOUT_AFTER_RESET;

        eeprom_write_block(&outCfg, (void*)(CONFIG_OUTS_OFFSET), sizeof(OutsCfg));

//********************************************
// Дизасм
        // Reset outputs config
        OutsCfg outCfg;
        outCfg.config16 = OUTPUTS_DEFAULT_CONFIG;
    35d4:    8a ea           ldi    r24, 0xAA    ; 170
    35d6
:    9a e0           ldi    r25, 0x0A    ; 10
    35d8
:    9a 83           std    Y+2, r25    ; 0x02
    35da
:    89 83           std    Y+1, r24    ; 0x01
E
:\PlatformIO\DailyTimer/src/Config.cpp:38
        outCfg
.beforeResetTimeout = OUTPUTS_TIMEOUT_BEFORE_RESET;
    35dc:    8e e1           ldi    r24, 0x1E    ; 30
    35de
:    8b 83           std    Y+3, r24    ; 0x03
E
:\PlatformIO\DailyTimer/src/Config.cpp:39
        outCfg
.resetTimeout = OUTPUTS_TIMEOUT_RESET;
    35e0:    12 e0           ldi    r17, 0x02    ; 2
    35e2
:    1c 83           std    Y+4, r17    ; 0x04
E
:\PlatformIO\DailyTimer/src/Config.cpp:40
        outCfg
.afterResetTimeout = OUTPUTS_TIMEOUT_AFTER_RESET;
    35e4:    85 e0           ldi    r24, 0x05    ; 5
    35e6
:    8d 83           std    Y+5, r24    ; 0x05
E
:\PlatformIO\DailyTimer/src/Config.cpp:42
        eeprom_write_block
(&outCfg, (void*)(CONFIG_OUTS_OFFSET), sizeof(OutsCfg));
    35e8:    45 e0           ldi    r20, 0x05    ; 5
    35ea
:    50 e0           ldi    r21, 0x00    ; 0
    35ec
:    68 e5           ldi    r22, 0x58    ; 88
    35ee
:    71 e0           ldi    r23, 0x01    ; 1
    35f0
:    ce 01           movw    r24, r28
    35f2
:    01 96           adiw    r24, 0x01    ; 1
    35f4
:    0e 94 d2 1e     call    0x3da4    ; 0x3da4 <eeprom_write_block>


Как видно, это банальная запись в оперативку и вызов процедуры записи в EEPROM.
Этот дизасм - результат компиляции кода, написанного на C++ ардуиновским компилятором и без оптимизации.

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Ср дек 08, 2021 10:38:09

DX168B, и ООП под силу AVR?

Добавлено after 1 hour 14 minutes 34 seconds:
GoldenAndy, еще интересен момент...

Код:
uint8_t var[3] = {245, 235, 255};
uint8_t sum;

sum = (var[0] + var[1] + var[2]) / 3;


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

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Ср дек 08, 2021 10:40:17

Да. Если сильно не увлекаться полиморфизмом объектов и вместо прямого полиморфизма использовать полиморфизм через шаблоны классов.

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Ср дек 08, 2021 10:54:29

Kalisnik, ООП АВРкам не под силу. Они умеют только машинные коды выполнять. А как сформированы эти коды - вопрос к компилятору. (И к прокладке)
А ООП - это к компилятору. Умеет компилятор ООП - будет работать на АВР, не умеет - не будет.
Ардуина - это плюсы, соответственно, все прелести ООП доступны. Но нужно уметь ими красиво пользоваться, что б не выжрать все ресурсы.

ЗЫ. Для МК предпочитаю голый Си.

Re: Как разбить двухбайтную переменную на 2 байта в СИ?

Ср дек 08, 2021 11:09:16

еще интересен момент...
тут лучше написать так:
Код:
uint8_t var[3] = {245, 235, 255};
uint8_t sum;

sum = (uint16_t) (var[0] + var[1] + var[2]) / 3;

тогда компилятор для промежуточных вычислений зарезервирует 16 бит переменную (2 регистра), а результат запишет в 8 битную выходную переменную.
Ответить