最近在使用树莓派与其他设备通过SPI
接口进行通信,使用一个GPIO
管脚触发读取数据的信号,为了简化开发,使用了Python
。
在实际运行过程中,发现当长时间运行的是,会出现中断管脚信号丢失的情况,在参考 Ubuntu 16.04 (x64)下从源代码为Raspberry Pi Zero W编译实时内核 更换为实时内核之后,短时间运行已经可以正常,但是在十几个小时之后,依然出现了中断丢失的现象。
这个现象初步评估为Python
的GC
动作时间过长导致的中断信号丢失。Python
本身并不是为实时系统设计的,因此在GC
进行垃圾回收的时候,是没有实时性的考虑的,因此在严格要求实时性的系统环境下,不是非常的合适。更何况很多的IO
操作默认都是阻塞的,更加容易导致实时性问题。
由于树莓派本身也是支持Lua
脚本的,默认安装的Lua
引擎默认是5.1.4
。Lua
本身在游戏中使用较多,而游戏本身对于实时性的要求是很高的。
尤其是Lua 5.1
开始使用最新的GC
已经能很好的解决实时性问题。
关于Lua
的GC
相关信息,参考如下的文章:
正是由于对于GC
的特殊要求,导致我们在实时系统上能选择的脚本非常有限。
目前的评估来看,貌似Lua
差不多成为唯一的选择了。
以下操作在如下系统版本上进行过可行性验证:
1 2 3 4 5 6 |
$ sudo lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 9.4 (stretch) Release: 9.4 Codename: stretch |
具体的使用方法如下(对于Raspberry Pi Zero W
如果已经设置成AP
模式,建议外接USB
有线网卡操作):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# 至少是5.1版本的lua,主要就是虚拟机的GC逻辑 $ sudo apt-get install lua5.1 liblua5.1-dev # 树莓派 Raspbian GNU/Linux 11 (bullseye) 上只能执行如下命令 # sudo apt-get install lua5.4 liblua5.4-dev # 网络功能 $ sudo apt-get install lua-socket $ sudo apt-get install luarocks # GPIO,SPI,I2C,Serial $ sudo luarocks install lua-periphery $ sudo luarocks install luasocket $ sudo apt-get install libssl-dev $ sudo ln -s /usr/lib/arm-linux-gnueabihf/libssl.so /usr/lib/libssl.so $ sudo luarocks install luasec # zeromq(目前默认 2.2.0),方便多线程通信,lua-periphery缺乏回调函数,导致必须增加一个线程任务 $ sudo apt-get install libzmq-dev # 树莓派 Raspbian GNU/Linux 11 (bullseye) 上只能执行如下命令 # sudo apt-get install libzmq3-dev # 修正链接问题,lua-zmq的库路径查找存在问题,我们需要手工链接一下文件 $ sudo ln -s /usr/lib/arm-linux-gnueabihf/libzmq.so /usr/lib/libzmq.so $ sudo ln -s /usr/lib/arm-linux-gnueabihf/libzmq.a /usr/lib/libzmq.a # 目前安装的版本为 lua-zmq 1.1-1 # https://github.com/Neopallium/lua-zmq $ sudo luarocks install lua-zmq # 树莓派 Raspbian GNU/Linux 11 (bullseye) 上只能执行如下命令 # sudo luarocks install lzmq # 线程相关 $ sudo luarocks install lua-llthreads $ sudo luarocks install lua-zmq-threads # 树莓派 Raspbian GNU/Linux 11 (bullseye) 上只能执行如下命令 # sudo luarocks install lua-llthreads2 # TcpServer $ sudo luarocks install coxpcall $ sudo luarocks install copas # json $ sudo luarocks install dkjson # struct # sudo luarocks install lua-struct $ sudo luarocks install luastruct # semaphore $ sudo luarocks install luaipc # string.pack/string.unpack support for lua 5.1 # use ' require "pack" ' $ sudo luarocks install lpack |
控制GPIO
的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
periphery = require('periphery') -- GPIO注意,与Python中的GPIO管脚定义不同,Python中的GPIO定义存在一张重定位表,真正的管脚需要查询这张表来确定,比如Python下 GPIO 22 对应真正的管脚是 GPIO 25 (Raspberry Zero W) -- 查询表参见 https://github.com/Tieske/rpi-gpio/blob/master/lua/RPi_GPIO_Lua_module.c -- 转换代码参考 https://www.mobibrw.com/?p=14673 --lua 5.1 -- Open GPIO 10 with input direction err, gpio_in = pcall(periphery.GPIO, 10,"in") -- Open GPIO 12 with output direction err, gpio_out = pcall(periphery.GPIO, 12,"out") value = gpio_in:read() gpio_out:write(not value) gpio_in:close() gpio_out:close() |
控制GPIO
边沿触发(阻塞等待)的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
periphery = require('periphery') -- GPIO注意,与Python中的GPIO管脚定义不同,Python中的GPIO定义存在一张重定位表,真正的管脚需要查询这张表来确定,比如Python下 GPIO 22 对应真正的管脚是 GPIO 25 (Raspberry Zero W) -- 查询表参见 https://github.com/Tieske/rpi-gpio/blob/master/lua/RPi_GPIO_Lua_module.c -- 转换代码参考 https://www.mobibrw.com/?p=14673 --lua 5.1 -- Open GPIO 10 with input direction err, gpio_in = pcall(periphery.GPIO, 10,"in") -- Open GPIO 12 with output direction err, gpio_out = pcall(periphery.GPIO, 12,"out") -- gpio_in.edge = "rising" gpio_in.edge = "falling" --lua 5.1 lua: (error object is not a string) --r = gpio_in:poll(1000) -- 1s --lua 5.1 err,r = pcall(periphery.GPIO.poll, gpio_in, 1000) -- 1s --lua 5.1 lua: (error object is not a string) --v = gpio_in:read() --lua 5.1 err,v = pcall(periphery.GPIO.poll, gpio_in, read) gpio_out:write(not v) gpio_in:close() gpio_out:close() |
控制SPI
的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
periphery = require('periphery') --lua 5.1 -- Open spidev1.0 with mode 0 and max speed 1MHz err, spi = pcall(periphery.SPI,"/dev/spidev0.0", 0, 1e6) #也可以动态创建数据写入 --data_out = {} --for i= 0, 4 do -- data_out[i] = 0 --end data_out = {0xaa, 0xbb, 0xcc, 0xdd} err, data_in = pcall(spi.transfer,spi,data_out) print(string.format("shifted out {0x%02x, 0x%02x, 0x%02x, 0x%02x}", unpack(data_out))) print(string.format("shifted in {0x%02x, 0x%02x, 0x%02x, 0x%02x}", unpack(data_in))) spi:close() |
使用ZeroMQ
的例子(PUB-SUB
)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
require "zmq" require "zmq.threads" local recv_worker_code = [[ local zmq = require "zmq" local zthreads = require "zmq.threads" local context = zthreads.get_parent_ctx() local subscriber, err = context:socket(zmq.SUB) -- 本地进程内(线程间)传输方式 "inproc://backend" subscriber:connect("tcp://localhost:5559") subscriber:setopt(zmq.SUBSCRIBE,"") while true do -- Read envelope with address and message contents local contents = subscriber:recv() print ("recv:"..contents) -- messages (for testing purposes our own ones) come in perfectly end subscriber:close() return nil ]] local zmq = require "zmq" -- 遍历输出成员变量 for key, value in pairs(zmq) do print(key) end local zthreads = require "zmq.threads" local context = zmq.init(1) -- Socket to talk to server publisher, err = context:socket(zmq.PUB) -- 本地进程内(线程间)传输方式 "inproc://backend" publisher:bind("tcp://*:5559") recv_worker = zthreads.runstring(context,recv_worker_code) recv_worker:start() print ("HI, this is lua script. We are running now") -- Initialize random number generator math.randomseed(os.time()) local idx for idx=0,99 do local workload = math.random() * 10000 + idx local msg = string.format("%d", workload) -- print(msg) publisher:send(msg) local socket = require("socket") socket.sleep(1) end recv_worker:join() publisher:close() recv_worker:close() context:term() |
使用ZeroMQ
的例子(PAIR-PAIR
,线程之间一对一通信,类似信号量)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
require "zmq" require "zmq.threads" local send_worker_code = [[ local zmq = require "zmq" local zthreads = require "zmq.threads" local context = zthreads.get_parent_ctx() local xmitter, err = context:socket(zmq.PAIR) -- 本地进程内(线程间)传输方式 "inproc://backend" xmitter:connect("inproc://zmq123") -- Initialize random number generator math.randomseed(os.time()) while true do local workload = math.random() * 10000 local msg = string.format("%d", workload) xmitter:send(msg) local socket = require("socket") socket.sleep(1) end xmitter:close() return nil ]] local zmq = require "zmq" -- 遍历输出成员变量 for key, value in pairs(zmq) do print(key) end local zthreads = require "zmq.threads" local context = zmq.init(1) receiver, err = context:socket(zmq.PAIR) -- 本地进程内(线程间)传输方式 "inproc://backend" receiver:bind("inproc://zmq123") send_worker = zthreads.runstring(context,send_worker_code) send_worker:start() print ("HI, this is lua script. We are running now") local idx for idx=0,99 do local msg = receiver:recv() print("recv:"..msg) end send_worker:join() receiver:close() send_worker:close() context:term() |
使用Copas
的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
local socket = require("socket") local copas = require("copas") local host = "*" --"127.0.0.1" -- 注意,如果使用低于1024的端口,需要root否则无法正确创建端口 local port = 8666 local server = socket.bind(host, port) function echoHandler(skt) skt = copas.wrap(skt) while true do local data = skt:receive() if data == "quit" then break end skt:send(data) end end copas.addserver(server, echoHandler) print ("HI, this is lua script. We are running now") copas.loop() |
使用Copas
与ZeroMQ
结合的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
require "zmq" require "zmq.threads" local send_worker_code = [[ local zmq = require "zmq" local zthreads = require "zmq.threads" local context = zthreads.get_parent_ctx() local xmitter, err = context:socket(zmq.PAIR) -- 本地进程内(线程间)传输方式 "inproc://backend" xmitter:connect("inproc://zmq123") -- Initialize random number generator math.randomseed(os.time()) while true do local workload = math.random() * 10000 local msg = string.format("%d\n", workload) xmitter:send(msg) local socket = require("socket") socket.select(nil, nil, 1) -- 传递0.1可以达到毫秒级别 end xmitter:close() ]] local copas = require("copas") local socket = require("socket") local zmq = require "zmq" -- 遍历输出成员变量 for key, value in pairs(zmq) do print(key) end local zthreads = require "zmq.threads" local context = zmq.init(1) local receiver, err = context:socket(zmq.PAIR) -- 本地进程内(线程间)传输方式 "inproc://backend" receiver:bind("inproc://zmq123") -- 如果runstring中传入的context为nil 则线程内部需要自己调用zmq.init(1)初始化自己的zmq上下文,但是如果传入nil无法使用zmq.PAIR模式 send_worker = zthreads.runstring(context,send_worker_code) send_worker:start() local host = "*" --"127.0.0.1" -- 注意,如果使用低于1024的端口,需要root否则无法正确创建端口 local port = 8666 local server = socket.bind(host, port) local clients = {} function copasHandler(skt) skt = copas.wrap(skt) table.insert(clients,skt) local idx = table.getn(clients) while true do local data = skt:receive() if data == "quit" then break end end table.remove(clients,idx) end copas.addthread(function() while true do local msg = receiver:recv(zmq.NOBLOCK) if msg then for _,v in pairs(clients) do v:send(msg) end end copas.sleep(0.1) -- 0.1 second interval end end) copas.addserver(server, copasHandler) print ("HI, this is lua script. We are running now") copas.loop() send_worker:join() receiver:close() send_worker:close() context:term() |
使用dkjson
的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
local dkjson = require("dkjson") --lua对象到字符串 local obj = { id = 1, name = "zhangsan", age = nil, is_male = false, hobby = {"film", "music", "read"} } local str = dkjson.encode(obj, {indent = true}) print(str.."\n") --字符串到lua对象 str = '{"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1,"age":null}' local obj, pos, err = dkjson.decode(str, 1, nil) print(obj.hobby[1].."\n") |
参考链接
- Lua Garbage Collection
- Using MicroPython for real-time software development
- MicroPython
- Lua In Real Time Programs
- Python or Lua - Realtime application
- RPi.GPIO.Lua
- Garbage Collection In Real Time Games
- Lua module for Raspberry Pi
- Lua 5.1 Reference Manual
- Multi Tasking
- ZeroMQ Get The Software
- Sending message from Lua Thread #505
- Multithreaded service in Lua
- ZeroMQ Guide with Lua examples
- zmq_inproc – ØMQ 本地进程内(线程间)传输方式
- zeromq模式介绍
- Coroutine Oriented Portable Asynchronous Services for Lua
- lua杂记01--链表
- Can lua-zmq make ZeroMQ work with Lua-Coroutine?
- 第五章 常用Lua开发库2-JSON库、编码转换、字符串处理
- lua bson 库
- BSON generator/parser in pure Lua (5.1)
- ZeroMq Lua bindings
- LuaStruct
- UlisseMini/luaStruct
写的非常好,lua确实是很好的可行方案
文章相当不错