1、前言
Modbus功能码05:写入单个线圈状态
Modbus功能码05(十六进制0x05)用于写入单个线圈(Coil)的状态。线圈是Modbus协议中的一种二进制输出设备,状态为ON(1)或OFF(0)。该功能码通过Modbus RTU或TCP协议向设备发送请求,强制修改指定线圈的状态。
请求报文格式
请求报文包含以下字段:
- 功能码:1字节,固定为0x05。
- 线圈地址:2字节,指定目标线圈的起始地址(从0开始)。
- 写入值:2字节,0x0000表示OFF,0xFF00表示ON(其他值可能导致设备异常)。
cpp
[设备地址][0x05][线圈地址高字节][线圈地址低字节][写入值高字节][写入值低字节]
响应报文格式
响应报文与请求报文结构相同,表示设备已成功执行操作。示例响应报文:
cpp
[设备地址][0x05][线圈地址高字节][线圈地址低字节][写入值高字节][写入值低字节]
典型应用场景
- 控制继电器开关:通过写入线圈状态控制继电器通断。
- 设备状态切换:如启动/停止电机、开关指示灯等。
- 自动化系统:PLC或SCADA系统中对二进制输出的实时控制。
注意事项
- 线圈地址范围:通常为0x0000至0xFFFF,具体取决于设备配置。
- 写入值限制:仅0x0000(OFF)或0xFF00(ON)有效,其他值可能被设备拒绝。
- 错误处理:若地址无效或值非法,设备返回异常码(功能码+0x80)及错误代码。
操作实例


0000表示OFF(关),0xFF00表示ON(开)
0000表示OFF(关),0xFF00表示ON(开)
0000表示OFF(关),0xFF00表示ON(开)
Modbus功能码15:写入多个线圈状态
Modbus功能码15(0x0F)用于写入多个线圈状态(ON/OFF)到从站设备。适用于批量控制开关、继电器等二进制输出设备。
请求报文格式
- 从站地址:1字节(0x01~0xFF,0x00为广播地址)。
- 功能码:1字节(0x0F)。
- 起始地址:2字节(大端序),指定首个线圈的地址(如0x0000)。
- 线圈数量:2字节(大端序),需写入的线圈总数(1~1968)。
- 字节计数 :1字节,表示后续数据字节数(计算公式:
ceil(线圈数量 / 8))。 - 数据字段:每字节包含8个线圈状态(LSB优先,高位不足补0)。
cpp
01 0F 00 00 00 0A 02 CD 01 70 68
01:从站地址1。0F:功能码15。00 00:起始地址0x0000。00 0A:写入10个线圈。02:数据占2字节(10/8=1.25→2字节)。CD 01:数据(二进制11001101 00000001,线圈0~7为ON/OFF/ON/ON/OFF/OFF/ON/ON,线圈8~9为ON/OFF)- 70 68:CRC校验码
响应报文格式
-
从站地址:1字节(与请求一致)。
-
功能码:1字节(0x0F)。
-
起始地址:2字节(回显请求中的地址)。
-
线圈数量:2字节(回显请求中的数量)。
注意事项
- 地址范围:Modbus线圈地址通常为0x0000~0xFFFF,但实际范围受设备限制。
- 字节对齐:数据字段需按8位对齐,多余位补0。
- 错误处理:若请求非法(如地址越界),从站返回异常码(功能码+0x80,附带错误代码)。
演示实例



2、开始干
1、触发信号


