Skip to content

AN19: Guide for CAN bus with Tapioca / TapNLink

Introduction

This Application note explains how to manage CAN messages/signals with Tapioca-CAN or a TapNLink module that features a CAN controller

Hardware

At the moment, the only TnLFIW103/TnLFIW113 modules support CAN protocol. The 'NFC only' module with CAN would be soon available.

Four models of the Tapioca-CAN exist: * Standard casing, * IP67 casing with no extension, * IP67 casing with a LoRa extension * IP67 casing with a LTE-M / NB-IOT extension (soon available). Switchable termination (120o) A switchable internal 120 ohm is present on all the Tapioca-CAN boards. By default the circuit is open (no termination) but if you wish to use it as a terminal resistor, you would have to open the case and to move the switch to the 'ON' position:

Connectors

On the TapNLink modules, a CAN transceiver should be connected to pins PB4 et PB5 on the PB extension port: * CAND signal on PB5 * CANR on PB4 On the standard Tapioca, the pinout is shown below:

Pin Signal Description
1 Power- (GND) Ground (0V)
2 CAN_L CAN_L bus line (dominant low)
3 (CAN_SHLD) Optional CAN shield
4 CAN_H CAN_H bus line (dominant high)
5 POWER+ Power supply (DC). Must be in the range [ 6V, 28V].

And for the IP67 versions, signals are available on two 5-pin A coded M12 'male/female' connectors:


Pin Signal Description
1 CAN_SHLD Optional CAN shield
2 POWER_V+ Power supply (DC). Must be in the range [ 6V,28V].
3 CAN_GND Ground (0V)
4 CAN_H CAN_H bus line (dominant high)
5 CAN_L CAN_L bus line (dominant low)

Features

The following diagram shows the Duetware architecture and how CAN messages are transferred:


This architecture allows to: 1. Listen and record (within 'InTap variables') CAN messages sent from external CAN nodes. In this case, the path is: {Node_xxx} => CAN bus => CAN Controller => Serial Slave Server => InTap variable.

The InTap variables are then accessible from a client: either the (mobile) application or a Java program running into the Java machine (JVM).

  1. Send messages with CAN IDs associated to external "target variables" . In this case, the request will be sent via the lwM2M machine, either by the (mobile) app or by the JVM. The message will be addressed to the CAN controller then transferred on the CAN-bus and readable by the external nodes. The path is now:

Client (mobile, Cloud, Java) => lwM2M => CAN Controller => CAN=> bus => {Node_xxx}

  1. Execute specific read requests (RTR). These requests are executed via the lwM2M as for sending the messages and the read values will be returned to the source of the request (either the JVM or the mobile/Cloud App). The path is the same as for sending a message, but we would have here both the request then the answer (if any).

Defining messages within IoTize Studio

Loading the message definitions

There are two ways to define CAN messages/signals with Studio: * Load a DBC file that contains both the messages and the definitions of the sub-signals (bitfields). In this case you can, when creating a new project, specify the pathname of the DBC file. It is also possible to add a DBC file later, on an existing project (popup menu on the DBC node of Resource view).


You will find the contents of the DBC file into the Resource panel: here, the messages are displayed with a preceding blue diamond and the signals with a preceding yellow diamond; It is then possible to select these variables and to move them into a bundle by a drag and drop operation or with the Add to current bundle command available in the popup menu (select a bundle, select a variable in the Resource tree and right click on the variable).


  • you can also manually create messages and signals. To add a message, you have to add an InTap volatile variable to record a read message, or a custom target variable if you wish to send a message.


You can also add and edit manually some signals after selecting a message by opening the popup menu (command add CAN signal).


Specify the variables qualifiers

When variables (messages/signals) are read from a DBC file, variables inherit of many parameters that are not modifiable:


However, you still have to specify an important parameter: the Direction.


When the Direction is set to Receive, messages will be listened and, when the ID match with one of the InTap (Receive) variables, the data of the message will be recorded into the value of this variable. Then, an external client (Mobile App, Cloud, Java program) would be able to read this value.

However, when Send will be specified, an external client could send a message with the ID: the destination variable will be considered as being external (somewhere on an external CAN node). It would be also possible to read such a "variable" and, in this case, a RTR request will be executed.

If you wish to execute these actions from the generated mobile App, you have to: * Enable the "Display" parameter (= Yes), * Specify the "Access abilities". If the message has only to be sent, select the "Write" option. If you wish to display a read value, select "Read". When "Read and Write" is selected, RTR requests will be launched and it would be also possible to send the message with the corresponding Id.

