树莓派4B与智能涡轮流量计通过RS485(modbus RTU协议)收发数据(一)

        目标:使用树莓派及CAN HAT扩展板读取智能涡轮流量计(RS485通讯改装,使用modbus-RTU协议)的各项测量数据。

        由于是初次学习,对智能仪表以及树莓派串口通信等知识比较陌生,在此对实验过程进行记录。本文主要记录树莓派的485串口测试工作,后续会加入智能仪表进行实操。

        实验材料:

树莓派4B/8G

 RS485 CANHAT扩展板

USB转485转换器

 涡轮流量计WL-LWYE-15

 24V电源

树莓派安装库:

BCM2835

#打开树莓派终端,并运行以下指令
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.71.tar.gz
tar zxvf bcm2835-1.71.tar.gz 
cd bcm2835-1.71/
sudo ./configure && sudo make && sudo make check && sudo make install
# 更多的可以参考官网:http://www.airspayce.com/mikem/bcm2835/

wiringPi

#打开树莓派终端,并运行以下指令
sudo apt-get install wiringpi
#对于树莓派2019年5月之后的系统(早于之前的可不用执行),可能需要进行升级:
wget https://project-downloads.drogon.net/wiringpi-latest.deb
sudo dpkg -i wiringpi-latest.deb
gpio -v
# 运行gpio -v会出现2.52版本,如果没有出现说明安装出错

#Bullseye分支系统使用如下命令:
git clone https://github.com/WiringPi/WiringPi
cd WiringPi
./build
gpio -v
# 运行gpio -v会出现2.60版本,如果没有出现说明安装出错

python

sudo apt-get update
sudo apt-get install python-serial
sudo pip install python-can

下载例程

sudo apt-get install unzip
wget https://www.waveshare.net/w/upload/4/4e/RS485_CAN_HAT_Code.zip
unzip RS485_CAN_HAT_Code.zip
sudo chmod 777 -R RS485_CAN_HAT_Code/

以上过程可参照官网
RS485 CAN HAT - Waveshare Wiki

PC端安装软件串口调试助手(微软应用商店)用于测试通讯

 一、准备工作

        树莓派终端输入

sudo raspi-config
选择Interfacing Options -> Serial,关闭shell访问,打开硬件串口
 
 

 /boot/config.txt文件中加入:

enable_uart=1

这里我还更换了默认端口,关闭了蓝牙有关启动项,串口映射如下(据说ttyAMA0会更稳定):

将USB转串口转换器短接测试其是否正常工作,即发送与接收的数据是否相同。确认无误后将各硬件正确接线(T/R+对应CANHAT的A,T/R-对应B),串口调试工具波特率调整为9600,无校验位,1停止位(模拟智能流量计的参数),然后打开串口。

 二、python文件编写

receive.py:

# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import serial

EN_485 =  4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.LOW)

ser = serial.Serial("/dev/ttyAMA0",9600,timeout=1)  # open first serial port    
while 1:  
    str = ser.readall()  
    if str:  
        print (str)
        string=str.decode()
        note=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='w')
        note.write(string)
        note.close()

(波特率需要调成9600与仪表一致)

send.py:

# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import serial

EN_485 =  4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.HIGH)

t = serial.Serial("/dev/ttyAMA0",9600)    
print (t.portstr)    
strInput = input('enter some words:')    
n = t.write(strInput.encode())    
print (n)    

先运行receive.py,再运行send.py,用PC端模拟485仪表,先由树莓派端发送一段命令,PC端收到后回复,确保双方通讯没有问题。

树莓派send.py发送命令:

PC端成功接收并回复命令:

 

树莓派receive成功接收并写入data.txt: 

在实际与流量计通讯的过程中,树莓派作为上位机依据modbus-RTU协议向流量计发送命令,对于命令的执行结果流量计会进行返码,预想的情景中我会通过脚本每3min调用一次send查询一次瞬时流量值与累计流量值,查询结果会在receive窗口显示并覆盖地存入data.txt,然后进行后续对数据的处理。依据流量计说明书,上位机与流量计通信发送和接收的数据是一串十六进制编码,为理解其含义还需要学习modbus-RTU协议。

三、modbus-RTU协议

设备必须要有RTU协议!这是modbus协议上规定的,且默认模式必须是RTU,Ascii作为选项。(也就是说,一般的设备只有RTU这个协议,ascii一般很少)本实验所用的流量计使用的就是modbus-RTU协议。

1.帧结构

每一帧发送和接收的数据都具有以下结构:

帧结构 = 地址 + 功能码 + 数据 + 校验

地址:占用一个字节,范围0-255,其中有效范围是1-247,其他有特殊用途,比如255是广播地址(广播地址就是应答所有地址,正常的需要两个设备的地址一样才能进行查询和回复)。