cpp
#include "switchbutton.h"
#include <QPaintEvent>
#include <QPainter>
SwitchButton::SwitchButton(QWidget *parent)
: QWidget{parent}
{}
void SwitchButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
//绘制矩形
int s=qMin(this->rect().width(),this->rect().height());
painter.setPen(Qt::transparent);
if(m_isChecked){
painter.setBrush(QColor("#3487F0"));//选中时的颜色.
if(!isEnabled()){
painter.setBrush(QColor("#883487F0"));//选中时的颜色.
}
}else{
painter.setBrush(QColor("#22000000"));//未选中的颜色
if(!isEnabled()){
painter.setBrush(QColor("#11000000"));//选中时的颜色.
}
}
painter.drawRoundedRect(this->rect(),s/2,s/2);//绘制圆角矩形
//绘制圆形
int wh= s-2;
int y=this->rect().height()-(wh+1);
if(m_isChecked){
y=1;
}
painter.setBrush(Qt::white);//改变笔刷为白色
painter.drawEllipse(1,y,wh,wh);//绘制白色的圆形
}
//鼠标点击事件
void SwitchButton::mousePressEvent(QMouseEvent *)
{
if(this->isEnabled()){
this->setIsChecked(!this->m_isChecked);
//触发信号
emit onCheckedChanged(this->m_isChecked,this->m_index);
}
}


2、绑定信号

