RS485 Modbus主机教程(繁版)
前言
本文章主要讲解繁版。
繁版和简版区别:
- 繁版更灵活,使用起来稍微繁琐一点,但能实现更灵活的 Modbus 主机通信功能;
- 简版使用起来非常简单,但主要专注于最频繁使用的 Modbus 读寄存器/读线圈的操作(例如 01、02、03和04功能码)
一、介绍
ShineBlink 提供Modbus主机库函数,方便开发者可以很容易实现Modbus主机功能,因此Core可以很容易扮演Modbus通信网络的主机角色,比如通过RS485串口读写从机设备。
Modbus主机库函数非常简单,仅有如下两个函数:
- LIB_MbRtuMasterSendTrans():将主机需要下发的命令转换成符合Modbus协议格式的字节流,该字节流以table数组形式返回。
- LIB_MbRtuMasterRecvTrans():将从机发来的应答数据字节流转换成更直观的结果,以方便程序后续处理。
目前支持的功能为:01,02,03,04,05,06,0f,10:
功能码 | 功能介绍 |
---|---|
01 | 读线圈 |
02 | 读离散量输入 |
03 | 读保持寄存器 |
04 | 读输入寄存器 |
05 | 写单个线圈 |
06 | 写单个寄存器 |
0F | 写多个线圈 |
10 | 写多个寄存器 |
二、功能码案例教程
基础演示代码框架:
以下代码是一个完整的演示Modbus主机读线圈功能的代码,可以作为后面其他功能码的代码框架,后面每个案例仅展示关键代码,就不占用篇幅了。
--配置Uart1作为485接口,初始默认波特率9600,并且D6作为自动收发切换引脚
LIB_Uart1Rs485Config("BAUDRATE_9600","D6")
--设置开发板上的"BTN1"(占用D10口)按键以低电平有效的方式检测按键动作
LIB_ButtonConfig("BTN1","D10","L")
--开始大循环
while(GC(1) == true)
do
--轮询按键事件
key_value = LIB_ButtonQuery("BTN1")
--如果开发板上的按键1短按过
if key_value == 1 then
--下发读线圈命令(设备地址=0x03,起始地址=1000,个数=3)
LIB_Uart1BlockSend(LIB_MbRtuMasterSendTrans("01", 0x03, 1000, 3))
--等待0.2秒的应答时间
LIB_DelayMs(200)
--判断刚刚的命令是否收到应答数据
recv_flag,recv_tab = LIB_Uart1Recv()
if recv_flag == 1 then
--解析从机发来的应答数据
result,content=LIB_MbRtuMasterRecvTrans("01",recv_tab)
if result > 0 then --打印content数组中的所有结果
for i, v in ipairs(content) do
print(i, v)
end
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end
end
end
end
从机可能返回的err_code异常码说明:
err_code = 1 | 非法功能码 |
---|---|
err_code = 2 | 非法数据地址 |
err_code = 3 | 非法数据值 |
err_code = 4 | 从站设备故障 |
err_code = 5 | ACK确认 |
err_code= 6 | 从站设备忙 |
err_code= 7 | 未定义ACK |
err_code = 8 | 存储奇偶错误 |
err_code = 9 | 未定义异常 |
err_code = 10 | 不可用网关路径 |
err_code = 11 | 不可用网关目标 |
err_code = 255 | 未知异常 |
(1)功能码"01":读线圈(bit)
首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:
--读线圈(设备地址=0x03,起始地址=1000,个数=3)
tab = LIB_MbRtuMasterSendTrans("01", 0x03, 1000, 3)
将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("01",recv_tab)
if result > 0 then
--content将会是一个table类型的数组,例如{0,1,0},表示返回的3个线圈的值
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end
(2)功能码"02":读离散量输入(bit)
首先生成主机需要发送的modbus命令字节流,以table形式返回 ,以供类似LIB_Uart0Send()这种串口发送函数使用:
--读线圈(设备地址=0x03,起始地址=2000,个数=5)
tab = LIB_MbRtuMasterSendTrans("02", 0x03, 2000, 5)
然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("02",recv_tab)
if result > 0 then
--content将会是一个table类型的数组,例如{0,1,0,1,1},表示返回的5个离散输入量的值
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end
(3)功能码"03":读保持寄存器(uint16)
首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:
--读保持寄存器(设备地址=0x03,起始地址=3500,个数=4)
tab = LIB_MbRtuMasterSendTrans("03", 0x03, 3500, 4)
然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("03",recv_tab)
if result > 0 then
--content将会是一个table类型的数组,例如{0xffff,0xffec,0x4133,0x3333},表示返回的4个保持寄存器的值
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end
额外重要知识:
Modbus通讯中,寄存器有可能按照各种不同的方式存储(例如LONG AB CD,LONG CD BA, LONG BA CD,FLOAT AB CD,FLOAT CD AB等等),所以如果需将Modbus多个保持寄存器的原始16位数据合成实际数值,可借用功能强大的LIB_BC()函数来实现,这样可避免复杂的Lua代码。LIB_BC()的转换功能很多,详细请在API手册中查阅,下面只介绍其中两种常见的:
- 例如上面收到的0xffff,0xffec实际为0xffffffec的拆分,如果按照Modbus种常用的数据格式(LONG AB CD)它实际是 "-20" 的补码形式:
--将0xffff和0xffec转换成-20
val = LIB_BC("BYTE16_I32", content[1], content[2])
print(string.format("val=%d", val))--打印结果:val=-20
- 例如上面收到的0x4133,0x3333实际为0x41333333的拆分,如果按照Modbus种常用的数据格式(Float AB CD)它实际是IEEE-754格式浮点数 "11.2" 的形式:
--将0x4133和0x3333转换成11.2
val = LIB_BC("BYTE16_F32", content[3], content[4])
print(string.format("val=%f", val))--打印结果:val=11.2
(4)功能码"04":读输入寄存器(uint16)
首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:
--读输入寄存器(设备地址=0x03,起始地址=4500,个数=4)
tab = LIB_MbRtuMasterSendTrans("04", 0x03, 4500, 4)
然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("04",recv_tab)
if result > 0 then
--content将会是一个table类型的数组,例如{0xffff,0xffec,0x4133,0x3333},表示返回的4个保持寄存器的值
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end
额外重要知识:
Modbus通讯中,寄存器有可能按照各种不同的方式存储(例如LONG AB CD,LONG CD BA, LONG BA CD,FLOAT AB CD,FLOAT CD AB等等),所以如果需将Modbus多个输入寄存器的原始16位数据合成实际数值,可借用功能强大的LIB_BC()函数来实现,这样可避免复杂的Lua代码。LIB_BC()的转换功能很多,详细请在API手册中查阅,下面只介绍其中两种常见的:
- 例如上面收到的0xffff,0xffec实际为0xffffffec的拆分,如果按照Modbus种常用的数据格式(LONG AB CD)它实际是 "-20" 的补码形式:
--将0xffff和0xffec转换成-20
val = LIB_BC("BYTE16_I32", content[1], content[2])
print(string.format("val=%d", val))--打印结果:val=-20
- 例如上面收到的0x4133,0x3333实际为0x41333333的拆分,如果按照Modbus种常用的数据格式(Float AB CD)它实际是IEEE-754格式浮点数 "11.2" 的形式:
--将0x4133和0x3333转换成11.2
val = LIB_BC("BYTE16_F32", content[3], content[4])
print(string.format("val=%f", val))--打印结果:val=11.2