本科时嵌入式有一个课堂设计,“上位机”桌面端串口软件发命令给“下位机”单片机,然后单片机执行相应得操作。

协议:

在这里插入图片描述

单片机端


typedef struct 
{
    uint8_t      buff[1024];
    uint32_t     index;
    enum
    {
	    UART_IDLE = 0,
	    UART_RECV_CMD_1,
	    UART_RECV_CMD_2,
	    UART_RECV_CMD_START,
	    UART_RECV_CMD_STOP,	    	    
	    UART_RECV_DATA_RAW,
	    UART_RECV_DATA_RAW_OK,
	    UART_ERROR = 0xFF,
    } uartState;
} rectBlock_t;

rectBlock_t _g_uart_Block = {0, 0, UART_IDLE};


static bool my_strcmp(const char* src, const uint8_t* dst)
{
	uint8_t len = strlen(src);
	while(len--)
	{
		if(src[len] != dst[len])
			return false;
	}
	return true;
}
// 串口中断处理函数
void uart_event_handle(app_uart_evt_t * p_event)
{
    switch (p_event->evt_type)
    {
        case APP_UART_DATA_READY:
            UNUSED_VARIABLE(app_uart_get(&_g_uart_Block.buff[_g_uart_Block.index]));
            _g_uart_Block.index++;
		
			if(_g_uart_Block.uartState == UART_IDLE)
			{
				if((_g_uart_Block.buff[0] == '{') && 
					(_g_uart_Block.buff[_g_uart_Block.index-1] == '}'))
				{
					if(my_strcmp("{cmd1}", &_g_uart_Block.buff[0]))
					{
						// test cmd1
						_g_uart_Block.uartState = UART_RECV_CMD_1;
					}
					else if(my_strcmp("{cmd2}", &_g_uart_Block.buff[0]))
					{
						// test cmd2
						_g_uart_Block.uartState = UART_RECV_CMD_2;
					}
					else if(my_strcmp("{stop}", &_g_uart_Block.buff[0]))
					{	
						_g_uart_Block.uartState = UART_RECV_CMD_STOP;
					}
					else if(my_strcmp("{start", &_g_uart_Block.buff[0]))
					{
						_g_image_file_index = _g_uart_Block.buff[_g_uart_Block.index-2];
						
						_g_uart_Block.uartState = UART_RECV_CMD_START;
					}
					_g_uart_Block.index = 0;
				}	
				
				if(_g_uart_Block.buff[0] != '{')
				{
					_g_uart_Block.index = 0;
				}			
			}
			else if(_g_uart_Block.uartState == UART_RECV_DATA_RAW)
			{
				if(_g_uart_Block.index == 1024)
				{
					_g_uart_Block.uartState = UART_RECV_DATA_RAW_OK;
				}
				else 
				{
					if((_g_uart_Block.buff[0] == '{') && 
						(_g_uart_Block.buff[_g_uart_Block.index-1] == '}'))
					{
						if(my_strcmp("{stop}", &_g_uart_Block.buff[0]))
						{
							_g_uart_Block.uartState = UART_RECV_CMD_STOP;
						}
						_g_uart_Block.index = 0;
					}
				}
			}
            break;
        default:
            break;
    }
}

void uart_response(unsigned char* str)
{
	printf("%s\r\n", str);
}

// 主循环中的串口数据处理函数
void Uart_Data_Handler(void)
{
	switch((uint8_t)_g_uart_Block.uartState)
	{
		case UART_IDLE:
			// Nothing stay here
			break;
		case UART_RECV_CMD_1:
			// do cmd1

		    memset(_g_uart_Block.buff, 0, 1024);
			_g_uart_Block.uartState = UART_IDLE;
			_g_uart_Block.index = 0;

			uart_response("{cmd1_ok}");
			break;
		case UART_RECV_CMD_2:
			// do cmd2

		    memset(_g_uart_Block.buff, 0, 1024);
			_g_uart_Block.uartState = UART_IDLE;
			_g_uart_Block.index = 0;

			uart_response("{cmd2_ok}");
			break;
		case UART_RECV_CMD_START:
			// to do start cmd
		    memset(_g_uart_Block.buff, 0, 1024);
			_g_uart_Block.uartState = UART_RECV_DATA_RAW;
		    _g_uart_Block.index = 0;

		    uart_response("{start_ok}");
			break;
		case UART_RECV_CMD_STOP:
			// to do stop cmd
			memset(_g_uart_Block.buff, 0, 1024);
		    _g_uart_Block.uartState = UART_IDLE;
		    _g_uart_Block.index = 0;

		    uart_response("{stop_ok}");
			break;		
		case UART_RECV_DATA_RAW:
			
			break;
		case UART_RECV_DATA_RAW_OK:
			// write raw data 
		    memset(_g_uart_Block.buff, 0, 1024);
			_g_uart_Block.uartState = UART_RECV_DATA_RAW;
		    _g_uart_Block.index = 0;
		
			uart_response("{write_ok}");
			break;
	}
}

