实用技能|如何使用Office Excel轻松实现CAN报文数据收发,简化CAN
KVASER克萨
2025年01月15日 19:30

从1980年代,Kvaser就开始CAN产品的研发,在相关产品开发领域有近40多年的经验,对CAN和相关总线技术有着非常深入的研究。我们将分享一些有趣的发现和一些特定情况的技术处理,欢迎关注“KVASER克萨”知乎账号获取更多知识分享和行业动态。

所有人的电脑上都有Microsoft Office,平时我们用它列表和计算,除此之外,它还可以被用来进行CAN总线数据收发的工作。通过自定义Excel工作表和VBA功能,用户可以根据项目需求创建灵活的数据记录和分析模板,进一步增强了数据处理的效率和灵活性。此外,Excel的跨平台兼容性和广泛的用户基础使得数据共享和协作变得更加容易,同时节省了额外购买专业软件的成本。因此,Excel在CAN通信领域的应用为工程师和技术人员提供了一种成本效益高、操作简便且功能强大的解决方案。 

本文由Kvaser客户软件经理Dan Arvidson分享如何巧用Excel进行该操作。

我们可以利用Windows电脑上的Microsoft Office Excel进行如下操作: 

◾从工作表中的任意单元格向CAN总线发送数据

◾以工作表的任意单元格从CAN总线接收数据

Kvaser CANlib可用于Visual Basic for Applications (VBA)。此多功能库支持Kvaser的所有适配器,并辅助你编写高级和创新的解决方案。

有这么多其他语言可以用,为什么还要使用VBA呢?

首先,如果你平时使用Microsoft Office,那么使用它会更容易。Excel使用广泛,通过VBA,你可应用Excel的所有功能,以及你自己的创意和创新想法。另外,使用Excel不会产生额外成本,也更方便与他人或其他公司分享数据。

本文我们会简要介绍如何在Microsoft Excel中使用CANlib、VBA向CAN总线发送/接收数据。 

应用此方法,你需要:

根据本文的步骤,你需要使用Kvaser CANlib和Excel VBA。任意版本的Microsoft Office都可以,但最好是Office 2010,因为VBA 7是在2010年推出的。64位和32位Office均可使用(请参阅下文“VBA实例”中的“32和64位Microsoft Office”内容)。本文中,使用的是Office 365和Excel(版本2202 Build 16.0.14931.20116)64位。

你还需要安装“用于Windows的Kvaser驱动程序”。请前往👉https://www.kvaser.cn/download/查找最新版本CANlib并依照其步骤进行安装。如果你需要进一步使用CANlib,我们建议下载Kvaser CANlib SDK,从上述同一链接即可获得。

VBA介绍 

VBA(Visual Basic for Applications)是Visual Basic的一种宏语言,是在其桌面应用程序中执行通用的自动化(OLE)任务的编程语言。主要能用来扩展Windows的应用程序功能,特别是Microsoft Office软件。它也可以说是一种应用程式视觉化的 Basic 脚本。 

VBA是Microsoft编程语言Visual Basic 6的一个子集,它使用同一编辑器的精简版本以及类似的调试功能。因此,如果你了解VB6,也就能了解VBA的使用方法。Visual Basic是为了简化编程而创建的,VBA编程的过程不难,使用它就类似于用英语的语句来告诉电脑该做什么。

Application.ActiveDocument.SaveAs ("New Document Name.docx")

将活动文档另存为“New Document Name.docx”

需要注意的是,VBA是单线程的,这意味着它将一次执行一个任务。(请参阅下文“VBA示例”中的“多任务处理”内容)

默认情况下,Office不显示“开发工具”选项卡,你必须通过如下步骤启用它:

  1. 从“File文件”选项卡中,选择“Options选项”打开对话框。

  2. 选择对话框左侧的“Customize Ribbon自定义功能区”。

  3. 在对话框左侧的“Choose Commands From从中选择命令”中,选择“Common Commands常用命令”。

  4. 在右侧的“Customize the ribbon自定义功能区”中,从下拉列表中选择“Main Tab主选项卡”,然后选择“Developer开发工具”复选框。

  5. 选择OK。

从“开发工具”选项卡,你可以打开编辑器并创建按钮、下拉菜单等。或者,你可以在Office应用程序中使用快捷键:ALT+F11。 

如果你还是不了解如何操作,可以在网上进行VBA示例搜索,即使需求不完全相同,你也能从中参考到类似的步骤。 

VBA实例

