Bit operations
Содержание
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.
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