从1980年代,Kvaser就开始CAN产品的研发,在相关产品开发领域有近40多年的经验,对CAN和相关总线技术有着非常深入的研究。我们将分享一些有趣的发现和一些特定情况的技术处理,欢迎关注❤️广州智维电子科技有限公司❤️!
所有人的电脑上都有Microsoft Office,平时我们用它列表和计算,除此之外,它还可以被用来进行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驱动程序”。请咨询客服13824417328获取最新版本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,请访问咨询客服vx:13824417328
有任何问题,你可以联系我们的技术支持人员,本文结尾处提供了联系方式。
调用 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的工具栏生成。
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总线接收数据。希望此示例可让你了解如何操作可行,并辅助你进一步分析数据或编写解决方案。如果您对本文有任何有问题,可以拨打下方电话13824417328,或者发送邮件至hsales@triv.cn