Lua:使用 Bit32 库更改 I/O 的状态

Lua: Working with Bit32 Library to Change States of I/O's

我正在尝试了解 Lua 中的编程如何通过 Modbus I/O 模块更改 I/O 的状态。我已阅读 modbus 协议并了解寄存器、线圈以及 read/write 字符串的外观。但是现在,我正试图掌握如何操作 read/write 位以及函数如何执行这些操作。我知道我现在可能非常含糊,但希望以下功能以及其中的一些问题将帮助我更好地传达我在哪里断开连接。自从我第一次了解 bit/byte 操作以来已经有很长时间了。

local funcCodes = { --[[I understand this part]]
    readCoil = 1, 
    readInput = 2,
    readHoldingReg = 3,
    readInputReg = 4,
    writeCoil = 5,
    presetSingleReg = 6,
    writeMultipleCoils = 15,
    presetMultipleReg = 16
}
local function toTwoByte(value)
    return string.char(value / 255, value % 255) --[[why do both of these to the same value??]]
end
local function readInputs(s)
    local s = mperia.net.connect(host, port)
    s:set_timeout(0.1)
    local req = string.char(0,0,0,0,0,6,unitId,2,0,0,0,6)
    local req = toTwoByte(0) .. toTwoByte(0) .. toTwoByte(6) ..
    string.char(unitId, funcCodes.readInput)..toTwoByte(0) ..toTwoByte(8)
    s:write(req)
    local res = s:read(10)
    s:close()
    if res:byte(10) then
        local out = {}
        for i = 1,8 do
            local statusBit = bit32.rshift(res:byte(10), i - 1) --[[What is bit32.rshift actually doing to the string?  and the same is true for the next line with bit32.band.
            out[#out + 1] = bit32.band(statusBit, 1)
        end
        for i = 1,5 do
            tDT.value["return_low"] = tostring(out[1])
            tDT.value["return_high"] = tostring(out[2])
            tDT.value["sensor1_on"] = tostring(out[3])
            tDT.value["sensor2_on"] = tostring(out[4])
            tDT.value["sensor3_on"] = tostring(out[5])
            tDT.value["sensor4_on"] = tostring(out[6])
            tDT.value["sensor5_on"] = tostring(out[7])
            tDT.value[""] = tostring(out[8])
            end
        end
        return tDT
    end

如果我需要更具体地回答我的问题,我一定会尝试。但现在我很难将这些点与这里 bit/byte 操作的实际情况联系起来。我已经阅读了关于 bit32 库和在线资源的两本书,但仍然不知道它们到底在做什么。我希望通过这些例子,我可以得到一些澄清。

干杯!

--[[why do both of these to the same value??]]

这里有两个不同的值:value / 255 和 value % 255。“/”运算符代表除法,“%”运算符代表(基本上)取除法的余数。

在继续之前,我要指出这里的 255 几乎肯定应该是 256,所以让我们在继续之前进行更正。进行此更正的原因应该很快就会清楚。

我们来看一个例子。

value = 1000
print(value / 256) -- 3.90625
print(value % 256) -- 232

糟糕!还有另一个问题。 string.char 想要整数(在 0 到 255 的范围内——有 256 个不同的值以 0 计),我们可以给它一个非整数。让我们解决这个问题:

value = 1000
print(math.floor(value / 256)) -- 3
-- in Lua 5.3, you could also use value // 256 to mean the same thing
print(value % 256) -- 232

我们在这里做了什么?让我们看看二进制的 1000。由于我们使用的是双字节值,并且每个字节为 8 位,因此我将包括 16 位:0b0000001111101000。 (0b 是一个前缀,有时用于指示后面的数字应解释为二进制。)如果我们将其拆分为前 8 位和后 8 位,我们将得到:0b00000011 和 0b11101000。这些数字是多少?

print(tonumber("00000011",2)) -- 3
print(tonumber("11101000",2)) -- 232

所以我们所做的就是将一个 2 字节的数字拆分为两个 1 字节的数字。那么为什么会这样呢?让我们暂时回到以 10 为基数。假设我们有一个四位数,比如说 1234,我们想把它分成两个两位数。那么,商 1234 / 100 是 12,除法的余数是 34。在 Lua 中,即:

print(math.floor(1234 / 100)) -- 12
print(1234 % 100) -- 34

希望您能很好地理解 base 10 中发生的事情。 (这里的更多数学不在这个答案的范围内。)那么,256 呢? 256是2的8次方,一个字节有8位。在二进制中,256 是 0b100000000——它是一个 1 后跟一串零。这意味着它具有与以 10 为基数的 100 相同的将二进制数分开的能力。

这里要注意的另一件事是字节顺序的概念。哪个应该先出现,3 还是 232?事实证明,不同的计算机(和不同的协议)对这个问题有不同的答案。我不知道你的情况是什么,你必须参考你的文档。您当前设置的方式称为 "big endian",因为数字的大部分在前。

--[[What is bit32.rshift actually doing to the string?  and the same is true for the next line with bit32.band.]]

让我们看看整个循环:

local out = {}
for i = 1,8 do
    local statusBit = bit32.rshift(res:byte(10), i - 1)
    out[#out + 1] = bit32.band(statusBit, 1)
end

为了举例,让我们选择一个具体的数字,比如 0b01100111。首先让我们看一下乐队("bitwise and" 的缩写)。这是什么意思?这意味着将两个数字排成一行,看看两个 1 出现在同一个位置的位置。

     01100111
band 00000001
-------------
     00000001

首先请注意,我在一个前面放了一堆 0。前面的零不会改变数字的值,但我想要两个数字的所有 8 位,以便我可以用第二个数字的每个数字检查第一个数字的每个数字(位)。在两个数字都有一个 1 的每个地方(顶部数字有一个 1 "and" 底部数字有一个 1),我为结果放一个 1,否则我放 0。这是按位和。

当我们像这里那样用 0b00000001 按位运算时,您应该能够看到我们只会得到 1 (0b00000001) 或 0 (0b00000000) 作为结果。我们得到的取决于另一个数字的最后一位。我们基本上已经从其余部分中分离出该数字的最后一位(通常称为 "masking")并将其存储在我们的输出数组中。

现在 rshift ("right shift") 怎么样?要右移一位,我们丢弃最右边的数字,并将其他所有内容移到右边一位 space 上。 (在左边,我们通常加一个 0 所以我们仍然有 8 位......和往常一样,在数字前面加一个位​​不会改变它。)

right shift 01100111
            \\\\
             0110011 ... 1 <-- discarded

(请原谅我可怕的 ASCII 艺术。)所以向右移动 1 会将我们的 0b01100111 更改为 0b00110011。 (你也可以认为这是砍掉最后一点。)

现在右移一个不同的数字是什么意思?好吧,移零不会改变数字。要移动不止一次,我们只需重复此操作,无论我们移动多少次。 (要移动 2,移动 1 两次,等等)(如果你更喜欢从斩波的角度思考,右移 x 就是砍掉最后的 x 位。)

所以在循环的第一次迭代中,数字不会移动,我们将存储最右边的位。

在循环的第二次迭代中,数字将移动 1,新的最右边的位将是之前从右边数第二位的位,因此按位和将屏蔽掉该位,我们将存储它。

在下一次迭代中,我们将移动 2,因此最右边的位将是最初从右数第三位的位,因此按位和将屏蔽掉该位并存储它。

在每次迭代中,我们存储下一位。

由于我们处理的是一个字节,只有 8 位,因此在循环 8 次迭代后,我们会将每一位的值存储到我们的 table 中。在我们的示例中,table 应该是这样的:

out = {1,1,1,0,0,1,1,0}

请注意,这些位与我们编写它们的方式相反 0b01100111 因为我们从二进制数的右侧开始查找,但是从左侧开始将内容添加到 table。

在你的情况下,看起来每一位都有不同的含义。例如,第三位中的 1 可能意味着传感器 1 打开,而第三位中的 0 可能意味着传感器 1 关闭。像这样的八种不同的信息被打包在一起,以便更有效地通过某些渠道传输它们。循环将它们再次分离成一种便于您使用的形式。