第一个示例将显示当某个单元出现更改时如何作出反应,并将该值发送到CAN总线。

在VBA编辑器中,双击你需要对其作出反应的单元格所在的工作表,然后选择该工作表的“更改(Change)”步骤。

每当该工作表中发生更改时,都会执行工作表更改事件,将其缩小到特定的一个单元格,我使用VBA中的Intersect()函数。此函数确定更改的单元格是否与我们指定的单元格“匹配”。为了简化此示例,我假设工作表处于活动状态,并且我们要发送的值介于0-255之间。有关canWrite中使用的参数,以及如何从CAN总线接收数据的更多信息,请见下一个示例。

在另一个示例中,我已将一个记录文件导入Excel(在此示例中,它是一个.ASC文件)。我这样做只是为了向CAN总线发送数据。

当然,你的数据可以是来自任何地方的,在VBA中打开一个文件,也可以在Excel中编写几个具体应发送的帧等。

在这里,不会深入探讨CANlib,这个示例主要是启发你可以如何利用VBA和CANlib。在本文,我将演示如何使用Kvaser CANlib从Excel中的任意单元格发送数据,以及如何以Excel中的任意单元格接收数据。

编码

CANlib API - 和句柄声明

Option Explicit 'Force explicit variable declaration so that an undeclared variable generates error.

#If VBA7 Then 

Private Declare PtrSafe Sub canInitializeLibrary Lib "CANLIB32.DLL" ()

Private Declare PtrSafe Function canUnloadLibrary Lib "CANLIB32.DLL" () As Long

Private Declare PtrSafe Function canGetNumberOfChannels Lib "CANLIB32.DLL" (ByRef channelCount As Long) As Long

Private Declare PtrSafe Function canGetChannelData Lib "CANLIB32.DLL" (ByVal channel As Long, ByVal item As Long, ByRef buffer As Any, ByVal bufsize As Long) As Long

Private Declare PtrSafe Function canOpenChannel Lib "CANLIB32.DLL" (ByVal handle As Long, ByVal Flags As Long) As LongPtr

Private Declare PtrSafe Function canClose Lib "CANLIB32.DLL" (ByVal handle As LongPtr) As Long

Private Declare PtrSafe Function canBusOn Lib "CANLIB32.DLL" (ByVal handle As LongPtr) As Long

Private Declare PtrSafe Function canBusOff Lib "CANLIB32.DLL" (ByVal handle As LongPtr) As Long

Private Declare PtrSafe Function canSetBusParams Lib "CANLIB32.DLL" (ByVal handle As LongPtr, ByVal freq As Long, ByVal tseg1 As Long, ByVal tseg2 As Long, ByVal sjw As Long, ByVal noSamp As Long, ByVal syncMode As Long) As Long

Private Declare PtrSafe Function canWrite Lib "CANLIB32.DLL" (ByVal handle As LongPtr, ByVal id As Long, ByRef msg As Any, ByVal dlc As Long, ByVal flag As Long) As Long

Private Declare PtrSafe Function canReadWait Lib "CANLIB32.DLL" (ByVal handle As LongPtr, ByRef id As Long, ByRef msg As Any, ByRef dlc As Long, ByRef flag As Long, ByRef time As Long, ByRef timeout As Long) As Long

#Else

 Private Declare Sub canInitializeLibrary Lib "CANLIB32.DLL" ()

 Private Declare Function canUnloadLibrary Lib "CANLIB32.DLL" () As Long

 Private Declare Function canGetNumberOfChannels Lib "CANLIB32.DLL" (ByRef channelCount As Long) As Long

 Private Declare Function canGetChannelData Lib "CANLIB32.DLL" (ByVal channel As Long, ByVal item As Long, ByRef buffer As Any, ByVal bufsize As Long) As Long

 Private Declare Function canOpenChannel Lib "CANLIB32.DLL" (ByVal handle As Long, ByVal Flags As Long) As Long

 Private Declare Function canClose Lib "CANLIB32.DLL" (ByVal handle As Long) As Long

 Private Declare Function canBusOn Lib "CANLIB32.DLL" (ByVal handle As Long) As Long

 Private Declare Function canBusOff Lib "CANLIB32.DLL" (ByVal handle As Long) As Long

 Private Declare Function canSetBusParams Lib "CANLIB32.DLL" (ByVal handle As Long, ByVal freq As Long, ByVal tseg1 As Long, ByVal tseg2 As Long, ByVal sjw As Long, ByVal noSamp As Long, ByVal syncMode As Long) As Long

 Private Declare Function canWrite Lib "CANLIB32.DLL" (ByVal handle As Long, ByVal id As Long, ByRef msg As Any, ByVal dlc As Long, ByVal flag As Long) As Long

 Private Declare Function canReadWait Lib "CANLIB32.DLL" (ByVal handle As Long, ByRef id As Long, ByRef msg As Any, ByRef dlc As Long, ByRef flag As Long, ByRef time As Long, ByRef timeout As Long) As Long

