技术博客

13/03/2018 作者 Lars-Göran Fredriksson

Kvaser 软件开发包SDK 从CAN CLASSIC 升级到CAN FD

这篇文章是介绍怎样从CAN CLASSIC 升级到 CAN FD。我们在此仅介绍在编程代码上需要的修改,如果你现在有一个Kvaser CANLib SDK支持的项目。当然你也可以借助本文开始一个新项目。

我们将在此跳过介绍CAN FD的定义和物理总线相应的修改。如果你需要对CAN FD了解更多,请访问我们的网页(https://www.kvaser.com/about-can/can-fd/)。

我们培训专栏也提供了更多信息 (https://www.kvaser.com/training-materials/)。你需要(免费)注册和登陆我们的网站来下载84培训资料

推荐下载和阅读:

  • CAN 总线协议教程
  • 为不同的客户服务的CAN FD
  • 比较 CAN FD 和 Classical CAN

CanFDTest

我们在Embarcadero DELPHI(Pascal)环境中建立了一个项目,名为“CanFDTest”。如果你没有DELPHI 环境, 可借助任何文本编辑器来阅读此代码。 最重要的代码被放在RunCanFDExamplePlease 程序中,并收录在附录A里。

在此程序中,我们初始化总线,发送一个帧,收到一个帧,完成总线。


所需硬件

我们将在虚拟一个计算机(Microsoft HYPER-V, WIN10 64bit)和一个标准笔记本电脑上进行这个范例的测试。在测试中,我们既使用虚拟适配器,也使用Kvaser 适配器。

  • 如果用Kvaser 驱动上的虚拟通道,就不需要用硬件 (Kvaser CAN适配器) 来运行这个范例项目。

“CanFDTest”需要的工具

此范例来自Delphi® 项目 “CanFDTest_xxx”。

运行此范例是没必要的,更重要的是打开文件“CanFDtestUnit.pas”,和学习RunCanFDExamplePlease()程序步骤。

EMBARCADERO DELPHI
Delphi®是由 EMBARCADERO®提供的一个软件。在写本文时(2018年1月10日),他们提供了一个免费版本“DELPHI STARTER”, 你能在这里看到: https://www.embarcadero.com/products/delphi/starter

Kvaser CANlib SDK
你需要CANlib 软件开发包(Kvaser Caleb SDK)来运行此范例。我们总是离不开CANlib。

你可以在这里找到SDK : https://www.kvaser.com/downloads-kvaser/

Kvaser CAN 硬件的驱动
这个驱动包括一个虚拟硬件,它可以做我们的各款硬件能做的任何事。这使我们不用任何物理适配器,就能测试CAN FD 。当你有一个Kvaser 适配器以后,就可以轻松地从虚拟驱动转移到真实CAN FD 总线上。

你可以在这里下载驱动: https://www.kvaser.com/downloads-kvaser/

我们提供Windows,Linux,DIADEM的驱动。本例程基于Windows 驱动平台。

We provide drivers for Windows, Linux and DIADEM. This example uses the Windows drivers.

Kvaser HELP 文件
当你访问KVASER网站, 也请看一下 CANlib Help (这里包括对 CANlib SDK支持) (http://www.kvaser.com/kvaser/site/CANlib-webhelp/)

  • DELPHI, CANlib SDK 和 DRIVER必须在运行范例(CanFDTest)之前安装好

CanFDTest程序

CanFDTest范例程序是由4个PAS 文件组成的:

  • CANlib.pas
  • CanFDTestUnit.pas
  • DummyUnit.pas
  • MyHelpUnit.pas

CANlib.pas
通常这个文件被包括在Kvaser CANlib SDK里。

为了方便用户, PAS文件被复制在我们的项目里,而不是在DELPHI里建立一个搜索路径到此文件。

CanFDTestUnit.pas
包括此范例的所有主要信息。

在本文的结尾部分包含运行RunCanFDExamplePlease() 的步骤。

DummyUnit.pas
包含一些测试用的“仿函数(dummy functions)” 。

这些函数“不做任何事”, 但它们能为测试设置分界点。编译程序不能优化仿函数中的变量,所以它们不影响测试。

MyHelpUnit.pas
包含一些能帮助我们,但SDK未介绍的功能。

怎样使用范例 CanFDTest
打开文件 “CanFDTest.dproj” (从 DELPHI打开),解压和打开此项目。

如果你不想运行此范例,则用一个你选的文本编辑器打开文件“CanFDTestUnit.pas”。


RunCanFDExamplePlease运行步骤

这分为4部分:

  • 初始化
  • 发送
  • 接收
  • 完成

这里展示的代码是节选的,不是全部。请查看源代码。

步骤: 初始化
选择CLASSIC CAN 或 CAN FD
设置常态MyApp 为 canFD 或canCLASSIC CAN。
将需要作为发送的适配器通道设置为TX_MySelCh 。
将需要作为接收的适配器通道设置为RX_MySelCh 。

几乎所有Kvaser CAN 适配器可同时为发送器和接收器。我们在这个范例里选择以一个为TX,另一个RX ,以使这个范例的代码容易被理解。

首先我们运行3个命令:
canInitializeLibrary()
canGetChannelData()

  • 不需要改变 Can FD

现在我们知道存在多少个适配器以及它们的名称。我们可以打开其中两个。

canOpenChannel()

  • 需要编辑 Can FD!

CAN CLASSIC
TX_MyHnd := canOpenChannel(TX_MySelCh – 1, canOPEN_ACCEPT_VIRTUAL);
RX_MyHnd := canOpenChannel(RX_MySelCh – 1, canOPEN_ACCEPT_VIRTUAL);

CAN FD
TX_MyHnd := canOpenChannel(TX_MySelCh – 1, canOPEN_ACCEPT_VIRTUAL OR canOPEN_CAN_FD);
RX_MyHnd := canOpenChannel(RX_MySelCh – 1, canOPEN_ACCEPT_VIRTUAL OR canOPEN_CAN_FD);

在打开一个FD通道时,使用旗标(FLAG)是重要的: canOPEN_CAN_FD

我们用联合命令“canOPEN_ACCEPT_VIRTUAL OR canOPEN_CAN_FD” 打开一个虚拟适配器,设置模式为FD。

现在适配器打开了,我们需要设置它们的参数。

canSetBusParams() and canSetBusParamsFD()

  • FD需要编辑和附加信息FD!
  • 可以用其他值,而不用预设值 (本文不介绍这部分)。

CAN CLASSIC
canSetBusParams()仅能与canBITRATE_xxx一起用.

TX := canSetBusParams(TX_MyHnd, canBITRATE_500K, 0, 0, 0, 0, 0);
RX := canSetBusParams(RX_MyHnd, canBITRATE_500K, 0, 0, 0, 0, 0);

CAN FD
canSetBusParams() and canSetBusParamsFD()能与 canFD_BITRATE_xxx一起用

TX := canSetBusParams(TX_MyHnd, canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0);
RX := canSetBusParams(RX_MyHnd, canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0);

TX := canSetBusParamsFD(TX_MyHnd, canFD_BITRATE_1M_80P, 0, 0, 0);
RX := canSetBusParamsFD(RX_MyHnd, canFD_BITRATE_1M_80P, 0, 0, 0);

  • 请注意, 不要在FD上使用canBITRATE_xxx 值!

现在所有参数都设置好了,我们可以上线了,所以我们要结束初始化过程,打开总线,做一些清理。

canBusOn()
canFlushTransmitQueue()
canFlushReceiveQueue()

  • 不需要改变CAN FD

 

步骤: 发送
我们激活了总线,适配器在等我们的下一步操作。我们应从一个选出的适配器发送一个帧。

canWrite()

  • 需要编辑CAN FD!

(我们可以用canWrite() 或 canWriteWait()命令,但在本范例中我们只用canWrite。)

推荐: 参阅THE HELP!

CAN CLASSIC
BUF  := ‘Hello!’;
id   := 111;
dlc  := 8; // For Classic CAN dlc can be at most 8*
Flag := canMSG_STD;

R := canWrite(TX_MyHnd, id, @BUF, dlc, Flag);

CAN FD
BUF  := ‘Hello World!’;
id   := 222;
dlc  := 16; // for CAN FD dlc CAN be one of the following 0-8,12, 16, 20, 24, 32, 48, 64
Flag := canMSG_STD OR canFDMSG_FDF OR canFDMSG_BRS;

R := canWrite(TX_MyHnd, id, @BUF, dlc, Flag);

  • 新 缓存容量可达64 bytes
  • canFDMSG_FDF 到旗标flag 上(表明此帧是一个FD 帧)
  • canFDMSG_BRS 到旗标 flag 上(表明该帧需以比特率调节发送)

请注意canFDMSG_BRS可选可不选。当加了BRS flag, 数据部分是以 FD比特率发送的,而不是以仲裁比特率发送的。

在你激活FD以后仍可以发送classic CAN 帧! 只能用canMSG_STD 或 canMSG_EXT 生成一个classic 帧。

 

步骤: 接收
canRead()

  • 需要编辑 CAN FD!

(可用来接收FD通讯的canRead() (至少)有四个版本的。在此范例中我们仅用canRead(),所以我们建议你阅读‘Help’ 部分!)

R   := canRead(RX_MyHnd, id, @BUF, dlc, Flag, myTime);

我们不需要对阅读命令做任何修改,但是 …

  • 确认缓存[BUF]的最小容量是64 bytes。即使我们并不想发送长过24 bytes的帧, 别人或许需要。
  • 研究被返回的旗标值,它们包含了一些新值。这些是canMSG_xxx, canMSGERR_xxxcanFDMSG_xxx 值的综合。

即使只有我们在总线上,而且我们仅发了一帧, 接受到的帧未必是我们想要的。例如,如果旗标里包括了值canMSG_ERROR_FRAME, 那么此帧是一个错误帧,不是我们希望看到的。.

  • 一定要总是检查返回的值[R] 和返回的旗标!

现在我们已经收到了我们的帧。

 

步骤: 完成
完成步骤不需要编辑。

canBusOff()
canClose()
canUnloadLibrary()

  • 不需要改变 CAN FD

总结

当然,可以用SDK做很多事, 但我们将此范例局限在发送和接收一个帧。

如果你要扩展此范例, 可以按你的需要修改它,并和Kvaser CANKing 软件结合起来使用,以发送和接收帧。你可在我们的网站上看到该软件(和 SDK ,驱动在同一个位置) 。你能用CANKing生成帧,模拟错误(以及更多)。

我们希望本文能帮助你在Kvaser 产品上使用CAN FD。


使用 CAN FD的命令:

请 格外注意下列命令,你使用CAN FD 和编辑你的代码时都需要它们。

请注意CAN FD 可能需要更多命令/旗标,而本文限于篇幅没有逐一介绍。

记住在使用我们提到的任何命令以前,总是先查看HELP 。

canOpenChannel()

canGetBusParamsFD()

canSetBusParams()
canSetBusParamsFD()
canWrite()

canWriteWait()
本文未用到

canRead()

canReadSpecific()
canReadSpecificSkip()
canReadWait()
本文未用到
本文未用到
本文未用到

附录 A RunCanFDExamplePlease运行步骤

procedure TFormCanFDtest.RunCanFDExamplePlease;
  var
    I          : integer;                 // INT32
    N          : integer;                 // INT32
    R          : integer;                 // INT32
    TX         : integer;                 // INT32
    RX         : integer;                 // INT32
    numCh      : integer;                 // INT32
    MyChNames  : array of string;         //
    TX_MyHnd   : canHandle;               // canHandle = integer = INT32
    RX_MyHnd   : canHandle;               // canHandle = integer = INT32
    BUF        : TByteBUF0064;            // Array 64 bytes of 8bit char
    MyStringBuf: TByteBUF1024;            // Array 1024 bytes of 8bit char
    id         : Longint;                 // Warning LONGINT is 32b or 64b depending of
                                          // target platform. check if using iOS or LINUX!
    dlc        : Cardinal;                // UINT32
    Flag       : Cardinal;                // UINT32
    HelpFlag   : THelpFlag absolute Flag; // UINT32, HelpFlag share adress with Flag
    myTime     : Cardinal;                // UINT32

  begin
    I := sizeof(id);
    dummy(I);

    begin

      case MyApp of
        canCL:
          begin
            Memo.Lines.Add('Hello! Starting TestCanLIB_STD. Using CLASSIC CAN')
          end;
        canFD:
          begin
            Memo.Lines.Add('Hello! Starting TestCanLIB_STD. Using FD CAN');
          end;
      end;

      /// *********************************************************
      /// Section one, INITIALIZATION
      /// *********************************************************
      // Open CanLib.DLL
      canInitializeLibrary;
      // No errorchecking here, will be done later when opening channel...

      // Find out how many channels(interfaces) we can use
      R := canGetNumberOfChannels(numCh);
      if MyErrorCheck(Memo, R, 'canGetNumberOfChannels') then
      begin
        exit;
      end;
      Memo.Lines.Add('Found ' + numCh.ToString + ' channels');

      setlength(MyChNames, numCh);
      for I := Low(MyChNames) to High(MyChNames) do
      begin
        canGetChannelData(I, canCHANNELDATA_DEVDESCR_ASCII, MyStringBuf, sizeof(MyStringBuf));
        canGetChannelData(I, canCHANNELDATA_CHAN_NO_ON_CARD, N, sizeof(N));
        MyChNames[I] := (I+1).ToString+'  '+string(MyStringBuf)+' ' + (N+1).ToString;
        Memo.Lines.Add('Found channel: ' + MyChNames[I]);
      end;

      // Check that the selected channels exist
      if MyErrorCheck(Memo, TX_MySelCh, RX_MySelCh, 1, numCh, 'numCh') then
      begin
        exit;
      end;

      Memo.Lines.Add('Selected TX:' + MyChNames[TX_MySelCh - 1]);
      Memo.Lines.Add('Selected RX:' + MyChNames[RX_MySelCh - 1]);

      // Next step is to open the Channels
      // Please check the FLAGs!
      // CAN FD needs different settings!
      case MyApp of
        canCL: // Classic CAN communication
          begin
            TX_MyHnd := canOpenChannel(TX_MySelCh - 1, canOPEN_ACCEPT_VIRTUAL);
            RX_MyHnd := canOpenChannel(RX_MySelCh - 1, canOPEN_ACCEPT_VIRTUAL);
            if MyErrorCheck(Memo, TX_MyHnd, RX_MyHnd, 'canOpenChannel') then
            begin
              exit;
            end;
          end;
        canFD: // CAN FD communication
          begin
            TX_MyHnd:=canOpenChannel(TX_MySelCh-1, canOPEN_ACCEPT_VIRTUAL OR canOPEN_CAN_FD);
            RX_MyHnd:=canOpenChannel(RX_MySelCh-1, canOPEN_ACCEPT_VIRTUAL OR canOPEN_CAN_FD);
            if MyErrorCheck(Memo, TX_MyHnd, RX_MyHnd, 'canOpenChannel FD') then
            begin
              exit
            end;
          end;
      end;

      /// *****************************************************************************
      ///
      /// Now is a good time to enable messages, but we do not need it in this example
      /// canSetNotify(MyHnd, Self.Handle, $FFFFFFFF);
      ///
      /// *****************************************************************************
      case MyApp of
        canCL: // Classic CAN communication
          begin
            TX := canSetBusParams(TX_MyHnd, canBITRATE_500K, 0, 0, 0, 0, 0);
            RX := canSetBusParams(RX_MyHnd, canBITRATE_500K, 0, 0, 0, 0, 0);
            if MyErrorCheck(Memo, TX, RX, 'canSetBusParams') then
            begin
              exit
            end;
          end;
        canFD: // CAN FD communication
          begin
            TX := canSetBusParams(TX_MyHnd, canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0);
            RX := canSetBusParams(RX_MyHnd, canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0);
            if MyErrorCheck(Memo, TX, RX, 'canSetBusParams') then
            begin
              exit
            end;

            TX := canSetBusParamsFD(TX_MyHnd, canFD_BITRATE_1M_80P, 0, 0, 0);
            RX := canSetBusParamsFD(RX_MyHnd, canFD_BITRATE_1M_80P, 0, 0, 0);
            if MyErrorCheck(Memo, TX, RX, 'canSetBusParamsFD') then
            begin
              exit
            end;
          end;
      end;

      // Turn BUS on
      TX := canBusOn(TX_MyHnd);
      RX := canBusOn(RX_MyHnd);
      if MyErrorCheck(Memo, TX, RX, 'canBusOn') then
      begin
        exit
      end;

      // Remove all pending transmissions, might not be needed
      TX := canFlushTransmitQueue(TX_MyHnd);
      RX := canFlushTransmitQueue(RX_MyHnd);
      if MyErrorCheck(Memo, TX, RX, 'canFlushTransmitQueue') then
      begin
        exit
      end;

      sleep(250); // Take a short nap...

      // Remove all pending transmissions, do we really wanna do this in a sharp application?
      // Might remove wanted frames.
      TX := canFlushReceiveQueue(TX_MyHnd);
      RX := canFlushReceiveQueue(RX_MyHnd);
      if MyErrorCheck(Memo, TX, RX, 'canFlushReceiveQueue') then
      begin
        exit
      end;

      // CAN is open for traffic
      Memo.Lines.Add('CAN open, waiting');

      /// *********************************************************
      /// Section two, Sending
      /// *********************************************************
      // Create one frame and transmit
      // Transmitting from TX_MySelCh

      case MyApp of
        canCL: // Classic CAN communication
          begin
            BUF := 'Hello!';
            id  := 111;
            dlc := 8;
            /// For Classic CAN dlc can be at most 8, unless canOPEN_ACCEPT_LARGE_DLC is used.
            /// CanWrite can only transfer max 8 bytes, regardless of dlc
            /// In this example BUF is 64 bytes wide, but only 8 bytes can be sent.
            Flag := canMSG_STD;

            R := canWrite(TX_MyHnd, id, @BUF, dlc, Flag);
            if MyErrorCheck(Memo, R, 'canWrite') then
            begin
              exit
            end;

            Memo.Lines.Add('TX ID:'+id.ToString+' dlc:'+dlc.ToString+' B :'+MyConvert(BUF, dlc));
          end;
        canFD: // CAN FD communication
          begin
            BUF  := 'Hello World!';
            id   := 222;
            dlc  := 16; 
            // for CAN FD dlc CAN be one of the following 0-8, 12, 16, 20, 24, 32, 48, 64
            Flag := canMSG_STD OR canFDMSG_FDF OR canFDMSG_BRS;

            R := canWrite(TX_MyHnd, id, @BUF, dlc, Flag);
            if MyErrorCheck(Memo, R, 'canWrite (FD)') then
            begin
              exit
            end;

            Memo.Lines.Add('TX ID:' + id.ToString + ' dlc:' + dlc.ToString + ' bytes :' +
             MyConvert(BUF, dlc));
          end;
      end;

      /// *********************************************************
      /// Section three, Recieving
      /// *********************************************************
      // Now if we are lucky, some bytes have arrived to RX
      BUF := '';
      I   := 0;
      repeat
        inc(I);
        sleep(100);

        // Always use 64 byte wide buffer to avoid errors!!!
        dlc  := 4;
        Flag := 0;
        R    := canRead(RX_MyHnd, id, @BUF, dlc, Flag, myTime);
        /// WARNING, make sure that BUF is mminimum 64 bytes wide!
        Memo.Lines.Add('Read attemp no ' + I.ToString + ' Result :' + R.ToString);
      until (I >= 5) OR (R = canOK);

      case R of
        canOK:
          begin
            dummy(id, BUF, dlc, Flag, HelpFlag.canMSG_Flag, myTime);

            // Examine the Flag(HelpFlag) and do someting...
            if MycanMSG_RTR in HelpFlag.canMSG_Flag then
              Memo.Lines.Add('RX Flag: <MycanMSG_RTR>');

            if MycanMSG_STD in HelpFlag.canMSG_Flag then
              Memo.Lines.Add('RX Flag: <MycanMSG_STD>');

            if MycanMSG_EXT in HelpFlag.canMSG_Flag then
              Memo.Lines.Add('RX Flag: <MycanMSG_EXT>');

            if MycanMSG_ERROR_FRAME in HelpFlag.canMSG_Flag then
              Memo.Lines.Add('RX Flag: <MycanMSG_ERROR_FRAME>');

            if MycanFDMSG_FDF in HelpFlag.canMSG_Flag then
              Memo.Lines.Add('RX Flag: <MycanFDMSG_FDF>');

            if MycanFDMSG_BRS in HelpFlag.canMSG_Flag then
              Memo.Lines.Add('RX Flag: <MycanFDMSG_BRS>');

            Memo.Lines.Add('RX ID:' + id.ToString + ' dlc:' + dlc.ToString + ' bytes :' +
             MyConvert(BUF, dlc));
          end;
        canERR_NOMSG:
          begin
            Memo.Lines.Add('Ooops, no data????');
          end;
      else
        begin
          Memo.Lines.Add('Some error happened, check! ' + R.ToString);
        end;
      end;

      /// *********************************************************
      /// Section three, Finalizing
      /// *********************************************************
      // We are done
      Memo.Lines.Add('DONE, start the cleanup process');

      TX := canBusOff(TX_MyHnd);
      RX := canBusOff(RX_MyHnd);
      if MyErrorCheck(Memo, TX, RX, 'canBusOff') then
      begin
        exit
      end;

      TX := canClose(TX_MyHnd);
      RX := canClose(RX_MyHnd);
      if MyErrorCheck(Memo, TX, RX, 'canClose') then
      begin
        exit
      end;

      Memo.Lines.Add('If you can read this, everything worked!');
    end;
  end;
Author Image

Lars-Göran Fredriksson