目录
电动汽车充电协议概括
工具设计及与实现
工具运行示例
生成.exe文件
电动汽车充电协议概括
电动汽车电动车非车载传导式充电机(以下简称充电机),电池管理系统(Battery Management System,以下简称BMS)之间基于控制局域网(CAN)之间的通信协议。
在充电过程中,充电机与BMS监测电压、电流和温度等参数,同时BMS根据充电控制算法管理整个充电过程;充电机与BMS之间的CAN通信网络应由充电机和BMS两个节点组成;国标中规定数据传输采用地位先发送的格式。正的电流值代表放电,负的电流值代表充电;物理层应符合ISO 11898-1:2003、SAE J1939-11:2006中之规定;充电机与BMS的通信应使用独立于动力总成控制系统之外的CAN接口。充电机与BMS之间的通信速率可选用50kbit/s、125kbit/s、250kbit/s,推荐采用250kbit/s。
整个充电过程包括四个阶段:充电握手阶段、充电参数配置阶段、充电阶段和充电结束阶段。在各个阶段,充电机和BMS如果在规定的时间内没有收到对方的报文或没有收到正确的报文,即判定为超时,超时时间除特殊规定外,均为5s,当出现超时后,BMS或充电机发送错误报文,并进入错误处理状态。
具体电动汽车充电协议概括请跳转:https://zhuanlan.zhihu.com/p/374943265
工具设计及与实现
UI界面设计
部分代码实现
串口相关代码
//*************************************************串口************************************************
//扫描串口按钮回调函数
void USBCAN::scanport()
{
QStringList newPortStringList;
newPortStringList.clear();
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
newPortStringList += info.portName();
if(newPortStringList.size() != portStringLine.size())
{
portStringLine = newPortStringList;
ui->PortBox->clear();
ui->PortBox->addItems(portStringLine);
}
}
//打开串口并验证
bool USBCAN::openCom(QSerialPort *myCom, QString &myportName, qint32 baudRate)
{
myCom->setPortName(myportName);
//验证串口是否打开
if(! myCom->open(QIODevice::ReadWrite))
{
myCom->close();
return false;
}
//设置波特率
myCom->setBaudRate(baudRate);
//设置数据流控制
myCom->setFlowControl(QSerialPort::NoFlowControl);
return true;
}
//打开端口控件
void USBCAN::on_OpenPort_Button_clicked()
{
//串口按钮显示 “打开串口”
if(ui->OpenPort_Button->text()==tr("打开串口"))
{
//先打开串口
QString portName = ui->PortBox->currentText();
QString baudRate = ui->BaudRateBox->currentText();
//串口打开失败
if(!openCom(Port_Box, portName, ui->BaudRateBox->currentText().toInt()))
{
ui->remindBrowser->append("串口打开失败");
if(Port_Box->isOpen())
{
Port_Box->close();
}
return;
}
//串口打开成功
ui->BaudRateBox->setCurrentText(baudRate);
ui->OpenPort_Button->setText(tr("关闭串口"));
ui->PortBox->setEnabled(false);
ui->BaudRateBox->setEnabled(false);
}
//串口按钮显示 “关闭串口”
else
{
ui->PortBox->setEnabled(true);
ui->BaudRateBox->setEnabled(true);
ui->OpenPort_Button->setText(tr("打开串口"));
//关闭串口
if(Port_Box->isOpen())
{
Port_Box->close();
}
}
}
//串口数据接收函数 -- 接收到就会触发函数
void USBCAN::PortBox_rece()
{
//添加读取的数据到COMrecebuf中
COMrecebuf.append(Port_Box->readAll());
// 如果定时器没有在运行,则启动定时器
if (!myTimer->isActive()) {
myTimer->start(50); // 可自行设置定时器间隔
}
}
//串口读写超时定时处理
void USBCAN::TimerUpdate_COM()
{
//定时器结束
myTimer->stop();
if(COMrecebuf.length()!=0)
{
//有数据则显示
show_receive_Message(COMrecebuf);
//qDebug() << "show_Msg";
int COMrecebuf_size = COMrecebuf.size();
//处理接收的信息
deal_receive_Message(COMrecebuf, COMrecebuf_size);
}
//清空COMrecebuf
COMrecebuf.clear();
}
//显示信息 -- 调用时会显示时间
void USBCAN::show_receive_Message(QByteArray str)
{
//在字符中间加空格
int i = 0;
QString str2;
QByteArray display_array = str.toHex().toUpper();
for(i = 0; i < display_array.size(); i++)
{
str2 += display_array[i];
if(i % 2 == 1)
{
str2 += ' ';
}
}
//获取系统现在的时间
QDateTime time = QDateTime::currentDateTime();
//设置显示格式
QString timeType = time.toString("hh:mm:ss: ");
if(str == COMrecebuf)
{
ui->remindBrowser->append(timeType + str2);
ui->remindBrowser->append("");//换行
}
else
{
}
display_array.clear();
}
//处理接收的信息
void USBCAN::deal_receive_Message(QByteArray str, int COMrecebuf_size)
{
int i = 0;
int idx = 0;
//如果协议头不正确 那么退出
while(1)//$$
{
if(++idx > COMrecebuf_size)return;
//查询到数据头
if((unsigned char)str[i] == 0xAA)
{
i += 1;
break;
}
i++;
}
unsigned char data =(unsigned char)str[i] ;
QString binaryString = hex2Bin(&data, 1);
//数据传输方向
DateTransfer="接收";
//数据长度
int Len= binaryString.right(4).toInt(nullptr, 2);//数据长度
USBCAN_FrameLen=QString::number(Len);
//判断帧类型,0:为标准帧 1:为扩展帧
if (binaryString.at(2) == QLatin1Char('0'))
{
USBCAN_Frametype="标准帧";
//帧ID
USBCAN_FrameID=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 2), 2);
USBCAN_FrameID=reversed(USBCAN_FrameID);
USBCAN_FrameID.remove(QRegExp("\\s"));//删除空格
//提取USBCAN_FrameID数据
QString PacketStr = USBCAN_FrameID.mid(2, 2);
//16进制数据
USBCAN_U16date=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 4), Len);
PacketCode="帧类型为 标准帧 无法解析";
AnalyseDate=QString("帧类型为 标准帧 无法解析");//显示
}
else
{
USBCAN_Frametype="扩展帧";
//帧ID
USBCAN_FrameID=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 2), 4);
USBCAN_FrameID=reversed(USBCAN_FrameID);
USBCAN_FrameID.remove(QRegExp("\\s"));//删除空格
//提取USBCAN_FrameID数据
QString PacketStr = USBCAN_FrameID.mid(2, 2);
//判断设备传输方向
if(USBCAN_FrameID.mid(4, 4).toInt(nullptr, 16)==0x56f4)
DeviceTransfer="BMS----充电机";
else if(USBCAN_FrameID.mid(4, 4).toInt(nullptr, 16)==0xf456)
DeviceTransfer="充电机----BMS";
else
DeviceTransfer="无法判断设备传输方向";
//16进制数据
USBCAN_U16date=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 6), Len);
DeterminePacketCode(PacketStr,USBCAN_U16date);//解析接收到的数据
}
//判断帧格式 0:数据帧 1:
if (binaryString.at(3) == QLatin1Char('0'))
{
USBCAN_Frameformat="数据帧";
}
else
{
USBCAN_Frameformat="远程帧";
}
i++;
if((unsigned char)str[COMrecebuf_size-1] == 0x55)
{
QModelIndex index = ui->tableView->model()->index(RecvCnt, 0); // 获取指定行的索引
ui->tableView->scrollTo(index); // 滚动到指定行
ShowAndWrite_Date_to_Excle();
}
RecvCnt+=1;
}
//显示信息到表格
void USBCAN::ShowAndWrite_Date_to_Excle()
{
QList<QStandardItem *> Rowtext;
QString index=QString::number(RecvCnt+1);
Show_Cell(index,RecvCnt,0);//显示序号
QString direct=DateTransfer; //接收
Show_Cell(direct,RecvCnt,1);//显示传输方向
QString systime=QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); //系统时间
Show_Cell(systime,RecvCnt,2);//显示系统时间
QString format=USBCAN_Frameformat; //帧格式
Show_Cell(format,RecvCnt,3);//显示帧格式
QString type=USBCAN_Frametype; //帧类型
Show_Cell(type,RecvCnt,4);//显示帧类型
QString FrameID=USBCAN_FrameID; //帧ID
Show_Cell(("0x"+FrameID),RecvCnt,5);//显示帧ID
QString datalen=USBCAN_FrameLen; //数据长度
Show_Cell(datalen,RecvCnt,6);//数据长度
QString DisplayData=USBCAN_U16date;
Show_Cell(DisplayData,RecvCnt,7);//16进制数据
QString directData=DeviceTransfer;
Show_Cell(directData,RecvCnt,8);//16进制数据
QString Packetdata=PacketCode; //报文代码
Show_Cell(Packetdata,RecvCnt,9);//报文代码
QString ANaldata=AnalyseDate;
Show_Cell(ANaldata,RecvCnt,10);//解析数据
}
//********************************************************串口END***********************
表格相关代码
//*************************************表格相关*************************************
//创建文件
void USBCAN::New_file()
{
QString folderPath = QCoreApplication::applicationDirPath() + "/log/";
QDir logDir(folderPath);
if (!logDir.exists() && !logDir.mkpath("."))
{
QMessageBox::warning(this, "警告", "创建文件夹" + folderPath + "失败");
return;
}
filename = folderPath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".txt";
file = new QFile(filename);
if (!file->open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::warning(this, "警告", "创建文件" + filename + "失败");
return;
}
}
//判断是否勾选自动保存
void USBCAN::on_radioButton_clicked(bool checked)
{
if(checked==true)
{
New_file();
//扫描串口及设置其定时器
File_timer = new QTimer();
// 设置定时器1秒钟超时
connect(File_timer, SIGNAL(timeout()), this, SLOT(SaveData2File()));
File_timer->start(30);
}
else
{
}
}
//清除数据
void USBCAN::on_ClearTable_Button_clicked()
{
// 清除整个模型数据
model->clear();
//表头都可视
ui->tableView->horizontalHeader()->setVisible(1);
ui->tableView->verticalHeader()->setVisible(false);
model = new QStandardItemModel(USBCAN_line,USBCAN_COL);//定义表格行列数
labels = QObject::trUtf8("序号,传输方向,时间标识,帧格式,帧类型,帧ID,数据长度,数据(16进制),设备传输方向,报文代码,数据解析").simplified().split(",");
model->setHorizontalHeaderLabels(labels);//给model设置表头
//QTableView设置QStandardItemModel
ui->tableView->setModel(model);
//设置表格字体格式
ui->tableView->horizontalHeader()->setStyleSheet("QHeaderView::section {"
"color: black;padding-left: 4px;border: 1px solid #6c6c6c;}");
//表头宽度
ui->tableView->setColumnWidth(0,40);//序号
ui->tableView->setColumnWidth(1,80);//传输方向
ui->tableView->setColumnWidth(2,160);//时间标识
ui->tableView->setColumnWidth(3,80);//帧格式
ui->tableView->setColumnWidth(4,80);//帧类型
ui->tableView->setColumnWidth(5,120);//帧ID
ui->tableView->setColumnWidth(6,80);//数据长度
ui->tableView->setColumnWidth(7,200);//数据(16进制)
ui->tableView->setColumnWidth(8,150);//设备传输方向
ui->tableView->setColumnWidth(9,200);//报文代码
ui->tableView->setColumnWidth(10,500);//数据解析
RecvCnt = 0; // 接收计数清零
}
//将表格数据保存至文件夹
void USBCAN::SaveData2File(QFile *file)
{
if (!file->exists())
{
QMessageBox::warning(this, "警告", "文件不存在");
return;
}
else
{
QTextStream stream(file);
QStandardItemModel *model = qobject_cast<QStandardItemModel *>(ui->tableView->model());
if (!model)
{
qDebug() << "Error: The model of the table view is not a QStandardItemModel.";
return;
}
// 轮询所有的行和列
for (int row = 0; row < model->rowCount(); row++)
{
for (int col = 0; col < model->columnCount(); col++)
{
// 获取当前行和列总量
QStandardItem *item = model->item(row, col);
// 检查项目是否有效
if (item)
{
// 从项目中获取文本并将其写入文件
stream << item->text() << "\t"; // 可以选择任何隔离符号
}
else
{
stream << "\t"; // 如果数据无效,则写入空字段
}
}
// 移动到文件中每行之后的下一行
stream << "\n";
}
}
}
//显示数据格 数据+行+列
void USBCAN::Show_Cell(QString str, int row, int col)
{
model->setItem(row, col, new QStandardItem(str));
}
//保存文件
void USBCAN::on_SaveTable_Button_clicked()
{
New_file();
// 检查文件是否已经创建
if (file && file->isOpen())
{
SaveData2File(file);
}
else
{
QMessageBox::warning(this, "警告", "文件未打开或无效");
}
}
//************************************表格相关**************************************
CAN设置相关代码
//************************************CAN相关**************************************
//配置can参数
void USBCAN::on_pushButton_clicked()
{
if(ui->OpenPort_Button->text()==tr("打开串口")){
QMessageBox::warning(this, "警告", "请先打开串口");
}
else{
//设置CAN协议类型
setCANProtocolType();
//设置CAN波特率
setCANBaudRate();
//s设置帧类型
setFrameType();
//设置CAN模式
setCANMode();
//计算校验值
calculateChecksum();
Port_Box->write(reinterpret_cast<const char*>(command), sizeof (command));}
}
//设置CAN协议类型
void USBCAN:: setCANProtocolType()
{
//索引为1-6
int intc = ui->ProtocolTypeBox->currentIndex();
//设置协议类型 0:20字节 1:可变字节
if(intc==0)
command[2] = 0x02;
else if(intc==1)
command[2] =0x12;
else
command[2] =0x02;
}
//设置CAN波特率
void USBCAN:: setCANBaudRate()
{
//索引为1-6
int intc = ui->BaudRate_ComB->currentIndex();;
QString baudRate = QString("%1").arg(intc+1, 2, 16, QLatin1Char('0'));//字符串前面加0,转为8位16进制
command[3] = baudRate.mid(0, 2).toInt(nullptr, 16);//将波特率放入数组里
}
//设置帧类型
void USBCAN:: setFrameType()
{
int intc = ui->FrameType_ComB->currentIndex();
if(intc==0)
command[4] = 0x01;
else if(intc==1)
command[4] =0x02;
else
command[4] =0x01;
}
//设置CAN模式
void USBCAN:: setCANMode()
{
int intc = ui->WorkMode_ComB->currentIndex();
if(intc==0)
command[13] = 0x00;//正常模式
else if(intc==1)
command[13] =0x01;//静默模式
else if(intc==2)
command[13] =0x01;//环回模式
else if(intc==3)
command[13] =0x01;//静默环回模式
else
command[13] =0x01;
}
//计算校验码
void USBCAN:: calculateChecksum()
{
uint16_t checksum = 0;
int leng=sizeof (command);
for (int i = 2;i < (leng - 1);i++)
{
checksum += command[i];
}
command[leng-1]= checksum & 0xFF;
}
//************************************CAN相关**************************************
工具运行示例图
生成.exe文件
先Windeployqt生成的.exe文件,最后利用Enigma Virtual Box解压打包,方便他人直接使用。