基于485总线的评分系统

这篇具有很好参考价值的文章主要介绍了基于485总线的评分系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基于485总线的评分系统

程序设计目标:

通过本案例加深理解RS485通信方式,实现上位机的主控制器与所有的下位机进行通信。

程序运行效果说明:

通过RS232/RS485转换器将多个带有485模块的下位机控制程序的单片机挂载在总线上。用一块单片机做为上位机,下载上位机接点软件中的hex文件,另外的单片机作为下位机,下载下位机程序。下位机单片机上电后,数码管前两位显示从机编号,后三位显示评分结果。首先按下导航按键的中心按钮进入设置模式,被选中设定的数码管小数点被点亮;然后通过控制导航按键的左右方向实现数码管的位选,上下方向实现数码管上数值的加减,再按一次中心按钮退出设置模式。接着按下KEY2、KEY3按键标志从机编号和评分设定完成,第1位和第8位LED灯被点亮;最后通过控制上位机的主控制器的从机检测和多机评分按钮,获取单片机设定的从机编号和评分,从而实现上位机与下位机的通信。

基于485总线的评分系统

程序相关电路及工作原理说明

本案例模拟Modbus协议,采用主、从技术,上位机的主控制器可以与所有的下位机通信,也可以单独与一个指定的下位机通信。模拟Modbus协议中,上下位机的数据包都只含5个字节,其基本格式为:数据包头(0x5A)+地址码(广播地址/从机地址)+功能码+携带数据(一个字节)+校验码字节,携带数据部分可以扩充多个字节,可以视情况进行修改。数据包具体定义如下:(协议中的检验字节,本打分系统采用累加和编码。)

基于485总线的评分系统

(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的作用是查询下位机是否正常。正常,下位机发送回应查询数据包;不正常,则下位机不予回应;数据传输过程发生错误,下位机发送回应错误数据包,上位机可以通过设置多次轮询来重新检测该设备是否正常。所以,只需要在串口助手上看到如下输出即是正确的:

基于485总线的评分系统

D、F的情况类似,同样可以得到如下的输出:

基于485总线的评分系统
基于485总线的评分系统

通过串口助手明确双机通信的功能需求。

接下来来实现代码:

以下,为主代码(使用的是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. 首先,判断输入的下位机编号是否存在,地址存在返回1,地址错误返回0,数据错误返回-1.
  2. 在判断下位机编号存在之后,接下来就需要获得下位机的分数(这里只实现了双机的程序)。将获得的分数输出即可。
  3. 接下来就需要实现,退出和复位两个作用,退出输入-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);
    }
    
}

运行结果如下:

基于485总线的评分系统
基于485总线的评分系统

(这里仅仅只是修改了判断的数字罢了。)

至此实验,全部完成。

心得体会:

经过几次的实验,不仅磨练了我的意志,坚定了我的信念,相信在未来的实验中同样可以获得很多的收获。

在这里,在多机上跑的程序如下:

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("从机已复位,可以开始下一轮评分。")

运行程序之后输入你板子上你设置的编号,多机运行的话各个机子的编号用空格隔开,然后回车等着就行了。文章来源地址https://www.toymoban.com/news/detail-462804.html

