• 创建串口调试界面
  • 配置串口
  • 打开串口
  • 发送中断并获取当前光照强度
  • 定时监控
  • 显示数据
  • 系统日志
  • 数据存储
  • 总结
  • 附录
  • 源码下载

  这个项目是18年初寒假开始的,初始想法是利用寒假时间学习Qt开发并且顺便把毕业设计做出来,后来毕业设计做出来了,这个项目也完成了,然后又投入了二战,所以分享开发的过程的Blog却一直没有时间写,现在收到了录取通知,又想起这个事情,可能思绪不太连贯了,但我还是想把这个事情坐下来。

创建串口调试界面

串口调试器的主要需求:

  1. 显示来自串口接收到的信息;
  2. 向串口发送中断;
  3. 配置串口。

根据需求设计UI界面

  其中界面的设计主要用到了QlabelQTextEditQCamboBoxQPushButton,其中较为复杂的应该是QTextEdit组件的输出格式控制。

配置串口

  由于Qt5内置了串口类,所以我们可以通过直接调用类方法进行串口的配置,这真的是一件十分Nice的事情,之前我也有通过Keil开发过C51的串口模块,相比较而言真的是能省不少心。

  当用户通过LogIn界面登陆进入串口界面时,此时程序已经开始查找当前可用串口,显示在端口号处。

    //查找可用的串口foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){QSerialPort serial;serial.setPort(info);if(serial.open(QIODevice::ReadWrite)){ui->combox->addItem(serial.portName());serial.close();}}//设置波特率下拉菜单默认显示第三项ui->baudrate->setCurrentIndex(3);//关闭发送按钮的使能//   ui->openport->setEnabled(false);qDebug()<<tr("界面设定成功!");

打开串口

  点击打开串口,系统自动读取用户所设置的串口配置,这里需要注意的是虽然有校验位选项,但是我没有使用,所以直接使用了serial->setParity(QSerialPort::NoParity)。从逻辑上来讲,当用户以当前设置打开了串口,设置变不能再更改了,所以在点击打开串口后,应将QCamboBox关闭使能,然后将QPushButton的文字内容改成“关闭串口”。

serial = new QSerialPort;//设置串口名serial->setPortName(ui->combox->currentText());//打开串口serial->open(QIODevice::ReadWrite);serialInfo = serialInfo + "Mode: ReadWrite" + '\n';//设置波特率serial->setBaudRate(ui->baudrate->currentText().toInt());//设置数据位数switch(ui->databit->currentIndex()){case 5: serial->setDataBits(QSerialPort::Data5); break;case 6: serial->setDataBits(QSerialPort::Data6); break;case 7: serial->setDataBits(QSerialPort::Data7); break;case 8: serial->setDataBits(QSerialPort::Data8); break;default: break;}serialInfo = serialInfo + "Data Bit: " + ui->databit->currentText() + '\n';//设置奇偶校验switch(ui->conparebit->currentIndex()){case 0: serial->setParity(QSerialPort::NoParity); break;default: break;}serialInfo = serialInfo + "Conpare Bit" + ui->conparebit->currentText() + '\n';//设置停止位switch(ui->stopbit->currentIndex()){case 1: serial->setStopBits(QSerialPort::OneStop); break;case 2: serial->setStopBits(QSerialPort::TwoStop); break;default: break;}serialInfo = serialInfo + "Stop Bit" + ui->stopbit->currentText() + '\n';//设置流控制serial->setFlowControl(QSerialPort::NoFlowControl);serialInfo = serialInfo + "FlowControl: No flow control." + '\n';//关闭设置菜单使能ui->combox->setEnabled(false);ui->baudrate->setEnabled(false);ui->databit->setEnabled(false);ui->conparebit->setEnabled(false);ui->stopbit->setEnabled(false);ui->openport->setText(tr("关闭串口"));ui->send->setEnabled(true);//连接信号槽QObject::connect(serial, &QSerialPort::readyRead, this, &usr::Read_Data);log("[usr.cpp]-[on_openport_clicked()]: System Start.");log("[usr.cpp]-[on_openport_clicked()]" +'\n' + serialInfo);

发送中断并获取当前光照强度

  发送中断是获取数据的最基本的方式,获取实时数据或者定时获取数据归根结底都是基于发送中断于定时发送中断实现的。得益于Qt5串口类的封装,发送中断变得很简洁。

//向单片机发送check
void usr::sendchk()
{QString chk = "check";serial->write(chk.toLatin1());log("[usr.cpp]-[sendchk()]: CHECK has been sent.");
}

