STM32103C8T6利用Modbus协议从云端读取温湿度数据


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


Modbus协议介绍

什么是Modbus协议

MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。

MODBUS 网络体系结构的实例
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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值