3、响应处理
cpp
#include "monitorview.h"
#include "ui_monitorview.h"
#include <QSerialPortInfo>
#include <QSerialPort>
#include <SystemUtils.h>
MonitorView::MonitorView(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MonitorView)
{
ui->setupUi(this);
SystemUtils utils;
utils.SetDropShadowEffect(ui->wdg_link_container,QColor("#80FF0000"),5);//设置连接设备的阴影效果
// 设置字体图标
QFont font=QFont(QString("zx_icons"),9);
// 监控状态
ui->lbl_status_title_icon->setFont(font);
ui->lbl_status_title_icon->setText(QChar(0xe807));
//串口区域的刷新按钮
QFont font2=QFont(QString("zx_icons"),11);
ui->pb_port_refresh->setFont(font2);
ui->pb_port_refresh->setText(QChar(0xe71e));
// OLED区域标题
ui->lbl_oled_title_icon->setFont(font);
ui->lbl_oled_title_icon->setText(QChar(0xe807));
//设置1-6号灯珠的图标及大小
font.setPixelSize(30);
ui->lbl_light_icon_1->setFont(font);
ui->lbl_light_icon_1->setText(QChar(0xe9e2));
ui->lbl_light_icon_2->setFont(font);
ui->lbl_light_icon_2->setText(QChar(0xe9e2));
ui->lbl_light_icon_3->setFont(font);
ui->lbl_light_icon_3->setText(QChar(0xe9e2));
ui->lbl_light_icon_4->setFont(font);
ui->lbl_light_icon_4->setText(QChar(0xe9e2));
ui->lbl_light_icon_5->setFont(font);
ui->lbl_light_icon_5->setText(QChar(0xe9e2));
ui->lbl_light_icon_6->setFont(font);
ui->lbl_light_icon_6->setText(QChar(0xe9e2));
//加载串口列表
//使用QSerialPortInfo::availablePorts()方法可以获取系统中所有可用的串口。这个方法返回一个QList<QSerialPortInfo>,其中包含了所有串口的信息
// 获取所有可用的串口信息 QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
ui->cb_port->addItem(info.portName());
}
//初始化波特率
ui->cb_baud->addItem("2400");
ui->cb_baud->addItem("4800");
ui->cb_baud->addItem("9600");
ui->cb_baud->addItem("19200");
ui->cb_baud->setCurrentText("9600");//设置默认选项
// 初始化校验位列表
ui->cb_parity->addItem("No");
ui->cb_parity->addItem("Odd");
ui->cb_parity->addItem("Even");
ui->cb_parity->addItem("Space");
ui->cb_parity->addItem("Mark");
ui->cb_parity->setCurrentText("No");//设置默认选项
// 初始化数据位列表
ui->cb_data->addItem("5");
ui->cb_data->addItem("6");
ui->cb_data->addItem("7");
ui->cb_data->addItem("8");
ui->cb_data->setCurrentText("8");//设置默认选项
// 初始化停止位列表
ui->cb_stop->addItem("One");
ui->cb_stop->addItem("OneAndHalf");
ui->cb_stop->addItem("Two");
ui->cb_stop->setCurrentText("One"); //设置默认选项
//关联灯珠状态开关的信号onCheckedChanged和槽函数onStatusChanged
connect(ui->sb_status_1,SIGNAL(onCheckedChanged(bool,int)),this,SLOT(onStatusChanged(bool,int)));
connect(ui->sb_status_2,SIGNAL(onCheckedChanged(bool,int)),this,SLOT(onStatusChanged(bool,int)));
connect(ui->sb_status_3,SIGNAL(onCheckedChanged(bool,int)),this,SLOT(onStatusChanged(bool,int)));
connect(ui->sb_status_4,SIGNAL(onCheckedChanged(bool,int)),this,SLOT(onStatusChanged(bool,int)));
connect(ui->sb_status_5,SIGNAL(onCheckedChanged(bool,int)),this,SLOT(onStatusChanged(bool,int)));
connect(ui->sb_status_6,SIGNAL(onCheckedChanged(bool,int)),this,SLOT(onStatusChanged(bool,int)));
}
MonitorView::~MonitorView()
{
delete ui;
}
//刷新串口列表
void MonitorView::on_pb_port_refresh_clicked()
{
//加载串口列表
ui->cb_port->clear();//清空
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
ui->cb_port->addItem(info.portName());
}
}
//连接设备
void MonitorView::on_pb_link_clicked()
{
// QSerialPort 打开 加电 数据发送 请求
// 过程需要后台线程处理
//获取选择的串口参数
QString port=ui->cb_port->currentText();
QString baud=ui->cb_baud->currentText();
QString parity=ui->cb_parity->currentText();
QString data=ui->cb_data->currentText();
QString stop=ui->cb_stop->currentText();
// 触发连接的信号
emit onConnect(port,baud,parity,data,stop);
}
//串口打开状态
void MonitorView::setOpenState(bool state)
{
//处理状态
if(state){//连接成功
ui->pb_link->setText("断开连接");
ui->cb_port->setEnabled(false);
ui->cb_baud->setEnabled(false);
ui->cb_parity->setEnabled(false);
ui->cb_data->setEnabled(false);
ui->cb_stop->setEnabled(false);
ui->pb_send->setEnabled(true);//发送按钮可用
// 改变所有状态灯的默认颜色
ui->lbl_temp_green->setStyleSheet("background-color:'#DD00FF00';");
ui->lbl_humi_green->setStyleSheet("background-color:'#DD00FF00';");
ui->lbl_bright_green->setStyleSheet("background-color:'#DD00FF00';");
//设置灯控开关的按钮全部为可用
ui->sb_status_1->setIsEnabled(true);
ui->sb_status_2->setIsEnabled(true);
ui->sb_status_3->setIsEnabled(true);
ui->sb_status_4->setIsEnabled(true);
ui->sb_status_5->setIsEnabled(true);
ui->sb_status_6->setIsEnabled(true);
}else{//连接失败或断开连接
ui->pb_link->setText("连接设备");
ui->cb_port->setEnabled(true);
ui->cb_baud->setEnabled(true);
ui->cb_parity->setEnabled(true);
ui->cb_data->setEnabled(true);
ui->cb_stop->setEnabled(true);
ui->pb_send->setEnabled(false);//发送按钮禁用
// 恢复所有状态灯的默认颜色
//1、红色灯
ui->lbl_temp_red->setStyleSheet("background-color:'#33FF0000';");
ui->lbl_humi_red->setStyleSheet("background-color:'#33FF0000';");
ui->lbl_bright_red->setStyleSheet("background-color:'#33FF0000';");
//2、黄色灯
ui->lbl_temp_yellow->setStyleSheet("background-color:'#33FF9000';");
ui->lbl_humi_yellow->setStyleSheet("background-color:'#33FF9000';");
ui->lbl_bright_yellow->setStyleSheet("background-color:'#33FF9000';");
//3、绿色灯
ui->lbl_temp_green->setStyleSheet("background-color:'#3300FF00';");
ui->lbl_humi_green->setStyleSheet("background-color:'#3300FF00';");
ui->lbl_bright_green->setStyleSheet("background-color:'#3300FF00';");
//设置灯控开关的按钮全部为禁用
ui->sb_status_1->setIsEnabled(false);
ui->sb_status_2->setIsEnabled(false);
ui->sb_status_3->setIsEnabled(false);
ui->sb_status_4->setIsEnabled(false);
ui->sb_status_5->setIsEnabled(false);
ui->sb_status_6->setIsEnabled(false);
}
}
//设置数据值
void MonitorView::setValue(uint16_t dtemp, uint16_t dhumi, uint16_t dbright)
{
ui->wdg_temp_meter->setValue(dtemp*0.1);
ui->wdg_humi_meter->setValue(dhumi*0.1);
ui->wdg_bright_meter->setValue(dbright*0.1);
//历史最高温度,最低温度
if(min_temp>dtemp){
min_temp=dtemp;
}
if(max_temp<dtemp){
max_temp=dtemp;
}
ui->lbl_temp1->setText(QString::number(max_temp*0.1));
ui->lbl_temp2->setText(QString::number(min_temp*0.1));
//历史最高湿度,最低湿度
if(min_humi>dhumi){
min_humi=dhumi;
}
if(max_humi<dhumi){
max_humi=dhumi;
}
ui->lbl_humi1->setText(QString::number(max_humi*0.1));
ui->lbl_humi2->setText(QString::number(min_humi*0.1));
//历史最高亮度,最低亮度
if(min_bright>dbright){
min_bright=dbright;
}
if(max_bright<dbright){
max_bright=dbright;
}
ui->lbl_bright1->setText(QString::number(max_bright*0.1));
ui->lbl_bright2->setText(QString::number(min_bright*0.1));
//对平均数据的处理逻辑,保存100个数据,大于100时移除第1个
//平均温度
templist.append(dtemp);
if(templist.count()>100){
templist.removeAt(0);
}
int32_t sum=0;
foreach(int16_t v,templist){
sum+=v;
}
avg_temp=sum/templist.count();//计算平均值
ui->lbl_temp3->setText(QString::number(avg_temp*0.1));
//平均湿度
humilist.append(dhumi);
if(humilist.count()>100){
humilist.removeAt(0);
}
sum=0;
foreach(int16_t v,humilist){
sum+=v;
}
avg_humi=sum/humilist.count();//计算平均值
ui->lbl_humi3->setText(QString::number(avg_humi*0.1));
//平均亮度
brightlist.append(dbright);
if(brightlist.count()>100){
brightlist.removeAt(0);
}
sum=0;
foreach(int16_t v,brightlist){
sum+=v;
}
avg_bright=sum/brightlist.count();//计算平均值
ui->lbl_bright3->setText(QString::number(avg_bright*0.1));
}
//设置灯珠状态
void MonitorView::setStatus(bool l1, bool l2, bool l3, bool l4, bool l5)
{
//5个灯珠
ui->sb_status_1->setIsChecked(l1);
ui->sb_status_2->setIsChecked(l2);
ui->sb_status_3->setIsChecked(l3);
ui->sb_status_4->setIsChecked(l4);
ui->sb_status_5->setIsChecked(l5);
//总控灯
ui->sb_status_6->setIsChecked(l1&l2&l3&l4&l5);
}
//发送文本
void MonitorView::on_pb_send_clicked()
{
QString oledtext=ui->te_oled_input->toPlainText();//得到文本输入的指令
emit onTextSend(oledtext);//激发信号
}
//灯珠状态改变
void MonitorView::onStatusChanged(bool state, int index)
{
emit onStatusSend(state,index);//激发信号
}



