0

CAN Bus example for ML-R 6-channel color sensor CAN Bus (mrm-col-can)

Prerequisites

If not already done so already, install Arduino software and make basic connections, as described in page for MRMS ESP32: Arduino, IMU, eFuse, BT, WiFi, CAN Bus (mrm-esp32). This setup is convenient, but it is not mandatory. Read on and we will describe how to connect some other microcontroller and power supply.

This setup is necessary for the rest of ML-R system. If You want to try just this example, don't copy all the libraries but only directories "mrm-common" and "mrm-can-bus". They provide CAN Bus support, but again just for ESP32 microcontroller, ML-R or any other. If You want to use some other MCU, You will have to start its CAN Bus driver. If You do so, this example will work for that board, too.

Task

We will order the lidar to start measuring distance and sending the result every few milliseconds.

Connections

Starting from a basic connection, let's add a MRMS LIDAR 2m VL53L0X, CAN Bus (mrm-lid-can-b). Connecting a ML-R CAN Bus device is easy. Only a cable, like MRMS CAN Bus cable 10 cm (mrm-jst-can10), is necessary. The cable will connect 2 CAN Bus lines (low and high) and will also supply 0 V and 5 V through 3. and 4. wire.

However, this bus is not only intended for ML-R devices, but for any other CAN Bus compatible. You can read here how to connect Your boards to ML-R CAN Bus.

Program

The program is short, but it hides some of the details in Mrm_can_bus class:

#include <mrm-can-bus.h>

#define COMMAND_SENDING_COLORS_1_TO_3 0x06
#define COMMAND_SENDING_COLORS_4_TO_6 0x07
#define COMMAND_SENSORS_MEASURE_CONTINUOUS 0x10

Mrm_can_bus can;

uint8_t data[8];        // Message content: 8 bytes
uint16_t readings[6];

void setup() {
  Serial.begin(115200);

  data[0] = COMMAND_SENSORS_MEASURE_CONTINUOUS;  // First byte of the content
  for (uint8_t i = 0; i < 8; i++)
    can.messageSend(0x310 + 2 * i, 1, data);
}

void loop() {
  // Receive a message
  uint8_t startIndex;
  while (!dequeEmpty()) {
  CANBusMessage *msg = dequeBack();
  dequePopBack();
    switch (msg->data[0]) {
    case COMMAND_SENDING_COLORS_1_TO_3:
      readings[0] = (msg->data[1] << 8) | msg->data[2]; // blue
      readings[1] = (msg->data[3] << 8) | msg->data[4]; // green
      readings[2] = (msg->data[5] << 8) | msg->data[6]; // orange
      break;
    case COMMAND_SENDING_COLORS_4_TO_6:
      readings[3] = (msg->data[1] << 8) | msg->data[2]; // red
      readings[4] = (msg->data[3] << 8) | msg->data[4]; // violet
      readings[5] = (msg->data[5] << 8) | msg->data[6]; // yellow
      break;
    }
  }

  // Display results
  static uint32_t ms = 0;
  if (millis() - ms > 300){
      printf("Blu:%3i Grn:%3i Ora:%3i Red:%3i Vio:%3i Ylw:%3i\n\r", readings[0], readings[1], readings[2], readings[3], readings[4], readings[5]);
    ms = millis();
  }
}
The program uses Mrm_can_bus class to access ESP32s CAN Bus hardware. First, library mrm-can-bus is included, which implements CAN Bus layer for ESP32. A few #DEFINE commands follow to enable us to write human readable names, like COMMAND_SENSORS_MEASURE_CONTINUOUS, instead of magic numbers, like 0x10. Next we define an object "can". As mentioned in various lessons, object-oriented programming is beyond scope of these lessons and it is not too important here. At last we declare 2 arrays as global variables: one for CAN Bus payloads, the other will hold measurement results.

setup() starts serial communication with PC so that Serial.print() can display the results. First we will use "data" array as payload for an outgoing message and so we set its first byte to COMMAND_SENSORS_MEASURE_CONTINUOUS. That's the command we'd like to execute. It will instruct the sensor to start sending measurement data continually. As You can have more than one color sensor of this kind in Your CAN Bus, each is supposed to listen to just one message id. The whole range is covered by the loop that follows: 0x310, 0x312,..., 0x31E. You can change address the sensor listens to but You may not know how the one You have is set up. So, we start them all.