#End If

'Constant declarations

Private Const canOK = 0

Private Const canOPEN_ACCEPT_VIRTUAL = &H20

Private Const canBITRATE_250K = -3

Private Const canCHANNELDATA_CARD_SERIAL_NO = 7 

'Declaration of CAN handles

#If VBA7 Then

 Private hnd0, hnd1 As LongPtr

#Else

 Private hnd0, hnd1 As Long

#End If

这些声明是必要的,以便指定哪些dll调用可用,并指出此dll的位置。通过Kvaser的安装程序安装时,CANlib32.dll位于系统路径中。这就是说你不必指定它的具体位置。

[ Public | Private ] Declare Sub name Lib “libname” [ ( [ arglist ] ) ]

[ Public | Private ] Declare Function name Lib “libname” [ ( [ arglist ] ) ] [ As type ]

使用Private表明,只有在声明它的模块中才能访问它。

Kvaser CANlib SDK目前不包含任何VB或VBA声明,因此你必须根据需要编写这些声明。有关在线Kvaser CANlib SDK,请访问https://kvaser.cn/canlib-webhelp/index.html。

有任何问题,你可以联系我们的技术支持人员。

调用CANlib API

初始化CANlib并获取可用通道数

Sub CANLib_Start()

 Dim chCount, stat, i As Long

 Dim buffer As String

 Dim myArr(32) As Byte

 Dim ws As Worksheet

      

 canInitializeLibrary

 stat = canGetNumberOfChannels(chCount)

 If stat <> canOK Then GoTo ErrorHandler

在使用任何其他函数之前,必须先调用canInitializeLibrary函数。它将初始化驱动程序。

canGetNumberOfChannels, 此函数将返回电脑中可用CAN通道的数量。虚拟通道包括在此通道数中。

准备工作表以读取一些设备信息

Sheets.Add(Before:=Sheets(1)).name = "Device info" ' Add a sheet called "Device info" to the first position

 

Range("A1").Value = "Nof channels"

Range("B1").Value = chCount

在这里,我们给第一个位置添加一个新的工作表,并在该工作表的单元格B1中写入可用通道数。

读取每个可用通道的一些设备信息

For i = 0 To chCount - 1

 stat = canGetChannelData(i, canCHANNELDATA_CARD_SERIAL_NO, myArr(0), 32)

 buffer = StrConv(myArr(), vbUnicode)

 If buffer <> Empty Then

   Cells(i + 2, 1).Value = "Serial"

   Cells(i + 2, 2).Value = buffer

 End If

Next i

在这里,我们查看每个可用的通道,并询问设备的序列号,并且我们将其写入每个通道的第二列中的新行(即B)。

打开通道,设置参数并启动总线

hnd0 = canOpenChannel(0, canOPEN_ACCEPT_VIRTUAL)

hnd1 = canOpenChannel(1, canOPEN_ACCEPT_VIRTUAL)

stat = canSetBusParams(hnd0, canBITRATE_250K, 0, 0, 0, 0, 0)

If stat <> canOK Then GoTo ErrorHandler

stat = canSetBusParams(hnd1, canBITRATE_250K, 0, 0, 0, 0, 0)

If stat <> canOK Then GoTo ErrorHandler

      

stat = canBusOn(hnd0)

If stat <> canOK Then GoTo ErrorHandler

stat = canBusOn(hnd1)

If stat <> canOK Then GoTo ErrorHandler

在这里,我们打开第一个和第二个通道,以获得所有其他调用所需的句柄。我们继续准备这两个打开的通道,为它们设置相同的比特率。

为读取数据准备工作表

DeleteSheet ("Read data")

Set ws = Sheets.Add()

ws.name = "Read data"

ws.Cells(1, 1).Value = "ID"

ws.Cells(1, 2).Value = "Data1"

ws.Cells(1, 3).Value = "Data2"

ws.Cells(1, 4).Value = "Data3"

ws.Cells(1, 5).Value = "Data4"

ws.Cells(1, 6).Value = "Data5"

