当前位置:网站首页>基于485总线的评分系统
基于485总线的评分系统
2022-07-06 09:25:00 【湖大金胜宇】
基于485总线的评分系统
程序设计目标:
通过本案例加深理解RS485通信方式,实现上位机的主控制器与所有的下位机进行通信。
程序运行效果说明:
通过RS232/RS485转换器将多个带有485模块的下位机控制程序的单片机挂载在总线上。用一块单片机做为上位机,下载上位机接点软件中的hex文件,另外的单片机作为下位机,下载下位机程序。下位机单片机上电后,数码管前两位显示从机编号,后三位显示评分结果。首先按下导航按键的中心按钮进入设置模式,被选中设定的数码管小数点被点亮;然后通过控制导航按键的左右方向实现数码管的位选,上下方向实现数码管上数值的加减,再按一次中心按钮退出设置模式。接着按下KEY2、KEY3按键标志从机编号和评分设定完成,第1位和第8位LED灯被点亮;最后通过控制上位机的主控制器的从机检测和多机评分按钮,获取单片机设定的从机编号和评分,从而实现上位机与下位机的通信。
程序相关电路及工作原理说明
本案例模拟Modbus协议,采用主、从技术,上位机的主控制器可以与所有的下位机通信,也可以单独与一个指定的下位机通信。模拟Modbus协议中,上下位机的数据包都只含5个字节,其基本格式为:数据包头(0x5A)+地址码(广播地址/从机地址)+功能码+携带数据(一个字节)+校验码字节,携带数据部分可以扩充多个字节,可以视情况进行修改。数据包具体定义如下:(协议中的检验字节,本打分系统采用累加和编码。)
(1)主机检测从机是否正常相关数据包:(主机与单个从机设备通信):
(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)+校验字节
功能:指示所有正常连接的从机进行复位操作,准备下一轮的评分。
实现过程:
- 通过杜邦线将51单片机与RS232/RS485转换器连接,再通过USB转RS232/RS485串口通讯线与PC机连接,下载hex文件,并给单片机上电;
- 如果直接用某一台单片机做主机,该单片机需要下载上位机程序中的接点软件而不是下位机软件;
- 下位机下载后的初始现象为:最左边两个数码管显示00表示从机编号,最右边3个数码管显示000表示评分;
- 按下导航按键中心按钮进入设置模式,将从机编号和评分设置完成后再按一次中心键退出设置模式,再按下KEY1,KEY2,标志设置完成;
- 通过控制上位机进行从机检测获取下位机编号,并获取其评分,数据显示上位机的主控制器上,最后结束评分,单片机LED灯熄灭。
思路具体如上图所示。
首先,我们可以通过串口助手来了解这个程序具体的作用,可以通过上面所说的A、D、F来大体了解一下我们需要做什么。A的作用是查询下位机是否正常。正常,下位机发送回应查询数据包;不正常,则下位机不予回应;数据传输过程发生错误,下位机发送回应错误数据包,上位机可以通过设置多次轮询来重新检测该设备是否正常。所以,只需要在串口助手上看到如下输出即是正确的:
D、F的情况类似,同样可以得到如下的输出:
通过串口助手明确双机通信的功能需求。
接下来来实现代码:
以下,为主代码(使用的是C++)
#include <cstdio>
#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include "serial.h"
#include <setjmp.h>
using namespace std;
typedef unsigned char uchar;
/* 基于RS485总线的评分系统-主机程序-双机评分 */
string format(const vector<uchar> &data); //数据处理为字符串
int check(serial &pipe, int addr); //设备检测
int get_score(serial &pipe, int addr); //获取分数
void reset(serial &pipe); //从机复位
int main() {
serial pipe("/dev/ttyUSB0", B9600);
int addr, check_ret, score;
while(1){
cout<<"输入说明:\n输入合法从机地址查询分数\n输入-2将从机复位\n输入-1退出程序\n如果程序无法正确运行,不能接收从机回应,请重启重试\n请输入指令:";
cin>>addr;
if(addr == -1) break;
if(addr == -2) {
reset(pipe);
continue;
}
check_ret = check(pipe, addr);
if(check_ret == 1){
cout<<"Equipment test normal"<<endl;
sleep(1);
if(!(score = get_score(pipe, addr)))
cout<<"Failed to get score"<<endl;
else
cout<<"score:"<<score<<endl;
}
else if(check_ret == 0){
cout<<"The address is incorrect. Please enter the address and try again"<<endl;
continue;
}
else{
cout<<"Data transfer error, please restart slave machine"<<endl;
}
//reset(pipe);
cout<<"The slave machine is reset. Enter -1 to exit"<<endl;
}
return 0;
}
/*****************************************************
将数据处理为字符串
******************************************************/
string format(const vector<uchar> &data) {
std::string str(2 * data.size() + 1, '\x00');
for (int i = 0; i < data.size(); i++) {
sprintf(&str[i * 2], "%02X", data[i]);
}
return str;
}
/*****************************************************
从机地址检测:
参数:serial串口,从机地址
主机发送进行数据检测:5a + 从机地址 + 检测功能码08 + 13 + 校验码
返回值:1(地址正确);0(地址错误);-1(数据错误)
******************************************************/
int check(serial &pipe,int addr){
int check_code = 117 + addr, ret; //校验码为累加和
vector<uchar> code = {0x5a, 0x08, 0x13};
vector<uchar> rec;
code.insert(code.begin()+1, (uchar)addr); //插入从机地址
code.push_back((uchar)check_code); //插入校验码
//cout << format(code) << endl;
/* 写入并接收回应数据包 */
pipe.myWrite(code);
sleep(1);
rec = pipe.myRead(5);
if(format(rec) == format(code)) //检验接收数据包是否与发送相同,相同则从机地址正确
ret = 1;
else{
if(rec[3] == 0x6f) ret = 0;
else ret = -1;
}
return ret;
}
/*****************************************************
获取从机分数:
参数:serial串口,从机地址
主机发送进行分数获取:5a + 00 + 读取功能码03 + 从机地址 + 校验码
返回值:分数(数据正确);-1(从机未准备好);-2(数据错误)
******************************************************/
int get_score(serial &pipe, int addr){
int check_code = 93 + addr, ret; //校验码为累加和
vector<uchar> code = {0x5a, 0x00, 0x03};
vector<uchar> rec;
code.insert(code.begin()+3, (uchar)addr); //插入从机地址
code.push_back((uchar)check_code); //插入校验码
//cout << format(data) << endl;
/* 写入并接收回应数据包 */
pipe.myWrite(code);
sleep(1);
rec = pipe.myRead(5);
if(rec[3] == 0x6f) ret = -1; //检验是否错误
else {
ret = (int)rec[3]; //转为数字
if(ret < 0 || ret > 100) ret =-2; //检测数字是否合法
}
return ret;
}
/*****************************************************
从机复位:
参数:serial串口,从机分数值
主机发送进行从机复位:5a + 广播地址00 + 复位功能码01 + 00 +校验字节
******************************************************/
void reset(serial &pipe){
vector<uchar> code = {0x5a, 0x00, 0x01,0x00,0x5b};
//cout <<"reset:"<< format(code) << endl;
/* 发送数据包 */
for(int i=0;i<600;i++) {
pipe.myWrite(code);
}
return;
}
思路如下:
- 首先,判断输入的下位机编号是否存在,地址存在返回1,地址错误返回0,数据错误返回-1.
- 在判断下位机编号存在之后,接下来就需要获得下位机的分数(这里只实现了双机的程序)。将获得的分数输出即可。
- 接下来就需要实现,退出和复位两个作用,退出输入-1,复位输入-2即可。
头文件的内容如下:
#ifndef SERIAL_H
#define SERIAL_H
#include <cstring>
#include <vector>
#include <sys/termios.h>
class serial {
private:
int board = -1, epfd = -1;
public:
serial(const char *board_path, speed_t baud_rate);
~serial();
std::vector<unsigned char> myRead(size_t n) const;
void myWrite(const std::vector<unsigned char> &data) const;
};
#endif //SERIAL_H
头文件中定义的函数如下:
#include "serial.h"
#include <cerrno>
#include <cstdio>
#include <iostream>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <sys/termios.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <signal.h>
#define err_check(code) if ((code) < 0) {
\ printf("Error: %s\n", strerror(errno)); \ _exit(1); \ }
serial::serial(const char *board_path, speed_t baud_rate) {
/** * O_RDWR 表示以读写模式 (myRead & myWrite) 打开文件 * 参考:https://man7.org/linux/man-pages/man3/open.3p.html */
err_check(board = open(board_path, O_RDWR | O_NOCTTY ))
termios attrs {
};
tcgetattr(board, &attrs);
// 设置波特率
err_check(cfsetispeed(&attrs, baud_rate))
err_check(cfsetospeed(&attrs, baud_rate))
attrs.c_iflag &= ~( BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF );
attrs.c_oflag &= ~( OPOST | ONLCR | OCRNL );
attrs.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG );
attrs.c_cflag &= ~( CSIZE | PARENB );
attrs.c_cflag |= CS8;
attrs.c_cc[VMIN] = 1;
attrs.c_cc[VTIME] = 0;
// 设置终端参数,所有改动立即生效
err_check(tcsetattr(board, TCSANOW, &attrs))
// 重新打开设备文件以应用新的终端参数
close(board);
err_check(board = open(board_path, O_RDWR | O_NOCTTY))
// 创建一个新的 epoll 实例,并返回一个用于控制的文件描述符
err_check(epfd = epoll_create(1))
epoll_event event {
.events = EPOLLIN | EPOLLET, // 当对端变为可读时触发事件
.data = {
.fd = board
}
};
// 将这个事件添加到 epoll 的监听列表中
err_check(epoll_ctl(epfd, EPOLL_CTL_ADD, board, &event))
}
serial::~serial() {
(~board) && close(board);
(~epfd) && close(epfd);
}
std::vector<unsigned char> serial::myRead(size_t n) const {
size_t count = 0;
std::vector<unsigned char> buffer(n);
while (count < n)
{
epoll_event event {
};
// 等待串口对端发来数据
epoll_wait(epfd, &event, 1, 5000); // 指定超时值,避免无限期阻塞等待
// 读取数据,然后根据读取到的数据数量决定是否需要继续读取
count += ::read(board, &buffer[count], n);
}
//tcflush(board,TCIOFLUSH);
return buffer;
}
void serial::myWrite(const std::vector<unsigned char> &data) const {
size_t count = 0;
//tcflush(board,TCOFLUSH);
while (count < data.size()) {
// 向串口写入数据
count += ::write(board, &data[count], data.size() - count);
}
}
运行结果如下:
(这里仅仅只是修改了判断的数字罢了。)
至此实验,全部完成。
心得体会:
经过几次的实验,不仅磨练了我的意志,坚定了我的信念,相信在未来的实验中同样可以获得很多的收获。
在这里,在多机上跑的程序如下:
import binascii
import serial.tools.list_ports
# init
plist = list(serial.tools.list_ports.comports()) # 获取端口列表
ser = serial.Serial(list(plist[0])[0], 9600, timeout=0.05) # 导入pyserial模块
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(map(int, input("请输入设备下位机,中间以' '隔开:").split())) # 存储设备列表
print(devices)
# part 1: 校验下位机设备
for device in devices:
data = [0x5A, device, 0x08, 0x13]
data.append(sum(data))
print("{}\n从机设备编号: {:2d} 校验信息为: {}\n尝试校验中...".format('-'*50, device, data))
flag = True
for _ in range(100):
ser.write(data)
retdata = read_times()
if retdata:
print(retdata)
retdata = [int(i,16) for i in retdata]
if retdata == data:
print("返回的校验信息为: {},从机正常。".format(retdata))
else:
print("从机传输结果异常")
flag = False
break
if flag:
print("从机无返回")
print('-'*50)
print('从机分数读取:')
for device in devices:
data = [0x5A, 0x00, 0x03, device]
data.append(sum(data))
print("{}\n从机设备编号: {:2d} 发送信息为: {}\n尝试获取分数中...".format('-'*50, device, data))
flag = True
for _ in range(100):
ser.write(data)
retdata = read_times()
if retdata:
print(retdata)
retdata = [int(i,16) for i in retdata]
print(retdata)
if retdata[1] == device and retdata[4] == sum(retdata[:4]):
print("该从机分数为: {},从机正常。".format(retdata[3]))
elif retdata[3] == 0x6F:
print("从机分数大于100,错误")
else:
print("从机传输结果异常")
flag = False
break
if flag:
print("从机无返回")
print('-'*50)
print('从机复位操作:')
data = [0x5A, 0x00, 0x01, 0x00, 0x5B]
ser.write(data)
print("从机已复位,可以开始下一轮评分。")
运行程序之后输入你板子上你设置的编号,多机运行的话各个机子的编号用空格隔开,然后回车等着就行了。
边栏推荐
- 软件测试行业的未来趋势及规划
- Which version of MySQL does php7 work best with?
- Database monitoring SQL execution
- Should wildcard import be avoided- Should wildcard import be avoided?
- Programmers, how to avoid invalid meetings?
- Statistics 8th Edition Jia Junping Chapter 1 after class exercises and answers summary
- Global and Chinese market for antiviral coatings 2022-2028: Research Report on technology, participants, trends, market size and share
- Réponses aux devoirs du csapp 7 8 9
- Servlet
- JDBC introduction
猜你喜欢
Portapack application development tutorial (XVII) nRF24L01 launch B
Want to learn how to get started and learn software testing? I'll give you a good chat today
软件测试方法有哪些?带你看点不一样的东西
软件测试工作太忙没时间学习怎么办?
Réponses aux devoirs du csapp 7 8 9
转行软件测试必需要知道的知识
What to do when programmers don't modify bugs? I teach you
Install and run tensorflow object detection API video object recognition system of Google open source
线程及线程池
Leetcode simple question: check whether two strings are almost equal
随机推荐
CSAPP homework answers chapter 789
The maximum number of words in the sentence of leetcode simple question
Brief description of compiler optimization level
Threads and thread pools
Emqtt distribution cluster and node bridge construction
150 common interview questions for software testing in large factories. Serious thinking is very valuable for your interview
Dlib detects blink times based on video stream
線程及線程池
Sleep quality today 81 points
Heap, stack, queue
Install and run tensorflow object detection API video object recognition system of Google open source
Global and Chinese market of goat milk powder 2022-2028: Research Report on technology, participants, trends, market size and share
Global and Chinese market of RF shielding room 2022-2028: Research Report on technology, participants, trends, market size and share
The minimum sum of the last four digits of the split digit of leetcode simple problem
What are the business processes and differences of the three basic business modes of Vos: direct dial, callback and semi direct dial?
遇到程序员不修改bug时怎么办?我教你
The salary of testers is polarized. How to become an automated test with a monthly salary of 20K?
Video scrolling subtitle addition, easy to make with this technique
Eigen User Guide (Introduction)
Leetcode simple question: check whether the numbers in the sentence are increasing