Битовые операции

Материал из WebHMI Wiki
Перейти к: навигация, поиск
Другие языки:
English • ‎русский

Битовые операции

Удобные битовые операции

Можно определить несколько удобных битовых операций, помня о том что в lua биты нумеруются с 1. Ссылка взята отсюда, там же есть и другие полезные функции.

function bit(n)
  return 2 ^ (n - 1)  -- возвращает число - вес разряда номер n
end

function hasbit(x, p)
  return x % (p + p) >= p  -- возвращает состояние бита p как true/false; if hasbit(value, bit(2)) then ...
end

function setbit(x, p)
  return hasbit(x, p) and x or x + p -- установить бит № p в х Пример использования:  х = setbit(х, bit(p))
end

function clearbit(x, p)
  return hasbit(x, p) and x - p or x -- тоже только снять бит
end

Преобразование битовой таблички в число

Данная функция может быть полезна для нестандартных преобразований, когда число хранится в специфичном формате и его необходимо обработать по частям, используя заданную позицию 1-го бита и длину поля значения.

function getNumberFromTab(tab,start,length) -- получить число из таблички 
    local result_str = "";
    
    for i=start,(start+length-1) do 
            result_str = result_str..tostring(tab[i]);
    end 
    return tonumber(result_str,2);
end -- getNumberFromTab

Преобразование числа в битовую табличку

В lua (в версии, используемой в WebHMI) нет встроенной операции получения строкового представления двоичного числа. Однако эта функция очень полезна в пользовательсиких протоколах и др. задачах, где требуется перераспределить или каким то образом обработать отдельные биты числа. Текст варианта такой функции:

function getBits(input_num,length)         -- работает с заданной длиной
    
local tab = {};                           -- пустая табличка для ответа
local max_i = length - 1;                 
local remainder = input_num;              -- остаток порязрядного взешивания
  
for i=max_i,0,-1 do
     if remainder - 2^i >= 0 then
       table.insert(tab, "1")    ;               
       remainder =  remainder - 2^i;
     else 
       table.insert(tab, "0")    ;
     end 
end
return tab;
end -- getBits

Далее можно переставить нужные биты местами и т.п. и получить нужное число. Ниже приведен пример перестановки битов 31 и 23 битов (при нумерации битов с 0) в ответе счетчика расходомера ВЛР 2301/2304 производства Асвега-У

function main (userId)
    
  local input_num = GetReg(3);               -- прочитать регистр с числом
  local bitTable = getBits(input_num,32);    -- таблица для обработки результата
  local tmp_bit = "";                        -- вспомогательный бит
  
  tmp_bit = bitTable[1];
  bitTable[1] = bitTable[9];                 -- 31 бит это 1 элемент так как таблица развернута слева-направо, номера элементов табл.в lua c 1
  bitTable[9] = tmp_bit;
  -- конкатенация готовой таблицы в строку и преобразование в число из строки двоичного представления 
  WriteReg(1, tonumber(table.concat(bitTable),2));       

end

Обработка чисел с плавающей точкой двойной точности

Некоторые устройства могут хранить данные в формате с повышенной точностью double float. В WebHMI текущей версии поддерживаются только 32 битные регистры, поэтому чтобы обработать 64 битное число необходимо будет использовать скрипт, которые "сцепит" два регистра в исходное число, а затем преобразует и запишет в обратно в float. Регистры должны быть типа double uint. Для "сцепки" чисел можно использовать описанную выше функцию getBits чтобы получить 2 таблицы, а затем дополнить первую таблицу битами из второй используя table.insert. В этом примере в качестве проверочного числа для простоты используется константа.

--------------------------------------
local test_var = 0xC1D312D000000000;
local NaN = tonumber("11111111111111111111111111111111",2);
--------------------------------------

function main (userId)
  -- получить результирующую табличку "0" "1"
local result_tab = getBits(test_var,64);  

WriteReg(11,table.concat(result_tab)); -- отладочная печать в регстр - строку 

local result_num = 0.0; -- для хранения результата 

local sign,exp,mantissa = 0,0,0;
local fraction_table = {}; -- табл. для дробной части 

-- определить знак
if result_tab[1] == "1" then 
    sign = -1;
else 
    sign = 1;
end 
-- экспоненту
exp = getNumberFromTab(result_tab,2,11);
                DEBUG("exp = "..tostring(exp)); -- отл. печать 
-- мантиссу 
for i=13,64 do 
    table.insert(fraction_table,result_tab[i]);
end 
-- посчитать мантиссу по-разрядно !!! 
for j=1,52 do 
    if fraction_table[j]=="1" then 
    mantissa = mantissa +(2^(-1*j));
    end 
end 

mantissa = mantissa +1;
                DEBUG("m = "..tostring(mantissa)); -- отл. печать 
result_num = sign*(2^(exp - 1023))*mantissa;

-- Обработка исключений 
if exp == 0 then -- subnormals
   result_num = sign*(2^(-1022))*(mantissa-1);
end 

if exp == 0x7ff then -- nan 
   result_num = NaN;
