目 录
5.1.1. 步骤1.1: 总体设计串口调试软件索要具备的功能模块 5
5.1.3. 步骤1.3:整个程序界面支持中文、发送和接收支持中文 6
5.2.3. 步骤2.3:设置波特率、校验位、数据位和停止位 9
5.4.1. 步骤5.4.1:接收串口信息及十六进制接收 15
5.4.2. 步骤5.4.2:保存接收到的信息到本地选定的目录 16
课程设计报告
1. 课程设计内容
本课程设计的内容是设计一个基于QT的串口调试软件,同时支持中文、英文数传输、十六进制数据传输、文件内容传输。
2. 课程设计目的
考察自己对课程的掌握程度,熟悉QT开发环境,以及自己实际的动手能力,C++编程能力。
3. 背景知识
串口通信原理
QT软件的应用
4. 工具/准备工作
硬件:
安装有QT的PC机一台
软件:
Windows 10操作系统
QT 4.2.0
5.设计步骤与方法
5.1.设计软件界面布局
1、总体设计串口调试软件所要具备的功能模块
2、通过已划定的功能模块来布局软件的界面,从而逐个实现其功能
3、软件功能流程图如下:
5.1.1. 步骤1.1: 总体设计串口调试软件索要具备的功能模块
1、串口通信参数设置
(1)选择计算机中的串口号
(2)波特率
(3)校验位
(4)数据为
(5)停止位
(6)串口启动
2、发送区
(1)发送信息
(2)设置自动发送时间及自动发送
(3)打开本地文件进行发送
(4)清空发送区域的文本内容
(5)设置十六进制数据发送
3、接收区
(1)接收串口信息
(2)以十六进制接收信息
(3)保存所接收的信息到用户选定的目录中
4、状态栏
(1)统计所发送的数据
(2)统计所接收的数据
(3)显示开启串口后用户所设定的串口通信参数
5.1.2. 步骤1.2:软件界面布局
5.1.3. 步骤1.3:整个程序界面支持中文、发送和接收支持中文
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF8")); QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF8"));
5.2. 步骤2:串口通信参数设置
5.2.1. 步骤2.1:开启串口通信
通过设置好一系列通信所需的参数后,点击按钮,程序即会根据ComboBox中选中的参数加载到串口通信的初始化工作中。
void MainWindow::on_pushButton_2_clicked() //点击开启 按钮
{
.......省略部分代码
if(flag==true)
{
QMessageBox::about(this,tr("提示信息"),tr("串口成功开启!"));
BaudRate();
DataBits();
Parity();
StopBits();
pMycom->setFlowControl(FLOW_OFF);
pMycom->setTimeout(500);
/*显示状态栏信息*/
baudrate=ui->comboBox_2->currentText();
databits=ui->comboBox_3->currentText();
parity=ui->comboBox_4->currentText();
stopbits=ui->comboBox_5->currentText();
ui->statusBar->addWidget(statusLabel);
statusLabel->setText("STATUS: "+com+" OPENED, "+baudrate+" | "+databits+" | "+parity+" | "+stopbits);
connect(pMycom,SIGNAL(readyRead()),this,SLOT(myRecv()));
swi=true; //**其他按钮
in=false;
}
}
.......省略部分代码
}
5.2.2.步骤2.2:实现自动识别pc机串口数
通过调用windows的API(#include<qt_windows.h>)来扫描pc的注册表,以实现自动识别串口数的功能。通过定义键值对变量来遍历注册表信息来识别计算机中的串口数量,最后关闭注册表,返回串口名字。
String MainWindow::getcomm(int index, QString keyorvalue) //自动识别串口
{
.....省略部分代码
indexnum = index;//要读取键值的索引号
keysize = sizeof(keyname);
valuesize = sizeof(keyvalue);
if (RegEnumValue(hKey, indexnum, keyname, &keysize, 0, &type, (BYTE*)keyvalue, &valuesize) == 0)
{
for (int i=0; i<(int)keysize; i++)
{
message = keyname[i];
keymessage.append(message);
}
for (int j=0; j<(int)valuesize; j++)
{
if (keyvalue[j] != 0x00)
valuemessage.append(keyvalue[j]);
}
if (keyorvalue == "key")
commresult = keymessage;
if (keyorvalue == "value")
commresult=valuemessage;
}
else
commresult = "nokey";
RegCloseKey(hKey);//关闭注册表
return commresult;
}
5.2.3. 步骤2.3:设置波特率、校验位、数据位和停止位
本程序使用头文件已经定义好的一系列参数名称,通过if语句判断ComboBox控件选中的参数来对串口通信进行参数选定,以波特率为例如下:
void MainWindow::BaudRate()
{
if(ui->comboBox_2->currentText() == tr("300"))
{
pMycom->setBaudRate(BAUD300);
}
else if(ui->comboBox_2->currentText() == tr("600"))
{
pMycom->setBaudRate(BAUD600);
}
.......省略部分代码
}
1、波特率
串口通信时的速率。如每秒钟传送1920个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),波特率为240Bd,比特率为10位*240个/秒=2400bps。
2、校验位
在串口通信中一种简单的检错方式。有三种检错方式:偶(E)、奇(O)、无(N)。
对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据不同步。
例如:需要传输“01001011”,数据中含有4个“1”,所以其奇校验位为“1”(如果数据中“1”的个数为奇数,则奇校验为“0”),同时把“01001011”传输给接收方,接收方收到数据后再一次计算奇偶性,“01001011”中仍然含有4个“1”,所以接收方计算出的奇校验位还是“1”,与发送方一致,表示此次传输过程中未发生错误。
3、数据位
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。
如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。
如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。
每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
4、停止位
用于表示单个数据包的最后一位。典型的值为1、1.5或2位。停止位不仅表示传输的结束,并且提供计算机校正时钟同步的机会。停止位的位数越多,不同时钟同步的容错程度越大,但同时数据传输率也越慢。
5.3.步骤3:发送区设计
实现功能如下:
(1)发送信息
(2)设置自动发送时间及自动发送
(3)打开本地文件进行发送
(4)清空发送区域的文本内容
(5)设置十六进制数据发送
5.3.1. 步骤3.3.1:发送信息
发送信息分为十进制发送和十六进制发送,所以在程序中的发送信息自动发送信息两个功能中需要判断是否为十六进制发送。使用pMycom->write(buf)来把信息通过串口发送出去。
void MainWindow::on_pushButton_clicked() //发送信息按钮
{
isHexWrite=ui->HexSend->isChecked();
if(swi==true)
{
QString str=ui->textEdit->toPlainText();
QByteArray buf;
if(isHexWrite) //此处勾选了十六进制发送
{
StringToHex(str,buf);
pMycom->write(buf);//发送到串口
inttx=inttx+(str.length())/2;
statusSend->setText("Tx:"+QString::number(inttx));
}
else //此处没有勾选了十六进制发送
{
buf=str.toUtf8();
pMycom->write(buf);
inttx=inttx+str.length();
statusSend->setText("Tx:"+QString::number(inttx));
}
}
else
QMessageBox::about(this,tr("提示信息"),tr("请打开串口"));
}
5.3.2. 步骤3.3.2:自动发送
自动发送功能的实现和发送信息的功能基本一致,在发送信息的功能上加入一个定时器,同时设置定时时长,然后开启定时器,实现自动发送功能。第一次按下“自动发送”按钮会启动定时器,进行自动发送,定时器的参数会根据lineEdit中输入的值进行定时,如果lineEdit中没有输入值,则无法进行自动发送。当第二次按下“自动发送”按钮,会停止定时器,终止自动发送。定时器的代码如下:
if(en==true)
{
QMessageBox::about(this,tr("提示信息"),tr("已开始自动发送"));
time=ui->lineEdit->text().toInt();//获取自动发送的时间间隔
pTimer =new QTimer();
connect(pTimer,SIGNAL(timeout()),this,SLOT(mytimeout()));
pTimer->start(time);
en=false;
}
else if(en==false)
{
pTimer->stop();
QMessageBox::about(this,tr("提示信息"),tr("已停止自动发送"));
en=true;
}
}
5.3.3. 步骤3.3.3:打开本地文件进行发送
通过QFileDialog实例化一个文件选择窗口,同时使用QFile来打开文件选择框,获取已选中的文件,对文件进行读操作,把读到的数据输出到软件的发送框中,并记录下文件的绝对路径,显示在界面上。
void MainWindow::selectFile()
{
QFileDialog *fileDialog = new QFileDialog(this);
QString displayString;
fileDialog->setFileMode(QFileDialog::AnyFile);
fileDialog->setViewMode(QFileDialog::Detail);
QStringList qt;
if(fileDialog->exec())
{
qt = fileDialog->selectedFiles();
}
QFile file(qt.at(0)); //打开文件对话框
QString str = qt.join("\\");
file.open(QIODevice::ReadOnly | QIODevice::Text);
/*Label自适应text大小,并自动换行*/
ui->fileLoad->adjustSize();
ui->fileLoad->setGeometry(QRect(328, 240, 329, 27*4)); //四倍行距
ui->fileLoad->setWordWrap(true);
ui->fileLoad->setAlignment(Qt::AlignTop);
ui->fileLoad->setText("路径:"+str);
while(!file.atEnd())
{
QByteArray line = file.readLine();
QString str(line);
displayString.append(str);
}
file.close();
ui->textEdit->clear();
ui->textEdit->setPlainText(displayString);
}
5.3.4. 步骤3.3.4:十六进制的转换
void MainWindow::StringToHex(QString str, QByteArray & senddata)
{
int hexdata,lowhexdata;
int hexdatalen = 0;
int len = str.length();
senddata.resize(len/2);
char lstr,hstr;
for(int i=0; i<len; )
{
hstr=str[i].toLatin1();
if(hstr == ' ')
{
i++; continue;
}
i++;
if(i >= len)
break;
lstr = str[i].toLatin1();
hexdata = ConvertHexChar(hstr);
lowhexdata = ConvertHexChar(lstr);
if((hexdata == 16) || (lowhexdata == 16))
break;
else
hexdata = hexdata*16+lowhexdata;
i++;
senddata[hexdatalen] = (char)hexdata;
hexdatalen++;
}
senddata.resize(hexdatalen);
}
char MainWindow::ConvertHexChar(char ch)
{
if((ch >= '0') && (ch <= '9'))
return ch-0x30;
else if((ch >= 'A') && (ch <= 'F'))
return ch-'A'+10;
else if((ch >= 'a') && (ch <= 'f'))
return ch-'a'+10;
else return ch-ch;//不在0-f范围内的会发送成0
}
5.4. 步骤4:接收区设计
(1)接收串口信息
(2)以十六进制接收信息
(3)保存所接收的信息到用户选定的目录中
5.4.1. 步骤5.4.1:接收串口信息及十六进制接收
接收信息分为十进制接收和十六进制接收,所以需要一个标记来说明。接收数据使用readAll(),使用bytesAvailable()统计接收到的字节数。
void MainWindow::myRecv()
{
isHexRead=ui->checkBox->isChecked();
intrx+=pMycom->bytesAvailable();
QString str=QString::number(intrx);
statusRecv->setText("Rx:"+str); //修改状态栏中Rx值的变化
if(!isHexRead)
{
QByteArray temp =pMycom->readAll();
ui->textBrowser->insertPlainText(temp);
}
if(isHexRead) //判断是否勾选十六进制接收数据
{
QByteArray temp = pMycom->readAll();
QDataStream out(&temp,QIODevice::ReadWrite); while(!out.atEnd())
{
qint8 outChar = 0;
out>>outChar;
QString strrx = QString("%1").arg(outChar & 0xFF,2,16,QLatin1Char('0'));
ui->textBrowser->insertPlainText(strrx.toUpper());
ui->textBrowser->insertPlainText(" ");
}
}
}
5.4.2. 步骤5.4.2:保存接收到的信息到本地选定的目录
1、选择文件要保存到的本地目录
(1) 实例化一个QFileDialog,调用selectedFiles()获取QStringList类型的文件目录,然后通过join()函数将其路径用“\”分割,保存到字符串变量中,再显示到界面中的Label上。
QFileDialog *SavefileDialog = new QFileDialog(this);
SavefileDialog->setFileMode(QFileDialog::DirectoryOnly);
SavefileDialog->setViewMode(QFileDialog::Detail);
QStringList qtSave;
if(SavefileDialog->exec())
{
qtSave = SavefileDialog->selectedFiles();
}
strSaveFileName = qtSave.join("\\");
ui->SaveFileLoad->setText("路径:"+strSaveFileName);
(2) 在选中的目录下新建文本文件,通过文件写操作把收到的数据写入到文本文件中并保存。若点击多次保存信息,所保存的文件名程序有序的生成(Recv0.txt、Recv1.txt......)
static int num=0;
num++;
QString RecvNum=QString::number(num);
strSaveFileName += "/Recv"+RecvNum+".txt";
QFile filename(strSaveFileName);
if(!filename.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::warning(this,tr("错误"),tr("打开文件失败 "),QMessageBox::Ok);
return;
}
else
{
QTextStream textStream(&filename);
QString str = ui->textBrowser->toPlainText();
textStream<<str;
}
QMessageBox::information(this,"保存文件","保存文件成功",QMessageBox::Ok);
filename.close();
int first = strSaveFileName.lastIndexOf ("/");
strSaveFileName= strSaveFileName.left(first);
6. 软件测试截图
中英数据的发送和接收
保存文件目录
打开文件并发送
十六进制接收数据
十六进制发送数据
7. 设计结果及分析
本程序成功的实现了串口通信的中英文发送和接送,同时拓展了自动发送,十六进制转换发送和接送、发送本地选中文件和保存接收到的数据到本地指定目录下的功能。
经过多次的调试,完善了程序的功能,修复一开始没有发现的逻辑错误,使程序更加完美。
8. 设计结论
经过在windows10上串口的相互通信和windows与虚拟机Ubuntu Linux虚拟串口上相互通信,成功实现了串口通信的中英文发送和接送,同时拓展了自动发送,十六进制转换发送和接送、发送本地选中文件和保存接收到的数据到本地指定目录下的功能。
9. 问题及心得体会
通过这次课程设计,大大提高了自己在Qt平台上C++的程序编写能力以及调试程序的能力。程序的总体设计到代码实现到最后调试这一系列的过程每一步都很重要,如果其中一步做不好都会影响到下一步的操作或者最终效果。在本次课程设计中,我首先明确了程序的具体框架和功能,然后通过事先绘制的脑图来一步一步实现其中的功能,最后进行程序的调试。调试的工作对于一个程序来说是非常重要的,作为开发者也作为调试者,自己要找出程序的逻辑错误其实并不容易,因为站在开发者的角度上看,自己写的基本上都是正确的,所以在调试一块花了不少时间,也发现了其中的一些在开发时没有注意到的问题。例如关于程序按钮中点击事件的时间范围,在没有开启串口通信的时候应该使其他全部按钮都无效,当串口启动了后才应该**其他操作。在统计发送和接收的字节数时,要注意中文和英文的字节数统计,在qt里面,中文一个汉字占3个字节,而数字、字符和字母只占1个字节,这个在一次开发的时候时没有注意到这个问题的,在调试程序的时候才发现并加上相应的判断语句才修改过来。
总的来说,这次的课程设计把一个完整的程序做了出来,也提升了自己编程的信心,在嵌入式Linux应用开发这门课上和最终的课程设计上,收获很多,感谢老师的指导。
10. 对本设计过程几方法、手段的改进建议
本次设计是在实验课的基础上对串口调试助手功能的完善,在课上只是实现了接收和发送的功能。在此基础上,增加了对文件流的操作、编码的转换等等,这些其实并不是很难,只要读懂了Qt里提供的类的使用,即可完成这些小功能。在本次课程设计过程中,我先把软件的整体功能框架列出来,然后逐个功能的去解决它。某些功能在实现之后可以对它进行优化,例如串口的识别,一开始只是单纯的设置了2个com1和com2的字符串,这样的灵活性不高,如果某台电脑还有其他串口,就不能发挥作用了,所以本次设计对此进行改进,使用扫描注册表的方式,从中读取串口信息。在保存接收文件中也进行了优化,本来只是简单的保存一个文件,现在实现了多次保存文件,文件不存在会自动创建,增加了程序使用的灵活性和便捷性。
11. 参考文献
【1】 UI Qt4编程(第2版) 兰切特 (Jasmin Blanchette)、萨默菲尔德 (Mark Summerfield)、闫锋欣、曾泉人 子工业出版社 2008