定时监控

  通过定时的向串口发送中断获取信息,来起到监控数据的功能。

void usr::on_startAutoControl_clicked()
{if(token == false){qDebug()<<tr("token == false");ui->startAutoControl->setText(tr("暂停监控"));qDebug()<<tr("暂停监控");token = true;qDebug()<<tr("token赋值为true");on_autoControlSwitch_stateChanged(Qt::Checked);log("[usr.cpp]-[on_startAutoControl_clicked()]: Auto Control has been started.");}else if(token == true){qDebug()<<tr("token == true");ui->startAutoControl->setText(tr("开始监控"));qDebug()<<tr("开始监控");token = false;qDebug()<<tr("token赋值为false");on_autoControlSwitch_stateChanged(Qt::Unchecked);log("[usr.cpp]-[on_startAutoControl_clicked()]: Auto Control has been stopped.");}
}

  定时功能使用了QTimer类,并且通过槽函数机制链接到sendchk()函数给串口发送中断。

//开启自动监控
void usr::on_autoControlSwitch_stateChanged(int arg1)
{if(arg1 == Qt::Checked){qDebug()<<tr("timer == checked");       //设置定时器timer = new QTimer(this);timer->setInterval(20000);               //设置时间间隔connect(timer,SIGNAL(timeout()),this,SLOT(sendchk()));      //链接槽函数qDebug()<<tr("Timer has been inited!");timer->start(1000);     //开始log("[usr.cpp]-[on_autoControlSwitch_stateChanged]: QTimer has been started.");}if(arg1 == Qt::Unchecked){qDebug()<<tr("arg1 == unchecked");timer->stop();                      //暂停定时器qDebug()<<tr("已暂停定时器");log("[usr.cpp]-[on_autoControlSwitch_stateChanged]: QTimer has been paused.");}
}

显示数据

  显示数据是我在开发过程中花费时间最长的模块,原因是我不太明白Qt接收到串口信息的数据类型以及如何进行格式转换。串口接收到的数据是十六进制的信息,此处需要对其进行格式转换,但是不知为何我在使用toInt()方法时总是有Bug,不太明白为什么,如果有大佬知道的话,请评论区解答一下,谢谢~
  所以这里提供另一种解决方案。总的逻辑是这样,首先通过寄存器buf暂存从串口中读取到的数据,然后通过bytesToInt(buf)转化成整型数据,然后再通过QString::number(decbuf,10)将其转化成字符串型输出。

//将QByteArray型转换成int型
int usr::bytesToInt(QByteArray bytes)
{int addr = bytes[0] & 0x000000FF;addr |= ((bytes[1] << 8) & 0x0000FF00);addr |= ((bytes[2] << 16) & 0x00FF0000);addr |= ((bytes[3] << 24) & 0xFF000000);return addr;
}//读取接收到的数据
void usr::Read_Data()
{QByteArray buf;int decbuf;//bool ok;//读取串口数据buf = serial->readAll();//对串口数据进行格式转换decbuf = bytesToInt(buf);QString strbuf = QString::number(decbuf,10);QString strTime = getTime();if(ifHandle == true){strbuf = strTime + ":      光照强度为:" + strbuf + "**********手动获取" + '\n';writeFile(strbuf);ifHandle = false;}else{strbuf = strTime + ":      光照强度为:" + strbuf + "**********自动获取" + '\n';writeFile(strbuf);}if(!buf.isEmpty()){QString str = ui->outputEdit->toPlainText();str+=strbuf + '\n';ui->outputEdit->clear();ui->outputEdit->append(str);}buf.clear();
}

系统日志

  这个模块不是必要模块,有没有都不影响程序运行,只是在开发过程调试Bug中大量使用了qDebug()打印运行信息,最后索性将其封装成一个单独的方法,用来保存运行信息,当系统宕机时,可以查看系统日志了解系统在哪个模块出现了问题。


//系统日志
void usr::log(QString str)
{QFile file("log.csv");QString strTime = getTime();str = '\n' + strTime + ":  " + str;file.open(QIODevice::ReadWrite | QIODevice::Append);QTextStream out(&file);out<<str<<endl;qDebug()<<"write success";
}

数据存储

  数据存储讲道理是应该架在数据库上的,但是我为了图方便没有使用使用数据库,只做了一张表格存储数据。说到这,上个月我去参加山大的研究生复试,面试的时候介绍我的项目,因为这个还被老师质疑了。

我:………………(介绍我的项目)……老师,我介绍完了。
老师:就这个?
我:???
我:就这个…
老师:你用了什么数据库?
我:没用数据库…
老师:没用数据库?
我:对。
老师:行,就这样吧。

  所以说这东西还有很多东西需要继续做,如果大家有兴趣可以继续做下去。

//写文件模块
void usr::writeFile(QString str)
{QFile file("data.csv");QDir dir;qDebug()<<dir.currentPath();file.open(QIODevice::ReadWrite | QIODevice::Append);QTextStream out(&file);out<<str<<endl;qDebug()<<"write success";log("[usr.cpp]-[writeFile(QString str)]: write success");
}

总结

  这个项目本身难度不算太高,跟着这三篇Blog自己单独的做下来我认为问题不大,但这恰恰失去了Debug本身的乐趣,我至今还记得解决每一个问题时的喜悦心情,那种欢愉是copy难以企及的。
  我认为程序猿最重要的学习方式就是在Coding过程中你所收获的新的解题思路以及欢愉。

附录

单片机代码
main.c

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "adc.h"extern u8 send_flag;int main(void){    u16 adcx;float temp;delay_init();           //延时函数初始化    NVIC_Configuration();      //设置NVIC中断分组2:2位抢占优先级,2位响应优先级uart_init(9600);       //串口初始化为9600LED_Init();              //LED端口初始化     Adc_Init();             //ADC初始化while(1){if(send_flag == 1 ){send_flag = 0;adcx=Get_Adc_Average(ADC_Channel_1,10);USART_SendData(USART1,adcx);}}}

usart.c

#include "sys.h"
#include "usart.h"
//
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_UCOS
#include "includes.h"                 //ucos 使用
#endifu8 send_flag = 0;
u8 rev_c_flag = 0;
u8 rev_h_flag = 0;
u8 rev_e_flag = 0;
u8 rev_c2_flag = 0;
u8 rev_k_flag = 0;
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{ int handle; }; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{ x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{      while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   USART1->DR = (u8) ch;      return ch;
}
#endif #if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0,   接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记   //初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);   //使能USART1,GPIOA时钟USART_DeInit(USART1);  //复位串口1//USART1_TX   PA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9//USART1_RX    PA.10GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA10//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;       //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  //收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断USART_Cmd(USART1, ENABLE);                    //使能串口 }void USART1_IRQHandler(void)                   //串口1中断服务程序{u8 Res;
#ifdef OS_TICKS_PER_SEC     //如果时钟节拍数定义了,说明要使用ucosII了.OSIntEnter();
#endifif(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾){Res =USART_ReceiveData(USART1);//(USART1->DR);   //读取接收到的数据if((rev_c_flag == 0)&&(rev_h_flag == 0)&&(rev_e_flag == 0)&&(rev_c2_flag == 0)&&(rev_k_flag == 0)){if(Res == 'c'){rev_c_flag = 1;}}if((rev_c_flag == 1)&&(rev_h_flag == 0)&&(rev_e_flag == 0)&&(rev_c2_flag == 0)&&(rev_k_flag == 0)){if(Res == 'h'){rev_h_flag = 1;}}if((rev_c_flag == 1)&&(rev_h_flag == 1)&&(rev_e_flag == 0)&&(rev_c2_flag == 0)&&(rev_k_flag == 0)){if(Res == 'e'){rev_e_flag = 1;}}if((rev_c_flag == 1)&&(rev_h_flag == 1)&&(rev_e_flag == 1)&&(rev_c2_flag == 0)&&(rev_k_flag == 0)){if(Res == 'c'){rev_c2_flag = 1;}}if((rev_c_flag == 1)&&(rev_h_flag == 1)&&(rev_e_flag == 1)&&(rev_c2_flag == 1)&&(rev_k_flag == 0)){if(Res == 'k'){rev_c_flag = 0;rev_h_flag = 0;rev_e_flag = 0;rev_c2_flag = 0;rev_k_flag = 0;send_flag = 1;}}}
#ifdef OS_TICKS_PER_SEC     //如果时钟节拍数定义了,说明要使用ucosII了.OSIntExit();
#endif
}
#endif

源码下载

Github

#上位机开发大师之路# 串口控制模块开发相关推荐

  1. STC15单片机-上位机通过Modbus-RTU协议与开发板通信

    上位机通过Modbus-RTU协议与开发板通信 Modbus协议 Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于 1979年为使用可编程 ...

  2. 如何调试上位机软件与串口进行通信

    为了在没有下位机连接的情况下调试上位机软件,看上位机软件是否能通过串口和下位机通信,以及通信的具体内容,下面给出解决方法: 1.下载"vspd虚拟串口" vspd虚拟串口软件是用来 ...

  3. python 串口上位机_如何使用Python开发串口通讯上位机(二)

    黑色的dos窗口对于大部分来说,页面极为不友好,且操作不方便,因此本篇主要讲讲如何结合QtDesigner创建一个UI并初步与串口Api链接.1 QtDesigner进行上位机页面设计 Python下 ...

  4. Labview上位机与单片机系统的开发

    目的: 大家都知道,现在无论是做项目或者做一个相对复杂一点的系统,对于上位机的需求都是必不可少的,因此本文为大家提供了Labview的安装包以及VISA(串口通信需要)安装包,在后期也会一步一步和大家 ...

  5. matlab制作以太网数据接收上位机_Python制作串口通讯上位机

    串口通讯具有简单易用的特点广泛应用于测试设备的通讯和数据传递.单片机与计算机的通讯等,本案例基于Python语言制作一个用于接收燃油质量流量计的串口通讯上位机,实现数据的读取和保存. 1. 相关知识点 ...

  6. 开发工业上位机 用pyqt5_用Pyqt5开发的基于MTCNN、FaceNet人脸考勤系统

    import sys import cv2 from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import * from ...

  7. C#串口上位机软件--IOT串口调试精灵

    最近用业余时间用C#写了一款适合自己目前岗位工作用的串口软件,"串口调试"板块的功能99.99%基本完成,后续会持续更新预留的功能或新增功能.欢迎下载使用本串口软件,"网 ...

  8. Python上位机,监视串口,逐字符匹配字头

    博主编写的这个匹配不用正则表达库,因为那个得先把字符串拿到才能匹配,博主直接从串口读一个字符匹配一下,感觉更高效一点,直接贴代码,如需自取: def multi_parse_check(strings ...

  9. Matlab GUI上位机界面实现串口通信

    Matlab GUI因项目需求,不得不学的又杂又浅,趁着还没彻底忘记,写下来一些关键注意点. 命令行窗口输入guide→Blank GUI→确定 根据自己的需求,拖动选择对应的工具,如下图所示 双击每 ...

  10. matlab写的串口通信图像上位机,MATLAB的串口通信

    属性 BaudRate        Specify the rate at which bits are transmitted BreakInterruptFcn        Specify t ...

最新文章

  1. opencv中的CommandLineParser类用法
  2. 抛物线交点式公式_2020“九校联考”中点公式解决平四存在性问题
  3. 【Python】Python处理图像五个有趣场景,很实用!
  4. boost::msm::mpl_graph::incidence_list_graph相关的测试程序
  5. java中把map转换成list
  6. android java split_Java中的split函数的用法
  7. android xml java混合编程_Android | 自动调整文本大小的 TextViews
  8. 2015 2020 r4烧录卡 区别_【2015年和2020年上半年市场资金结构有何差异?】东北证券金融工程择时周报20200802...
  9. 如何在SVN创建分支版本
  10. 通信电子线路实验-调幅模块仿真(发送与接收)
  11. oracle优化distinct,oracle 索引优化之distinct
  12. java毕业设计_员工绩效考评系统
  13. mysql年龄最大_使用MySQL子查询选择年龄最大的所有用户?
  14. 搭建网站基本步骤(搭建一个网站的步骤)
  15. 多重背包二进制优化(wzk吃小鸡腿)
  16. 关于游戏中的数据分析
  17. 2020互联网大厂职级对应薪资一览表。
  18. HTML网页设计【足球科普】学生DW静态网页设计
  19. UPS不间断电源调试注意事项
  20. 0503《软件工程》的简单小总结与展望

热门文章

  1. 个人知识管理能解决什么问题?
  2. 「代码随想录」 377. 组合总和 Ⅳ 【动态规划】力扣详解!
  3. 如何阻止 AcrobatPro DC自动更新升级?
  4. 区块链开发(一)搭建基于以太坊的私有链环境
  5. Mac电脑风扇转速调节工具Macs Fan Control
  6. C++11之 Move semantics(移动语义)(转)
  7. 织云Lite发布:详解包管理核心能力
  8. LINUX下的21个特殊符号
  9. SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解——跟着开涛学SpringMVC...
  10. 理解OAuth 2.0[摘]