CANopen Brushless Motor Position Profile Control with Arduino and SOLO
Introduction
If you are making an industrial Motion system that is going to be used in harsh environments, under noisy conditions with tight safety requirements most probably your first choice of Main Controlling Unit (MCU), especially for a product will not be a bare Arduino as they are infamous for lacking the quality and sophistication to be used in real-time systems where every micro-seconds matters!
Don’t get me wrong, Arduino is probably the best thing that happened so far for hardware education, and their role to make the use of challenging microcontrollers easy is not going to be forgotten any time soon, but believe it or not, it still among industries that the reliability is one of the most important factors, they are facing difficulties, mostly due to the fact that they are not initially built for such purposes and their use-cases were majorly targeted for Labs or educational environments.
However this paradigm is changing, especially for Motor controlling solutions that SOLO is offering in conjunction with Arduino devices, we were aware of the fact that many people and industries will want to use sweat Arduino even in harsh conditions and they want it to operate extremely well (we all love magics ;) ), so that’s why we adapted our CANopen solution with Arduino devices, to provide a robust and industry-standard protocol for all those who were waiting to finally make a reliable Motion controlling solution with Arduino.
As a side note, we believe SOLO is the first or let’s say one of the early adapters of CANopen solutions for Arduino devices at this scale, if you doubt it have a look on our Git library developed for Arduino here!
What is CANopen

You might have already heard about CAN protocol, but you might have been wondering what is this CANopen that people are talking about, to answer it simply, one can say, CANopen is the software layer built on top of CAN communication protocol that is acting like a soul to a body, in another word, CAN protocol operates on two differential signal lines known as CAN High and CAN Low, and initially developed for Automotive solutions to provide them more robust and reliable solutions compared to already existing protocols at the time like RS232 or RS485, etc, but like any other hardware communication protocol, the whole job of the CAN bus is to deliver a bunch of bytes from one side to the other side safely, but then what happens after that?
And here comes the role of CANopen, as we know ourselves as engineers, we love to be creative and make our own rules, but imagine a network of devices that each are having its own creative data packeting and formatting! Then what happens if you want to put all these devices together to work in a network? The answer is simple, a huge unmanageable mess… and here comes the beauty of something like CANopen, that puts standards for people across various industries on how to deal with CAN messages, and how to format and communicate with other devices in a harmonious manner, so they can all work together without interfering with each other.
So all seems beautiful till you start going from CAN to CANopen and then you’ll see it requires a huge amount of work to make a CAN-enabled system compatible with CANopen standards, maybe this is the biggest drawback of something like CANopen that many simply won’t have the time to adapt to it…
Story short, we actually were not one of those, and we tried to adapt our products fully with CANopen standards introduced by CIA organization especially for Motor Drives and Servo solutions.
How to commit an Arduino with SOLO operating on a CANbus
The answer to this question lies in the very first fact that Arduino devices ( at least till recently) are not offering any native on-chip CAN capabilities, but this will not stop you from making the CAN protocol work with them, but the question is How?
Simply using SPI to CAN converter chips made by Microchip Technology including MCP2515 and MCP2551 used in various Arduino shields like CAN-BUS Shield V2.0 or Arduino MKR CAN Shield.

CAN-BUS Shield V2.0 for interfacing Arduino to CANbus
After using one of these chips or shields properly, by just connecting the CANH and CANL to CANH and CANL of SOLO and making sure the GND of both devices are shared at least in a single point, you’d be able to fully SET or READ any command in the existing dictionary of SOLO Motor Controllers devices following our CANopen user manual.
Example
The example below demonstrates how you can robustly communicate with a SOLO MINI unit through CANopen and programming your Arduino to command SOLO for having some cyclic position profiles on a Brushless motor equipped with Incremental Encoders with very high performance in Field-Oriented control mode.
Wiring and System Setup
After using one of these chips or shields properly, by just connecting the CANH and CANL to CANH and CANL of SOLO and making sure the GND of both devices are shared at least in a single point, you’d be able to fully SET or READ any command in the existing dictionary of SOLO Motor Controllers devices following our CANopen user manual.
SOLO MINI to Arduino CAN shield V2 for operating in CANopen mode

SOLO UNO to Arduino CAN shield V2 for operating in CANopen mode

