0

Lidar CAN example for MRMS ESP32: Arduino, IMU, eFuse, BT, WiFi, CAN Bus (mrm-esp32)

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.

Pins

×
5 pins on the left are used for firmware update.

The other 2 are CAN Bus connectors. You can use any of them. You can read more about this topic here.

You can also daisy-chain devices on CAN Bus. Connect neighbouring devices using both connectors.

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.

Again, to learn more about CAN Bus setup, check this page.

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.

CAN Message

×
In order to learn more about CAN Bus protocol, check Wikipedia. In a nutshell, You can identify the following parts of the message in the code below:
  • arbitration - the part we use as an "address", like 0x180,
  • control - specifically DLC part (data length count), length of the payload in bytes,
  • data - we have an array with the same name - payload.
"CAN H" and "CAN L" are electrical signals on the bus. You can check them with an oscilloscope.

"CAN RX" is the signal between MCU and transceiver and these are the logical levels MCU's CAN Bus pins send and receive. Typically You cannot see them on the oscilloscope as there are no external pins to hook the scope, but a scope with CAN Bus support can produce this wave and can also decode and print CAN Bus messages the wave represents.

"CAN RX" also shows stuff bits. You can learn more about them in Wikipedia but You can also safely ignore them as they do not appear in software implementation.

Program

#include <mrm-can-bus.h>			// Driver's library

#define COMMAND_SENSORS_MEASURE_CONTINUOUS 0x10	// It is better to use named constants than values. 0x10 instructs the lidar to start measuring constantly
#define COMMAND_SENSORS_MEASURE_SENDING 0x13	// 0x13 denotes CAN Message that carries a measurement.

Mrm_can_bus can;				// "can" is an C++ object that implements library's functionality.

uint8_t data[8];				// Message content: up to 8 bytes

void setup() {					// setup() is executed only once, before loop().
	Serial.begin(115200);

	data[0] = COMMAND_SENSORS_MEASURE_CONTINUOUS;	// First byte of the content. It is always a command. All commands are single-byte. The rest of up to 7 bytes is payload.

	for (uint8_t i = 0; i < 8; i++)		// These 2 loops will send 16 CAN messages to addresses 0x180, 0x182, etc. So, all the sensors will get the command to start measuring distances.
		can.messageSend(0x180 + 2 * i, 1, data);
	for (uint8_t i = 0; i < 8; i++)
		can.messageSend(0x280 + 2 * i, 1, data);
}

void loop() {
	CANBusMessage* msg = can.messageReceive();				// Receive a message. Functionality implemented in the library.
	if (msg != NULL && msg->data[0] == COMMAND_SENSORS_MEASURE_SENDING){	// If message is NULL, nothing is received. If something, check command. Only if it is 0x13, read the content;
		uint16_t mm = (msg->data[2] << 8) | msg->data[1];		// Reconstruct integer from 2 bytes.
		Serial.print(mm);						// Display result.
		Serial.println(" mm");
	}
}

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

It uses Mrm_can_bus class to access ESP32s CAN Bus hardware. setup() starts serial communication with PC so that Serial.print() can display the results. Array data is payload CAN Bus messages use. First we will use it 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 many lidars of this kind in Your CAN Bus, each is supposed to listen to just one message id. The whole range is covered by the 2 loops that follow: 0x180, 0x182,..., 0x18E, and the next one 0x280,..., 0x28E. 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 convert bytes 2 and 3 into millimeters and will print the result.

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.

Is this code production-grade? It is not. Using this kind of program will result in a very poor robot's performance. The same problem exists for any sensor on any interface that waits for the results of measurement. The problem is the loop which checks for the results. Waiting for many sensors, a usual robot's feature, will spend too much time. To correct that, You should either use ML-R framework, or build Your own asynchronous one.

ESP32 library

Mrm_can_bus::Mrm_can_bus(Robot* robot) {
	...
	can_general_config_t general_config = {
	   .mode = CAN_MODE_NORMAL,
	   .tx_io = (gpio_num_t)GPIO_NUM_5,
	   .rx_io = (gpio_num_t)GPIO_NUM_4,
	   .clkout_io = (gpio_num_t)CAN_IO_UNUSED,
	   .bus_off_io = (gpio_num_t)CAN_IO_UNUSED,
	   .tx_queue_len = 20,
	   .rx_queue_len = 65,
	   .alerts_enabled = CAN_ALERT_NONE,
	   .clkout_divider = 0 };
	can_timing_config_t timing_config = CAN_TIMING_CONFIG_250KBITS();
	can_filter_config_t filter_config = CAN_FILTER_CONFIG_ACCEPT_ALL();

	if (can_driver_install(&general_config, &timing_config, &filter_config) != ESP_OK)
		strcpy(errorMessage, "Error init. CAN");
	...
}
If You want even lower level of access to the sensor, you can use ESP32 library directly.

ML-R library is built on top of ESP32 library. As mentioned, it can be found in C:\Users\<Your login name>\Documents\Arduino\libraries\mrm-can-bus\src . If You want to use it, check this file.

if You are satisfied with ML-R library, You can skip this part.

Initialization is performed in Mrm_can_bus constructor.
void Mrm_can_bus::messageSend(uint32_t stdId, uint8_t dlc, uint8_t data[8]) {
	can_message_t message;
	message.identifier = stdId;
	message.flags = 0;
	message.data_length_code = dlc;
	for (int i = 0; i < dlc; i++) {
		message.data[i] = data[i];
	}
	...

	//Queue message for transmission
	if (can_transmit(&message, pdMS_TO_TICKS(1000)) != ESP_OK)
		strcpy(errorMessage, "Error sending");

	...
}
Here is the function that sends a message.

You can check the source code in ML-R library and check ESP32 documentation to see all the options.

CAN Bus commands

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 MRMS LIDAR 2m VL53L0X, CAN Bus (mrm-lid-can-b) to return a measured distance, 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
Calibration0x05If a temperature varies greatly, a calibration will improve measuring. Calibration data are saved in sensor's flash.
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 - 14. Each value chooses addresses in one of 2 ranges: 0x180, 0x182,..., 0x18E and 0x280,..., 0x28E. 0 chooses 0x180, 1 0x182, etc.
Alive0xFF id0xFFIf the sensor with address "id" is present, it will return the message.
Reset0x1B
Measure once0x110x13 LowByte HighByteMeasures just once and returns the result. Result in mm = (HighByte << 8) & LowByte
Measure continuous0x100x13 LowByte HighByteInitiates measuring and returning of the measured temperature each few ms, if the distance changes. Result in mm = (HighByte << 8) & LowByte.
Stop measuring0x12
Ranging type0x42 type"type" is 0 for long range, 1 for high speed, and 2 for high accuracy. Data are retained in flesh memory during power loss.

Program

#include <mrm-can-bus.h>

#define STOP_MEASURING 0x12

void setup() {	
	uint8_t data[8] = {STOP_MEASURING};
	Mrm_can_bus can;
	can.messageSend(0x180, 1, data);
}

void loop() {
}
Here is implementation of one of the commands above.

You can execute any of them in a similar manner.

CAN Bus limitations

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