ws.Cells(1, 7).Value = "Data6"

ws.Cells(1, 8).Value = "Data7"

ws.Cells(1, 9).Value = "Data8"

这里我们准备一个工作表来存储读取的数据。我首先删除“读取数据(Read data)”表,如果它已经存在。然后我创建此工作表并命名,同时添加一些标题注释,以便更好地理解输出内容。

发送、接收和填充单元格

Sub CANlib_Traffic()

 Dim tb As ListObject

 Dim iCol, iRow As Integer

 Dim sData(1 To 8), sCol As String

 Dim bDataTx(1 To 8) As Byte

 Dim bDataRx(1 To 8) As Byte

 Dim stat, lID, lDlc, lFlags, lTime As Long

      

      

 Worksheets("Imported ASC").Activate

 Set tb = ActiveSheet.ListObjects("TestLog")

 

 For iRow = 1 To tb.Range.Rows.Count

   lDlc = tb.DataBodyRange.Cells(iRow, tb.ListColumns("DLC").Index) ' Get how many data bytes

   For iCol = 1 To lDlc

     sCol = "Data" + Trim(Str(iCol)) 'Create the headline to read from

     sData(iCol) = tb.DataBodyRange.Cells(iRow, tb.ListColumns(sCol).Index)

     bDataTx(iCol) = CByte("&H" & sData(iCol)) ' Convert the Hex value to decimal

   Next iCol

   ' Send the byte stream of CAN data on the first channel    

   stat = canWrite(hnd0, CLng(iRow), bDataTx(1), lDlc, 0)

   DoEvents

   ' Read out the received data on the second channel

   stat = canReadWait(hnd1, lID, bDataRx(1), lDlc, lFlags, lTime, 50)

   If stat = canOK Then

     With Worksheets("Read data") ' Populate cells in Excel with read CAN data

       .Cells(lID + 1, 1).Value = lID

       ' .Cells(lID + 1, 2).Value = CStr(Hex(bDataRx(1))) ' Use this if value should be in hexadecimal

       .Cells(lID + 1, 2).Value = bDataRx(1)

       .Cells(lID + 1, 3).Value = bDataRx(2)

       .Cells(lID + 1, 4).Value = bDataRx(3)

       .Cells(lID + 1, 5).Value = bDataRx(4)

       .Cells(lID + 1, 6).Value = bDataRx(5)

       .Cells(lID + 1, 7).Value = bDataRx(6)

       .Cells(lID + 1, 8).Value = bDataRx(7)

       .Cells(lID + 1, 9).Value = bDataRx(8)

     End With

   End If

 Next iRow

      

 MsgBox "Traffic done!"

End Sub

首先确定要读取的工作表已激活。在我的示例中,我将导入数据的工作表命名为TestLog,并将其设置为ListObject变量。我这样做是为了在获取要发送的值时更容易循环操作,这样我可以使用工作表的标题来指定我正在读取的列。第一个循环设置为读取导入数据的整个范围。我读取数据长度代码(dlc)值,以了解还要读取和稍后发送的字节数。第二个循环迭代数据字节,将它们从文本格式的十六进制转换为十进制值,并将值存储在字节数组中。

然后,通过调用CANlib函数canWrite,将CAN数据的字节数组与报文id(在本例中为行号)和数据长度代码(dlc)一起发送。

DoEvents可以更轻松地停止正在运行的宏。DoEvents函数允许中断执行代码,并允许计算机处理器同时运行其他任务。使用DoEvents会延长执行时间,但另一方面,它也会让宏停止运行。

canReadWait从接收缓冲区读取报文。如果没有可用的报文,则该函数将等待报文到达或超时。 

最后,用该读取值填充之前创建的“读取数据”工作表并使用报文id指定单元格行。通过在写入单元格时使用命名工作表,我不必激活该工作表(Worksheets(“Read data”). Cells (Row, Column).Value),同时能保持保存导入数据的工作表处于活动状态。

操作后清理

Sub CANLib_Stop()

 canBusOff (hnd0)

 canBusOff (hnd1)

 canUnloadLibrary

 MsgBox "CANlib is unloaded!"

End Sub

canBusOff把指定句柄关闭。如果同一通道上没有其他句柄处于活动状态,则也将把此通道关闭。canUnloadLibrary将释放分配的内存,卸载已加载的DLL canlib32.,并取消初始化数据结构。

结果

本示例的结果如下图所示: 

