How to use

Platform support

Framework:

  • Arduino

This library is made to be used with:

  • ESP32 family

Necessary inclusions

Only the header has to be included to you project

#include "libDM_can.hpp"

Operating mode

The can library is can be used with 2 modes:

1. Loopback mode

The driver won’t need ack form network nodes to send messages. This is useful for testing. The problem is that the driver will ack the frames itself and every sent frames will be also received. This is not a problem if you only need to send or to receive signals (and not both send and receive) on can bus. For example you can configure libDM_msg_center easily to only do one of the two. Also if you need to visualize frames without adding your own node, this is useful. This mode is activated by default.

2. Normal mode

The driver will need ack from network nodes to send messages and will not be able to operate without it. This mode should be used if you need to send and receive signals on can bus. This mode is activated at object construction

Message emission

1. Create a CAN_UTILS object

Important

API ref: CAN_DRIVER

1streamLogger streamObj(&myTimer, sdioConf);
2timerTool myTimer;
3CAN_DRIVER can(CAN_ENUM::BAUDRATE::_500kbps, pinCanRx, pinCanTx, &streamObj, &myTimer);

2. Initialize the CAN_DRIVER object

1can.begin();

3. add signals

The library is made to facilitate the message handling. The CAN_DRIVER::addVariable() is made to concatenate signals in the indicated frame in the same order they are added, with a limit of 8 bytes by frame (standard frame limit).

Example

 1float mFloat        = 0.1234567890F;
 2uint8_t mUint8      = 1U;
 3uint16_t mUint16    = 2;
 4int32_t mInt32      = -6;
 5float mSinWave      = 0.F;
 6uint8_t mSquareWave = 0.F;
 7
 8can.addVariable(&mFloat, "mFloat", 0x1, "0x1");
 9can.addVariable(&mUint8, "mUint8", 0x1, "0x1");
10can.addVariable(&mInt32, "mInt32", 0x1, "0x1");
11can.addVariable(&mUint16, "mUint16", 0x2, "0x2");
12can.addVariable(&mInt32, "mInt32", 0x3, "0x3");
13
14can.printMappingInfo(true, true); // Optional print of the mapping table

Attention

Ensure added variables are global as their references will be stored in the CAN_DRIVER object.

4. Send signals

Library allows signals sending in two ways:

  • Periodic send: all added signals will be sent at a given frequency.

  • Asynchronous send: added signals will be sent when the user wants, in a more controlled manner.

4.1. Synchronous send

This is the easiest way to send signals. Just call periodically the CAN_DRIVER::sendAllMessages() function and all the signals will be sent.

Important

API ref: CAN_DRIVER::sendAllMessages()

 1float mFloat        = 0.1234567890F;
 2uint8_t mUint8      = 1U;
 3
 4can.addVariable(&mFloat, "mFloat", 0x1, "0x1");
 5can.addVariable(&mUint8, "mUint8", 0x1, "0x1");
 6
 7// Send all messages at approximatly 200Hz
 8while (true)
 9{
10    delay(5);
11    can.sendAllMessages();
12}

4.2. Asynchronous send

This is the best method as each frame is controlled individually. This is the method used in libDM_msg_center for example. Call the function CAN_DRIVER::serializeAndSendFrame() with the ID of the frame you want to send.

Important

API ref: CAN_DRIVER::sendAllMessages()

 1float mFloat        = 0.1234567890F;
 2uint8_t mUint8      = 1U;
 3
 4can.addVariable(&mFloat, "mFloat", 0x1, "0x1");
 5can.addVariable(&mUint8, "mUint8", 0x1, "0x1");
 6
 7can.addVariable(&mUint16, "mUint16", 0x2, "0x2");
 8
 9// Send all messages at approximatly 200Hz
10uint32_t counter1 = 0U;
11uint32_t counter2 = 0U;
12while (true)
13{
14   // Send frame 0x1 every 100ms
15   if (counter1 >= 100U)
16   {
17      counter1 = 0U;
18      can.serializeAndSendFrame(0x1);
19   }
20
21   // Send frame 0x2 every 10msinde
22   if (counter2 >= 10U)
23   {
24      counter2 = 0U;
25      can.serializeAndSendFrame(0x2);
26   }
27
28    delay(1);
29}

Message reception

Message reception is less controlled than message emission. The received messages are stored in a buffer the is made available with the use of the function CAN_DRIVER::getRawRxBuffer(). The deserialization process is the user responsability as it depends on the add messages format.

1. Create a CAN_UTILS object

Important

