基于485总线的评分系统实验报告
计科210X wolf 202108010XXX
同组成员:wolf、DragonKing、Eliak
验收分数:90(未加入可视化的代码最高90分)
1 实验项目
1.1 项目名称
基于485总线的评分系统
1.2 实验目的
通过本案例加深理解RS485通信方式,实现上位机的主控制器与所有的下位机进行通信。
1.3 实验资源
PC、STC学习板等
1.4 实验简介
1.5 实验通信协议
本案例模拟Modbus协议,采用主、从技术,上位机的主控制器可以与所有的下位机通信,也可以单独与一个指定的下位机通信。模拟Modbus协议中,上下位机的数据包都只含5个字节,其基本格式为:数据包头(0x5A)+地址码(广播地址/从机地址)+功能码+携带数据(一个字节)+校验码字节,携带数据部分可以扩充多个字节,可以视情况进行修改。数据包具体定义如下:
(1)主机检测从机是否正常相关数据包:(主机与单个从机设备通信)
A、设备正常检测数据包:
方向:上位机----->下位机
数据包消息:数据包头+从机地址+检测功能码(Fun_CheckSlave)+自定义内容(Check_Content)+校验字节
功能:查询下位机是否正常。正常,下位机发送回应查询数据包;不正常,则下位机不予回应;数据传输过程发生错误,下位机发送回应错误数据包,上位机可以通过设置多次轮询来重新检测该设备是否正常;
B、回应查询数据包:
方向:下位机----->上位机
数据包消息:数据包头+从机地址+检测功能码(Fun_CheckSlave)+自定义内容(接收自主机Check_Content)+校验字节
C、回应错误数据包:
方向:下位机----->上位机
数据包消息:数据包头+从机地址+检测功能码(Fun_CheckSlave)+错误码(ErrorInfo)+校验字节
(2)主机获取从机评分相关数据包:(主机与单个从机设备通信)
D、获取多、单机评分数据包:
方向:上位机----->下位机
数据包消息:数据包头+检测正常从机地址(0x00)+读下位机功能码(Fun_ReadInfo)+从机地址+校验字节
功能:对检测正常的设备,进行一次轮询,获取评分已经准备好的从机的分数。对于单机直接进行通信,没有轮询。
E、结果返回数据包:
方向:下位机----->上位机
数据包消息:数据包头+从机地址+读下位机功能码(Fun_ReadInfo)+从机返回的分数值+校验字节(分数值>100:表示上面提及的未准备好,回应错误数据包)
(3)此轮评分结束相关数据包
F、复位数据包:(主机与所有从机通信)
方向:上位机----->下位机
数据包消息:数据包头+广播地址+复位功能码(Fun_Reset)+从机返回的分数值(0x00)+校验字节
功能:指示所有正常连接的从机进行复位操作,准备下一轮的评分。
2 实验任务
2.1 实验任务一
任务名称:B级任务
2.1.1 实验步骤
1、 阅读程序系统流程框图,明确双机通信的功能需求。
2、 熟悉上一节中模拟MODBUS协议的数据包结构,相关功能码及附加数据定义
功能码 | 读下位机功能码 | 0X03 |
---|---|---|
检测功能码 | 0X08 | |
地址错误功能码 | 0X10 | |
复位功能码 | 0X01 | |
附加数据 | 错误码 | 0X6F |
包头 | 0X5A | |
广播地址 | 0X00 | |
自定义内容 | 0X13 |
1、 回顾485总线数据收发实验,搭建双机通信电路。参考上一节内容确保STC从机编号和评分设定完成后,按下KEY2、KEY3按键标志,第1位和第8位LED灯被点亮。
2、 PC端串口设置如下:
串口波特率:9600 数据位:8位 校验位:无 停止位:1
3、 所编写的PC端程序应参考上一节中的通信协议完成一次完整的评分过程:
- 需要包含串口的设置
- 主节点发起从机检测过程:发送指定从机编号正常检测数据包,判断回应查询数据包是否符合上一节中的通信协议。
- 主机获取从机评分过程:发送指定从机评分相关数据包,判断回应查询数据包是否符合上一节中的通信协议。
- 主机发起结束评分的过程,若复位成功,STC从机上第1位和第8位LED灯会熄灭。
- 展示串口相关信息,展示检测到的从机编号和从机的评分等。
2.1.2 程序代码
由于直接实现A级任务,这一部分略去。
2.1.3 运行结果分析
由于直接实现A级任务,这一部分略去,在A类任务中再叙述。
2.2 实验任务二
任务名称:A级任务
2.2.1 实验步骤
在B级任务基础上,扩充程序功能如:允许最大从机数、轮询次数、错误数据包的处理、统计多人评分的平均分等。
以下是新增的功能
★添加了自动查询所有在线从机数并返回每一位从机的在线情况。
★添加了“允许最大从机数、轮询次数”的配置,而且可以从配置文件中进行读取而不是在代码中手动敲入。
★添加了错误数据包的处理:可以处理超过100分的分数,不计入;可以处理未锁定分数的情况,返回错误信息。
★添加了平均分的计算。
★添加了调试信息是否输出的选择性开关“debug”。
2.2.2 程序代码
注意,由于实现了从配置文件中读取配置信息,故在同目录下需同时放有config.txt
。
import binascii
import serial.tools.list_ports
#read config
def openreadconfig(file_name):
data = []
file = open(file_name, 'r',encoding='utf-8') # 打开文件
file_data = file.readlines() # 读取所有行
count = 0
for row in file_data:
tmp_list = row.split(' ') # 按‘,'切分每行的数据
tmp_list[-1] = tmp_list[-1].replace('\n','') #去掉换行符
count+=1
if count == 1:
data.append(float(tmp_list[1])) # 将每行数据插入data中
else:
data.append(int(float(tmp_list[1]))) # 将每行数据插入data中
return data
config = openreadconfig('config.txt')
my_timeout = config[0] #timeout 设置为0.04
device_upper = config[1] #机器编号范围上限是多少
polling_num = config[2] #轮询次数 设置为15
debug = config[3] #是否打印调试信息 1为打印
if debug: print("读入参数列表为{}".format(config))
# initial
plist = list(serial.tools.list_ports.comports()) # 获取端口列表
ser = serial.Serial(list(plist[0])[0], 9600, timeout=my_timeout) # 导入pyserial模块
#若上面出现错误,则考虑手动设置端口,也就是使用下面的这一条,注意COM多少
#ser = serial.Serial("COM6", 9600, timeout=my_timeout)
#其中timeout参数为操作未完成时发生超时之前的毫秒数
#9600为波特率,不需要调节
#从串口读数据
def read_times():
while 1:
dic = []
reading = ser.read(5) # 读取串口数据
if reading != b'':
a = str(hex(int(binascii.hexlify(reading), 16)))
b = a.replace("0x", "")
for index in range(0, len(b), 2):
dic.append(b[index] + b[index + 1])
return dic
devices=list(range(0,device_upper)) #初次遍历机器的范围
print(devices)
onlineDevices=[] # 在线的机器
# part 1: 查询在线从机信息
print("查询在线从机信息")
for device in devices:
data = [0x5A, device, 0x08, 0x13]
data.append(sum(data))
if debug: print("{}\n从机设备编号: {:2d} 校验信息为: {}\n尝试校验中...".format('-'*50, device, data))
for _ in range(polling_num):
ser.write(data)
retdata = read_times()
if retdata:
if debug: print("返回值:{}".format(retdata))
retdata = [int(i,16) for i in retdata]
if retdata == data:
if debug: print("返回的校验信息为: {},从机正常。".format(retdata))
print("从机{:2d}在线".format(device))
onlineDevices.append(device)
break
# part 2: 读取从机分数
total=0
print('-'*50)
print('从机分数读取:')
for device in onlineDevices:
data = [0x5A, 0x00, 0x03, device]
data.append(sum(data))
if debug: print("{}\n从机设备编号: {:2d} 发送信息为: {}\n尝试获取分数中...".format('-'*50, device, data))
flag = True
while 1:
ser.write(data)
retdata = read_times()
if retdata:
if debug: print("返回值:{}".format(retdata))
retdata = [int(i,16) for i in retdata]
if debug: print("返回值:{}".format(retdata))
if retdata[1] == device and retdata[4] == sum(retdata[:4]):
if retdata[3] == 0x6F:
print("读取错误,从机{:2d}分数未确认".format(device))
else:
if debug: print("该从机分数为: {},从机正常。".format(retdata[3]))
if retdata[3]>0x64:
print("分数错误:超过100分,记为0分")
else:
total+=retdata[3]
else:
print("从机传输结果异常")
flag = False
break
if flag:
print("从机{:2d}无返回".format(device))
# part 3: 计算输出平均分
average = total/len(onlineDevices)
print('-'*50)
print("平均分为{}".format(average))
print('-'*50)
# part 4: 从机复位操作
print('从机复位操作:')
data = [0x5A, 0x00, 0x01, 0x00, 0x5B]
for _ in range(100):
ser.write(data)
print("从机已复位,可以开始下一轮评分。")
下面是配置文件config.txt
。
timeout: 0.06
机器编号范围上限: 10
轮询次数: 20
是否打印调试信息: 1
同时,我试图进行可视化的探索,在宿舍成功了。但在现场验收时又碰上了串口的错误,再由于时间较为紧急,故果断舍弃可视化部分的代码,直接使用终端作为输出载体。
下面是我可视化的代码,采取生产可视化窗口并调用上面的功能代码的方式。
import tkinter as tk
import sys
import subprocess
#from b import average
def start_button_clicked():
#
var1 = 1#average
# 在各个文本框中显示变量的值
var1_text.delete("1.0", tk.END)
var1_text.insert(tk.END, var1)
# 在输出文本框中显示 Python 的输出信息
result = subprocess.Popen(['python', 'b.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = result.communicate()
print(output[0].decode('utf-8'))
# 创建主窗口
window = tk.Tk()
# 创建变量1的文本框
var1_label = tk.Label(window, text="average:")
var1_label.grid(row=0, column=0)
var1_text = tk.Text(window, width=20, height=1)
var1_text.grid(row=0, column=1)
# 创建开始按钮
start_button = tk.Button(window, text="开始", command=start_button_clicked)
start_button.grid(row=6, column=0, columnspan=2)
# 创建输出文本框
output_text = tk.Text(window, width=40, height=10)
output_text.grid(row=0, column=2, rowspan=7)
# 重定向输出
class StdoutRedirector:
def __init__(self, text_widget):
self.text_widget = text_widget
def write(self, text):
self.text_widget.insert(tk.END, text)
self.text_widget.see(tk.END)
def flush(self):
pass
# 将标准输出重定向到文本框
output_text_redirector = StdoutRedirector(output_text)
sys.stdout = output_text_redirector
# 运行主循环
window.mainloop()
这部分代码仍待探索,暂时没有完成。
2.2.3运行结果分析
测试1:
运行界面:
下面是终端输出结果截图
下面是终端输出结果的文字版
C:\Users\y\AppData\Local\Programs\Python\Python310\python.exe C:/Users/y/Desktop/工训/b.py
读入参数列表为[0.04, 10, 20, 1]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
查询在线从机信息
--------------------------------------------------
从机设备编号: 0 校验信息为: [90, 0, 8, 19, 117]
尝试校验中...
--------------------------------------------------
从机设备编号: 1 校验信息为: [90, 1, 8, 19, 118]
尝试校验中...
返回值:['5a', '01', '08', '13', '76']
返回的校验信息为: [90, 1, 8, 19, 118],从机正常。
从机 1在线
--------------------------------------------------
从机设备编号: 2 校验信息为: [90, 2, 8, 19, 119]
尝试校验中...
返回值:['5a', '02', '08', '13', '77']
返回的校验信息为: [90, 2, 8, 19, 119],从机正常。
从机 2在线
--------------------------------------------------
从机设备编号: 3 校验信息为: [90, 3, 8, 19, 120]
尝试校验中...
返回值:['5a', '03', '08', '13', '78']
返回的校验信息为: [90, 3, 8, 19, 120],从机正常。
从机 3在线
--------------------------------------------------
从机设备编号: 4 校验信息为: [90, 4, 8, 19, 121]
尝试校验中...
返回值:['5a', '04', '08', '13', '79']
返回的校验信息为: [90, 4, 8, 19, 121],从机正常。
从机 4在线
--------------------------------------------------
从机设备编号: 5 校验信息为: [90, 5, 8, 19, 122]
尝试校验中...
返回值:['5a', '05', '08', '13', '7a']
返回的校验信息为: [90, 5, 8, 19, 122],从机正常。
从机 5在线
--------------------------------------------------
从机设备编号: 6 校验信息为: [90, 6, 8, 19, 123]
尝试校验中...
--------------------------------------------------
从机设备编号: 7 校验信息为: [90, 7, 8, 19, 124]
尝试校验中...
--------------------------------------------------
从机设备编号: 8 校验信息为: [90, 8, 8, 19, 125]
尝试校验中...
--------------------------------------------------
从机设备编号: 9 校验信息为: [90, 9, 8, 19, 126]
尝试校验中...
--------------------------------------------------
从机分数读取:
--------------------------------------------------
从机设备编号: 1 发送信息为: [90, 0, 3, 1, 94]
尝试获取分数中...
返回值:['5a', '01', '03', '00', '5e']
返回值:[90, 1, 3, 0, 94]
该从机分数为: 0,从机正常。
--------------------------------------------------
从机设备编号: 2 发送信息为: [90, 0, 3, 2, 95]
尝试获取分数中...
返回值:['5a', '02', '03', '0c', '6b']
返回值:[90, 2, 3, 12, 107]
该从机分数为: 12,从机正常。
--------------------------------------------------
从机设备编号: 3 发送信息为: [90, 0, 3, 3, 96]
尝试获取分数中...
返回值:['5a', '03', '03', '6f', 'cf']
返回值:[90, 3, 3, 111, 207]
读取错误,从机 3分数未确认
--------------------------------------------------
从机设备编号: 4 发送信息为: [90, 0, 3, 4, 97]
尝试获取分数中...
返回值:['5a', '04', '03', '32', '93']
返回值:[90, 4, 3, 50, 147]
该从机分数为: 50,从机正常。
--------------------------------------------------
从机设备编号: 5 发送信息为: [90, 0, 3, 5, 98]
尝试获取分数中...
返回值:['5a', '05', '03', '96', 'f8']
返回值:[90, 5, 3, 150, 248]
该从机分数为: 150,从机正常。
分数错误:超过100分,记为0分
--------------------------------------------------
平均分为12.4
--------------------------------------------------
进程已结束,退出代码0
运行结果分析:
我们设置了编号为12345的从机,其中3号机的分数未确认,5号机的分数超过100分。这是两个测试点。可以看出来,程序较好地通过了这两个测试点,能实现纠错的功能。
程序通过轮询,正确地确定了在线的从机编号为12345,并成功地从这几个从机中试图读取分数。对于错误也能做很好的反映。
唯一缺陷在于稳定性有待提高,这一点在我修改了代码之后,得到了修复,在测试2中稳定性得到了满足。
测试2:
运行界面:
下面是终端输出结果。
C:\Users\y\AppData\Local\Programs\Python\Python310\python.exe C:/Users/y/Desktop/工训/b.py
读入参数列表为[0.06, 10, 20, 1]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
查询在线从机信息
--------------------------------------------------
从机设备编号: 0 校验信息为: [90, 0, 8, 19, 117]
尝试校验中...
--------------------------------------------------
从机设备编号: 1 校验信息为: [90, 1, 8, 19, 118]
尝试校验中...
返回值:['5a', '01', '08', '13', '76']
返回的校验信息为: [90, 1, 8, 19, 118],从机正常。
从机 1在线
--------------------------------------------------
从机设备编号: 2 校验信息为: [90, 2, 8, 19, 119]
尝试校验中...
返回值:['5a', '02', '08', '13', '77']
返回的校验信息为: [90, 2, 8, 19, 119],从机正常。
从机 2在线
--------------------------------------------------
从机设备编号: 3 校验信息为: [90, 3, 8, 19, 120]
尝试校验中...
返回值:['5a', '03', '08', '13', '78']
返回的校验信息为: [90, 3, 8, 19, 120],从机正常。
从机 3在线
--------------------------------------------------
从机设备编号: 4 校验信息为: [90, 4, 8, 19, 121]
尝试校验中...
返回值:['5a', '04', '08', '13', '79']
返回的校验信息为: [90, 4, 8, 19, 121],从机正常。
从机 4在线
--------------------------------------------------
从机设备编号: 5 校验信息为: [90, 5, 8, 19, 122]
尝试校验中...
返回值:['5a', '05', '08', '13', '7a']
返回的校验信息为: [90, 5, 8, 19, 122],从机正常。
从机 5在线
--------------------------------------------------
从机设备编号: 6 校验信息为: [90, 6, 8, 19, 123]
尝试校验中...
--------------------------------------------------
从机设备编号: 7 校验信息为: [90, 7, 8, 19, 124]
尝试校验中...
--------------------------------------------------
从机设备编号: 8 校验信息为: [90, 8, 8, 19, 125]
尝试校验中...
--------------------------------------------------
从机设备编号: 9 校验信息为: [90, 9, 8, 19, 126]
尝试校验中...
--------------------------------------------------
从机分数读取:
--------------------------------------------------
从机设备编号: 1 发送信息为: [90, 0, 3, 1, 94]
尝试获取分数中...
返回值:['5a', '01', '03', '16', '74']
返回值:[90, 1, 3, 22, 116]
该从机分数为: 22,从机正常。
--------------------------------------------------
从机设备编号: 2 发送信息为: [90, 0, 3, 2, 95]
尝试获取分数中...
返回值:['5a', '02', '03', '21', '80']
返回值:[90, 2, 3, 33, 128]
该从机分数为: 33,从机正常。
--------------------------------------------------
从机设备编号: 3 发送信息为: [90, 0, 3, 3, 96]
尝试获取分数中...
返回值:['5a', '03', '03', '6f', 'cf']
返回值:[90, 3, 3, 111, 207]
读取错误,从机 3分数未确认
--------------------------------------------------
从机设备编号: 4 发送信息为: [90, 0, 3, 4, 97]
尝试获取分数中...
返回值:['5a', '04', '03', '32', '93']
返回值:[90, 4, 3, 50, 147]
该从机分数为: 50,从机正常。
--------------------------------------------------
从机设备编号: 5 发送信息为: [90, 0, 3, 5, 98]
尝试获取分数中...
返回值:['5a', '05', '03', '79', 'db']
返回值:[90, 5, 3, 121, 219]
该从机分数为: 121,从机正常。
分数错误:超过100分,记为0分
--------------------------------------------------
平均分为21.0
--------------------------------------------------
进程已结束,退出代码0
运行结果分析:
我们设置了编号为12345的从机,其中3号机的分数未确认,5号机的分数超过100分。这是两个测试点。可以看出来,程序较好地通过了这两个测试点,能实现纠错的功能。
程序通过轮询,正确地确定了在线的从机编号为12345,并成功地从这几个从机中试图读取分数。对于错误也能做很好的反映。
由于修改了代码,采取while的方式进行读取,并且timeout值也进行了宽容性调整,本次运行可以在多次测试下稳定运行。
3总结
3.1小组分工,个人任务总结
完成了所有主要任务,包括代码编写、可视化探索、新功能添加、bug解决、测试验证等。
3.2心得体会
首先是感谢柳杨老师在关键的时候提出关键的建议,与他严格但充满殷切希冀的要求,这推动着我继续完成代码的编写工作与优化工作。然后是代码的优化是真的有用,对于串口延迟参数的调整是十分关键的。实验的要点就是要找到一个轮询次数或是延迟参数和稳定性正确性的平衡。简单来说就是正确性和时间期望的平衡。因为如果要求得到真的稳定,它真的要花上好多的时间。这个时间有时是我们根本不能接受的。所以我们得冒上一点险,去尝试降低这个轮询的次数或是延迟参数,使得串口取样的时间简短,但是在一定的范围内这无疑是很好的,既能满足需求,又能让用户高兴。但这个限度时需要我们一次次去探寻的。在今天最后找到那个微妙的平衡点的时候,真的能高兴地跳起来。
最后,给出我舍友完成的作为参考。
97/100的验收成绩,被作为下一届的标杆,估计老师的演示就是用他的吧,链接在这里
(想直接抄的同学,可以试试老师能不能看出来)
袁神的可视化成果链接
可以看到,从完成度上来说,薄纱我的成果。这再一次体现了袁神的厉害!