Take care of keeping a full consistency between "Access abilities" and the "Direction" parameter.

Note that you can use for the signals (bitfields) the same graphical components that are available for the standard (non-bitfield) variables.

General settings for the CAN driver

The CAN driver can be initialized from the general settings, and it does not require any Java program.

lwM2M without JVM

You will find two parts concerned by the CAN driver in the "Duetware Configuration | Target Link" section:

The "target protocol" needs to be set to CAN. You can then enable the "Serial Slave Server" that will scan the received messages to record their values (when the IDs match) in some "Intap" variables.

There are now various general parameters to set: * Bitrate (frequency in kbps) * Mode: Normal (recommended), "No ack" and "listen only" * An acceptance filter defined by 3 parameters (see next section).

Acceptance Filter

The CAN controller features one hardware acceptance filter which can be used to filter messages of a particular ID. When the controller filters out a message, this message will not be "received" but the controller will still acknowledge it. Acceptance filters can make the management of the variables more efficient by filtering out messages sent over the bus that are irrelevant. The acceptance filter is configured using two 32-bit known as the acceptance code and the acceptance mask.

The acceptance code specifies the bit sequence which a messag's ID, RTR, and data bytes must match for the message to be received by the CAN controller. The acceptance mask is a bit sequence specifying which bits of the acceptance code can be ignored. This allows for a messages of different IDs to be accepted by a single acceptance code.

The acceptance filter can be set under two different modes: Single or Dual. Single Filter Mode will use the acceptance code and mask to define one single filter. This allows for the first two data bytes of a standard frame to be filtered, or the entirety of an extended frame's 29-bit ID. The following diagram illustrates how the 32-bit acceptance code and mask will be interpreted under Single Filter Mode (the yellow and blue fields represent standard and extended frame formats respectively).


Bit layout of single filter mode (Right side MSBit)

Dual Filter Mode will split the registers and use the two parts to define two separate filters allowing for increased flexibility of ID's to accept but does not allow for all 29-bits of an extended ID to be filtered. The following diagram illustrates how the 32-bit acceptance code and mask will be interpreted under Dual Filter Mode (Note: The yellow and blue fields represent standard and extended frame formats respectively).


Bit layout of dual filter mode (Right side MSBit)

Example: if you wish to listen only one message (defined by its UID), use the "single filter mode" with: * Code = UID << (32-len) ;
* Mask = (1<<(32-len))-1 ;

with len=29 for an extended frame message and len=11 for a standard frame.

Note that only the last 16 MSB can be considered with a dual filter mode when you use some extended frames (the 13 LSB will be ignored).

If you wish to accept any message, keep 0xffffffff for the "mask": all bits will be ignored whatever the frame format and the filter mode. This value will disable the filter but you could miss some messages (depending on both the traffic density and the availability of the lwM2M).

In practice

CAN messages are managed as physical variables

If you wish to display some signals/messages that are transferred onto the CAN bus, you must start the "Serial Slave Server" that will copy automatically the contents of these messages into "InTap variables" previously defined. These variables will be then accessible by your mobile application to be displayed.

If you wish to send some messages onto the CAN bus, you will have to declare them as write-only "target variables".

When a target variable is not declared as "write-only", it will be read by generating a RTR request.

DBC files make things easier

When loading a DBC file, these concepts will still exist, but the "direction" parameter will allow to specify simply "receive" (managed as "Intap variable") or "send" (managed as "target").

CAN signals

For "receive" messages, CAN signals allow to display subsets (bitfields) of the whole contents. But for the App generator, a signal can be displayed as an independent variable.

For "send" message (write only), you must define an initial value. Modifying a signal will always generate the transmission of the whole message. The generated app will manage this mechanism automatically.

In both cases, the signal is managed only at the Client level. It means that the Tap ignore everything about the signals. Only the messages are received, sent or stores. The concept of "signal" concerns the only HMI (generated app).

Adding a Java program

If you need your Tap to be active on the CAN bus when the client (mobile app) is not connected, you must add a simple Java program. Please look at the General IoTize Documentation to learn more about the embedded Java Virtual Machine and the available native classes. We are going to do here very simple things:

  • Enable the option "Use internal JVM". If you have created your project with the wizard, a Java program has been generated from the default template. This default program has been copied into your project directory.
  • In the HAL Peripheral section, leave CAN as "Disabled". You will have to switch this option only if you wish to manage by yourself the CAN status/errors/recovery.