桌面端软件–Qt


界面:
在这里插入图片描述

serialfiletransfer.pro

QT       += core gui
QT       += serialport

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QFileDialog>
#include <QFile>
#include <QTime>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_openButton_clicked();

    void on_selectButton_clicked();

    void on_sendButton_clicked();

    void on_eraseButton_clicked();

    void on_stopButton_clicked();

    void initView();

    void initSerial();

    void readSerailData();

    void sendFileData();

    void cookSerialRawData(QByteArray raw);

    void receiverSerialResponse(QString cmd);

    void serialCmdSender(QString cmd);

    void serialDataSender(QString data);

    void on_refreshButton_clicked();

    void on_testButton_clicked();

private:
    Ui::MainWindow *ui;
    QSerialPort    *serial;
    QStringList     gFileDir;
    qint64          gFileIndex;
    qint64          gFileSize;
    int16_t         gFileName;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    initView();

    initSerial();
}

MainWindow::~MainWindow()
{
    delete ui;
}

// 初始化界面
void MainWindow::initView()
{
    // 初始化进度条
    ui->progressBar->setValue(0);
    ui->progressBar->setOrientation(Qt::Horizontal);
    ui->progressBar->setAlignment(Qt::AlignVCenter);
    ui->progressBar->setInvertedAppearance(false);

    gFileIndex = 0;
}

// 初始化串口
void MainWindow::initSerial()
{
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        QSerialPort serial;
        serial.setPort(info);
        if(serial.open(QIODevice::ReadWrite))
        {
            ui->PortBox->addItem(serial.portName());
            serial.close();
        }
    }
    ui->BaudBox->setCurrentIndex(0);
    ui->PortBox->setCurrentIndex(1);

    ui->sendButton->setEnabled(false);
    ui->selectButton->setEnabled(false);
    ui->eraseButton->setEnabled(false);
    ui->testButton->setEnabled(false);
    ui->stopButton->setEnabled(false);
}

// 刷新串口
void MainWindow::on_refreshButton_clicked()
{
    ui->PortBox->clear();

    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        QSerialPort serial;
        serial.setPort(info);
        if(serial.open(QIODevice::ReadWrite))
        {
            ui->PortBox->addItem(serial.portName());
            serial.close();
        }
    }
}

// 处理串口数据
void MainWindow::cookSerialRawData(QByteArray raw)
{
    /* *
     *
     * 筛选出“{x,yyyyyy}”格式的数据
     * x--通道
     * y--数值
     *
     * */
    static QString      _tempData;
    static QString      _readData;

    //异常类:无头且变量为空,已丢失头部,数据不可靠,直接返回
    if ((!raw.contains("{"))&(_tempData.isNull()))
    {
        return;
    }
    //第一种:有头无尾,先清空原有内容,再附加
    if ((raw.contains("{"))&(!raw.contains("}")))
    {
        _tempData.clear();
        _tempData.append(raw);
    }
    //第二种:无头无尾且变量已有内容,数据中段部分,继续附加即可
    if ((!raw.contains("{"))&(!raw.contains("}"))&(!_tempData.isNull()))
    {
        _tempData.append(raw);
    }
    //第三种:无头有尾且变量已有内容,已完整读取,附加后输出数据,并清空变量
    if ((!raw.contains("{"))&(raw.contains("}"))&(!_tempData.isNull()))
    {
        _tempData.append(raw);
        _readData = _tempData;
        _tempData.clear();
    }
    //第四种:有头有尾(一段完整的内容),先清空原有内容,再附加,然后输出,最后清空变量
    if ((raw.contains("{"))&(raw.contains("}")))
    {
        _tempData.clear();
        _tempData.append(raw);
        _readData = _tempData;
        _tempData.clear();
    }

    QString cook;
    QStringList list= _readData.split("{");

    for(int i=0; i < list.length();i++)
    {
        if(!list.at(i).isEmpty())
        {
            cook = list.at(i);
            if(!cook.isEmpty())
            {
                cook.insert(0, '{');

                qDebug() << "cook:" << cook;

                if(cook.contains("{",Qt::CaseSensitive) && cook.contains("}",Qt::CaseSensitive))
                {
                    receiverSerialResponse(cook.split('{').at(1).split('}').at(0)); // 取{}中间的值
                }
            }
        }
    }
    _readData.clear();
}