API ref: CAN_DRIVER

1streamLogger streamObj(&myTimer, sdioConf);
2timerTool myTimer;
3CAN_DRIVER can(CAN_ENUM::BAUDRATE::_500kbps, pinCanRx, pinCanTx, &streamObj, &myTimer);

2. Initialize the CAN_DRIVER object

1can.begin();

3. Receive signals

The library will fetch CAN incoming buffer with the use of CAN_DRIVER::receiveFrames(), and store received message until a limit of 50 messages. The user can then ask a for this reception buffer with CAN_DRIVER::getRawRxBuffer() and loop through it to decode the messages. The frame numbers in the buffer can be obtained with CAN_DRIVER::getFrameNumber(). Both methods are presented

Attention

The reception buffer if overriden at each call of CAN_DRIVER::receiveFrames(). Ensure to copy it before calling the function again.

3.1. With partial decoding

This method is less efficient but easier to use. This is the method used in libDM_msg_center for example.

Example

We expect to find 2 float in the frame with ID = 0x1, and one uint8_t and one uint16_t in the frame with ID = 0x2.

 1float mReceivedFloat1 = 0.F;
 2float mReceivedFloat2 = 0.F;
 3
 4uint8_t mReceivedUint8 = 0U;
 5uint16_t mReceivedUint16 = 0U;
 6
 7using float_2_binary = union
 8{
 9   float floatData = 0.F;
10   uint8_t byteTable[4];
11};
12
13using uint16_2_binary = union
14{
15      uint16_t uint16Data = 0U;
16      uint8_t byteTable[2];
17};
18
19float_2_binary sharedMemFloat;
20uint16_2_binary sharedMemUint16;
21
22can.receiveFrames();
23CAN_DATATYPES::frameStruct frame = CAN_DATATYPES::frameStruct();
24
25if (can.decodeRawRxBuffer(0x1, frame))
26{
27   std::copy(frame.receivedFrameData, frame.receivedFrameData + 4, sharedMemFloat.byteTable);
28   mReceivedFloat1 = sharedMemFloat.floatData;
29   std::copy(frame.receivedFrameData + 4, frame.receivedFrameData + 8, sharedMemFloat.byteTable);
30   mReceivedFloat2 = sharedMemFloat.floatData;
31}
32
33if (can.decodeRawRxBuffer(0x2, frame))
34{
35   mReceivedUint8 = frame.receivedFrameData[0];
36   std::copy(frame.receivedFrameData + 1, frame.receivedFrameData + 3, sharedMemUint16.byteTable);
37   mReceivedUint16 = sharedMemUint16.uint16Data;
38}

3.2. With manual decoding

This method is more efficient as there is only one loop through the buffer but less generic as it uses twai_message_t which is esp32 specific.

Example

We expect to find 2 float in the frame with ID = 0x1, and one uint8_t and one uint16_t in the frame with ID = 0x2.

 1float mReceivedFloat1 = 0.F;
 2float mReceivedFloat2 = 0.F;
 3
 4uint8_t mReceivedUint8 = 0U;
 5uint16_t mReceivedUint16 = 0U;
 6
 7using float_2_binary = union
 8{
 9   float floatData = 0.F;
10   uint8_t byteTable[4];
11};
12
13using uint16_2_binary = union
14{
15      uint16_t uint16Data = 0U;
16      uint8_t byteTable[2];
17};
18
19float_2_binary sharedMemFloat;
20uint16_2_binary sharedMemUint16;
21
22can.receiveFrames();
23uint32_t frameNumber = can.getRawBufferSize();
24const std::array<twai_message_t, CAN_BUFFER_MAX_FRAME_NUMBER>* canRxBuffer =
25   can.getRawRxBuffer();
26
27for (size_t i = 0; i < frameNumber; i++)
28{
29   const twai_message_t* frame = &canRxBuffer->at(i);
30
31   if (frame.identifier == 0x1)
32   {
33      std::copy(frame->data, frame->data + 4, sharedMemFloat.byteTable);
34      mReceivedFloat1 = sharedMemFloat.floatData;
35      std::copy(frame->data + 4, frame->data + 8, sharedMemFloat.byteTable);
36      mReceivedFloat2 = sharedMemFloat.floatData;
37   }
38
39   if (frame.identifier == 0x2)
40   {
41      mReceivedUint8 = frame->data[0];
42      std::copy(frame->data + 1, frame->data + 3, sharedMemUint16.byteTable);
43      mReceivedUint16 = sharedMemUint16.uint16Data;
44   }
45}