提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
Modbus协议介绍
什么是Modbus协议
MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。
MODBUS 网络体系结构的实例
每种设备(PLC、HMI、控制面板、驱动程序、动作控制、输入/输出设备)都能使用 MODBUS协议来启动远程操作。
在基于串行链路和以太 TCP/IP 网络的 MODBUS 上可以进行相同通信。
一些网关允许在几种使用 MODBUS 协议的总线或网络之间进行通信。
Modbus比其他通信协议使用的更广泛的主要原因有:
- 公开发表并且无版权要求
- 易于部署和维护
- 对供应商来说,修改移动本地的比特或字节没有很多限制
Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。
总体描述
协议描述
MODBUS 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。特定总线或网络上的 MODBUS 协议映射能够在应用数据单元(ADU)上引入一些附加域。
数据编码
MODBUS 使用一个‘big-Endian’ 表示地址和数据项。这意味着当发射多个字节时,首先发送最高有效位。例如:
寄存器大小 值
16 – 比特 0x1234 发送的第一字节为 0x12 然后 0x34
MODBUS 数据模型
MODBUS 以一系列具有不同特征表格上的数据模型为基础。四个基本表格为:
通讯过程
Modbus是主从方式通信,也就是说,不能同步进行通信,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。(所以说,这也算是一个缺点了)
就像打电话,你得知道对方的电话(这就是唯一地址),然后你打电话过去,相当于主机查找从机,然后对方接通电话,给你回复(返回数据),正常是这样的。
如果这时候,对方正在打电话,你应该听到的是"sorry,you…"这一串英文,说明对方忙,但是Modbus总线不能判断对方是否忙,也没有对应的仲裁机制。
Modbus-RTU协议
设备必须要有RTU协议!这是Modbus协议上规定的,且默认模式必须是RTU,ASCII作为选项。
1、帧结构
帧结构 = 地址 + 功能码+ 数据 + 校验
-
地址: 占用一个字节,范围0-255,其中有效范围是1-247,其他有特殊用途,比如255是广播地址(广播地址就是应答所有地址,正常的需要两个设备的地址一样才能进行查询和回复)。
-
功能码:占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改数据,所以不同功能码对应不同功能。
-
数据:根据功能码不同,有不同结构,在下面的实例中有说明。
-
校验:为了保证数据不错误,增加这个,然后再把前面的数据进行计算看数据是否一致,如果一致,就说明这帧数据是正确的,我再回复;如果不一样,说明你这个数据在传输的时候出了问题,数据不对的,所以就抛弃了。
2、常用功能码
Modbus-RTU协议一般我们用的最多功能码就是03和06,大部分都是用modbus来查询传感器上的信息用03查询功能码,如果需要修改传感器寄存器的值就用06修改功能码。
1.查询功能码0x03
功能描述:现在我是主机,我要查询从机地址为01的数据。我现在用电脑的modbus调试助手来代替主机,stm32来代替从机。
发送数据解析
01-地址,也就是你传感器的地址
03-功功能码,03代表查询功能,查询传感器的数据
00 00-代表查询的起始寄存器地址.说明从0x0000开始查询。这里需要说明以下,Modbus把数据存放在寄存器中,通过查询寄存器来得到不同变量的值,一个寄存器地址对应2字节数据
00 01-代表查询了一个寄存器.结合前面的00 00,意思就是查询从0开始的1个寄存器值
84 0A-循环冗余校验,是modbus的校验公式,从首个字节开始到84前面为止;
回复数据解析
01-地址,也就是你传感器的地址
03-功功能码,03代表查询功能,查询传感器的数据。这里要注意的是注意发给从机的功能码是啥,从机就要回复同样的功能码,如果不一样说明这一帧数据有错误
02-代表后面数据的字节数,因为上面说到,一个寄存器有2个字节,所以后面的字节数肯定是2*查询的寄存器个数;
19 98-寄存器的值是19 98,结合发送的数据看出,01这个寄存器的值为19 98
B2 7E-循环冗余校验
基本流程就是:
发送:从机的地址+我要干嘛的功能码+我要查的寄存器的地址+我要查的寄存器地址的个数+校验码
回复:从机的地址+主机发我的功能码+要发送给主机数据的字节数+数据+校验码
2.修改功能码0x06
如果我要修改从机的数据呢?那么这个协议有吗?当然有,那就是0x06
主机发送: 01 06 00 00 00 01 48 0A
从机回复: 01 06 00 00 00 01 48 0A
发送数据解析
01-主机要查询的从机地址
06-功能码,06代表修改单个寄存器功能,修改有些不同,有修改一个寄存器和修改多个寄存器;
00 00-代表修改的起始寄存器地址.说明从0x0000开始.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
48 0A-循环冗余校验,是modbus的校验公式,从首个字节开始到48前面为止;
回复数据解析
01-从机返回给主机自己的地址,说明这就是主机查的从机
06-功能码,代表修改单个寄存器功能,主机发啥功能码,从机就必须回什么功能码;
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
48 0A-循环冗余校验,是modbus的校验公式,从首个字节开始到48前面为止;
如果回复的一样,说明这个数据是修改成功的;如果功能码不是06,而是别的,说明从机回复的数据有误,主机可以做相应的处理。
Modbus从云端获取信息(python语言)
从云端获得信息主要有两种方式,一种是一是通过TCP协议进行请求,二是通过UDP协议进行请求,下面主要分析使用python进行TCP协议进行请求,UDP协议除了需要每次请求数据时都要与进行连接以外,其它数据请求格式和解析接收的数据与TCP一致。
TCP方式
1.建立连接
tcp = socket.socket(socket.AF_INET,
socket.SOCK_STREAM,
socket.IPPROTO_TCP)
tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp.settimeout(5)
try:
tcp.connect(('demo-monitor.igong.com', 8002))
print("TCP连接成功")
except:
print("连接TCP失败")
sys.exit(1)
2.生成crc16校验位
def crc16(string):
# data = bytes.fromhex(string)
data = string
crc = 0xFFFF
for pos in data:
crc ^= pos
for i in range(8):
if ((crc & 1) != 0):
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return hex(((crc & 0xff) << 8) + (crc >> 8))
3.指令发送和接收
def getStain(cmd, num, time):
# print(cmd)
# print(num)
cmd = bytes.fromhex(cmd)
crc = crc16(cmd)
crc = bytes.fromhex(crc[2:])
cmd = cmd + crc
# print(cmd)
# 发送对应的指令
tcp.send(cmd)
try:
data = tcp.recv(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc1 = crc16(data[:-2])
crc1 = crc1[2:]
if len(crc1) == 3:
crc1 = '0' + crc1
crc1 = bytes.fromhex(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
yb, wd = struct.unpack('>ii', data[4:12])
yb = yb / 100.0
wd = wd / 100.0
print("应变:", yb, "温度:", wd)
print(time)
yb = str(yb)
wd = str(wd)
AddData(num, yb, wd, time)
4.数据储存
# 连接数据
def MySQLConnect():
connection = pymysql.connect(
host='localhost', # IP,MySQL数据库服务器IP地址
port=3306, # 端口,默认3306,可以不输入
user='你的名字', # 数据库用户名
password='你的密码', # 数据库登录密码
database='sensor', # 要连接的数据库
charset='utf8' # 字符集,注意不是'utf-8'
)
return connection
# 插入数据到数据库
def AddData(num, yb, wd, time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO strain_sensor(id ,mic, strain_temp, time) VALUES (%s,%s,%s,%s); "
cursor.execute(sql, [num, yb, wd, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
Modbus—TCP采集完整代码
import socket
import sys
import struct
import time
import tcp
import threading
import _thread
import pymysql
# 本程序是应变传感器采集,可以通过发送ALL指令进行全部传感器的采集
def crc16(string):
# data = bytes.fromhex(string)
data = string
crc = 0xFFFF
for pos in data:
crc ^= pos
for i in range(8):
if ((crc & 1) != 0):
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return hex(((crc & 0xff) << 8) + (crc >> 8))
# 连接数据
def MySQLConnect():
connection = pymysql.connect(
host='localhost', # IP,MySQL数据库服务器IP地址
port=3306, # 端口,默认3306,可以不输入
user='你的用户名', # 数据库用户名
password='你的密码', # 数据库登录密码
database='sensor', # 要连接的数据库
charset='utf8' # 字符集,注意不是'utf-8'
)
return connection
# 插入数据到数据库
def AddData(num, yb, wd, time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO strain_sensor(id ,mic, strain_temp, time) VALUES (%s,%s,%s,%s); "
cursor.execute(sql, [num, yb, wd, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
# 获取一次数据
def getStain(cmd, num, time):
# print(cmd)
# print(num)
cmd = bytes.fromhex(cmd)
crc = crc16(cmd)
crc = bytes.fromhex(crc[2:])
cmd = cmd + crc
# print(cmd)
# 发送对应的指令
tcp.send(cmd)
try:
data = tcp.recv(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc1 = crc16(data[:-2])
crc1 = crc1[2:]
if len(crc1) == 3:
crc1 = '0' + crc1
crc1 = bytes.fromhex(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
yb, wd = struct.unpack('>ii', data[4:12])
yb = yb / 100.0
wd = wd / 100.0
print("应变:", yb, "温度:", wd)
print(time)
yb = str(yb)
wd = str(wd)
AddData(num, yb, wd, time)
def setCircleData(cmd, num):
count = 0
flag = 0
last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
last1 = time.time()
while True:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
now1 = time.time()
# print(now)
if (flag == 0):
count = count + 1
flag = 1
getStain(cmd, num, last)
last = now
last1 = now1
if now1 - last1 > 5:
if count >= 5:
str = input("请选择是否继续采集(y表示继续,n表示退出):")
if str == 'y':
count = 0
continue
else:
break
count = count + 1
getStain(cmd, num, now)
last = now
last1 = now1
# 同时采集全部应变传感器
def setCircleAll(cmd):
flag = 0
count = 0
last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
last1 = time.time()
while True:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
now1 = time.time()
# count=0
# print(now)
if (flag == 0):
flag = 1
count = count + 1
for cmd1 in cmd:
id = '00' + cmd1[0:2]
# print(id)
# print(cmd1)
getStain(cmd1, id, last)
last = now
last1 = now1
if now1 - last1 > 5:
if count >= 5:
str = input("请选择是否继续采集(y表示继续,n表示退出):")
if str == 'y':
count = 0
continue
else:
break
count = count + 1
for cmd1 in cmd:
id = '00' + cmd1[0:2]
getStain(cmd1, id, now)
last = now
last1 = now1
if __name__ == '__main__':
tcp = socket.socket(socket.AF_INET,
socket.SOCK_STREAM,
socket.IPPROTO_TCP)
tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp.settimeout(5)
try:
tcp.connect(('demo-monitor.igong.com', 8002))
print("TCP连接成功")
except:
print("连接TCP失败")
sys.exit(1)
flag = 0
while True:
print("具体指令给格式为000+传感器编号(1,2,3,4,5)")
num = input("请输入采集传感器的编号(All表示采集全部传感器,0表示退出采集):")
if num == '0001':
cmd = '010300010002'
setCircleData(cmd, num)
elif num == '0002':
cmd = '020300010002'
setCircleData(cmd, num)
elif num == '0003':
cmd = '030300010002'
setCircleData(cmd, num)
elif num == '0004':
cmd = '040300010002'
setCircleData(cmd, num)
elif num == '0005':
cmd = '050300010002'
setCircleData(cmd, num)
elif num == 'All':
cmd = {'010300010002', '020300010002', '030300010002', '040300010002', '050300010002'}
setCircleAll(cmd)
elif num == '0':
break
else:
print("输入信息不合法,请重新输入")
UDP方式
1.建立连接
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
udp.settimeout(5)
源代码如下:
import socket
import struct
import sys
import time
import pymysql
#实现采集温度传感器和静力水准仪
def crc16(string):
#data = bytes.fromhex(string)
data=string
crc = 0xFFFF
for pos in data:
crc ^= pos
for i in range(8):
if ((crc & 1) != 0):
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return hex(((crc & 0xff) << 8) + (crc >> 8))
#连接数据库
def MySQLConnect():
connection = pymysql.connect(
host='localhost', # IP,MySQL数据库服务器IP地址
port=3306, # 端口,默认3306,可以不输入
user='你的用户名', # 数据库用户名
password='你的密码', # 数据库登录密码
database='sensor', # 要连接的数据库
charset='utf8' # 字符集,注意不是'utf-8'
)
return connection
#插入温湿度采集到数据库
def AddData1(wd,sd,time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO temp_hum_sensor(temp, hum, time) VALUES (%s,%s,%s); "
cursor.execute(sql, [wd, sd, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
#插入静力水准仪采集到数据库
def AddData2(id,water_level,time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO static_level(id, water_level, time) VALUES (%s,%s,%s); "
cursor.execute(sql, [id, water_level, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
#采集温度传感器的数据
def getDataTemp(cmd):
#flag标志采集的次数
flag=0
last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(last)
last1 = time.time()
cmd = bytes.fromhex(cmd)
#print(cmd)
crc = crc16(cmd)
crc = bytes.fromhex(crc[2:])
#得到发送的指令(modbus协议定义内容+校验)
cmd = cmd + crc
udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
try:
data, addr = udp.recvfrom(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc1 = crc16(data[:-2])
crc1 = crc1[2:]
if (len(crc1) == 3):
crc1 = '0' + crc1
crc1 = bytes.fromhex(crc1)
# print(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
# 解析数据
wd, sd = struct.unpack('>ii', data[4:12])
wd = wd / 100.
print("温度:", wd, "湿度:", sd)
AddData1(wd, sd, last)
flag=flag+1
while True:
now= time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
#print(s)
now1=time.time()
#每隔5s获取一次数据
if(now1-last1>5):
udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
try:
data, addr = udp.recvfrom(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc2=bytes.hex(crc)
#print(crc2)
crc1 = crc16(data[:-2])
crc1=crc1[2:]
if(len(crc1)==3):
crc1='0'+crc1
#print(crc1)
crc1=bytes.fromhex(crc1)
#print(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
#解析数据
wd, sd = struct.unpack('>ii', data[4:12])
wd = wd / 100.0
#当前时间
print(now)
#获取得到的数据
print("温度:", wd, "湿度:", sd)
last=now
last1=now1
wd=str(wd)
sd=str(sd)
AddData1(wd,sd,now)
flag = flag + 1
if flag >= 5:
str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
if str1 == 'y':
flag = 0
continue
else:
break
def getDataStaticLevel(cmd):
id=cmd[0:2]
#print(id)
if id=='02':
#print("2号")
id='00'+id
getData(id,cmd)
elif id=='03':
#print("3号")
id = '00' + id
getData(id,cmd)
elif id=='04':
#print("4号")
id = '00' + id
getData(id,cmd)
elif id=='05':
#print("5号")
id = '00' + id
getData(id,cmd)
def getData(id,cmd):
flag=0
last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(last)
last1 = time.time()
cmd = bytes.fromhex(cmd)
crc = crc16(cmd)
crc = crc[2:]
if (len(crc) == 3):
crc = '0' + crc
crc = bytes.fromhex(crc)
cmd = cmd + crc
udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
print("发送数据成功")
try:
data, address = udp.recvfrom(8192)
#print(data)
except socket.timeout:
print("超时")
sys.exit(1)
#print(len(data))
crc = data[-2:]
#print(data[:-2])
crc1 = crc16(data[:-2])
#print(crc1)
crc1=crc1[2:]
if len(crc1) == 3:
crc1 = '0' + crc1
#print(crc1)
crc1 = bytes.fromhex(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
#print(data[4:8])
nd = struct.unpack('>i', data[4:8])
#print(nd)
nd1 = nd[0]*10.0
nd1=str(nd1)
#print(last)
print("挠度:"+nd1)
AddData2(id,nd1,last)
flag=flag+1
while True:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# print(s)
now1 = time.time()
# 每隔5s获取一次数据
if (now1 - last1 > 5):
udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
try:
data, addr = udp.recvfrom(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc2 = bytes.hex(crc)
# print(crc2)
crc1 = crc16(data[:-2])
crc1 = crc1[2:]
if (len(crc1) == 3):
crc1 = '0' + crc1
# print(crc1)
crc1 = bytes.fromhex(crc1)
# print(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
# 解析数据
nd = struct.unpack('>i', data[4:8])
nd = nd[0] * 10.0
nd1=str(nd)
print(now)
print("挠度:" + nd1)
nd=str(nd)
AddData2(id, nd1, now)
last=now
last1=now1
flag = flag + 1
if flag >= 5:
str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
if str1 == 'y':
flag = 0
continue
else:
break
if __name__ == '__main__':
print("开始程序")
print("程序相关说明:")
print("本程序采用UDP协议,其中当输入指令为0就退出整个程序。")
print("命令格式类似于地址(01,02,03,04,05)+03+传感器地址(0001)+传感器个数(0001,0002)")
print("例如:010300010002(温度传感器),020300010001(静力水准仪1)")
print("如果出现不合法指令就输出提示信息,并重新输入指令。")
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
udp.settimeout(5)
while True:
cmd = input("请输入相关命令:")
#print(len(cmd))
num = cmd[8:]
#print(num)
if num == '0001' and cmd[0:2] == '01':
print("此处不实现只对温度采集!!!")
elif num == '0001':
print("一个传感器")
getDataStaticLevel(cmd)
elif num == '0002':
print("两个传感器")
getDataTemp(cmd)
elif cmd == '0':
break
else:
print("指令不合法!!!")
实现效果
Modbus从云端获取信息(C语言)
1.初始化socket.dll
WORD winsock_version = MAKEWORD(2,2);
WSADATA wsa_data;
if (WSAStartup(winsock_version, &wsa_data) != 0) {
printf("Failed to init socket!\n");
return 1;
}
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client_socket == INVALID_SOCKET) {
printf("Failed to create server socket!\n");
return 2;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("Failed to connect server: %ld !\n", GetLastError());
return 3;
}
2.生成crc16校验码
uint16_t CRC_16(uint8_t *temp)
{
uint8_t i,j;
uint16_t CRC_1 = 0xFFFF; //声明CRC寄存区,也就是步骤1
for(i = 0;i < 6;i++) //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
{
CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
for(j = 0;j < 8;j++) //用来将异或后的低八位全部移出的for循环
{
if(CRC_1 & 0x01) //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
{
/*一定要先移位,再异或*/
CRC_1 >>=1; //移位后再异或,就是步骤4
CRC_1 ^= 0xA001; //0xA001为0x8005的逆序
}
else //若不为1,则直接移位。
{
CRC_1 >>=1;
}
}
}
// CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
// printf("%04x\r\n",CRC_1); //用于打印检测CRC校验码
return(CRC_1);
}
3.分析数据并取出
int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
if (ret < 0) {
printf("Failed to receive data!\n");
break;
}
recv_data[ret]=0; // correctly ends received string
char yb[4],wd[4];
for(int i=0;i<4;i++){
//TODO
yb[i] = recv_data[4+i];
wd[i] = recv_data[8+i];
}
float mic = hexToDec(yb)/100.0;
float strain_temp = hexToDec(wd)/100.0;
printf("应变:%f\r\n",mic);
printf("温度:%f\r\n",strain_temp);
完整代码如下:
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <winsock2.h>
#include <math.h>
#include "stdint.h"
#define length_8 8 //定义一个宏,为传入8位16进制数的个数
#define PORT 8002
#define SERVER_IP "123.56.90.74"
#define BUFFER_SIZE 4196
const char* kExitFlag = "exit";
/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
if(ch >= '0' && ch <= '9')
{
return ch - '0';
}
if(ch >= 'A' && ch <='F')
{
return ch - 'A' + 10;
}
if(ch >= 'a' && ch <= 'f')
{
return ch - 'a' + 10;
}
return -1;
}
/* 十六进制数转换为十进制数 */
int hexToDec(char *source)
{
int sum = 0;
int t = 1;
int i, len=4;
char low,high;
for(int i=0,j=7;i<4;i++){
//TODO
high = (source[i] & 0xf0)>>4;
low = source[i] & 0x0f;
sum += high*pow(16,j--)+low*pow(16,j--);
}
return sum;
}
const unsigned char *fromhex(const char *str)
{
static unsigned char buf[512];
size_t len = strlen(str) / 2;
if (len > 512) len = 512;
for (size_t i = 0; i < len; i++) {
unsigned char c = 0;
if (str[i * 2] >= '0' && str[i*2] <= '9')
c += (str[i * 2] - '0') << 4;
if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F')
c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9')
c += (str[i * 2 + 1] - '0');
if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
buf[i] = c;
}
return buf;
}
uint16_t CRC_16(uint8_t *temp)
{
uint8_t i,j;
uint16_t CRC_1 = 0xFFFF; //声明CRC寄存区,也就是步骤1
for(i = 0;i < 6;i++) //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
{
CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
for(j = 0;j < 8;j++) //用来将异或后的低八位全部移出的for循环
{
if(CRC_1 & 0x01) //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
{
/*一定要先移位,再异或*/
CRC_1 >>=1; //移位后再异或,就是步骤4
CRC_1 ^= 0xA001; //0xA001为0x8005的逆序
}
else //若不为1,则直接移位。
{
CRC_1 >>=1;
}
}
}
// CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
// printf("%04x\r\n",CRC_1); //用于打印检测CRC校验码
return(CRC_1);
}
int main() {
// 初始化socket dll。
WORD winsock_version = MAKEWORD(2,2);
WSADATA wsa_data;
if (WSAStartup(winsock_version, &wsa_data) != 0) {
printf("Failed to init socket!\n");
return 1;
}
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client_socket == INVALID_SOCKET) {
printf("Failed to create server socket!\n");
return 2;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("Failed to connect server: %ld !\n", GetLastError());
return 3;
}
char recv_data[BUFFER_SIZE+1];
while (true) {
uint8_t data[length_8];
printf("0+传感器编号(1,2,3,4,5)0300010002\r\n");
scanf("%s",data);
uint16_t crc;
unsigned char * cmd;
char crc1[8];
cmd = fromhex(data);
crc = CRC_16(cmd);
uint8_t a = 0xFF;
for(int i=0;i<6;i++){
//TODO
crc1[i] = cmd[i];
}
crc1[6] = a & crc;
crc1[7] = (crc >> 8) & a;
if (send(client_socket, crc1, 8, 0) < 0) {
printf("Failed to send data!\n");
break;
}
int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
if (ret < 0) {
printf("Failed to receive data!\n");
break;
}
recv_data[ret]=0; // correctly ends received string
char yb[4],wd[4];
for(int i=0;i<4;i++){
//TODO
yb[i] = recv_data[4+i];
wd[i] = recv_data[8+i];
}
float mic = hexToDec(yb)/100.0;
float strain_temp = hexToDec(wd)/100.0;
printf("应变:%f\r\n",mic);
printf("温度:%f\r\n",strain_temp);
// printf("Receive data from server: \"%x\"\n",recv_data);
if (strcmp(data,kExitFlag)==0) {
printf("Exit!\n");
break;
}
}
closesocket(client_socket);
WSACleanup();
return 0;
}
实现效果
总结
通过本次实验,学习到了Modbus协议通信的原理,了解了它的特点,记住了常用的功能码,实验中对UDP和TCP协议进行实验,也对Modbus主从通信的方式有了更深刻的体会和理解。
参考连接:
https://zhuanlan.zhihu.com/p/145546574