Qt 提供了多种互斥锁(Mutex)机制,用于多线程同步。QMutex 是 Qt 中最基本的互斥锁,用于保护共享资源。它提供了锁定和解锁功能,确保同一时间只有一个线程可以访问共享数据。
cpp
#include "mainwindow.h"
#include "systemutils.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QMouseEvent>
#include <QThread>
#include <QtConcurrent/QtConcurrentRun>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1.设置窗口无边框和窗口透明
this->setWindowFlags(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_TranslucentBackground);
// 设置相关阴影效果
SystemUtils * utils=new SystemUtils();
utils->SetDropShadowEffect(ui->wdg_root,Qt::gray,10);
//设置关闭,最小,最大的图标
// 设置相关字体图标
QFont font=QFont(QString("zx_icons"),9);
// 关闭
ui->pb_close->setFont(font);
ui->pb_close->setText(QChar(0xe653));
// 最大化
ui->pb_max->setFont(font);
ui->pb_max->setText(QChar(0xe694));
// 最小化
ui->pb_min->setFont(font);
ui->pb_min->setText(QChar(0xe7e6));
//设置第二行消息的图标
ui->lbl_message_icon->setFont(font);
ui->lbl_message_icon->setText(QChar(0xe7ff));
//设置标题字体
QFont font2=QFont(QString("钉钉进步体"),14);
ui->lbl_title->setFont(font2);
//设置"实时监控"按钮的信号和槽函数
connect(ui->nb_monitor,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
//设置"趋势图表"导航按钮的信号和槽函数
connect(ui->nb_trend,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
//设置"异常报警"导航按钮的信号和槽函数
connect(ui->nb_alarm,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
//设置"系统设置"导航按钮的信号和槽函数
connect(ui->nb_settings,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
//绑定监控页面Monitor的信号和槽函数关联,即monitor页面中的信号函数onConnect由本页面的槽函数onConnect响应处理
connect(ui->Monitor,SIGNAL(onConnect(QString,QString,QString,QString,QString)),SLOT(onConnect(QString,QString,QString,QString,QString)));
//绑定监控页面Monitor的信号和槽函数关联,即monitor页面中的信号函数onTextSend由本页面的槽函数onTextCompleted响应处理
connect(ui->Monitor,SIGNAL(onTextSend(QString)),this,SLOT(onTextCompleted(QString)));
//绑定监控页面Monitor的信号和槽函数关联,即monitor页面中的信号函数onStatusSend由本页面的槽函数onStatusCompleted响应处理
connect(ui->Monitor,SIGNAL(onStatusSend(bool,int)),this,SLOT(onStatusCompleted(bool,int)));
//创建线程及对象,处理串口
thread=new QThread();
worker=new QWorker();
//1、本页面的信号函数onOpen由worker对象的槽函数openPort响应处理
connect(this,SIGNAL(onOpen(QString,QString,QString,QString,QString)),worker,SLOT(openPort(QString,QString,QString,QString,QString)));
//2、worker对象中的信号函数openCompleted由本页面的onSetopenState槽函数响应处理
connect(worker,SIGNAL(openCompleted(bool)),this,SLOT(onSetopenState(bool)));
//3、本页面的信号函数onSend由worker对象的槽函数sendData响应处理
connect(this,SIGNAL(onSend(QByteArray,int)),worker,SLOT(sendData(QByteArray,int)));
//4、worker对象中的信号函数sendCompleted由本页面的槽函数onSendCompleted响应处理
connect(worker,SIGNAL(sendCompleted(QByteArray,int)),this,SLOT(onSendCompleted(QByteArray,int)));
worker->moveToThread(thread);//把对象worker放在多线程中
thread->start();//启动线程
//f1=QtConcurrent::run(this,&MainWindow::onMonitor);//这是qt5.X的写法,这个写法在6.x中不支持
//实例化异步任务
f1 = QtConcurrent::run([this]() {
this->onMonitor(); // 替换为你的成员函数名
});
}
MainWindow::~MainWindow()
{
delete ui;
}
//监控事件
void MainWindow::onMonitor()
{
while(this->isMonitor){
if(m_isConnected){
// 从设备中获取到相关数据
QByteArray bytes;
bytes.resize(6);
bytes[0]=0x01;//设备地址
bytes[1]=0x03;//功能码
bytes[2]=0x00;//开始地址
bytes[3]=0x00;
bytes[4]=0x00;//请求数量
bytes[5]=0x03;
// CRC16算法
auto crc=this->CRC16(bytes);
uint8_t hi=uint8_t(crc>>8);
uint8_t lo=uint8_t(crc);
bytes.resize(8);
bytes[6]=lo;//crc校验码
bytes[7]=hi;
// 需要给QWorker对象发送一个信号
emit onSend(bytes,1);//向传感器发送信号
QByteArray bytes2;
bytes2.resize(6);
bytes2[0]=0x01;//设备地址
bytes2[1]=0x01;//功能码
bytes2[2]=0x00;//开始地址
bytes2[3]=0x00;
bytes2[4]=0x00;//请求数量
bytes2[5]=0x05;
// CRC16算法
auto crc2=this->CRC16(bytes2);
uint8_t hi2=uint8_t(crc2>>8);
uint8_t lo2=uint8_t(crc2);
bytes2.resize(8);
bytes2[6]=lo2;//crc校验码
bytes2[7]=hi2;
emit onSend(bytes2,2);//向灯珠发送信号
}
QThread::msleep(3000);
}
}
//鼠标按下事件
void MainWindow::mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) { // 如果按下左边按钮
m_drag = true;//表示要移动
// 获取当前光标的位置
m_dragPos = event->pos();
// 当前鼠标点相对于桌面屏幕左上角的坐标(0,0),全局坐标;
m_resizeDownPos = event->globalPosition().toPoint();
// 获取当前窗口的相关参数,包括位置,大小等等各种参数
m_mouseDownRect = this->rect();
}
}
//鼠标移动事件
void MainWindow::mouseMoveEvent(QMouseEvent *event){
// 如果是鼠标在拖动时,当前窗口是全屏,不做任何处理
if (isFullScreen()) {
return;
}
if (m_move) {
move(event->globalPosition().toPoint() - m_dragPos);
return;
}
setCursor(Qt::ArrowCursor);
if (m_drag && (event->buttons() & Qt::LeftButton)) {
m_move = true;
move(event->globalPosition().toPoint() - m_dragPos);
}
}
//鼠标释放事件
void MainWindow::mouseReleaseEvent(QMouseEvent *event){
Q_UNUSED(event)
m_drag = false;
if (m_move) {
m_move = false;
}
setCursor(Qt::ArrowCursor);
}
//关闭事件
void MainWindow::on_pb_close_clicked()
{
QMessageBox::StandardButton ret=QMessageBox::question(this,tr("关闭系统运行"),tr("您确定要退出系统吗?"),QMessageBox::Yes | QMessageBox::No,QMessageBox::Yes );
if(ret== QMessageBox::Yes){
this->isMonitor=false;
this->close();
}
}
//最大化
void MainWindow::on_pb_max_clicked()
{
this->showFullScreen(); // 全屏展示
}
//最小化
void MainWindow::on_pb_min_clicked()
{
this->showMinimized();
}
//窗体双击事件
void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
Q_UNUSED(event); // 标记参数未使用,消除警告
if (isFullScreen()) {
showNormal(); // 如果是全屏,就恢复到非全屏
} else {
showFullScreen(); // 否则就变成全屏的
}
}
//导航按钮的点击响应事件
void MainWindow::onNavClicked(int index)
{
ui->sw_pages->setCurrentIndex(index);
}
//连接串口设备
void MainWindow::onConnect(QString port, QString baud, QString parity, QString data, QString stop)
{
//执行串口对象的连接动作,动作必须在后台线程中处理
//触发信号,让qworker这个对象来接收信号
emit onOpen(port,baud,parity,data,stop);
}
//串口连接状态
void MainWindow::onSetopenState(bool state)
{
m_isConnected=true;
//接收到worker对象中的打开状态信号
ui->Monitor->setOpenState(state);
}
//计算CRC校验码
uint16_t MainWindow::CRC16(QByteArray bytes)
{
int len=bytes.size();
uint16_t wcrc=0XFFFF;//预置16位crc寄存器,初值全部为1
uint8_t temp;//定义中间变量
int i=0,j=0;//定义计数
for(i=0;i<len;i++)//循环计算每个数据
{
temp=bytes.at(i);
wcrc^=temp;
for(j=0;j<8;j++){
//判断右移出的是不是1,如果是1则与多项式进行异或。
if(wcrc&0X0001){
wcrc>>=1;//先将数据右移一位
wcrc^=0XA001;//与上面的多项式进行异或
}
else//如果不是1,则直接移出
wcrc>>=1;//直接移出
}
}
temp=wcrc;//crc的值
return wcrc;
}
//发送完成后
void MainWindow::onSendCompleted(QByteArray bytes, int flag)
{
if(flag==1){
if(bytes.length()>3&&(bytes[1]&0x80)==0){//这个条件说明返回的数据没有问题,这里只是一个简单的判断
bool ok;
//截取指定位置指定长度的数据,转换成16进制,再转换成10进制
uint16_t temp=bytes.mid(3,2).toHex().toUInt(&ok,16);
uint16_t humi=bytes.mid(5,2).toHex().toUInt(&ok,16);
uint16_t bright=bytes.mid(7,2).toHex().toUInt(&ok,16);
// qDebug()<<"温度:"<<temp;
// qDebug()<<"湿度:"<<humi;
// qDebug()<<"亮度:"<<bright;
ui->Monitor->setValue(temp,humi,bright);
}
}else if(flag==2){
//处理灯珠状态,如果不是写的命令,即是读的命令时才执行读取灯珠状态
if(!m_isWriting&&bytes.length()>3&&(bytes[1]&0x80)==0){
bool ok;
//解析状态数据
bool s1=bytes[3]&1;
bool s2=bytes[3]&2;
bool s3=bytes[3]&4;
bool s4=bytes[3]&8;
bool s5=bytes[3]&16;
ui->Monitor->setStatus(s1,s2,s3,s4,s5);//将数据交给界面显示
}
if(this->m_isWriting){
m_isWriting=false;//修改写的状态为假,即读
}
}
}
//发送指令
void MainWindow::onTextCompleted(QString text)
{
QByteArray data(text.toLatin1());
uint16_t len=data.size();
uint16_t count=len/2;
if(len%2>0) count+=1;
// 将前面文本数据拼接在一起发给设备
QByteArray req;
req.resize(7);
req[0]=0x01;
req[1]=0x10;
req[2]=0x00;
req[3]=0x08;
req[4]=uint8_t(count>>8);
req[5]=uint8_t(count);
req[6]=len;
req.append(data);
auto crc=this->CRC16(req);
uint8_t hi=uint8_t(crc>>8);
uint8_t lo=uint8_t(crc);
req.append(lo);
req.append(hi);
// 将指定数据指令发到QWorker里处理
emit onSend(req,3);
}
//灯珠状态发送指令完成
void MainWindow::onStatusCompleted(bool state, int index)
{
this->m_isWriting=true;
QByteArray bytes;
if(index==5){// 灯珠统一设置的命令
char l1=state?0x01:0x00;
char l2=state?(0x01<<1):0x00;
char l3=state?(0x01<<2):0x00;
char l4=state?(0x01<<3):0x00;
char l5=state?(0x01<<4):0x00;
bytes.resize(8);
bytes[0]=0x01;
bytes[1]=0x0F;
bytes[2]=0x00;
bytes[3]=0x00;
bytes[4]=0x00;
bytes[5]=0x05;
bytes[6]=0x01;
bytes[7]=0x00|l1|l2|l3|l4|l5;
auto result=this->CRC16(bytes);
uint8_t hi=uint8_t(result>>8);
uint8_t lo=uint8_t(result);
bytes.append(lo);
bytes.append(hi);
}
else {// 单个灯珠设置的命令
bytes.resize(6);
bytes[0]=0x01;
bytes[1]=0x05;
bytes[2]=0x00;
bytes[3]=uint8_t(index);
bytes[4]=state?0xFF:0x00;
bytes[5]=0x00;
auto result=this->CRC16(bytes);
uint8_t hi=uint8_t(result>>8);
uint8_t lo=uint8_t(result);
bytes.append(lo);
bytes.append(hi);
}
emit onSend(bytes,3);
}
4、运行效果




5、小结
Modbus功能码05:写入单个线圈状态,Modbus功能码15:写入多个线圈状态
原创不易,打字不易,截图不易,撸码不易,整理不易,走过路过,不要错过,欢迎点赞,收藏,转载,复制,抄袭,留言,灌水,请动动你的金手指,祝您早日实现财务自由。
