网易首页 > 网易号 > 正文 申请入驻

替代单片机的极简方式开发RS485 Modbus-RTU通信

0
分享至

RS485 Modbus主机教程(繁版)
前言

本文章参考 FlexLua 官网教程。

繁版和简版区别:

  1. 繁版更灵活,使用起来稍微繁琐一点,但能实现更灵活的 Modbus 主机通信功能;
  2. 简版使用起来非常简单,但主要专注于最频繁使用的 Modbus 读寄存器/读线圈的操作(例如 01、02、03和04功能码)


一、介绍

ShineBlink 提供Modbus主机库函数,方便开发者可以很容易实现Modbus主机功能,因此Core可以很容易扮演Modbus通信网络的主机角色,比如通过RS485串口读写从机设备。

Modbus主机库函数非常简单,仅有如下两个函数:

  1. LIB_MbRtuMasterSendTrans():将主机需要下发的命令转换成符合Modbus协议格式的字节流,该字节流以table数组形式返回。
  2. 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

(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位数据合成实际数值,可借用功能强大的LIBBC()函数来实现,这样可避免复杂的Lua代码。LIBBC()的转换功能很多,详细请在API手册中查阅,下面只介绍其中两种常见的:

  1. 例如上面收到的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

  1. 例如上面收到的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位数据合成实际数值,可借用功能强大的LIBBC()函数来实现,这样可避免复杂的Lua代码。LIBBC()的转换功能很多,详细请在API手册中查阅,下面只介绍其中两种常见的:

  1. 例如上面收到的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

  1. 例如上面收到的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

(5)功能码"05":写单个线圈(bit)

首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:

--写单个线圈(设备地址=0x03,地址=0x0000的线圈)的值为0
tab = LIB_MbRtuMasterSendTrans("05", 0x03, 0x0000, 0)
--写单个线圈(设备地址=0x03,地址=0x0000的线圈)的值为1
tab = LIB_MbRtuMasterSendTrans("05", 0x03, 0x0000, 1)
然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("05",recv_tab)
if result > 0 then
print("Write single coil ok")
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end

(6)功能码"06":写单个寄存器(uint16)

首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:

--写单个寄存器(设备地址=0x03,地址=0x0000的寄存器)的值为0x1234
tab = LIB_MbRtuMasterSendTrans("06", 0x03, 0x0000, 0x1234)
然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("06",recv_tab)
if result > 0 then
print("Write single register ok")
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end

(7)功能码"0F":写多个线圈

首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:

data = {1,0,1,1}
--将data数组中的4个线圈值写入设备地址为0x03,线圈地址为0x0000~0x0003的四个位置中。注:函数中会根据起始地址0x0000,以及data数组中的元素个数,自动计算线圈地址范围为0x0000~0x0003
tab = LIB_MbRtuMasterSendTrans("0F", 0x03, 0x0000, data)
然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:
--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("0F",recv_tab)
if result > 0 then
print("Write multiple coils ok")
else
err_code = -128 - result --转换成实际modbus异常码
print(string.format("Fail,err_code = %d",err_code))
end

(8)功能码"10":写多个寄存器

首先生成主机需要发送的modbus命令字节流,以table形式返回,以供类似LIB_Uart0Send()这种串口发送函数使用:

data = {0xffff,0xffec,0x4133,0x3333}
--将data数组中的4个16bit值写入设备地址为0x03,寄存器地址为3500~3503的四个位置中。注:函数中会根据起始地址3500,以及data数组中的元素个数,自动计算寄存器的地址范围为3500~3503
tab = LIB_MbRtuMasterSendTrans("10", 0x03, 3500, data))

然后将接收到的从机应答字节流(recv_tab)进行解析,并返回结果:

--解析从机发来的应答字节流
result,content=LIB_MbRtuMasterRecvTrans("10",recv_tab)
if result > 0 then
print("Write multiple registers ok")
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等等),所以开发者在Lua编程中如果需要将某个带符号整型数据或浮点数据拆分成适合Modbus传输的原始16位数据,可借用功能强大的LIBBC()函数来实现,这样可避免复杂的Lua代码。LIBBC()的转换功能很多,详细请在API手册中查阅,下面只介绍其中两种常见的:

  1. 例如需要将“-20”这个带符号整型数据以(LONG AB CD)形式通过Modbus字节流发送,并写入从机
  2. 例如需要将“11.2”这个浮点小数以(FLOAT AB CD)形式通过Modbus字节流发送,并写入从机
data = {} --定义一个空数组,用来缓存下面这些需要写入的16bit寄存器值
aa = -20
--由于-20的(LONG AB CD)形式为32位补码0xffffffec,所以下面的函数会返回val1=0xffff,val2=0xffec
val1, val2 = LIB_BC("I32_BYTE16", aa)
data[1] = val1
data[2] = val2
--由于11.2的(FLOAT AB CD)形式为32位0x41333333,所以下面的函数会返回val1=0x4133,val2=0x3333
ff = 11.2
val1, val2 = LIB_BC("F32_BYTE16", ff)
data[3] = val1
data[4] = val2
--将data数组中的4个16bit值写入设备地址为0x03,寄存器地址为3500~3503的四个位置中。注:函数中会根据起始地址3500,以及data数组中的元素个数,自动计算寄存器的地址范围为3500~3503
tab = LIB_MbRtuMasterSendTrans("10", 0x03, 3500, data)

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
社评:《我的阿勒泰》为什么让西方媒体破防?