end 
-- Вывод результата в регистр типа float 
WriteReg(10, result_num);

end -- main

Формирование общего регистра аварий по разным условиям

В WebHMI есть механизм аварий. Т.е. по состоянию бита в регистре можно автоматически генерировать записи в журнале о моменте возникновения, снятия аварии, квитировании ее оператором.

Однако в некоторых случаях использовать данный механизм непосредственно, как есть, может не совсем быть удобно. Например - аварийным считается определенный бит или их комбинация, разные коды ошибок (и их надо отличать друг от друга), значение больше или меньше порога и т.д. Если регистр имеет уже настроенные состояния для аварийной ситуации, используемое для отображения, то его нужно просто продублировать, выставив бит в общем аварийном регистре. Также может потребоваться введение задержки на срабатывание аварии, так как значение может иметь "дребезг" который надо фильтровать, прежде чем генерировать аварию в журнале. Кроме этого удобнее, когда все аварии описаны в одном месте, а не разбросаны по сотням регистров. Т.е. рациональнее использовать один или несколько несколько 32-битных регистров для описания всех возможных аварий, и скрипт в котором эти аварии будут записываться в этот регистр по разным условиям. Ниже приведен пример такого скрипта:

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

Alerts.png


Тогда скрипт, который обработает все эти ситуации и сформирует нужные флаги на своих местах может выглядеть так:

allAlerts = {          -- регистр    бит - код ошибки    тип операции
              {srcAlias = "pv1Status", bits = {[1] = 1}, checktype = "bit9"},
              {srcAlias = "vfdStatus_1", bits = {[2] =  10, [3] = 20, [4] = 30, [5] = 40}, checktype = "=="},
              {srcAlias = "vfdStatus_2",   bits = {[6] =  10, [7] = 20, [8] = 30, [9] = 40}, checktype = "=="},
              {srcAlias = "pv1ZoneHumidity", bits = {[10] = 90}, checktype = ">="}
 }

intAlertReg = "alertsReg" -- ОБЩИЙ  РЕГИСТР АВАРИЙ

function main (userId)

                                DEBUG("Entered script")
local alertInput, alertOut = 0,0 -- временные переменные для чтения аварий и формирования результата 
local digit = 0 -- номер бита в исходном регистре, указывающий на аварию..
--[[
Если назвать единообразно все  параметры, 
из которых надо формировать аварии и сложить их в одну структуру, 
тогда можно использовать только один цикл for, например так:
--]]
   for i,v in pairs(allAlerts) do 
         alertInput = GetReg(v.srcAlias) -- читаем регистр
                                DEBUG("Регистр для анализа = ".." "..v.srcAlias.." = "..alertInput)
            for j,k in pairs(v.bits) do -- выполнится по числу элементов в под-табличке bits
                          -- проверяем тип операции - точное совпадение или другое условие 
                    if (v.checktype == "==") then 
                                DEBUG("Тип сравнения - точное совпадение")
-- проверяем регистр на один из кодов ошибок
                       if (alertInput == k) then
                          alertOut = setbit(alertOut, bit(j))
                       else
                          alertOut = clearbit(alertOut, bit(j))
                       end
-- проверяем определенный бит
                    elseif (string.find(v.checktype, "bit%d+") ~= nil) then             
            -- %d+    паттерн любая последовательность цифр
                                                 DEBUG("Тип сравнения - проверка бита")
                            _, _, digit = string.find(v.checktype,"(%d+)")
                            digit = tonumber(digit)
                                                DEBUG("digit = "..digit)
                       if hasbit(alertInput, bit(digit)) then                 -- если заданный бит установлен во входном регистре
                          alertOut = setbit(alertOut, bit(j))
                       else 
                          alertOut = clearbit(alertOut, bit(j))
                       end 
            elseif  (v.checktype == ">=") then    
                -- параметр больше чем значение 
                    if (alertInput >= k) then
                      alertOut = setbit(alertOut, bit(j))
                    else
                      alertOut = clearbit(alertOut, bit(j))
                   end
            else 
                    --- другие операции
            end  -- if checktype 
            end -- for bits inside 

end -- for allAlerts 
                    -- теперь просто записать то что получилось в общий регистр аварий
WriteReg(intAlertReg,  alertOut)

end -- main 


----------------- ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ----------------------------------

function bit(n)
  return 2 ^ (n - 1)  -- возвращает число - вес разряда номер n
end

function hasbit(x, p)
  return x % (p + p) >= p  -- возвращает состояние бита p как true/false; if hasbit(value, bit(2)) then ...
end

function setbit(x, p)
  return hasbit(x, p) and x or x + p -- установить бит № p в х Пример использования:  х = setbit(х, bit(p))
end

function clearbit(x, p)
  return hasbit(x, p) and x - p or x -- тоже только снять бит
end

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


for i,v in pairs(allAlerts) do 
     alertInput = GetReg(v.srcAlias) -- читаем регистр
     alertOut = MyGetAlerts(alertInput, alertOut, v) -- вызываем функцию, которая посмотрит что надо сделать по полю v[3], т.е. allAlerts[3] 
end -- for