Bit operations

Материал из WebHMI Wiki
Перейти к: навигация, поиск
Эта страница — перевод страницы Битовые операции. Перевод выполнен на 78%.

Другие языки:
English • ‎русский

Bit operations

Convenient bit operations

You can define several convenient bit operations, remembering that in lua bits are numbered from 1. The link was taken from here, there are many other useful functions.

function bit(n)
  return 2 ^ (n - 1)  -- returns weight of the binary digit
end

function hasbit(x, p)
  return x % (p + p) >= p  -- return on / off state as true/false   if hasbit(value, bit(2)) then ...
end

function setbit(x, p)
  return hasbit(x, p) and x or x + p -- set bit # p in х examle  х = setbit(х, bit(p))
end

function clearbit(x, p)
  return hasbit(x, p) and x - p or x -- same as previous but clears bit
end

Convert binary table to number

This function can be useful for non-standard conversions when the number is stored in a specific format and it must be processed in parts using the specified position of the 1st bit and the length of the value field.

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

Convert number to binary table

In lua (in the version used in WebHMI) there is no built-in operation to get a string representation of a binary number. However, this feature is very useful in custom protocols and others when it is needed to redistribute or somehow treat the individual bits of the number. The text of the variant of such function:

function getBits(input_num,length)         -- works with defined length
    
local tab = {};                           -- empty table for reply 
local max_i = length - 1;                 
local remainder = input_num;              -- remainder of successive approximation 
  
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

Then you can rearrange the necessary bits in places, etc. and get the right number. Below is an example of exchanging bits 31 and 23 (with bit numbering from 0) in the response of the flow meter of VLR 2301/2304 produced by Asvega-U.

function main (userId)
    
  local input_num = GetReg(3);               -- 
  local bitTable = getBits(input_num,32);    -- table to process result
  local tmp_bit = "";                        -- 
  
  tmp_bit = bitTable[1]; -- exchange
  bitTable[1] = bitTable[9];                 
  bitTable[9] = tmp_bit;
  WriteReg(1, tonumber(table.concat(bitTable),2));       

end

Processing extended precision float numbers (double)

Some devices can store data in a format with increased accuracy double float. In the current version of WebHMI only 32 bit registers are supported, so to process a 64-bit number, you will need to use a script that will 'chain' the two registers into the original number, and then convert and write back into float. Registers must be of type double word uint. For the 'coupling' of numbers, you can use the function getBits described above to get 2 tables, and then add the first table with bits from the second using table.insert. In this example, a constant is used as the verification number for simplicity.

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

function main (userId)
  -- getresult tab of 0 and 1 
local result_tab = getBits(test_var,64);  

WriteReg(11,table.concat(result_tab)); -- debug print into a string register 

local result_num = 0.0; -- to store the result 

local sign,exp,mantissa = 0,0,0;
local fraction_table = {}; -- table for fraction part 

-- get sign 
if result_tab[1] == "1" then 
    sign = -1;
else 
    sign = 1;
end 
-- get exponent
exp = getNumberFromTab(result_tab,2,11);
                DEBUG("exp = "..tostring(exp)); -- debug printing
-- mantissa
for i=13,64 do 
    table.insert(fraction_table,result_tab[i]);
end 
-- get mantissa bit - by - bit 
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)); -- debug printing 
result_num = sign*(2^(exp - 1023))*mantissa

-- exception handling
if exp == 0 then -- subnormals
   result_num = sign*(2^(-1022))*(mantissa-1);
end 

if exp == 0x7ff then -- nan 
   result_num = NaN;
end 
-- output the result into a single float register 
WriteReg(10, result_num);

end -- main

Grouping alerts into a single alert register

WebHMI has a alerts mechanism. I.e by the state of the bit in the alert register, you can automatically generate entries in the log about the time of occurrence, removal of the accident, acknowledgment by the operator.

However, in some cases, using this mechanism directly, as it is, may not be entirely convenient. For example, a certain bit or a combination thereof, different error codes (and they must be distinguished from each other), a value greater than or less than a threshold, etc. are considered emergency. If the register has already configured states for the emergency used for display, then it must be simply duplicated by setting the bit in the common emergency register. It may also be necessary to introduce an alarm delay, since the value may have a 'bounce' which must be filtered before generating an accident in the log. In addition, it is more convenient when all accidents are described in one place, rather than scattered over hundreds of registers. I.e. it is more rational to use one or more several 32-bit registers to describe all possible accidents, and a script in which these accidents will be written to this register under different conditions. Below is an example of such a script:

For example, there is a certain functional unit, for example, an air-handling unit, in which there is some common sign of an accident, there are several registers with codes for current errors from frequency converters and a situation where one of the registers exceeded alert limit (humidity, CO2, etc.) which is also an emergency situation.

Alerts eng.png


Then the script that will handle all these situations and generate the necessary flags in their places can look like this:

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) -- calling the function which will look what to do 
end -- for