当前位置:网站首页>基于485总线的评分系统
基于485总线的评分系统
2022-07-26 22:42:00 【lzl_0408】
实操任务
B级任务(80%)
要求:
使用两块STC板分别下载上一节所提供的.hex文件,搭建485双机通信电路,在linux中编程实现打分程序代码。
步骤:
阅读程序系统流程框图,明确双机通信的功能需求。
熟悉上一节中模拟MODBUS协议的数据包结构,相关功能码及附加数据定义
| 功能码 | 读下位机功能码 | 0X03 |
| -------------- | -------------- | ---- |
| 检测功能码 | 0X08 | |
| 地址错误功能码 | 0X10 | |
| 复位功能码 | 0X01 | |
| 附加数据 | 错误码 | 0X6F |
| 包头 | 0X5A | |
| 广播地址 | 0X00 | |
| 自定义内容 | 0X13 | |
协议中的检验字节,本打分系统采用累加和编码。
3. 回顾485总线数据收发实验,搭建双机通信电路。参考上一节内容确保STC从机编号和评分设定完成后,按下KEY2、KEY3按键标志,第1位和第8位LED灯被点亮。
4. PC端串口设置如下:
串口波特率:9600 数据位:8位 校验位:无 停止位:1
5. 所编写的PC端程序应参考上一节中的通信协议完成一次完整的评分过程:
- - 需要包含串口的设置
- 主节点发起从机检测过程:发送指定从机编号正常检测数据包,判断回应查询数据包是否符合上一节中的通信协议。
- 主机获取从机评分过程:发送指定从机评分相关数据包,判断回应查询数据包是否符合上一节中的通信协议。
- 主机发起结束评分的过程,若复位成功,STC从机上第1位和第8位LED灯会熄灭。
- 展示串口相关信息,展示检测到的从机编号和从机的评分等。
代码设计
#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<<"设备检测正常"<<endl;
sleep(1);
if(!(score = get_score(pipe, addr)))
cout<<"获取分数失败"<<endl;
else
cout<<"分数:"<<score<<endl;
}
else if(check_ret == 0){
cout<<"地址错误,请输入地址重试"<<endl;
continue;
}
else{
cout<<"数据传输错误,请重启从机"<<endl;
}
//reset(pipe);
cout<<"从机已复位,输入-1退出"<<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;
}
```
```c
//serial.h
#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
```
```c
//serial.c
#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) {
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);
}
}
程序设计目标:通过本案例加深理解RS485通信方式,实现上位机的主控制器与所有的下位机进行通信。
程序运行效果说明:通过RS232/RS485转换器将多个带有485模块的下位机控制程序的单片机挂载在总线上。用一块单片机做为上位机,下载上位机接点软件中的hex文件,另外的单片机作为下位机,下载下位机程序。下位机单片机上电后,数码管前两位显示从机编号,后三位显示评分结果。首先按下导航按键的中心按钮进入设置模式,被选中设定的数码管小数点被点亮;然后通过控制导航按键的左右方向实现数码管的位选,上下方向实现数码管上数值的加减,再按一次中心按钮退出设置模式。接着按下KEY2、KEY3按键标志从机编号和评分设定完成,第1位和第8位LED灯被点亮;最后通过控制上位机的主控制器的从机检测和多机评分按钮,获取单片机设定的从机编号和评分,从而实现上位机与下位机的通信。
测试方法
1.通过杜邦线将51单片机与RS232/RS485转换器连接,再通过USB转RS232/RS485串口通讯线与PC机连接,下载hex文件,并给单片机上电;
2.如果直接用某一台单片机做主机,该单片机需要下载上位机程序中的接点软件而不是下位机软件;
3.下位机下载后的初始现象为:最左边两个数码管显示00表示从机编号,最右边3个数码管显示000表示评分;
4.按下导航按键中心按钮进入设置模式,将从机编号和评分设置完成后再按一次中心键退出设置模式,再按下KEY1,KEY2,标志设置完成;
5.通过控制上位机进行从机检测获取下位机编号,并获取其评分,数据显示上位机的主控制器上,最后结束评分,单片机LED灯熄灭。
实验体会
RS232接口在总线上只允许连接1个收发器, 即单站能力。而RS485接口在总线上是允许连接多达128个收发器。即具有多站能力,这样用户可以利用单一的RS485接口方便地建立起设备网络。RS485属于半双工通信,数据可以在一个信号载体的两个方向上传输,但是不能同时进行传输。电平转换采用差分电路方式,A、B两线的电压差大于0.2认为是逻辑“1”,小于-0.2认为是逻辑“0”,只有通信双方一方处于发送,一方处于接收时,通信才能正常进行。RS485广泛运用在工业自动化控制、视频监控、门禁对讲以及楼宇报警等各个领域。
本实验让我们对于RS485和RS232有了更多的了解,明白了它们是怎样工作的。
边栏推荐
- onSaveInstanceState和onRestoreInstanceState方法的调用
- SQL学习(1)——表相关操作
- Status management in Flink
- Flink sliding window understanding & introduction to specific business scenarios
- Kubernetes 是什么 ?
- Spark源码学习——Memory Tuning(内存调优)
- Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=
- 深度学习报告(3)
- SQL学习(2)——表的基础查询与排序
- MySQL索引优化:索引失效以及不适合建立索引的场景
猜你喜欢

Flink sliding window understanding & introduction to specific business scenarios

Deep understanding of golang - closures

adb.exe已停止工作 弹窗问题

VSCode2015下编译darknet生成darknet.ext时error MSB3721:XXX已退出,返回代码为 1。

Flinksql multi table (three table) join/interval join

Hidden index and descending index in MySQL 8.0 (new feature)

SQL学习(3)——表的复杂查询与函数操作

FlinkSql多表(三表) join/interval join

Spark On YARN的作业提交流程

游戏项目导出AAB包上传谷歌提示超过150M的解决方案
随机推荐
李宏毅机器学习(2021版)_P7-9:训练技巧
forward和redirect的区别
李宏毅机器学习(2017版)_P6-8:梯度下降
浅析ContentValues
In 2022, will there be opportunities for mobile Internet apps and short video live tracks?
Game project export AAB package upload Google tips more than 150m solution
MySQL index optimization: scenarios where the index fails and is not suitable for indexing
基于Flink实时计算Demo—关于用户行为的数据分析
adb shell截屏录屏命令
Flink1.11 write MySQL test cases in jdcb mode
Hidden index and descending index in MySQL 8.0 (new feature)
腾讯云直播插件MLVB如何借助这些优势成为主播直播推拉流的神助攻?
李宏毅机器学习(2017版)_P1-2:机器学习介绍
堆排序相关知识总结
Scala-模式匹配
Understanding of Flink interval join source code
Data warehouse knowledge points
MySQL Article 1
The difference between golang slice make and new
In depth learning report (2)