Kvaser SDK migration from CAN CLASSIC to CAN FD

  • March 6, 2018
  • Lars-Göran Fredriksson

The purpose with this document is to show how to migrate from CAN CLASSIC to CAN FD. We will only cover what’s needed to change in your code if you have an existing project (based on Kvaser CANLib SDK). Of course, you can also use this text if you intend to start a new project.

We will not cover the definition of CAN FD and the changes needed on the physical bus. For more information about CAN FD, please look at our homepage (https://www.kvaser.com/about-can/can-fd/).

There is also more information in our training section (https://www.kvaser.com/training-materials/). You will need to register (free) and login to be able to download the documents.

Recommended documents to download (and read):

  • CAN Protocol Tutorial
  • CAN FD For Different Stakeholders
  • Comparing CAN FD with Classical CAN

CanFDTest

We have created a project called “CanFDTest” in the Embarcadero DELPHI environment (Pascal). If you don’t have the DELPHI environment, our intention is that the code can be read with any text editor. All-important code is located in the RunCanFDExamplePlease procedure, which is listed in appendix A.

In this procedure, we initialize the bus, send one frame, receive one frame and finalize the bus.


Hardware needed

This example has been tested on both a virtual computer (Microsoft HYPER-V, WIN10 64bit) and a standard laptop. We have used both virtual interfaces and Kvaser interfaces when testing the example.

  • No hardware (Kvaser CAN interfaces) are needed to run the example if the virtual channels on the Kvaser driver are used.

Tools needed for “CanFDTest”

The example is found in the Delphi® project “CanFDTest_xxx”.

It is not necessary to run the example, more important is to open the file “CanFDtestUnit.pas” and study the procedure RunCanFDExamplePlease().

EMBARCADERO DELPHI
Delphi® is a software provided by EMBARCADERO®. At the time of writing (10.01.2018), they provide a free version called “DELPHI STARTER”, which can be found here: https://www.embarcadero.com/products/delphi/starter

Kvaser CANlib SDK
The CANlib Software Development Kit (Kvaser Caleb SDK) is needed to run the example, but is really life without CANlib, worth living?

You can find the SDK at: https://www.kvaser.com/downloads-kvaser/

Driver for Kvaser CAN hardware
This driver includes a virtual hardware that can do just about everything our real hardware can do. This makes it possible to try CAN FD without a physical interface. Once you get a Kvaser interface you can seamlessly transition from the virtual driver to a real CAN FD bus-line.

You can find the drivers at: https://www.kvaser.com/downloads-kvaser/

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

Kvaser HELP files
When you visit the KVASER homepage, please also visit CANlib Help (this help is included in CANlib SDK) (http://www.kvaser.com/kvaser/site/CANlib-webhelp/)

  • DELPHI, CANlib SDK and DRIVER must be installed before running this example (CanFDTest).

The program CanFDTest

The example program, CanFDTest, is built up using four PAS files:

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

CANlib.pas
Normally, this file is distributed in the Kvaser CANlib SDK.

To make life a bit easier, the PAS file is copied to our project instead of setting up a search path in DELPHI to the file.

CanFDTestUnit.pas
Contains all main information in this example.

The procedure RunCanFDExamplePlease() is included at the end of this document.

DummyUnit.pas
Contains some “dummy functions” for debugging.

These functions “do nothing”, but they are a good point to set a breakpoint on for debugging. The compiler cannot optimize the variables used in the dummy functions, so they are always available for inspection.

MyHelpUnit.pas
Contains some functions that helps us but do not call on the SDK.

How to use the example CanFDTest
Unzip it and open the project by opening the file “CanFDTest.dproj” (requires DELPHI).

If you don’t want to run the example, then open the file “CanFDTestUnit.pas” in a text editor of your choosing.


The procedure RunCanFDExamplePlease

This is divided into four sections:

  • Initialization
  • Sending
  • Receiving
  • Finalization

The code shown here is condensed and not complete, please look at the source code.

Section: Initializing
Selecting CLASSIC CAN or CAN FD
Set the constant MyApp to either canFD or canCLASSIC CAN.
Set TX_MySelCh to the interface you want to use as sender.
Set RX_MySelCh to the interface you want to use as receiver.

All (almost) Kvaser CAN interfaces can be both senders and receivers at the same time. We select to use one as TX and one as RX in this example to make it easier to follow the code.

First we run three commands:
canInitializeLibrary()
canGetChannelData()

  • No change for Can FD

We know now how many interfaces we have and their names. We are now ready to open two of them.

canOpenChannel()

  • Editing needed for 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);

It is important to use the FLAG: canOPEN_CAN_FD when opening a FD channel.

We are using combination of “canOPEN_ACCEPT_VIRTUAL OR canOPEN_CAN_FD” to open a virtual interface and to set the mode to FD.

The interfaces are opened and now we must set their parameters.

canSetBusParams() and canSetBusParamsFD()

  • Editing and adding information needed for FD!
  • It is possible to use other values than the predefined (not covered here).

CAN CLASSIC
Uses only canSetBusParams() together with the 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
Uses canSetBusParams() and canSetBusParamsFD() together with the 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);

  • Please note, do NOT use the canBITRATE_xxx values for FD!

All parameters are now set and we are ready to go online so we end the initializing section by opening the bus and doing some cleaning.

canBusOn()
canFlushTransmitQueue()
canFlushReceiveQueue()

  • No change for CAN FD

 

Section: Sending
We have activated the bus and the interfaces wait for us to do something. We shall now send one frame from one of our selected interfaces.

canWrite()

  • Editing needed for CAN FD!

(We can use the commands canWrite() and canWriteWait() but we only use canWrite in this example.)

Recommendation: READ 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);

  • New buffer size to hold up to 64 bytes
  • Add canFDMSG_FDF to flag (Indicates that the frame is an FD frame)
  • Add canFDMSG_BRS to flag (Indicate if frame should be sent with bit rate switch)

