目录
一、说明
工作中需要,编写硬件上位机,协议为can协议。can转usb可直接连接设备与电脑。can协议需要外部库,来实现调用。
二、环境说明
VS2015,Qt5.9.6,我在VS2015中建立qt项目,我比较喜欢VS的界面,qt在我电脑比较卡。
三、can卡说明
我这边用的can卡是这里USB转can ca...-淘宝网 (taobao.com);商家提供了源代码、外部库,及学习资料,很丰富。
四、项目实现
4.1项目建立+外部库包含
在vs2015中新建项目后,下载商家提供的资源,在其中找到需要的外部库文件,统一复制到项目运行文件夹中。然后再项目的属性中向项目文件夹中添加外部库文件:
然后需要在项目-属性中添加lib库.
五、代码说明
实际中,通信调通是第一步,在调通中理解了,后续再深入学习。
在商家给的代码上稍微修改了下,结合具体的协议,完成了代码编写。
4.2 头文件
接收can消息用了一个线程。
#pragma once
#include <QtWidgets/QWidget>
#include "ui_myCan.h"
#include <QThread>
#include <QDate>
#include "hcanbus.h"
#include <unordered_map> //非排序图
#include <qstring.h>
#include <qdebug.h>
#include <unordered_map>
class thread_receive : public QThread
{
Q_OBJECT
public:
thread_receive();
~thread_receive();
void run(void);
void stop(void);
private:
int breakout = 0;
Can_Msg rmgs[500];
signals:
void receivemsg(Can_Msg *msg, int lenth);
};
class myCan : public QWidget
{
Q_OBJECT
public:
myCan(QWidget *parent = Q_NULLPTR);
~myCan();
private slots:
void readcan(Can_Msg *rmgs, int lenth);
void on_opencan_clicked();
void hotplug_func(void);
void on_btnsend_clicked();
void on_btnstop_clicked();
private:
int txflag;
int canport;
int devices;
QDate lastday;
int baudrate;
thread_receive *threc;//接收线程
int rxlenth = 0;
bool usbportstate = false;
int winitcan();
std::unordered_map<int, unsigned int> baudRateMap =
{
{ 0,1000 },{ 1,900 },{ 2,800 },{ 3,666 },{ 4,600 },{ 5,500 },{ 6,250 },{ 7,200 },
{ 8,125 },{ 9,100 },{ 10,80 },{ 11,50 },{ 12,40 },{ 13,20 },{ 14,10 },{ 15,5 }
};
std::unordered_map<int, QString> Sys_status1 =
{
{ 0,QString::fromLocal8Bit("waring1") },
{ 1,QString::fromLocal8Bit("waring2") },
{ 2,QString::fromLocal8Bit("waring3") },
{ 3,QString::fromLocal8Bit("waring4") },
{ 4,QString::fromLocal8Bit("waring5") },
{ 5,QString::fromLocal8Bit("waring6") },
{ 6,QString::fromLocal8Bit("waring7") },
{ 7,QString::fromLocal8Bit("waring8") }
};
std::unordered_map<int, QString> Sys_status2 = {
{ 7, QString::fromLocal8Bit("waring9") },
{ 6, QString::fromLocal8Bit("waring10") },
{ 5, QString::fromLocal8Bit("waring11") },
{ 4, QString::fromLocal8Bit("waring12") },
{ 3, QString::fromLocal8Bit("waring13") },
{ 2, QString::fromLocal8Bit("waring14") },
{ 1, QString::fromLocal8Bit("waring15") },
{ 0, QString::fromLocal8Bit("waring16") }
};
Ui::myCanClass ui;
signals:
void sendhotplugs(void);
};
4.3ui界面布局
界面布局,及各部件命名如下图,这里将接收的数据做了解析,实际中也是将接收数据中的Data解析出来。
4.4.cpp实现
#include "myCan.h"
#include <QMessageBox>
#include <QDebug>
#include "datpro.h"
#include <stdio.h>
#define s8 signed char
#define u8 unsigned char
#define s16 signed short
#define u16 unsigned short
#define s32 signed int
#define u32 unsigned int
unsigned int cpot;
Can_Msg tmsg[100000];
//数据接收线程
thread_receive::thread_receive() {}
thread_receive::~thread_receive() {}
void thread_receive::stop(void) { breakout = 0; }
void thread_receive::run()
{
int lenth;
breakout = 1;
while (breakout)
{
lenth = CAN_Receive(cpot, rmgs, 100, 1000);
if (lenth < 0) { msleep(100); continue; } //操作失败,设备未打开
emit receivemsg(rmgs, lenth);
}
}
myCan::myCan(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
cpot = 0;
usbportstate = false;
qRegisterMetaType<Can_Msg>("Can_Msg");//qt类型注册,才能在connect中引用
threc = new thread_receive;
connect(threc, SIGNAL(receivemsg(Can_Msg*, int)), this, SLOT(readcan(Can_Msg*, int)));//接收线程
connect(this, SIGNAL(sendhotplugs()), this, SLOT(hotplug_func()));//热拔插
ui.btnsend->setEnabled(false);
ui.baudrate->setCurrentIndex(5);
this->setWindowTitle(QString::fromLocal8Bit("CAN通信"));
hotplug_func();
}
//检测设备热插拔,设备状态改变就发消息
void myCan::hotplug_func(void)
{
int i, devs;
QString str;
QString str2;
devs = CAN_ScanDevice();//can设备扫描
devices = 0;
qDebug() << "CAN_ScanDevice" << devs;
if (usbportstate)
{
usbportstate = false;
ui.canpot->clear();
QMessageBox::information(this, QString::fromLocal8Bit("注意!"), QString::fromLocal8Bit("设备被移除!"));
return;
}
if (devs > 0)
{
if (devs != devices)
{
devices = devs;//
ui.canpot->clear();
for (i = 0; i < devices; i++)
{
str.sprintf("%d", i);
ui.canpot->addItem(str);
}
canport = 0;
if ((usbportstate == false) && (devices > 0) && (canport < ui.canpot->count()))
{
ui.canpot->setCurrentIndex(canport);
}
}
}
else
{
devices = 0;
ui.canpot->clear();
}
}
//接收数据解析
void myCan::readcan(Can_Msg *rmgs, int lenth)
{
int i;
QString str;
//int tmplenth = rxlenth;//表格
if (lenth <= 0) return;
qDebug() << "ID:" << rmgs->ID; //0x401接收数据
if (rmgs->ID == 0x401)
{
unsigned int total_V = (static_cast<uint32_t>(rmgs->Data[0]) << 8) | (static_cast<uint32_t>(rmgs->Data[1]));
ui.lineEdit_outV->setText(QString::number(total_V));
unsigned int total_A = (static_cast<uint32_t>(rmgs->Data[2]) << 8) | (static_cast<uint32_t>(rmgs->Data[3]));
ui.lineEdit_totalA->setText(QString::number(total_A));
unsigned int soc = (static_cast<uint32_t>(rmgs->Data[4]) << 8) | (static_cast<uint32_t>(rmgs->Data[5]));
ui.lineEdit_soc->setText(QString::number(soc));
//Sys_status1
uint8_t waring_cod = rmgs->Data[6]; // 第7个字节
for (int i = 0; i < 8; i++)
{
if (waring_cod & (1 << i))
{
ui.lineEdit_status1->setText(Sys_status1[i]);
}
}
//Sys_status2
uint8_t waring_value = (unsigned char)rmgs->Data[7]; // 第8个字节
for (int i = 0; i < 8; i++)
{
if (waring_value & (1 << i))
{
ui.lineEdit_status2->setText(Sys_status2[i]);
}
}
}
}
myCan::~myCan()
{
threc->stop();
threc->wait();
}
//can口初始化
int myCan::winitcan()
{
int rtn;
Can_Config cancfg;
cancfg.Model = 0;
int baudindex = ui.baudrate->currentIndex();
cancfg.Baudrate = 1000 * baudRateMap[baudindex];
cancfg.Configs = 0;
cancfg.Configs |= 0x0001; //接通内部匹配电阻 //最低位(从右边数起的第一位)设置为1(位或操作,两者只要有一个是1,就赋1)
cancfg.Configs |= 0x0002; //开启离线唤醒模式 最低位(从右边数起的第二位)设置为1
cancfg.Configs |= 0x0004; //开启自动重传 最低位(从右边数起的第三位)设置为1
rtn = CAN_Init(cpot, &cancfg);//根据设置信息,初始化can设备
return rtn;
}
//打开can口
void myCan::on_opencan_clicked()
{
if (usbportstate)
{
ui.opencan->setText(QString::fromLocal8Bit("打开"));
usbportstate = !usbportstate;
int ret = CAN_CloseDevice(cpot);
qDebug() << "CloseDevice" << ret;
ui.btnsend->setEnabled(false);
}
else
{
if (ui.canpot->count() == 0)
{
QMessageBox::information(this, QString::fromLocal8Bit("错误!"), QString::fromLocal8Bit("没有找到CAN设备!")); return;
}
cpot = ui.canpot->currentIndex();
int ret = CAN_OpenDevice(cpot);//先打开can设备
if (ret != 0)
{
QMessageBox::information(this, QString::fromLocal8Bit("错误!"), QString::fromLocal8Bit("打开CAN设备失败!")); return;
}
qDebug() << "winitcan" << winitcan();//can设备初始化
ui.opencan->setText(QString::fromLocal8Bit("关闭"));
usbportstate = !usbportstate;
threc->start();
ui.btnsend->setEnabled(true);
}
}
//按键处理函数,发送报文
void myCan::on_btnsend_clicked()
{
int i, itm, stimes;
u32 dlytime, txed;
QString str;
txflag = 1; //赋值发送标识为1。
ui.btnsend->setEnabled(false); //失能发送按钮,防止重复点击。
int senditms = ui.sdtimes->text().toInt(); //获取发送次数
if (senditms > 100000) senditms = 100000;
else if (senditms <1) senditms = 1; //根据实际需求限定范围
ui.sdtimes->setText(str.sprintf("%d", senditms)); //重新设置发送次数
dlytime = ui.sddely->text().toDouble() * 1000; //获取帧间隔时间
int canid = ui.canid->text().toInt(); //can口ID
//根据发送次数
//报文结构,
for (i = 0; i < senditms; i++) //初始化CAN报文结构体数组
{
tmsg[i].ID = canid; //报文ID 0
tmsg[i].DataLen = str2u8(tmsg[i].Data, ui.candt->text()); //界面获取str,根据str得到数据长度,同时赋值数据
if (ui.checkBox->isChecked()) { tmsg[i].TimeStamp = i*dlytime; } //如果是定时发送,计算时间戳 //1
else { tmsg[i].TimeStamp = 0; } //非定时发送,时间戳清零
tmsg[i].FrameType = 0; //帧类型 2
}
//本示例处理发送数据是在UI线程中。实际运用可以单独创建线程,处理发送函数,系统响应更迅速。
if (ui.checkBox->isChecked()) //如果是定时发送
{
tmsg[0].FrameType = 0x10; //第一帧报文类型赋值 0x10,表示启动定时发送2
tmsg[senditms - 1].FrameType = 0x20; //最后一帧报文类型赋值0x20,表示结束定时发送2
itm = 20000 / dlytime; //以10毫秒发送报文数为依据赋值单次发送报文个数
if (itm < 2) { itm = 2; } //限制单次发送报文个数最小值
else if (itm > 50) { itm = 50; } //限制单次发送报文个数最大值
stimes = senditms / itm; //计算发送次数整数
for (i = 0; i < stimes; i++)
{
CAN_TransmitRt(cpot, tmsg + i*itm, itm, &txed, 100); //使用定时发送函数,发送报文
if (txflag != 1) { break; } //若发送标识不为1,退出循环停止发送。
QApplication::processEvents(); //启动事件循环,避免假死机
}
if (senditms%itm > 0) //若余数大于零,继续发送剩余报文。
{
CAN_TransmitRt(cpot, tmsg + i*itm, (senditms%itm), &txed, 100);
}
}
else //非定时发送。
{
for (i = 0; i < senditms / 100; i++) //每次发送100帧
{
CAN_Transmit(cpot, tmsg + i * 100, 100, 100); //使用一般发送模式发送报文。
if (txflag != 1) { break; } //若发送标识不为1,退出循环停止发送。
QApplication::processEvents(); //启动事件循环,避免假死机
}
if (senditms % 100 > 0) //若余数大于零,继续发送剩余报文。
{
CAN_Transmit(cpot, tmsg + i * 100, (senditms % 100), 100);
}
}
ui.btnsend->setEnabled(true); //使能发送按钮。
}
void myCan::on_btnstop_clicked()
{
txflag = 0;
}
4.5 bug修复
程序运行之后发现,热插拔函数没有响应--表现位,can卡已经连接电脑,但是通道没有刷新出来。然后将热插拔函数放在构造函数中调用,发现通道就能刷新,但是还是没有响应can卡的插拔事件。
于是我搜索该函数在哪里调用了,信号在哪里调用了,最后发现,热插拔函数需要在main中注册。
参考源程序:
然后,对我们的mycan项目进行修改:
main程序改为:
#include "myCan.h"
#include <QtWidgets/QApplication>
myCan *w;
void func_hotplugs()
{
w->sendhotplugs();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
w = new myCan;
Reg_HotPlug_Func(&func_hotplugs);
w->show();
return a.exec();
}
运行程序,can通道刷新出来,拔掉can卡,有提示;重新插入can卡,也有提示,自动刷新。
五、总结
整个程序,参考商家提供的代码,但是那是为了展示can通信来的,实际中,我们需要从接收的数据中解析出数据。我自己编写的一部分协议,运行结果如下。
整个程序,逻辑上不难,自己从头打一遍实在费工夫。参考其他人的程序,然后能够做出自己的修改,完成自己的工作,如果后面工作又深入需求,那么继续学习就行。