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 send CAN Bus messages to the controller in order to control motor's speed and direction. At the same time we will be using Hall sensors as encoders to read motors' rotational progress.
Pinout
Note positions of the necessary pins/connectors:
Connections
Starting from a basic connection, let's add a
ML-R BLDC Motor Driver 2x50A reverse protection (mrm-bldc2x50). 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. The voltage and current supplied by the CAN Bus cable will not be sufficient to drive the motors so a separate cable, like
ML-R Cable KK396-KK396 10 cm (mrm-kk3.96-3.96-10) will be needed and another one for low voltage electronics, like
ML-R Cable KK254-KK254 10 cm (mrm-kk2.54-2.54-10).
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. For this board, You can supply 5 V to the motor controller by using 5 V (labeled red in the picture left) and GND pins of the 4-pin Dupont 0.1" connector. CAN Bus can be connected to the inner 2 pins of the same connector, LO and HI (CAN Bus low and high).
Program
The program is simple, but it hides some of the details in Mrm_can_bus class:
#include <mrm-can-bus.h>
#define ID_MOTOR1 0x110
#define ID_MOTOR2 0x112
#define COMMAND_SENSORS_MEASURE_CONTINUOUS 0x10
#define COMMAND_SENSORS_MEASURE_SENDING 0x13
#define COMMAND_SPEED_SET 0x20
uint32_t motorId[2] = {ID_MOTOR1, ID_MOTOR2};
Mrm_can_bus can;
uint8_t data[8]; // Message content: 8 bytes
void setSpeed(uint8_t motorNumber, int8_t speed) {
data[0] = COMMAND_SPEED_SET;
data[1] = speed + 128;
can.messageSend(motorId[motorNumber], 2, data);
}
void setup() {
Serial.begin(115200);
data[0] = COMMAND_SENSORS_MEASURE_CONTINUOUS;
for (uint16_t motor = 0; motor < 2; motor++)
can.messageSend(motorId[motor], 1, data);
}
void loop() {
for (uint16_t motor = 0; motor < 2; motor++){
int8_t step = 1;
for (int8_t speed = 1; !(speed == 0 && step == 1); speed += step){
setSpeed(motor, speed);
uint32_t ms = millis();
while (millis() - ms < 30){
// Receive a message
CANBusMessage* msg = can.messageReceive();
if (msg != NULL && msg->messageId == motorId[motor] + 1 && msg->data[0] == COMMAND_SENSORS_MEASURE_SENDING){
uint32_t enc = (msg->data[4] << 24) | (msg->data[3] << 16) | (msg->data[2] << 8) | msg->data[1];
Serial.println(enc);
}
}
if (abs(speed) == 127)
step = -step;
}
}
}
The code uses Mrm_can_bus class to access ESP32s CAN Bus hardware. First line includes mrm-can-bus library. It handles CAN Bus messages. 2 #define commands follow. They enable us, for example, to write word "ID_MOTOR1" later in the code (and it seems meaningful), instead of "0x110" (a magic number which doesn't). In this way we increase clarity of the code. So, these 2 are CAN Bus message identification bytes, 1 for each of the 2 motor controllers in the ML-R BLDC Motor Driver 2x50A reverse protection (mrm-bldc2x50). Each of them listens only to its message id (like an address). You can use 4 controllers mrm-bldc2x50 in Your system, each with its own pairs of addresses. The first possible pair is in the program: 0x110 and 0x112. The are other possibilities: (0x114, 0x116), (0x118, 0x11A), and (0x11B, 0x11E). If You changed Your controller's addresses, this example would not work. In that case use the selected pair (#define ID_MOTOR1 0x114, etc).
The same can be said for COMMAND_SPEED_SET 0x20. After this line, we can write "COMMAND_SPEED_SET" and the compiler will swap this term with 0x20, command to set speed.
motorId is an array which is not necessary, but will make the code verbatim as we can iterate an array. We want to do the same action for every motor, an ideal opportunity for iteration. After that we define "can" as an object of class Mrm_can_bus. Explaining objects is beyond scope of this short lesson and is not very important for this program. We can say that it is something that can encapsulate functions, like the one we need: sending a CAN Bus message. The last global variable is data[8], an array of bytes, which will be used for CAN Bus message's payload.
setSpeed() is a function to drive motors. First argument selects a motor (0 - 1), the second its speed (-127 to 127). A negative value spins the motor in the other direction of the positive one. The function sets first byte as the command we want to execute, setting speed. The second byte chooses the speed, translating the input into a positive number. It finally sends the CAN Bus message, using the object "can".
loop() contains 4 nested loops:
- The outer one iterates the 2 motors.
- Next inner loop changes variable speed in order to gradually change motor's speeds; from standstill to maximum rotation, then gradually to maximum opposite rotation, and in the end towards stop. Command setSpeed() actually sets the chosen speed.
- Before the next inner loop we record number of milliseconds (variable ms) and then the loop follows. It continues running as long the difference between the current moment and the one before the loop started is less than 30 ms. Therefore, it is similar to delay(30). Why not using the simpler solution, delay(30)? Because we have to listen to incoming CAN Bus messages. During 30 ms we may lose many end can even end with input buffer overflow.
- The innermost loop receives CAN Bus messages. We store the received messages in CANBusMessage structure and read it in the instructions that follow.
First we check if the motor, that we are currently controlling, sent the message (its message id will be 1 added to its address) and if the message carries encoder data (COMMAND_SENSORS_MEASURE_SENDING). If everything's fine, we will assemble encoder value, enc, from 4 bytes in payload and print it.
You can use an oscilloscope or a logic analyzer to check the signals.
How is it possible to have encoder values from a motor that doesn't have an encoder? This controller uses Hall sensors in motors for commutation. They report rotor's position. So it uses that data as encoder values.
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 MRMS CAN Bus devices, the easiest option for CAN Bus usage will be to stick to the MRMS framework which takes care about messaging so CAN Bus layer is not exposed at all. You can call a simple function to set the speed of the motor controller and the function is the same for all of the MRMS motor controllers.
Here is the complete CAN Bus interface:
Command | MCU -> controller | Controller -> MCU | Comment |
---|
Firmware | 0x19 | 0x1A LowByte HighByte | Firmware version = (HighByte << 8) & LowByte. |
FPS | 0x30 | 0x31 LowByte HighByte | Frames Per Second (sensor's local loop frequency) = (HighByte << 8) & LowByte. |
Id change | 0x40 NewId | | Changes ids the motor controller listens to. It is close to its address. NewId can have value 0, 1, 2, or 3. 0 sets the controllers addresses to (0x110, 0x114), 1 (0x114, 0x116), 2 (0x118, 0x11A), and 3 (0x11B, 0x11E). |
Alive | 0xFF id | 0xFF | If the controller with address "id" is present, it will return the message. |
Reset | 0x1B | | |
Set speed | 0x20 Speed | | Sets speed of the selected motor to be "Speed" (value of that byte). |
Start sending encoder data. | 0x10 | 0x13 LowestByte, LowByte, HighByte, HighestByte | A command to the controller to start sending Hall sensor's count. Check the example above to see how the resulting 4 bytes in the return message can form 32-bit integer, encoder value. |
CAN Bus limitations
For 1 node You will have no problems. If You plan to have many of them, check this page.