August 18, 2020

Управление битовыми полями

В TwinCAT 3 появился тип данных BIT. Он предназначен искючительно для создания новых типов данных с точно заданной структурой и удобного обмена данными с периферией (железом). Применять его можно только при объявлении новых структур данных:

TYPE BitBang :
STRUCT
    b0: BIT;
    b1: BIT;
    b2: BIT;
END_STRUCT
END_TYPE

Биты будут упаковываться в байт. По мере заполнения места в байте и превышения его размера система увеличит размер структуры на еще один байт и так далее, экономя место. Аналогичная структура из полей типа BOOL будет занимать в восемь раз больше места, так как в памяти под одно поле типа BOOL отводится сразу целый байт.

Статьи в инфосисе: BIT и Structure → Bit access in structures.

Экономия не дается просто так. Для доступа к битовым полям требуется больше времени из-за операций упаковки-распаковки. Поэтому используйте их только для обмена данными с устройствами, а затем транслируйте в тип BOOL. Ниже результат, а код будет в конце поста. По вертикали время на исполнение цикла (меньше — быстрее):

Где-то в полтора раза больше времени требуется на доступ к отдельным битам. На двух-трех операциях это будет незаметно. Глядя на результат выше, держите в голове, что счет там идет на десятки тысяч (до 100 000) операций за цикл. И это не то место, где уместно экономить на абстракциях.


Биты целочисленных данных

Целочисленные данные INT, BYTE, ... с самого начала позволяли читать и устанавливать свои отдельные биты. Достаточно написать iVar.3 и получить доступ к биту номер 3 (счет идет от нуля). Внезапно, нам подарили интересный механизм именования отдельных битов целочисленных данных. Проще говоря, теперь можно дать имена отдельным (или вообще всем) битам целого числа. Для этого лучше всего подходят константы, ведь в дальнейшем их значение нельзя будет изменить. Тем самым номер бита будет определен один единственный раз и не сможет изменяться в дальнейшем.

Можно создавать как локальные так и глобальные синонимы. С глобальными есть нюанс, поэтому испытания начнем с них. Идем в GVLs, создаем список GVL, название можно выбрать произвольное, пишем следующее:

//{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    Enable:  INT   := 3;
    Disable: BYTE  := 5;
    Error:   DWORD := 15;
END_VAR

Важно (!) убрать атрибут 'qualified_only' и указать, что это константы. С включенным атрибутом работать не будет. Конкретный тип данных не важен, главное чтобы он был целочисленным. Значение константы (число) обозначает номер бита, нумерация по прежнему начинается с нуля.

Теперь ближе к коду, что именно можно с этим сделать?

PROGRAM MAIN
VAR
    iVar: LINT;
    bVar: BYTE;
END_VAR
VAR CONSTANT
    Enable:  WORD := 3;
    Ready:   WORD := 3;
    Disable: WORD := 5;
    Error:   WORD := 15;
END_VAR

iVar.Enable := TRUE;
IF iVar.Ready THEN
    iVar.Disable := FALSE;
END_IF

// bVar.Error := FALSE; >>> `Error` is no valid bit number for `bVar`

iVar.Enable установит бит разрешения #3, а iVar.Disable сбросит бит запрета #5. Выйти за разрядную сетку не получится, компилятор бдит еще на этапе сборки (см. комментарий про bVar).

Можно дать одному и тому же биту разные названия. В примере выше поля Enable и Ready — это имена-синонимы четвертого бита (бит №3). Иногда это удобно и наглядно:

iVar.Enable := TRUE;
// iVar = 2#0000_1000
// iVar.Ready == TRUE

iVar.Disable := TRUE;
// iVar = 2#0010_1000

iVar.Ready := FALSE;
// iVar = 2#0010_0000
// iVar.Enable = FALSE
  


Производительность

Любые операции с отдельными битами, независимо от типа операции, чтение ли это или установка бита, приведут к дополнительным операциям упаковки/распаковки битов в байты. Это конечно же отразиться на производительности. Да, это будут копейки относительно других операций (см. картинку выше), но давайте посмотрим как обстоят дела с операциями чтения и записи по отдельности, а в конце еще раз всё вместе:

Чтение требует чуть меньше времени, чем запись. Операции с отдельными битами требуют больше времени, чем работа с булевым типом. Комплексные операции чтение-запись привносят еще большую нагрузку. Но(!) еще раз повторюсь, здесь счет идет на десятки тысяч операций за один цикл. Не стоит выигрывать наносекунды за счет читаемости кода. Кстати, код:

No comments

Post a Comment

Note: Only a member of this blog may post a comment.