前述工作:
(1)基于QT的CAN通讯数据实时波形显示(连载二)已经可以正确打印接收到的数据,并且观察了debug出的数据,得出接收前期没有丢点的现象。
(2)采用接收按钮的槽函数,每次只能点一次接收一次数据,并且接收到的数据个数等于程序中的100。
ReceiveNum = VCI_Receive(nDeviceType,nDeviceInd,nCANInd,Receive,100,400);
注意:下位机上传数据位10ms一次,一次5帧,一次8个uint8_t(char)数据。具体数据格式为:
具体说明下:128为报头,第2行到第6行为10ms的数据,第7行到第11行为20ms的数据,以此往下,每次传输数据都以128为报头开始传输。这个就是打印出来的数据(仅仅是数据对应,这个表是后来的),其中20是can的id地址,暂时不管。
第二部分:receive函数介绍
ReceiveNum = VCI_Receive(nDeviceType,nDeviceInd,nCANInd,Receive,100,400);
函数为接收can帧函数,pdf中介绍receivenum为接收到帧的个数,验证后确实是(这个....说话有点问题,肯定是了)。函数中三个形参可以看看pdf就可以了解了,设置见《基于QT的CAN通讯数据实时波形显示(连载二)》,里面介绍的很详细。最后一个参数400是返回时间,可以不管,can正确的时候没啥用,can不正确的时候才有用,没研究,一个软件接收在实验室里面应该没啥问题,就算出故障了,也没办法了,下层硬件和传输线都很好,可能在调试不出来的时候才有用吧,暂且搁置,等can出现故障的时候再研究研究这个参数。
最重要的是这个100。说明下试验步骤:
(1)can帧1s传输一次,将100改成5,这个可以随意,也就是能接收到5帧。一直点“接收按钮”,也就是函数不到1s就执行一次,出现receivenum等于0的现象。解释:执行函数,没有接收到can帧,所以接收到帧数等于0。点几次之后,出现receivenum等于1,也就是接收到了1帧can帧。当间隔时间长一点点击接收按钮,差不多3s去执行一次接收函数,这时会出现receivenum等于3的现象。解释:can帧的接收存储在一个缓存里,具体位置应该是QT分配的,具体位置不知道,多大不知道。如果将点的时间加长,比如隔10s点一次,其实缓存里已经存储了10帧的can数据,这个时候就会等于receivenum就会等于5,再次点击,会再出现5帧,再次点击的话,receivenum就等于0了。所以得出结论:can帧会在软件里面缓存,目前没有测试具体能缓存多少帧,不知其上限值。100这个参数即为调出缓存里面数据的最大量。
(2)can帧20ms传输一次。试验前猜测:点击非常快,比如20ms点击一次(有点夸张,其实用的是定时器),那么应该每次receivenum应该是5才对,应为20ms传输5帧。但是我错了。receivenum前期会很小,慢慢的receivenum会靠近100。receivenum数值和点击的频率没有关系,而是缓存100个数据之后,一次抛出。这个和串口接收挺像。所以这个参数最好设置为大于传输最大数据量一点点,这样就能正确传输了。
第三部分:timer线程函数
直接点击接收按钮来接收太费劲,所以搞了个按钮,定时去执行receive函数。在h文件里添加。头文件,定义和槽函数。
#include <QtCore/QTimer>
public:
QTimer m_timer;
private slots:
void handleTimeout();
主函数里面添加下列代码,第一句就不用了.......。里面的100是100ms执行一次。其他数据也行,只要别太小,至于最小多少,网上没有统一说法。
ui->setupUi(this);
//定时器
connect(&m_timer, &QTimer::timeout, this, &MainWindow::handleTimeout);
m_timer.setInterval(100);
槽函数
void MainWindow::handleTimeout()
{
}
receive函数就放在这里,初始代码在《基于QT的CAN通讯数据实时波形显示(连载二)》有贴出。这样就能规定时间导出can帧数据了。
第四部分:table制作
为了显示接收到的can数据,采用制表位。选择table widget。
主程序中加入以下程序,设置下这个表。具体的效果参照第一张图。只搞了id号和8个数据位。这个仅仅是表格的制作,没有数据放置的程序。同时加入相对应头文件。
#include <QTableWidget>
#include <QTableWidgetItem>
ui->MESSAGE->setColumnCount(9);
ui->MESSAGE->setHorizontalHeaderLabels(QStringList() << tr("ID") << tr("数据0") << tr("数据1") << tr("数据2") << tr("数据3") << tr("数据4") << tr("数据5") << tr("数据6") << tr("数据7"));
ui->MESSAGE->setColumnWidth(0,40);
ui->MESSAGE->setColumnWidth(1,40);
ui->MESSAGE->setColumnWidth(2,40);
ui->MESSAGE->setColumnWidth(3,40);
ui->MESSAGE->setColumnWidth(4,40);
ui->MESSAGE->setColumnWidth(5,40);
ui->MESSAGE->setColumnWidth(6,40);
ui->MESSAGE->setColumnWidth(7,40);
ui->MESSAGE->setColumnWidth(8,40);
ui->MESSAGE->setFixedWidth(400);
ui->MESSAGE->horizontalHeader()->setStretchLastSection(true);
ui->MESSAGE->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->MESSAGE->setSelectionBehavior(QAbstractItemView::SelectRows);
第五部分:数据代入
代码如下:
void MainWindow::handleTimeout()
{
ReceiveNum = VCI_Receive(nDeviceType,nDeviceInd,nCANInd,Receive,300,400);
int CANID;
uint8_t Data[8];
if(ReceiveNum>0)
{
for (int i = 0; i < ReceiveNum; i++)
{
CANID = Receive[i].ID;
Data[0] = Receive[i].Data[0];
QString strCANID = QString::number(CANID);
int nCount = ui->MESSAGE->rowCount();
ui->MESSAGE->insertRow(nCount);
QTableWidgetItem *pItem = new QTableWidgetItem;
pItem->setText(strCANID);
ui->MESSAGE->setItem(nCount, 0, pItem);
pItem = new QTableWidgetItem;
pItem->setText(strData0);
ui->MESSAGE->setItem(nCount, 1, pItem);
ui->MESSAGE->scrollToBottom();
}
}
}
解释:(1)函数名:时间中断的槽函数,
(2)ReceiveNum为接收到数据的个数。当ReceiveNum大于0时,有数据接收到,执行下列程序。
(3)receive为结构体的数组,上篇中详细介绍过。将can的id和data放入变量中,代码仅仅显示了id和一个变量的放置。余下的可以自己添加。
(4)定义字符串,并将变量数据转化为字符串,为啥要转换呢,就是这样用的,没办法啊。
(5)ncount为行数计数,并且有数据时插入一行,同时显示到表格的最左侧。第一张图中最左侧个数不是自己添加的,而是执行这句话之后自己出来的。
(6)定义与tablewidget相关的参数,固定格式,将转化为字符串的canid放入其中,同时第一个数据的字符串也放入进去。这样就能在表格中显示接收到的数据了。
此时可以在制表位中显示can传输的数据了,将表格中的数据测试了下,和下位机传输的can帧相同,可以确定接收到的数据没有丢点。
第六部分:保存
制表位中的数据直接看的话,需要拉进度条,不太方便,可以存放在excel中。
(1)加入保存按钮,加入保存按钮槽函数。
private slots:
void on_SAVE_clicked();
(2) 主体函数。里面啥意思没看,添加进去就可以用了。将头文件放在前面,后面是代码。注意:保存后的excel是不能直接处理的,里面有空格之类的(不能十进制转化为十六进制),原因和制表位里面的数据格式有关系,先保存,将数据存到txt里面,然后再拷回到excel中,就可以保存为数据类型了。
#include <QDesktopServices>
#include <QUrl>
#include <QFileDialog>
#include <QAxObject>
#include <QMessageBox>
void MainWindow::on_SAVE_clicked()
{
QString fileName = QFileDialog::getSaveFileName(this,tr("Excle file"),QString("./test.xls"),tr("Excel Files(*.xls)")); //设置保存的文件名
if(fileName != NULL)
{
QAxObject *excel = new QAxObject;
if(excel->setControl("Excel.Application"))
{
excel->dynamicCall("SetVisible (bool Visible)",false);
excel->setProperty("DisplayAlerts",false);
QAxObject *workbooks = excel->querySubObject("WorkBooks"); //获取工作簿集合
workbooks->dynamicCall("Add"); //新建一个工作簿
QAxObject *workbook = excel->querySubObject("ActiveWorkBook"); //获取当前工作簿
QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1);
QAxObject *cell;
int rowCount = ui->MESSAGE->rowCount();
int columnCount = ui->MESSAGE->columnCount();
/*添加Excel表头数据*/
for(int i = 1; i <= columnCount ; i++)
{
cell = worksheet->querySubObject("Cells(int,int)", 1, i);
cell->setProperty("RowHeight", 40);
cell->dynamicCall("SetValue(const QString&)", ui->MESSAGE->horizontalHeaderItem(i-1)->data(0).toString());
}
/*将form列表中的数据依此保存到Excel文件中*/
for(int j = 2; j <= rowCount + 1;j++)
{
for(int k = 1;k <= ui->MESSAGE->columnCount();k++)
{
cell = worksheet->querySubObject("Cells(int,int)", j, k);
cell->dynamicCall("SetValue(const QString&)",ui->MESSAGE->item(j-2,k-1)->text()+ "\t");
}
}
/*将生成的Excel文件保存到指定目录下*/
workbook->dynamicCall("SaveAs(const QString&)",QDir::toNativeSeparators(fileName)); //保存至fileName
workbook->dynamicCall("Close()"); //关闭工作簿
excel->dynamicCall("Quit()"); //关闭excel
delete excel;
excel = NULL;
if (QMessageBox::question(NULL,QString::fromUtf8("完成"),QString::fromUtf8("文件已经导出,是否现在打开?"),QMessageBox::Yes|QMessageBox::No)==QMessageBox::Yes)
{
QDesktopServices::openUrl(QUrl("file:///" + QDir::toNativeSeparators(fileName)));
}
}
}
}
第七部分:总结
(1)can数据可以正确接收,不漏帧。(2)can数据可以显示到制表位中。(3)制表位中数据可以保存为excel,方便处理。
注:由于小伙伴需要源代码的时间不同,登录邮箱界面太多麻烦,所以建立了一个订阅号,如果有问题或者需要源码,可添加订阅号,留言后会发送源代码或者有任何问题可留言,将积极解决提出的问题。