15 LUA 自由串口协议
大彩串口屏支持的协议有以下几类,但同时只能兼容一种:
- 大彩协议:报文格式EE ... FF FC FF FF。另外,用户可以自定义大彩协议,使用EE B5 ... FF FC FF FF格式,也会触发LUA脚本的串口接收函数:on_uart_recv_data(packet),且packet是完整的一帧数据。
- MODBUS-RTU:标准MODBUS RTU协议,主机、从机均可以实现
- 三菱协议FX2N:标准Mitsubishi FX2N协议
- XGUS:自定义帧头,通过寻址方式实现
- 自由串口协议:LUA脚本处理,需要对系统变量uart_free_protocol 赋值为1,触发on_uart_recv_data(packet),用户对数据进行解析处理
如果用户设备是第三方,且不支持二次开发,需要屏幕来处理交互报文时,可以通过LUA脚本实现自由串口协议,本文档通过介绍常见的几种报文结构,来说明LUA如何开发自由串口协议。
15.1 说明
LUA脚本开启自由串口协议时(uart_free_protocol = 1),屏幕接收数据会自动触发on_uart_recv_data(packet)函数,需要注意以下点:
- packet(数据类型为数组):不一定是完整一帧报文,存在存在分包,且帧和帧之间的也可能存在粘包的情况
- 串口不属于中断回调,需要在初始化on_init(),设置超时为0,即uart_set_timeout(0, 0)
- 在LUA中需要声明一个全局数组,每次触发LUA接收数据时,将报文的每一个字节存在数组中。通过报文中的标识(帧头、帧尾或固定长度),把报文一帧一帧提取处理
本例程用灯的的开关和亮度控制做说明。并举例说明常见的报文格式的解析,常见的报文格如下所示:
- 帧头 + ...+ crc16 + 帧尾
- 帧头 + 数据长度
- 报文定长
适用范围:M系列、W系列、X系列、F系列(固件版本 >= V4.2.401.0)
例程下载链接:《LUA - 自由串口协议》(点击跳转)
15.2 帧头+...+CRC16校验+帧尾
以帧头:0x5A、帧尾:0xA5 0x5A 0xA5 0xA5为例,且带CRC16校验,屏幕对该帧结构做以下解析示例
基本思路:
- 触发on_uart_recv_data(packet),检索是否有帧头0x5A;当检索到帧头且标志cmd_head_tag=0时,将帧头标志位置位cmd_head_tag = 1
- 填充缓存buff
- 检测到帧尾cmd_head_tag = 0XA55AA5A5,表示接收到完整一帧数据
- 调用add_crc16(start, n, data),进行CRC16检验
- 检验成功,调用my_processmessage(msg)执行相关操作
- 最后清除相关的标记和缓冲,完成一帧的读取,继续获取下一帧数据代码清单如程序清单 1所示:
local cmd_end = 0xA55AA5A5 --帧尾
local buff = {} --缓冲区
local cmd_length = 0 --帧长度
local cmd_head_tag = 0 --帧头标识
local cmd_end_tag = 0 --帧尾标识
--calculate CRC16
--@data : t, data to be verified
--@n : number of verified
--@return : check result
function add_crc16(start, n, data)
local carry_flag, a = 0
local result = 0xffff
local i = start
while(true )
do
result = result ~ data[i]
for j = 0, 7
do
a = result
carry_flag = a & 0x0001
result = result >> 1
if carry_flag == 1
then
result = result ~ 0xa001
end
end
i = i + 1
if i == start + n
then
break
end
end
return result
end
--Instruction analysis
--@msg:data table
function my_processmessage(msg)
local funccode = msg[1]
if funccode == Func_lampState
then
local xth_lamp = msg[2]
local xth_lamp_state = msg[3]
my_set_lamp_state(xth_lamp, xth_lamp_state)
elseif funccode == Func_lampLight
then
local xth_lamp = msg[2]
local xth_lamp_light = msg[3]
my_set_lamp_light(xth_lamp, xth_lamp_light)
end
end
-- 系统函数: 初始化
function on_init()
uart_set_timeout(0, 0)
end
-- 系统函数: 串口接收函数
function on_uart_recv_data(packet)
local recv_packet_size = (#(packet))
local check16 = 0
for i = 0, recv_packet_size
do
if packet[i] == cmd_head and cmd_head_tag == 0
then
cmd_head_tag = 1
end
if cmd_head_tag == 1
then
buff[cmd_length] = packet[i]
cmd_length = cmd_length + 1
cmd_end_tag = (cmd_end_tag << 8) | (packet[i])
if (cmd_end_tag & cmd_end)== cmd_end
then
check16 = ((buff[cmd_length - 6] << 8) |
buff[cmd_length - 5]) & 0xFFFF
if check16 == add_crc16(1, cmd_length - 7, buff)
then
my_processmessage(buff) --提取到完整的数据
buff = {}
cmd_length = 0
cmd_end_tag = 0
cmd_head_tag = 0
else
buff = {}
cmd_length = 0
cmd_end_tag = 0
cmd_head_tag = 0
end
end
end
end
end
15.3 帧头+数据长度
以帧头0x5A + 长度LEN + DATA0...DATAn (LEN 表示DATA的字节数)为例,屏幕对该帧结构做以下解析示例
基本思路:
- 触发on_uart_recv_data(packet),检索是否有帧头0x5A;当检索到帧头且标志cmd_head_tag=0时,将帧头标志位置位cmd_head_tag = 1
- 填充缓存buff
- 记录data的长度data_length = packet[1]
- 检测到data_length + 2 = cmd_lenght,表示接收到完整一帧数据
- 调用my_processmessage(msg)执行相关操作
- 最后清除相关的标记和缓冲,完成一帧的读取,继续获取下一帧数据代码清下所示:
-- 系统函数: 初始化
function on_init()
uart_set_timeout(0, 0)
end
local cmd_head = 0x5A --帧头
local buff = {} --缓冲区
local cmd_length = 0 --帧长度
local data_length = 0
local cmd_head_tag = 0
--Instruction analysis
--@msg:data table
function my_processmessage(msg)
local funccode = msg[2]
print('my_processmessage')
print('msg len = '..(#(msg)))
if funccode == Func_lampState
then
local xth_lamp = msg[3]
local xth_lamp_state = msg[4]
my_set_lamp_state(xth_lamp, xth_lamp_state) -- 设置灯开关
elseif funccode == Func_lampLight
then
local xth_lamp = msg[3]
local xth_lamp_light = (msg[4] << 8) | msg[5]
my_set_lamp_light(xth_lamp, xth_lamp_light) --设置灯亮度
end
end
-- 系统函数: 串口接收函数
function on_uart_recv_data(packet)
local recv_packet_size = (#(packet))
local check16 = 0
for i = 0, recv_packet_size
do
if packet[i] == cmd_head and cmd_head_tag == 0
then
cmd_head_tag = 1
end
if cmd_head_tag == 1
then
buff[cmd_length] = packet[i]
cmd_length = cmd_length + 1
if cmd_length == 2
then
data_length = buff[cmd_length]
end
if (data_length + 2) == cmd_length
then
my_processmessage(buff)
buff = {}
cmd_length = 0
data_length = 0
cmd_head_tag = 0
end
end
end
end
15.4 报文定长
当没有特殊的帧头或帧尾标识,且每一帧的报文都是固定长度的,本章节假设指令报文长度就3个字节,进行解析
基本思路:
- 触发on_uart_recv_data(packet),开始填充缓存buff
- 检测到cmd_lenght = 3,表示接收到完整一帧数据
- 调用my_processmessage(msg)执行相关操作
- 最后清除相关的标记和缓冲,完成一帧的读取,继续获取下一帧数据
--Instruction analysis
--@msg:data table
function my_processmessage(msg)
local funccode = msg[0]
if funccode == Func_lampState
then
local xth_lamp = msg[1]
local xth_lamp_state = msg[2]
my_set_lamp_state(xth_lamp, xth_lamp_state)--设置灯状态
elseif funccode == Func_lampLight
then
local xth_lamp = msg[1]
local xth_lamp_light = msg[2]
my_set_lamp_light(xth_lamp, xth_lamp_light)--设置灯亮度
end
end
-- 系统函数: 初始化
function on_init()
uart_set_timeout(0, 0)
end
local buff = {} --缓冲区
local cmd_length = 0 --帧长度
-- 系统函数: 串口接收函数
function on_uart_recv_data(packet)
local recv_packet_size = (#(packet))
local check16 = 0
for i = 0, recv_packet_size
do
buff[cmd_length] = packet[i]
cmd_length = cmd_length + 1
if cmd_length == 3
then
my_processmessage(buff)
buff = {}
cmd_length = 0
end
end
end
15.5 LUA串口发送
LUA中,串口发送函数为uart_send_data(packet)
形参:packet,下表从0开始,表示发送的字节数组
以程序清单 4为例,用户发送报文时,建议封装一个函数,将变量设置成形参传递,给对应数组元素赋值。
--send notify of lamp status
--@xth_lamp: which lamp
--@state: state of lamp, 0-colse,1-open
function my_uartsend_lampstate_notify(xth_lamp,state)
local lamp_state_notify = {}
local send_crc16 = 0
lamp_state_notify[0] = 0xA5
lamp_state_notify[1] = Func_lampState
lamp_state_notify[2] = xth_lamp
lamp_state_notify[3] = state
send_crc16 = add_crc16(1, 3, lamp_state_notify)
lamp_state_notify[4] = (send_crc16 >> 8) & 0xFF
lamp_state_notify[5] = (send_crc16 >> 0) & 0xFF
lamp_state_notify[6] = 0x5A
lamp_state_notify[7] = 0xA5
lamp_state_notify[8] = 0x5A
lamp_state_notify[9] = 0x5A
uart_send_data(lamp_state_notify)
end