功能码:占用一个字节,功能码的意义就是,知道这个指令是干啥的,不同功能码对应不同功能,常用的功能主要有查询(03)和单个寄存器修改(06),以及修改多个寄存器(10)。

数据: 根据功能码不同,有不同结构,在后续的实例中有说明。

校验: 为了保证数据不错误,增加这个,然后再把前面的数据进行计算看数据是否一致,如果一致,就说明这帧数据是正确的,我再回复;如果不一样,说明你这个数据在传输的时候出了问题,数据不对的,所以就抛弃了。

2.应用实例

2.1查询

现在我是主机,我要查询从机地址为1的数据,于是发送以下数据:

主机发送: 01 03 00 00 00 01 84 0A
从机回复: 01 03 02 12 34 B5 33

依据帧结构的解析,对这两组数据解读如下:

/*发送数据解析*/
01-地址
03-功能码,03代表查询功能
00 00-代表查询的起始寄存器地址.说明从0x0000开始查询.
(这里需要说明以下,Modbus把数据存放在寄存器中,通过查询寄存器来得到不同变量的值,一个寄存器地址对应2字节数据;)
00 01-这个数据表示查询的寄存器数量,这里代表查询了一个寄存器.结合前面的00 00,意思就是查询从0开始的1个寄存器值;
84 0A-循环冗余校验,是modbus的校验公式;
/*回复解析*/
01-地址
03-功能码
02-代表后面数据的字节数,因为上面说到,一个寄存器有2个字节,所以后面的字节数肯定是2*查询的寄存器个数,即如果查询了2个寄存器则此处会为04;
12 34-寄存器的值是12 34,结合发送的数据看出,01这个寄存器的值为12 34
B5 33-循环冗余校验

对于查询操作,基本流程就是:
发送:地址正确+我要查的寄存器个数+校验
回复:从机的地址+数据的字节数+数据+校验

2.2修改

这里只了解一下单个寄存器的修改~

为了修改从机的数据,我们发送以下代码:

主机发送: 01 06 00 00 00 01 48 0A
从机回复: 01 06 00 00 00 01 48 0A

可以发现修改操作完成之后从机会回复相同的一串代码,如果回复的功能码不是06而是86,那么说明修改出错了。

*发送数据解析*/
01-主机要查的地址
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前面为止;

修改操作主要流程:
发送:起始地址+数据内容+校验(因为你只需要修改一个,所以起始地址就是所要修改的地址)

回复:起始地址+数据内容+校验

可以看到发送与接受的代码均包含最后两位的校验位,而流量计采用的是无校验,当然无校验并不是真正意义上的没有校验,而是默认采用的CRC循环冗余校验,通过对校验位之前的所有数据位进行计算最终得出两位校验码,用于确保前面数据的准确性。校验位数据可以通过CRC16校验算法得出:

#include <iostream>
using namespace std;

unsigned short CRC16_Modbus1(unsigned char* pcData, int iDatalen)
{
    unsigned short crc = 0xFFFF;
    for (int j = 0; j < iDatalen; j++)
    {
        crc = crc ^ pcData[j];
        for (int i = 0; i < 8; i++)
        {
            if ((crc & 0x0001) > 0)
            {
                crc = crc >> 1;
                crc = crc ^ 0xa001;
            }
            else
                crc = crc >> 1;
        }
    }
    return crc;
}
 
int main()
{
    unsigned char data[6] = { 0x01,0x06,0x00,0x06,0x00,0x01 };
    unsigned short CRC= CRC16_Modbus1(data, 6);
    std::cout << CRC;
}

通过以上算法获得的校验位结果还需要依据“低字在前,高字在后”进行交换才能得到最终结果,以代码中的命令为例,计算的命令为“01 06 00 06 00 01”,最终得出结果十进制为2984,转化成十六进制0BA8,高低字交换得到“A8 0B”,这就是最终的校验位数值。

在实际应用场景中如果只是查询指定寄存器的数值或者清零指定的寄存器一般都是固定的一条命令,因此校验位不用每次重新计算,比如本实验中流量计查询瞬时流量值或者清零累计流量值,为了方便我会通过脚本调用指定的计算好的命令;但是在较复杂的应用情景下,如果需要频繁修改不同的寄存器为不同的值,则需要每次对命令进行CRC16计算得到其校验位,再拼接成一条完整的命令进行发送。

四、总结

本篇基本配置好了树莓派串口的参数与收发函数,与PC端的模拟通信调试无误;学习了modbus-RTU协议,理解了命令结构与意义。之后准备用涡轮流量计进行实操,会在下一篇博客记录一下过程~

猜你喜欢

转载自blog.csdn.net/qq_43824745/article/details/125860297