Notes before diving into the code:
1. Prior to running this example on your SOLO unit, you need to make sure your Encoder and Motor are fully calibrated with SOLO, to do that you need to follow our tutorial here at least one time till you find the best combination for Motor and encoder’s wirings, as long as your Encoder and Motor remains the same this calibration will hold, and there will be no need to repeat it every single time you turn on your unit.
2. Make sure your SOLO unit is in Closed-loop mode, this can happen by pressing pin# 5 on SOLO UNO’s piano switch down or on SOLO MINI keeping the mode switch at the “CL” position.
Code
// Copyright: (c) 2021, SOLO motor controllers project
// GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
/*
Title: Position Control of a BLDC-PMSM Motor with Arduino and SOLO - Position Profile
Author: SOLOMOTORCONTROLLERS
Date: 2022
Code version: 4.1.0
Availability: https://github.com/Solo-FL/SOLO-motor-controllers-ARDUINO-library
Please make sure you are applying the right wiring between SOLO and your ARDUINO
The Code below has been tested on Arduino UNO
The Motor used for Testings: teknic m-2310P-LN-04K
Read more about this code here:https://www.solomotorcontrollers.com/position-control-brushless-arduino-and-solo/
*/
#include "SOLOMotorControllersCanopen.h"
#define PI 3.1415926535897932384626433832795
#define sgn(x) ((x) < 0 ? -1 : ((x) > 0 ? 1 : 0))
//For this Test, make sure you have calibrated your Encoder before
//to know more please read: https://www.solomotorcontrollers.com/how-to-connect-calibrate-incremental-encoder-with-solo/
//instanciate a SOLO object:
SOLOMotorControllers *SOLO_Obj1;
//the device address of SOLO:
unsigned char SOLO_address1 = 0;
//Desired Switching or PWM Frequency at Output
long pwmFrequency = 20;
//Motor's Number of Poles
long numberOfPoles = 8;
//Motor's Number of Encoder Lines (PPR pre-quad)
long numberOfEncoderLines = 1000;
//Speed controller Kp
float speedControllerKp = 0.08;
//Speed controller Ki
float speedControllerKi = 0.008;
//Position controller Kp
float positionControllerKp = 0.25;
//Position controller Ki
float positionControllerKi = 0.02;
// Current Limit of the Motor
float currentLimit = 10.0;
// Battery or Bus Voltage
float busVoltage = 0;
// Desired Speed Limit[RPM]
long desiredSpeedLimit = 1000;
// Desired Position Reference
long desiredPositionReference = 0;
// Motor speed feedback
long actualMotorSpeed = 0;
// Motor position feedback
long actualMotorPosition = 0;
void setup() {
//In this example, make sure you put SOLO into Closed-Loop by
// pressing the Piano Switch NO# 5 DOWN. in SOLO UNO
//used for Monitoring in Arduino Only
Serial.begin(115200);
//Initialize the SOLO object
int SOLOdeviceAddress = 0;
int chipSelectPin = 9; //SPI CS pin for CANshield
SOLO_Obj1 = new SOLOMotorControllersCanopen(SOLOdeviceAddress, chipSelectPin);
delay(1000);
Serial.println("\n Trying to Connect To SOLO");
delay(1000);
//wait here till communication is established
while (SOLO_Obj1->CommunicationIsWorking() == false)
{
delay(500);
}
Serial.println("\n Communication Established succuessfully!");
// Initial Configurations
SOLO_Obj1->SetOutputPwmFrequencyKhz(pwmFrequency);
SOLO_Obj1->SetCurrentLimit(currentLimit);
SOLO_Obj1->SetMotorPolesCounts(numberOfPoles);
SOLO_Obj1->SetIncrementalEncoderLines(numberOfEncoderLines);
SOLO_Obj1->SetCommandMode(SOLOMotorControllers::CommandMode::digital);
SOLO_Obj1->SetMotorType(SOLOMotorControllers::MotorType::bldcPmsm);
//run the motor identification to Auto-tune the current controller gains Kp and Ki needed for Torque Loop
//run ID. always after selecting the Motor Type!
//ID. doesn't need to be called everytime, only one time after wiring up the Motor will be enough
//the ID. values will be remembered by SOLO after power recycling
SOLO_Obj1->MotorParametersIdentification(SOLOMotorControllers::Action::start);
Serial.println("\n Identifying the Motor");
//wait at least for 2sec till ID. is done
delay(2000);
//Operate while using Quadrature Incremental Encoders
SOLO_Obj1->SetFeedbackControlMode(SOLOMotorControllers::FeedbackControlMode::encoders);
SOLO_Obj1->SetControlMode(SOLOMotorControllers::ControlMode::positionMode);
//Speed Controller Tunings
SOLO_Obj1->SetSpeedControllerKp(speedControllerKp);
SOLO_Obj1->SetSpeedControllerKi(speedControllerKi);
//Position Controller Tunings
SOLO_Obj1->SetPositionControllerKp(positionControllerKp);
SOLO_Obj1->SetPositionControllerKi(positionControllerKi);
}
void loop() {
//Homming
//Speed Controller Tunings
SOLO_Obj1->SetSpeedControllerKp(speedControllerKp);
SOLO_Obj1->SetSpeedControllerKi(speedControllerKi);
desiredSpeedLimit = 2000;
SOLO_Obj1->SetSpeedLimit(desiredSpeedLimit);
SOLO_Obj1->SetPositionReference(0);
actualMotorPosition = SOLO_Obj1->GetPositionCountsFeedback();
Serial.println("\n Number of Pulses passed: ");
Serial.println(actualMotorPosition);
delay(2000);
//Sine profile
//set a desired Speed Limit for trajectory in RPM
desiredSpeedLimit = 6000;
SOLO_Obj1->SetSpeedLimit(desiredSpeedLimit);
for (int j = 0; j <= 3; j += 1) {
for (float i = 0; i <= 2 * PI; i += 0.01) {
SOLO_Obj1->SetPositionReference(+80000 * sin(i));
delay(1);
actualMotorPosition = SOLO_Obj1->GetPositionCountsFeedback();
Serial.println("\n Number of Pulses passed: ");
Serial.println(actualMotorPosition);
}
}
delay(1000);
desiredPositionReference = -1;
//set a desired Speed Limit for trajectory in RPM
desiredSpeedLimit = 2000;
SOLO_Obj1->SetSpeedLimit(desiredSpeedLimit);
//Speed Controller Tunings
SOLO_Obj1->SetSpeedControllerKp(0.03);
SOLO_Obj1->SetSpeedControllerKi(0.003);
//Step profile
for (int j = 0; j < 2; j += 1) {
desiredPositionReference = -1 * 80000 * sgn(desiredPositionReference);
SOLO_Obj1->SetPositionReference(desiredPositionReference);
delay(3000);
actualMotorPosition = SOLO_Obj1->GetPositionCountsFeedback();
Serial.println("\n Number of Pulses passed: ");
Serial.println(actualMotorPosition);
}
delay(1000);
}
Results
As you could probably see in the video above, we were able to send Motion profiles in Sinosoidal as well as Step type of shapes in cyclic mode, the most interesting part is under CANopen, you can reach up to 1ms of loop delay within arduino environment to communicate with SOLO, commanding to it and reading parameters in a robust manner.