A Java program could allow you to decide specific actions on the bus (sending messages or launching RTR requests), managing the controller in the way you prefer or, for example, transferring data to the Cloud when a Cloud connection is available.

Example

Let's create a new project (Tapioca-CAN) based on a DBC file:


We place into two different bundles ("Receive" and "Send") signals from two different messages:


The signals are highlighted in yellow, the messages in green and the bundles in blue. We had to place the messages in different bundles because one of them will be read (Intel unsigned) and the other one (Intel signed) will be only written. And for optimization purposes, whole bundles are read by the generated Apps when updating a dashboard as soon as one variable of the bundle has to be read.

Now we set the different options of the two bundles:

  • We link both bundles to the "anonymous" profile. But we set the access to "read" for the bundle "Read" and to "write" for the bundle "Write"
  • We set the option of the signal Intel_U9:


  • Then those of the Send signal:


The "write" setting for the "Access abilities" of the signal is inherited of the same parameter of the parent message. Therefore, we must modify first the message, then the signal (by temporarily switching to "Yes" the "Display" option of the message).

Note that we have also to define the initial values for all the signals contained into the message. Neutral values could be the offset specified for these signals.

We can now launch a quick test from Studio by looking at the generated HTML page. We establish the connection by clicking on the "Connect" button. The "Receive" value (Intel_U9) is properly updated and the "Send" value appears in green because it is a "write only" data. We can send an unique message by clicking on the small gear logo at its right:


Next step: we will modify the initial Java program to send a periodic value for the message "Intel_Signed". The initial Java program has been generated to provide a default management of the two LEDs of the Tapioca. We want now to extend it to send this periodic message.

First, we select the "Intel Signed" variable in the bundle and, with the popup menu, we run the command "add to Java":


Indeed, the declaration below has been added to the Java file:

public TapNLinkVarByteArray Intel_signed = new TapNLinkVarByteArray( ID_INTEL_SIGNED, "Intel_signed", 1030, 8); //{$R:3}

Because we don't want the value "Intel_Signed" to be read, we must replace the 1030 by 0. Parameter "1030" means that the message will be read periodically every 1030ms with a RTR request, and we don"t want that.

In the default program, there is another periodic action (see line 28):

MainClass.startTimer(500);

The timer triggers a call every 500ms to the method "onEvent"

public void onEvent(Object event) {
    //{$onEvent_body}
    // On a timer tick, we check the client connection state.
    if (event instanceof MainClass) {
        checkClientConnectionState();
    }
}

We can use this event to send periodically our message. For example, we can fill it with the value of a global counter:

int counter = 0;
byte [] intel_s  = new byte[8]; 
public void onEvent(Object event) {

//{$onEvent_body}
    // On a timer tick, we check the client connection state.
    if (event instanceof MainClass) {     //timer event
        checkClientConnectionState();
        counter++;  //increment the counter every 500ms
        intel_s[0]=(byte)(counter & 0xff); //Assign counter to S9 
        intel_s[1] = (byte)((intel_s[1] & 0xFE)|((counter & 0x100)>>8));
        Intel_signed.setValue(intel_s); //Send the message
    }
}

Once the Java program has been edited, we must: 1. Save the file 2. Compile it 3. Configure our Tapioca to record the new Java file.


Finally, let's generate a mobile App. Because 'Intel Signed' is sent automatically, we'll add in the bundle another message to be sent: 'Moto Unsigned' that we configure as we did for 'Intel Signed'.


To set up the mobile App:

  1. We change the default setting for "App type" from "HTML web page" to "Generated App".
  2. We assign a slider to Moto_U9 as graphical representation.
  3. We specify 512 as max value
  4. And we set this variable as a write only variable (access abilities=write, Editable=Yes, Display=Yes).
  5. In the same way, we assign a Gauge to represent the signal Intel_S9, specify -300 and +300 for the min and max values.
  6. Finally we can push on the button "Generate App" (we assume that we already created an account on the TapCloud server).


After a couple of minutes, we get by email a link to the simple APK to be installed onto an Android device. When approaching the mobile close to the Tapioca, the App is automatically launched on the monitoring page. The gauge displays the signal "Intel_U9" and the slider allows to send (when modifying the position) a "Moto Unsigned" message with the updated Moto_U9 signal (the other bits keep the "initial value" defined in the configuration file).


Back to top