社评:《我的阿勒泰》为什么让西方媒体破防?

环球时报国际
2024-06-20 00:00:24
皇马震怒,干涉法国计划!不满姆巴佩推迟手术,勒令其连休2场!

皇马震怒,干涉法国计划!不满姆巴佩推迟手术,勒令其连休2场!

风过乡
2024-06-20 07:22:07
34℃,北京今日雷阵雨,局地阵风较大

34℃,北京今日雷阵雨,局地阵风较大

BRTV新闻
2024-06-20 09:45:02
深圳项目陷风波,华润发声!

深圳项目陷风波,华润发声!

老玉爱发明
2024-06-20 09:39:21
终于来了?美联储也是被逼无奈,美国货币战可以宣告失败了

终于来了?美联储也是被逼无奈,美国货币战可以宣告失败了

小宇宙双色球
2024-06-20 01:25:41
文案鬼才,爆改车标!

文案鬼才,爆改车标!

小影的娱乐
2024-06-19 13:26:15
太可惜!7年3次开颅,188天选大帅哥遭遇毁容,阳光男孩励志重生

太可惜!7年3次开颅,188天选大帅哥遭遇毁容,阳光男孩励志重生

闻言
2024-05-22 19:34:06
原来妇产科的瓜都这么炸裂的吗!网友们分享让人大跌眼镜!

原来妇产科的瓜都这么炸裂的吗!网友们分享让人大跌眼镜!

滑稽斑马呀
2024-06-18 20:13:35
俄罗斯副外长:任何想加入金砖的国家,均不应参与非法的单方制裁

俄罗斯副外长:任何想加入金砖的国家,均不应参与非法的单方制裁

新时光点滴
2024-06-20 04:25:07
突然决定退出独行侠!难了,欧文!整个NBA唯一支持者……

突然决定退出独行侠!难了,欧文!整个NBA唯一支持者……

篮球实战宝典
2024-06-19 14:56:49
出卖孟晚舟的真凶被挖出来后,如今遭到了哪些报应?

出卖孟晚舟的真凶被挖出来后,如今遭到了哪些报应?

老白调研室
2024-01-23 12:11:51
这是选美还是选丑?香港小姐2024佳丽引群嘲,网友:真不如以前的

这是选美还是选丑?香港小姐2024佳丽引群嘲,网友:真不如以前的

番茄说史聊
2024-06-18 23:57:10
50岁曹颖住院,透露7天没洗头要休养1个月,网友:生二胎坐月子?

50岁曹颖住院,透露7天没洗头要休养1个月,网友:生二胎坐月子?

娱小小新
2024-06-20 10:16:54
CCTV5直播!女排总决赛中日对决,12人大名单公布,冠军奖励100万

CCTV5直播!女排总决赛中日对决,12人大名单公布,冠军奖励100万

林子说事
2024-06-19 19:07:49
中纪委网站通报,“80后”纪亮被查

中纪委网站通报,“80后”纪亮被查

极目新闻
2024-06-20 11:30:21
已做好牺牲准备!海警登检菲船并不轻松:画面显示我方穿了防弹衣

已做好牺牲准备!海警登检菲船并不轻松:画面显示我方穿了防弹衣

青年的背包
2024-06-19 20:01:11
事闹大了,南方医科大领导召开紧急会议,俞莉医生疑似被停职。

事闹大了,南方医科大领导召开紧急会议,俞莉医生疑似被停职。

文雅笔墨
2024-06-18 01:43:02
黑龙江一考生因电话停机错过考编递补 为其充话费的人社局局长:该考生已主动联系

黑龙江一考生因电话停机错过考编递补 为其充话费的人社局局长:该考生已主动联系

红星新闻
2024-06-19 21:10:27
离岸人民币对美元短线跳水约50个基点,创去年11月以来新低

离岸人民币对美元短线跳水约50个基点,创去年11月以来新低

每日经济新闻
2024-06-20 09:23:06
网红学位房14万/平跌到4万/平?记者实探:几乎每月都有成交 购房者看中租金回报

网红学位房14万/平跌到4万/平?记者实探:几乎每月都有成交 购房者看中租金回报

北青网-北京青年报
2024-06-20 12:49:08
2024-06-20 15:26:44
FlexLua
FlexLua
用 Lua 编程语言开发 IoT 物联网硬件
107文章数 1关注度
往期回顾 全部

科技要闻

小米SU7流量泼天,富贵却被蔚来接住了

头条要闻

深圳网红学位房每平从14万跌到4万 中介:每月都有成交

头条要闻

深圳网红学位房每平从14万跌到4万 中介:每月都有成交

体育要闻

绿军的真老大,开始备战下赛季了

娱乐要闻

叶舒华参加柯震东生日聚会,五毒俱全

财经要闻

日本银行巨头突然爆雷!

汽车要闻

售价11.79-14.39万元 新一代哈弗H6正式上市

态度原创

房产
教育
时尚
健康
军事航空

房产要闻

海棠湾!一所重量级国际学校真的来了!

教育要闻

三人连环画共60本,甲给乙3本,乙给丙5本后,三人一样多,原来各

衣不穿花,裙不上膝,这才是50岁女人夏季应有的打扮,太美了

晚餐不吃or吃七分饱,哪种更减肥?

军事要闻

普京再送金正恩轿车 两人轮流当司机

无障碍浏览 进入关怀版