// 串口中断接收数据
void MainWindow::readSerailData()
{
    static QByteArray sumData;
    QByteArray tempData = serial->readAll();

    if(!tempData.isEmpty())
    {
        sumData.append(tempData);
        ui->textEdit->append(sumData);
        // 接收的字符串中包含了字符'{'和字符'}'
        if(sumData.contains("{") && sumData.contains("}"))
        {
            cookSerialRawData(sumData); // 数据解析

            sumData.clear();
        }
    }
    tempData.clear();
}

void MainWindow::sendFileData()
{
    ui->textEdit->append("name:" + QString().number(gFileName) +" total:" +
                         QString().number((gFileSize/1024)+1) +
                         " current:" +
                         QString().number(gFileIndex));

    ui->progressBar->setValue(gFileIndex);

    if (gFileIndex == ((gFileSize/1024)+1))
    {
        // 发送完了就发送一个stop命令
        serialCmdSender("stop");

        gFileIndex = 0;
    }
    else
    {
        QFile file(QString(gFileDir.join("/")));
        if (!file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
        {
            qDebug() << "Open file failed.";
        } else {
            file.seek(gFileIndex*1024);
            serial->write(file.read(1024), 1024);
            gFileIndex++;
            qDebug() << gFileIndex;
            file.close();

        }
    }
}

// 接收串口指令
void MainWindow::receiverSerialResponse(QString cmd)
{
//    qDebug() << "CMD:" << cmd;
    ui->rcvTB->clear();
    ui->rcvTB->append("<" + cmd);
    if(QString::localeAwareCompare(cmd, "start_ok") == 0)
    {
        qDebug() << "CMD:" << "start_ok";
        // 发送文件
        sendFileData();
    }
    else if(QString::localeAwareCompare(cmd, "cmd1_ok") == 0)
    {
        qDebug() << "CMD:" << "cmd1_ok";
    }
    else if(QString::localeAwareCompare(cmd, "cmd2_ok") == 0)
    {
        qDebug() << "CMD:" << "cmd2_ok";
    }
    else if(QString::localeAwareCompare(cmd, "stop_ok") == 0)
    {
        qDebug() << "CMD:" << "stop_ok";
    }
    else if(QString::localeAwareCompare(cmd, "write_ok") == 0)
    {
        qDebug() << "CMD:" << "write_ok";
        //发送文件
        sendFileData();
    }

}

// 打开串口
void MainWindow::on_openButton_clicked()
{
    if(ui->openButton->text()==QStringLiteral("打开串口"))
    {
        serial = new QSerialPort;

        serial->setPortName(ui->PortBox->currentText());

        serial->open(QIODevice::ReadWrite);

        serial->setBaudRate(ui->BaudBox->currentText().toInt());

        switch(ui->BitNumBox->currentIndex())
        {
        case 8: serial->setDataBits(QSerialPort::Data8); break;
        default: break;
        }

        switch(ui->ParityBox->currentIndex())
        {
        case 0: serial->setParity(QSerialPort::NoParity); break;
        default: break;
        }

        switch(ui->StopBox->currentIndex())
        {
        case 1: serial->setStopBits(QSerialPort::OneStop); break;
        case 2: serial->setStopBits(QSerialPort::TwoStop); break;
        default: break;
        }

        serial->setFlowControl(QSerialPort::NoFlowControl);

        ui->PortBox->setEnabled(false);
        ui->BaudBox->setEnabled(false);
        ui->BitNumBox->setEnabled(false);
        ui->ParityBox->setEnabled(false);
        ui->StopBox->setEnabled(false);
        ui->openButton->setText(QStringLiteral("关闭串口"));

        ui->sendButton->setEnabled(true);
        ui->selectButton->setEnabled(true);
        ui->eraseButton->setEnabled(true);
        ui->testButton->setEnabled(true);
        ui->stopButton->setEnabled(true);

        // 信号连接连接串口数据处理槽函数
        QObject::connect(serial, &QSerialPort::readyRead, this, &MainWindow::readSerailData);
    }
    else
    {
        serial->clear();
        serial->close();
        serial->deleteLater();

        ui->PortBox->setEnabled(true);
        ui->BaudBox->setEnabled(true);
        ui->BitNumBox->setEnabled(true);
        ui->ParityBox->setEnabled(true);
        ui->StopBox->setEnabled(true);
        ui->openButton->setText(QStringLiteral("打开串口"));
        ui->sendButton->setEnabled(false);
        ui->selectButton->setEnabled(false);
        ui->eraseButton->setEnabled(false);
        ui->testButton->setEnabled(false);
        ui->stopButton->setEnabled(false);
    }
}

// 选择文件
void MainWindow::on_selectButton_clicked()
{
    QFileDialog *fileDialog = new QFileDialog(this);

    fileDialog->setWindowTitle("choice file");

    fileDialog->setDirectory(".");

    //fileDialog->setNameFilter(tr("mp3(*)")); // 文件类型过滤

    fileDialog->setFileMode(QFileDialog::ExistingFiles);

    fileDialog->setViewMode(QFileDialog::Detail);

    QStringList fileNames;
    if(fileDialog->exec())
    {
        fileNames = fileDialog->selectedFiles();
    }
    ui->textEdit->append(QString(fileNames.join("/")));
    gFileDir = fileNames;
}

// 发送文件头
void MainWindow::on_sendButton_clicked()
{
    QString  fileName;
    QString  filefullName;
    int i;

    QFile file(QString(gFileDir.join("/")));
    if (!file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
    {
        qDebug() << "Open file failed.";
    }
    else
    {
        // 获得文件名
        filefullName = gFileDir.at(0);
        for (i = filefullName.length()-1; i>0; i--)
        {
            if(filefullName[i] == "/")
                break;
        }
        fileName = filefullName.mid(i+1).split('.').at(0);

        gFileSize = file.size();

        qDebug() << "fileName" << fileName;
        qDebug() << "fileName" << fileName.toInt(nullptr, 16);

        qDebug() << "Size:" << gFileSize;
        qDebug() << "index:" << gFileIndex;

    }
    file.close();

    ui->progressBar->setMaximum((gFileSize/1024)+1);
    ui->progressBar->setMinimum(0);
    ui->progressBar->setRange(0, (gFileSize/1024)+1);

    // 文件名为:数字.文件类型
    char fileIndex = fileName.toInt(nullptr, 10);

    gFileName = fileName.toInt(nullptr, 10);

    // 发送一个开始信号,带着文件类型代码
    char sendCmd[] = {'s', 't', 'a', 'r', 't', fileIndex};

    serialCmdSender(sendCmd);

    ui->progressBar->reset();
}

// 全片擦除命令
void MainWindow::on_eraseButton_clicked()
{
    QString cmd = "cmd2";

    serialCmdSender(cmd);
}
// 停止发送
void MainWindow::on_stopButton_clicked()
{
    QString cmd = "stop";

    serialCmdSender(cmd);
}
// 测试命令
void MainWindow::on_testButton_clicked()
{
    QString cmd = "cmd1";

    serialCmdSender(cmd);
}
// 串口命令发送
void MainWindow::serialCmdSender(QString cmd)
{
    serial->write("{" + cmd.toUtf8() + "}"); // "{cmd}"format

    ui->sendCmdTB->clear();

    ui->sendCmdTB->append(">" + cmd);

    qDebug() << cmd;
}

// 串口数据发送
void MainWindow::serialDataSender(QString data)
{
    serial->write(data.toUtf8());

    ui->sendCmdTB->clear();

    ui->sendCmdTB->append(">" + data);

    qDebug() << data;
}


Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