Please note that canFDMSG_BRS is optional. When adding the BRS flag, the data part is sent with the FD-bitrate instead of the arbitration bitrate.

It is still possible to send classic CAN frames even if you have enabled FD! Using only canMSG_STD or canMSG_EXT creates a classic frame.

 

Section: Receiving
canRead()

  • Editing needed for CAN FD!

(There are four versions (at least) of canRead() that can be used for receiving FD traffic. We only use canRead() in this example, so we recommend that you read the ‘Help’ file!)

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

We do not need to change anything in the read command, but …

  • Make sure that the buffer has a minimum length of 64 bytes. Even if we don’t intend to send frames longer than 24 bytes, someone else might.
  • Study the returned FLAG values, which contains some new values. They are a combination of the canMSG_xxx, canMSGERR_xxx and canFDMSG_xxx

Even if we are alone on the bus and we only sent one frame, the received frame might not be the one we expected. If, for example, the value canMSG_ERROR_FRAME is included in the Flag, then the frame is an error frame instead of our expected frame.

  • Always check the returned value [R] and the returned Flag!

We have now received our own frame.

 

Section: Finalization
The finalization does not need editing.

canBusOff()
canClose()
canUnloadLibrary()

  • No change for CAN FD

Summary

Of course, there are plenty that can be done with the SDK, but we limit this example to just sending one frame and then receiving it.

If you want to extend this example, modify it as you wish and use it in combination with the Kvaser CANKing software to send and receive frames. You can find this free software on our homepage (same place as the SDK and drivers).  With CANKing you can generate frames and also simulate errors (and more).

We hope this document helps you and gives you some information when using CAN FD together with Kvaser equipment.


Commands used for CAN FD:

Please pay extra attention to the commands mentioned below, they are all necessary for CAN FD and editing of your code is needed.

Please note that there may be more commands/flags needed for CAN FD that are not mentioned in this document.

Always check HELP before using any of the mentioned commands.

canOpenChannel()

canGetBusParamsFD()

canSetBusParams()
canSetBusParamsFD()

canWrite()

canRead()

 

Not used in this document
canWriteWait()  not used in this document

canReadSpecific()  not used in this document
canReadSpecificSkip() not used in this document
canReadWait()   not used in this document


Appendix A Procedure 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

Lars-Göran Fredriksson is a Field Application Engineer for Kvaser AB. His background is in geograph...