如果选择将输出数据格式化为十六进制,这样更便于比较,然后将单元格格式化为文本: ws.Columns(“A:I”).NumberFormat = “@”,当将值放入这些单元格时,需要进行如下转换:Cells(col, row).Value = CStr(Hex(MyDecValue))。

但在本次操作中,为了让后续分析更方便,我选择了十进制。(也可以使用十六进制值,但有时需要进行转换,因为VBA和Excel中的图表对象需要其值为十进制格式。)

可使用Excel图表可视化数据,例如: 

图表可以在VBA代码中生成,也可以在以后使用Excel的工具栏生成。要进一步深入了解VBA的功能,请前往https://learn.microsoft.com/en-us/office/vba/api/overview/osoft文档。 

32位与64位Microsoft Office

两个Office版本都可用,但VBA版本7中添加了一些新的64位功能。

VBA中除数据类型Byte外,没有无符号数据类型。但这并不是完全不可行 – 可以在VBA中读取无符号值,例如本例介绍了如何对2147483647以上的值使用双精度类型。 

Private Const MAX_UINT32 = 4294967296#

Private Const MAX_INT32 = 2147483647

Function LongToUnsigned(ByVal Value As Long) As Double

 If Value < 0 Then

   LongToUnsigned = Value + MAX_UINT32

 Else

   LongToUnsigned = Value

 End If

End Function

 

Function UnsignedToLong(ByVal Value As Double) As Long

 If Value < 0 Or Value >= MAX_UINT32 Then Error 6

 If Value <= MAX_INT32 Then

   UnsignedToLong = Value

 Else

   UnsignedToLong = Value - MAX_UINT32

 End If

End Function

 

多任务处理

VBA是单线程的,这意味着它将一次执行一个任务。仍然可以创建一个线程或使用回调函数,如CANlib中的kvSetNotifyCallback,但我不建议这样做。如果关闭一个线程并在主线程中等待,则创建一个线程是可以的,但创建更多线程或尝试写入主线程外的单元格,可能会导致Excel冻结并关闭。为了避免麻烦和花费额外时间,建议保持它的简单性和主线程。 

对于那些有使用VBA经验的用户,有一些方法可以解决这个问题,例如从代码中的循环中启动一个新的Excel实例,并从该新实例调用主工作簿中的一个过程。

我的建议是保持在主线程里。

结论

在本文中,我们简要介绍了如何在Microsoft Excel中使用CANlib和VBA向CAN总线发送和从CAN总线接收数据。希望此示例可让你了解如何操作可行,并辅助你进一步分析数据或编写解决方案。

免费且好用的Kvaser软件

尽管使用Excel可以实现基本的数据分析,但对于执行更为复杂的CAN总线分析,专业的软件是不可或缺的。Kvaser不仅提供高品质且坚固耐用的硬件产品,还致力于为客户提供卓越的使用体验,为此提供了一系列免费、通用且向后兼容的软件产品。

CAN总线分析软件——CanKing 7:

它与所有Kvaser CAN接口以及Kvaser虚拟CAN总线兼容。用户可以通过直观的图形化测量窗口,轻松设置报文接收或数值触发的过滤、流量生成和日志记录功能。👉前往https://www.kvaser.cn/downloads了解并下载。

Windows驱动程序——Kvaser Drivers for Windows:

适用于所有Kvaser CAN硬件,包含一个用于虚拟CAN总线的虚拟驱动程序,可以方便你在无法访问真实CAN总线硬件时进行测试和评估。 

CANlib 软件开发包(SDK):

适用于所有Kvaser硬件的平台,所包含的虚拟硬件能处理真实硬件可以做的所有事。

Linux CANlib SDK:

包含用于Linux环境开发的驱动程序和SDK,可以处理所有Windows开发包能做的事。Linux CANlib API让Windows应用与Linux无缝对接。

Kvaser REST API:

允许任何支持HTTP的设备与Kvaser CAN接口(Kvaser Ethercan和Kvaser Blackbird v2)进行通信。使用Kvaser REST API设置比特率,读取和写入CAN帧,读取错误帧,过滤和刷新接收缓存。API和示例代码参见CANlib SDK。


自1981年以来,Kvaser逐步成长为控制器局域网(CAN)领域的先驱者,高品质产品获得客户广泛赞誉。我们将在保留CAN数据传输专业度的同时积极拓展其他领域相关通讯技术,为客户提供先进的连接解决方案及高品质产品。如果您想了解关于Kvaser克萨的更多信息,您可前往https://www.kvaser.cn/。

或关注微信公众号:Kvaser克萨