qt5 usb2can卡通信上位机-can通信协议理解-实际协议实现-数据收发

目录

一、说明

二、环境说明

三、can卡说明

四、项目实现

4.1项目建立+外部库包含

4.2 头文件

4.3.cpp实现

4.4 bug修复

五、总结


一、说明

工作中需要,编写硬件上位机,协议为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通信来的,实际中,我们需要从接收的数据中解析出数据。我自己编写的一部分协议,运行结果如下。

整个程序,逻辑上不难,自己从头打一遍实在费工夫。参考其他人的程序,然后能够做出自己的修改,完成自己的工作,如果后面工作又深入需求,那么继续学习就行。

QT5是一种流行的跨平台应用程序开发框架,usbcan-2e-u是一种USB接口的CAN总线通信设备。上位机开发是指基于上位机计算机进行软件开发,用于与其他设备通信、控制、数据处理等应用。 QT5提供了丰富的图形界面和多媒体功能,使得上位机软件开发变得更加简单和高效。通过使用QT5编写的软件可以实现usbcan-2e-u设备的连接和通信开发者可以利用QT5的API和功能来实现CAN总线数据的读取、写入、解析和显示等操作。 在进行QT5 USBCAN-2E-U上位机开发时,首先需要了解usbcan-2e-u设备的功能和特性,包括其USB接口、CAN总线通信协议数据格式等。然后,使用QT5开发工具来创建桌面应用程序项目,并添加必要的界面组件和控件。通过编写数据交互的代码,实现usbcan-2e-u设备的连接和通信。 对于CAN总线数据的读取,可以使用usbcan-2e-u提供的API接口进行调用,通过连接到的设备来读取CAN总线上的数据帧。读取的数据可以通过QT5的界面进行显示和处理,可以使用图表、表格等控件来展示CAN总线数据的实时变化。 对于CAN总线数据的写入,需要根据需要设置CAN总线的波特率、ID和数据信息等,并通过usbcan-2e-u设备将数据帧发送到CAN总线上。 在QT5 USBCAN-2E-U上位机开发过程中,还可以加入其他的功能模块,如数据记录、数据分析、参数设置等,以满足不同应用场景的需要。 总之,QT5 USBCAN-2E-U上位机开发可以通过QT5框架和usbcan-2e-u设备的API接口进行实现,其具体的开发步骤包括了解设备功能、创建项目、编写代码、连接设备、操作数据等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值