到了这里,关于基于485总线的评分系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • RS485通信总线详解

    RS-485 是美国电子工业协会(EIA)在 1983 年批准了一个新的平衡传输标准(Balanced Transmission Standard)也称作差分,EIA 刚开始将 RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,将 RS 改为 EIA/TIA,所以目前该标准的名称为 TIA-485,但目前工程师们依旧习

    2024年02月05日
    浏览(46)
  • 【总线】一文看懂RS232和RS485通信总线

    目录   RS232概述 RS232特性 RS485 概述 RS485 特性 RS232 和 RS485 的区别 区别总结   RS-232接口符合电子工业联盟(EIA)建立的串行数据通信接口标准。原始编号是EIA-RS-232(简称232,RS232)。它广泛用于计算机串行接口外设连接。连接电缆以及机械、电气、信号和传输过程。 RS-232-C标

    2024年02月02日
    浏览(52)
  • RS485总线的抗干扰处理

    地线隔离:在RS485总线的两端分别使用隔离器对地线进行隔离,以消除地线干扰。 增加线路阻抗:通过在总线两端加入电阻,使总线的阻抗增加,降低信号反射,提高信号质量。 RS485总线通常需要在总线两端(也就是接口处)连接120欧姆的终端电阻。这个终端电阻的作用是抑

    2024年02月06日
    浏览(85)
  • STM32+收发器实现CAN和485总线

    RS485总线是一种常见的(Recommended Standard)串行总线标准(485是它的标识号),采用平衡发送与差分接收的方式,因此具有抑制共模干扰的能力。CAN是控制器局域网络(Controller Area Network, CAN)的简称,是一种能够实现分布式实时控制的串行通信网络,属于CSMA(多路载波侦听)/CD(冲突检测

    2024年02月05日
    浏览(52)
  • 【正点原子STM32】RS485串行通信标准(串口基础协议 和 MODBUS协议、总线连接、通信电路、通信波形图、RS485相关HAL库驱动、RS485配置步骤、)

    一、RS485介绍 二、RS485相关HAL库驱动介绍 三、RS485配置步骤 四、编程实战 五、总结 串口、UART、TTL、RS232、RS422和RS485之间的关系可以如此理解: 串口 :是一个广义术语,通常指的是采用串行通信协议的接口,它可以包括多种具体的物理接口标准和逻辑电平标准。 UART (通用

    2024年04月13日
    浏览(68)
  • SPI、UART、RS232、RS485、IIC 5种嵌入式经典通信总线协议

      UART即通用异步收发器,是一种通用的串行、异步通信总线。该总线有两条数据线,可以实现全双工的发送和接收。在嵌入式系统种常用于主机与辅助设备之间的通信。UART就是串口,也是一种通信协议/总线协议。 电脑上已经逐步没有了,换成了usb,但是在嵌入式领域是最

    2024年01月16日
    浏览(61)
  • 关于485、CAN、单总线、SPI、I2C的特点、区别、协议、使用方法、通信方式。

    485(一般称作 RS485/EIA-485)。 是隶属于 OSI 模型物理层电气特性规定为 2 线半双工多点通信标准。 它的电气特性和 RS-232 大不一样,用缆线两端的电压差值来表示传递信号。 特性 1.    接口电平低,不易损坏芯片 RS485 的电气特性:逻辑“1”以两线间的电压差为+(2~ 6)V表示;

    2024年02月07日
    浏览(60)
  • Modbus的常见问题解答:多台设备如何连接?为什么要加终端电阻?RS485总线可挂接多少个设备?在RS485通讯中,最大传输距离是多少?

    多台RS485设备如何连接呢? 使用屏蔽双绞线,采用手拉手菊花链式拓扑结构将网关和各串行设备节点连接起来,并在网络起始端和末尾端设备的RS485+和RS485-之间各并接一个120Ω电阻以减少信号在两端的反射。 什么情况下在RS485总线上要增加终端电阻? RS485总线随着传输距离的

    2024年02月10日
    浏览(71)
  • 485、CAN、单总线、SPI、I2C的概念,特点,协议,使用方法及通信方式,还有它们之间的区别

    1. 485:485(一般称作 RS485/EIA-485)是隶属于 OSI 模型物理层的电气特性规定为 2 线,半双工,多点通信的标准。它的电气特性和 RS-232 大不一样。用缆线两端的电压差值来表示传递信号。 2. CAN:CAN,全称为“Controller Area Network”,即控制器局域网,是一种多主方式的串行通讯总

    2024年02月10日
    浏览(54)
  • 基于微信小程序的高校宿舍管理系统设计与实现(亮点:选择宿舍、宿舍评分、宿舍报修)

    💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 2023-2024年最值得选的微信小程序毕业设计

    2024年04月25日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包