Position profile sent to SOLO MINI to follow from CANopen lines
The speed feedback is also can be seen below, and as shown is having a very high resolution offering up to +/-1RPM in steady-state conditions, it worth mentioning that in this code, the Speed profile has not been controlled exclusively, and we just set a maximum speed ( speed limit) for the profile.

Speed and Position profile feedbacks
As some one who deals with servo-driving systems, you should always have an eye on torque on the motor too, even if the motion is great, but the torque controlling capabilities can make a lot of differences specially in energy consumptions as well as the system response under loaded conditions.

Torque and Position profile feedbacks
One of the biggest advantages of Motion systems operating under FOC is the fact that they make sure the whole motion is controlled by controlling Torque, Speed and Positions at the same time, resulting in having higher performances and safety levels, below you can see the three plots brought together:

Torque, Speed and Position profile feedbacks at the same time
Products in this article


Related articles

SOLO Arduino Library v5.5.0: Now Supporting Arduino H7
SOLO Arduino library v5.5.0 adds support for the Arduino H7 platform, improving compatibility so developers can integrate SOLO controllers with H7 boards.

SOLO Arduino Library – Hello World
Get started with the SOLO Arduino library: set up the Arduino IDE, wire SOLO over UART or CANopen, and run your first example code to command the controller.

Sensorless Speed Control of a Brushless Motor with Arduino and SOLO in Digital Mode Using UART Communication
Here how you can use SOLO’s ARDUINO Library to control the speed of a brushless motor using only UART communication in sensorless mode.
