上位机和下位机的概念
上位机:指的是可以直接发送操作指令的计算机或者单片机,一般提供用户操作交互界面并向用户展示反馈数据。
典型设备:电脑、平板、手机、面板、触摸屏
下位机:指的是与机器相连接的计算机或者单片机,一般用于接收和反馈上位机的指令,并根据指令控制机器执行动作以及从机器传感器读取数据。
典型设备:PLC、stm32、51、FPGA、ARM等各类可编程芯片。
上位机软件:用于完成上位机操作交互的软件
- 上位机给下位机发送控制命令,下位机接收到此命令并执行相应的动作;
- 上位机给下位机发送状态获取命令,下位机接收到此命令后调用传感器测量,然后够转化为数字信息反馈给上位机。
- 下位机主动发送状态信息或者报警信息给上位机
为了实现以上过程,上位机和下位机都需要独立进行编程,都需要专门的开发人员在各自的平台上编写代码。
上位机和下位机之间进行通信,常见的是有线连接,比如485,串口,USB等等,当然利用无线连接也可以,上位机和下位机的关系也有点类似主机和从机,通常上位机是电脑或平板,下位机是单片机设备。
编写程序
开发环境的搭建参考以往的文章:QT C++入门学习(1) QT Creator安装和使用
这里我们选择串口作为电脑端上位机软件与下位机的通信方式,串口作为嵌入式领域最常用的通信方式被广泛使用。
通信协议我们定义:AT+LED=ON\r\n 和 AT+LED=OFF\r\n 分别是开灯和关灯(\r\n是回车换行符,用来指示命令结尾)
1、新建一个serial_led工程
更改名称:
基类选择QWidget:
后面都是默认点下一步即可。
创建项目有疑问可以查看https://blog.csdn.net/weixin_44788542/article/details/130413466
完成后项目文件结构默认如下:
2、上位机界面设计
Qt 有一个可视化的界面设计工具:Qt 设计器(Qt Designer)。我们双击widget.ui文件就可以进入Qt Designer,在Qt Designer中我们可以通过拖动控件的方式来设计我们的界面
分别添加两个Label,两个Combo Box,和四个PushButton控件布局成如下图所示:
选中所有控件,点击栅格布局,已经放置的控件就自动对齐了。
默认的界面太大了,拉小一点:
3、完善控件参数
双击波特率对应的Combo Box,点击加号新增两个常用的波特率9600和115200备选项
选中控件,然后在右下角的对象名称属性那里更改名称
逐一按下图更改对象名称:
4、编写程序:
打开serial_led.pro文件,在第一行添加serialport
QT += core gui serialport
widget.h文件编写:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSerialPort>
#include <QSerialPortInfo>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QSerialPort *serialPort;//定义串口指针
private slots:
/*以下为widget.ui文件中点击“转到槽”自动生成的函数*/
void on_openBt_clicked();
void on_btnSerialCheck_clicked();
void on_btnLedOn_clicked();
void on_btnLedOff_clicked();
};
#endif // WIDGET_H
widget.cpp文件编写:
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("serial_led");
QStringList serialNamePort;
serialPort = new QSerialPort(this);
ui->serailCb->clear();
//通过QSerialPortInfo查找可用串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
ui->serailCb->addItem(info.portName());
}
}
//检测通讯端口槽函数
void Widget::on_btnSerialCheck_clicked()
{
ui->serailCb->clear();
//通过QSerialPortInfo查找可用串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
ui->serailCb->addItem(info.portName());
}
}
/*打开串口*/
void Widget::on_openBt_clicked()
{
// 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数
serialPort->setPortName(ui->serailCb->currentText());
serialPort->setBaudRate(ui->baundrateCb->currentText().toInt());
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setParity(QSerialPort::NoParity);
// 根据初始化好的串口属性,打开串口
// 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。
if(ui->openBt->text() == "打开串口"){
if(serialPort->open(QIODevice::ReadWrite) == true){
ui->openBt->setText("关闭串口");
// 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色)
ui->serailCb->setEnabled(false);
}else{
QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n该串口可能被占用\r\n请选择正确的串口");
}
}else{
serialPort->close();
ui->openBt->setText("打开串口");
// 端口号下拉框恢复可选,避免误操作
ui->serailCb->setEnabled(true);
}
}
void Widget::on_btnLedOn_clicked()
{
serialPort->write("AT+LED=ON\r\n");
}
void Widget::on_btnLedOff_clicked()
{
serialPort->write("AT+LED=OFF\r\n");
}
Widget::~Widget()
{
delete ui;
}
main.cpp不需要改动。
5、添加上位机图标
在网上找一个相关的.ico后缀的图标下载放到我们的工程路径下,如:
免费的ico文件下载参考该文:https://zhuanlan.zhihu.com/p/431105940
推荐https://www.iconfinder.com/
然后在我们的serial_led.pro文件中添加如下一行代码:
RC_ICONS = serial_led.ico
注意后面的文件名就是放在工程目录下的ico文件名
点击三角符号运行程序
界面左上角的图标就是我们放入的ico图标
这个图标还将作为打包生成的exe文件的图标。
后续打包的过程在另一文章有介绍,这里不赘述。传送门:QT如何打包生成独立可执行.exe文件、
测试验证
实际工作中,我们自己开发好上位机,需要等下位机弄好后与之联调测试,如果下位机未准备好,我们可以自己先尽可能自测。
比如我这里用了两个USB转串口模块,两个模块的GND连接,TX和RX连接,两个模块都连接到电脑上,然后打开我们开发的上位机,选择其中一个端口进行连接,对于另一个模块,我们用常用的串口助手连接。这样我们点击开灯和关灯的按钮,正常会在另一个串口助手上收到相应的指令。
只要上位机这块的功能无误,下位机再按照这个指令去开发功能即可。