A Controller Area Network (CAN Bus) is a vehicle bus standard designed to allow microcontrollers and devices to communicate with each other in applications without a host computer. It is a message-based protocol, designed originally for multiplex electrical wiring within automobiles, but is also used in many other contexts.

The ConnectCore 8X has several CAN ports to communicate with other devices using this protocol. In the ConnectCore 8X Hardware Reference Manual, you can find information about the available CAN channels.

Digi adds to Android an API to manage these CAN interfaces. You can configure the CAN settings, write, read, and add listeners among other things. In the Digi APIx javadoc you can find a complete list of the available methods in this API.

Unless noted, all CAN API methods require the com.digi.android.permission.CAN permission.

If your application does not have the com.digi.android.permission.CAN permission it will not have access to any CAN service feature.

First, a new CANManager object must be instantiated by passing the Android Application Context.

Instantiating the CANManager
import android.app.Activity;
import android.os.Bundle;

import com.digi.android.can.CAN;
import com.digi.android.can.CANManager;

public class CANSampleActivity extends Activity {

    CANManager canManager;

    [...]

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Instantiate the CAN manager object.
        canManager= new CANManager(this);

        [...]
    }

    [...]
}

Open/close a CAN interface

The CANManager allows you to create a CAN object for a specific CAN interface.

Getting a CAN object
import com.digi.android.can.CAN;
import com.digi.android.can.CANManager;

[...]

CANManager canManager = ...;

// Create CAN object for interface number 0.
CAN can0 = canManager.createCAN(0);

To work with a CAN interface, you must open it so you can read and write data. To manage the interfaces use the following CAN class methods:

Method Description

open()

Opens the CAN interface

close()

Closes the CAN interface

isInterfaceOpen()

Returns the CAN interface status

open() and close() methods may fail for the following reasons:

  • If the interface number represented by the CAN object does not exist, the open() method throws a NoSuchInterfaceException.

  • If there is any error while opening or closing the CAN interface an IOException is thrown.

Opening/closing a CAN interface
import com.digi.android.can.CAN;
import com.digi.android.can.CANManager;

[...]

CANManager canManager = ...;

// Create CAN object for interface number 0.
CAN can0 = canManager.createCAN(0);

// Check if the CAN 0 is already opened. If not, open it.
if (!can0.isInterfaceOpen())
    can0.open();

[...]

// Close CAN 0.
can0.close();

Configure a CAN interface

Before sending and receiving data, the CAN interface bitrate must be configured. By default, it is set to 500 kbps.

Use the setBitrate(int) method to change the bitrate of a CAN interface. This method may fail if:

  • The CAN interface does not support bitrate changes. In this case a UnsupportedOperationException is thrown.

  • Any error occurs while setting the new bitrate, then an IOException is thrown.

Setting CAN bitrate
import com.digi.android.can.CAN;
import com.digi.android.can.CANManager;

[...]

CANManager canManager = ...;
CAN can0 = ...;

[...]

// Set the new bitrate.
can0.setBitrate(125000);

[...]

Send data

Once the bitrate is properly configured, you can send data through the CAN interface. The CAN API includes a class called CANFrame to represent the data over the CAN bus. To instantiate a CAN frame, you need to provide the following parameters:

  • A CAN object to send the frame.

  • A CANId object, the identifier of the frame.

  • The data to send.

Creating a CANFrame
import com.digi.android.can.CAN;
import com.digi.android.can.CANFrame;

[...]

CAN can0 = ...;
CANId id = new CANId(50, false);
byte[] dataToSend = new byte[]{'D', 'a', 't', 'a', ' ', 't', 'o', ' ', 's', 'e', 'n', 'd'};

// Instantiate a new frame to send.
CANFrame frame = new CANFrame(can0, id, dataToSend);

[...]

To send the data, use the write(CANFrame) method with the CANFrame object that contains the data to be transmitted. This method may fail if the interface is closed or if any error occurs while transmitting the data throwing an IOException.

Sending a CANFrame
import com.digi.android.can.CAN;
import com.digi.android.can.CANFrame;

[...]

CAN can0 = ...;
CANId id = ...;
byte[] dataToSend = ...;

if (!can0.isInterfaceOpen())
    can0.open();

[...]

CANFrame frame = new CANFrame(can0, id, dataToSend);
// Transmit the data.
can0.write(frame);

[...]
When you are done with the CAN interface you need to close it by calling the close() method.

Receive data