loop() receives messages. You should be familiar with CAN Bus message structure to understand data sent and received. When a message is received, the program will check first byte of the payload to see if it is a measurement result. If it is, it will decode the results. A CAN Bus message can hold up to 8 bytes of data. That is not enough to transfer 6 analog readings, consisting of 2 bytes each. That is the reason the results will arrive split in 2 messages. switch() command will determine which of the 2 messages arrived and will reconstruct the readings it conveys in the for-loop that follows. In the end, another for-loop will display the results.

Note that the program is intended to be simple so will work only when a single ML-R 6-channel color sensor CAN Bus (mrm-col-can) sensor is connected. If there are more of them, You will have to determine which one sent the results, according to message id, and reconstruct its reading array, which will have to a 2-dimensional array, instead of 1-dimensional, like here.

You can use an oscilloscope or a logic analyzer to check the signals.

The program uses mrm-can-bus library which can be found in C:\Users\<Your login name>\Documents\Arduino\libraries\mrm-can-bus\src. Open mrm-can-bus.cpp in an editor. Notepad++ is a good choice. If You want to change bus speed from the default 250 kbps, find line with "CAN_TIMING_CONFIG_250KBITS" and change the constant. If You like to have messages' content displayed, change "#define VERBOSE 0" into "#define VERBOSE 1". This library is based on Espressif's native driver so Espressif original documentation will give You all the information.

If You use ML-R CAN Bus devices, by far the easiest option for CAN Bus usage will be to stick to the ML-R framework which takes care about messaging so CAN Bus layer is not exposed at all. When You demand ML-R 6-channel color sensor CAN Bus (mrm-col-can) to return measured colors, You just call a function. The framework takes care that the reasonably fresh result is supplied. The details of this work are not so easy to replicate.

Here is the complete CAN Bus interface:
CommandMCU -> sensorSensor -> MCUComment
Firmware0x190x1A LowByte HighByteFirmware version = (HighByte << 8) & LowByte.
FPS0x300x31 LowByte HighByteFrames Per Second (sensor's local loop frequency) = (HighByte << 8) & LowByte.
Id change0x40 newIdChanges message's id the sensor listens to. It is close to its address. Valid "newId" values are 0 - 7. Each value chooses address in range 0x310, 0x312,..., 0x31E. 0 chooses 0x310, 1 0x312, etc.
Alive0xFF id0xFFIf the sensor with address "id" is present, it will return the message.
Reset0x1B
Switch to HSV0x51All the future messages will be in HSV format instead of 6 colors.
Switch to 6 colors0x52All the future messages will be in 6-colors format instead of HSV.
Measure once0x116 colors mode: 0x06 Byte1 ... Byte6 or 0x07 Byte1 ... Byte7. HSV mode: 0x06 Ignore, ByteH, Ignore, ByteS, Ignore, ByteV, PatternMeasures just once and returns the results. Results can arrive in 2 messages, with first byte 0x06 or 0x07. Check the program above. Additionally, there is Byte7 when sending colors 4 to 6. First 4 bits represent recognized pattern by 6 colors and the second 4 bits by HSV (hue, saturation, value). Ignore "Ignore" bytes. Read about patterns below.
Measure continuous0x106 colors mode: 0x06 Byte1 ... Byte6 or 0x07 Byte1 ... Byte7. HSV mode: 0x06 Ignore, ByteH, Ignore, ByteS, Ignore, ByteV, PatternInitiates measuring and returning of the measured colors each few ms, if any value changes. See the format a line above.
Stop measuring0x12
Illumination0x50 ByteIllumination setting. 0 - turn LED off. 1 - 3 LED's intensity.
Gain0x55 ByteSensor's amplifier gain. Values for Byte: 0 - 1x (default), 1 - 3.7x, 2 - 16x, 3 - 64x.
Integration time0x54 ByteIntegration time will be Byte x 5.6 ms. Byte is between 0 and 255.
Record a pattern0x56 ByteRecord a pattern (color's 6 values) of the color currently in front of sensor. Byte is pattern number and can be between 0 and 15. When presented a color later, the sensor will in each message indicate which pattern is the closest match. Patterns will be retained during power-off.
Request patterns0x580x57 PatternNumber HByte SByte VByteSensor will respond by sending all the patterns, each in its message. HByte is hue, SByte is saturation, and VByte is value.
Erase patterns0x59Erase all patterns.

CAN Bus limitations

For 1 node You will have no problems. If You plan to have many of them, check this page.