Битовые операции
Содержание
Битовые операции
Удобные битовые операции
Можно определить несколько удобных битовых операций, помня о том что в 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 и др.) вышел за допустимый диапазон, что является также аварийной ситуацией.
Тогда скрипт, который обработает все эти ситуации и сформирует нужные флаги на своих местах может выглядеть так:
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