To receive data you must:

Once you received all the required data, you can stop the reading process and unregister the listener.

Register for frame notifications

To receive data you must subscribe an ICANListener to the CAN object that represents the interface where you are going to read data. Use the registerListener(ICANListener) method to register for the notification of new CAN frames notifications.

ICANListener registration
import com.digi.android.can.CAN;
import com.digi.android.can.CANFrame;

[...]

CAN can0 = ...;

// Create the CAN listener.
MyCANListener myCANListener = ...;

// Register the CAN listener.
can0.registerListener(myCANListener);

[...]

The registered listener class, MyCANListener, must implement the ICANListener interface. This interface defines the frameReceived(CANFrame) method that is called every time a CAN frame is received.

ICANListener implementation example, MyCANListener
import com.digi.android.can.CANFrame;
import com.digi.android.can.ICANListener;

public class MyCANListener implements ICANListener {
    @Override
    public void frameReceived(CANFrame frame) {
        System.out.println("Received on CAN " + frame.getCAN().getInterfaceNumber()
                            + ": " + new String(frame.getData()));
    }
}

Start the data reception

The listener starts receiving frames as soon as the startRead(List<CANFilter>) method is called with the list of filters specified. This allows you to listen only to specific CAN identifiers and masks.

The listener processes each received CAN frame through all configured filters (ID/mask pairs) to remove undesired messages. The listener uses the filter mask to determine which received CAN frame identifier bits to compare to the filter ID:

  • If a mask bit is set to a zero, the corresponding CAN frame ID bit won’t be checked against the filter ID. It will automatically be accepted, regardless of the value of the filter bit.

  • If a mask bit is set to a one, the corresponding CAN frame ID bit will be compared with the value of the filter ID bit. If they match, it is accepted; otherwise, the frame is rejected.

For example, for a filter ID of 0x100 and mask of 0xFF, the CAN IDs 0x100, 0x200, 0x300 will be allowed, as the bits 8 and 9 of the mask are not enabled. However, using the same filter ID and mask, CAN IDs such as 0x101, 0x201, 0x301 will be rejected, as the bit 0 of the mask is set to one and the bit 0 of the CAN frame IDs do not match the bit 0 of the filter ID. A mask value of 0x00 in a filter leaves the filter without effect, as no bits will be tested and all CAN frames will go through.

When working with CAN IDs and filter IDs, if any ID exceeds the 0x7FF value (which is the standard CAN ID length of 11 bits), the 'use extended ID' flag must be set to true while creating the CANId object:

[...]

CANId id = new CANId(0x8FF, true);
CANFilter f = new CANFilter(id, 0x1FFFFFFF);

[...]
Starting reading
import com.digi.android.can.CAN;
import com.digi.android.can.CANId;
import com.digi.android.can.CANFilter;

[...]

CAN can0 = ...;
MyCANListener myCANListener = ...;

[...]

can0.registerListener(myCANListener);

// Define filters.
ArrayList<CANFilter> filters = new ArrayList<CANFilter>();
CANId id = new CANId(0x50, false);
CANFilter f = new CANFilter(id, 0x7FF); // Only accept messages with ID = 0x50.
filters.add(f);
// Start reading for frames on CAN 0.
can0.startRead(filters);

[...]

The startRead(List<CANFilter>) method throws an IOException if the CAN interface is closed.

Stop the data reception

You can have more than one ICANListener waiting for frames in the same CAN interface. To stop all CAN interface notifications, use the stopRead() method.

If you no longer wish to receive CAN frames in a determined listener, use the unregisterListener(ICANListener) method to unsubscribe an already registered listener.

Stopping reading
[...]

CAN can0 = ...;
MyCANListener myCANListener = ...;
ArrayList<CANFilter> filters = ...;

[...]

can0.registerListener(myCANListener);

can0.startRead(filters);

[...]

// Stop reading from CAN 0.
if (can0.isReading())
    can0.stopRead();

// Remove the CAN listener.
can0.unregisterListener(myCANListener);

[...]

The stopRead() method throws an IOException if the CAN interface is closed.

When you are done with the CAN interface you need to close it by calling the close() method.

CAN example

Example: CAN

The CAN Sample Application demonstrates the usage of the CAN API. In this example, you can configure all the settings of the CAN interface, write, and read data from the port.

You can import the example using Digi’s Android Studio plugin. For more information, see Import a Digi sample application. To look at the application source code, go to the GitHub repository.