Controlling a Nema 23 closed loop stepper motor with Arduino Nano (2023)

Code walkthrough for Arduino C++ code loaded into the famous Arduino Nano board to control a closed loop Nema 23 hybrid stepper motor.

What is covered

  • Importing libraries, assigning values ​​to constants and variables
  • setup() function
  • loop() function
  • auxiliary functions



Importing libraries, assigning values ​​to constants and variables

We need theCableLibrary to be able to receive new settings via I2C from the ESP32 master board, setting up the Arduino Nano as a slave. The fantastic and very well knownAccelStepperLibrary is of course used to control our stepper motor.

Don't forget that you can download the Fritzing .fzz file fromhereto have a look at the breadboard and schematic views. It would help you to visualize the project better.

Between lines 6 and 9 we assign the pin values.home furnishings,isHoming,isStopare control variables to detect the current operation in which the stepper motor is involved.stepperParamshave a magnitude of 3 (steps to move, steps per second to inhale, steps per second to exhale).maxBVMVolumeis used to determine the relationship between the received volume setting and this maximum possible volume.

(Video) Easy way !! Arduino closed loop stepper motor control

This maximum volume can be pushed at half a turn by the crankshaft connected to the stepper motor axis. That's why we need to know them toosteps per revolution.maximum speedis for safety anddefaultSpeedis for movements where steps per second are not specified.the settingsandprevioussettingsare used to track the data sent by the master ESP32 card.istClockWiseregisters the direction of rotation and is also a controlled variablenewStepperParams.

#contain <Wire.h>#contain <AccelStepper.h>AccelStepper Stepper(1,3,2);art intRelayPin =4;art inttrackerPin =9;art intI2CSlaveAdresse =4;boolhomeSet =NOT CORRECT;boolisHoming =true;boolisStopping =NOT CORRECT;art intstepperParamsSize =3;int stepperParams[stepperParamsSize] = {0,0,0};art intmaxBVMVolume =1600;art intsteps per revolution =500;art intmaximum speed =3000;art intdefaultSpeed ​​=1000;art intSettingsSize=4;int the settings[settingsSize] = {0,0,0,0};int previoussettings[settingsSize] = {0,0,0,0};boolisClockWise =true;boolnewStepperParams =NOT CORRECT;

setup() function

On lines 2-3 we set the pin mode for the relay and for the line tracer used to get home in the stepper motor. In lines 7-8 we initiate the Wire I2C communication and assign an event listener for thereceiveEvent. In line 13 we open the relay, in lines 16-17 we set the maximum and default speed for theStepperinstance ofAccelStepperClass.

Empty to install() { pinMode(RelayPin, OUTPUT); pinMode(trackerPin, INPUT);  digitalWrite(RelaisPin, LOW); Cable.Start(I2CSlaveAdresse); Cable.onReceive(Receive Event);  Serial.Start(115200); delay(3000); digitalWrite(RelayPin, HIGH); delay(4000); Stepper.setMaxSpeed(maximum speed); Stepper.set speed(default speed);  Serial.println(""); Serial.println("beginning");}

loop() function

Please note that we do not have any stepper induced blocking events. We callstepper.runSpeedToPosition()on each loop cycle and when a step is due, it is executed. First we set the "Home" position. Then we check ifisStopand we call the helper functionStopStepperagain and again until this controlled variable isNOT CORRECT.

(Video) Grizzly G0704 CNC Conversion - Exploring Closed Loop Stepper Motors - Video #9

We only proceed if there is no homing or stopping procedure in progress. We then check if new settings are received (line 11). In line 14 we check if the new settings are associated with a stop command (when all 4 values ​​are set to 0). If we're good to go, wecomputeNewParams()(total steps to move, steps per second for inspiration and expiration) using the newly received settings.

Of course, there are cases where there is nothing to do, and we need to check lines 31-33. ifnewStepperParamsWe need to move the motor back to the home position before we start the movement with the new settings (line 35-49). Then and only then do we call themmoveStepper()Function.

Empty Ribbon() { if(homeSet ==NOT CORRECT) { run homing();} if(is stopped ==true) { StopStepper();} if(isHoming ==NOT CORRECT&& is stopped ==NOT CORRECT) { if(hasNewValues() ==true) { Serial.println("hasNewValues");  if(isItStopping() ==true) {isStopping =true;} if(is stopped ==NOT CORRECT) { if(previoussettings[0] !=0) {newStepperParams =true;} computeNewParams();} Pro(inti =0; i < settingssize; i++) { previoussettings[I] =the settings[I];}} if(Nothing to do() ==true) { return;} if(newStepperParams ==true) { Serial.println("move newStepperParams to zero"); Stepper.move to(0); Stepper.set speed(default speed); Stepper.runSpeedToPosition(); printPositionalData(); if(Stepper.DistanceToGo() !=0) { return;}anders{ Serial.println("Stepper is back to zero");newStepperParams =NOT CORRECT;isClockWise =true;}}  moveStepper();}}

auxiliary functions

  • moveStepperis the most important thing: we set the speed and move the stepper to position, clockwise and counterclockwise.

If the previous checks allow it, this will be called in a continuous back and forth motion every loop cycle.

(Video) Stepper Motors and Arduino - The Ultimate Guide

Empty moveStepper() { if(isClockWise ==true) { Stepper.move to(stepperParams[0]); Stepper.set speed(stepperParams[1]); printPositionalData(); if(Stepper.DistanceToGo() ==0) {isClockWise =NOT CORRECT;}}anders{ Stepper.move to(0); Stepper.set speed(stepperParams[2]);  printPositionalData(); if(Stepper.DistanceToGo() ==0) {isClockWise =true;}} Stepper.runSpeedToPosition();}
  • StopStepperis responsible for stopping all previous commands and bringing the motor to the “zero” position. When its job is done, it sets theisStopcontrolled variable tooNOT CORRECTto let other parts of the code do their work.
Empty StopStepper() { Serial.println("Move StopStepper to zero"); Stepper.move to(0); Stepper.set speed(default speed); Stepper.runSpeedToPosition(); printPositionalData(); if(Stepper.DistanceToGo() !=0) { return;}anders{ Serial.println("Stepper is back to zero");isStopping =NOT CORRECT;}}
  • run homingsets the "zero" position with the help of the push button sensor. Again, it's the attitudeisHomingcontrolled variable tooNOT CORRECTto signal other parts of the code.
Empty run homing() { art intnotAtHome =digital reading(trackerPIN); if(notAtHome ==NOT CORRECT) { Serial.println("Homing successful"); Stepper.SetCurrentPosition(0);// Now the current motor speed is zerohomeSet =true;isHoming =NOT CORRECT; return;} Stepper.running speed();}
  • computeNewParamsjust looks complicated. The relationship between the volume setting and the maximum volume is determined. With this he determines how much of the half turn, measured in steps, is allowed to move. Then, based on the inspiration/expiration ratio and the number of breaths per minute, the steps per second for inspiration and expiration are determined.
Empty computeNewParams() { Serial.println("computeNewParams"); Serial.println("settings[0]: "+line(the settings[0])); Serial.println("Settings[1]: "+line(the settings[1])); Serial.println("Settings[2]: "+line(the settings[2])); Serial.println("Settings[3]: "+line(the settings[3])); // Calculate steps to move// TODO measures precise volumes to determine them// the correlation between piston amplitude and volume of air pushed// The current formula assumes a linear relationship stepperParams[0] =ceiling((hover(StepsPerRev) /2) * (hover(the settings[0]) /hover(maxBVMVolume))); art hoverseconds per fraction =60/ ((hover(the settings[2]) +hover(the settings[3])) *hover(the settings[1])); // calculate the steps per second for inspiration stepperParams[1] =ceiling(hover(stepperParams[0]) / (seconds per fraction *hover(the settings[2])));// Calculation of steps per second for expiration stepperParams[2] =ceiling(hover(stepperParams[0]) / (seconds per fraction *hover(the settings[3]))); Serial.println("stepperParams[0]: "+line(stepperParams[0])); Serial.println("stepperParams[1]: "+line(stepperParams[1])); Serial.println("stepperParams[2]: "+line(stepperParams[2]));}
  • receive eventis the handler for the I2C receive event. It needs to check how many bytes were sent in the received stream. Then it parses that array buffer character by character until it finds axvalue separator. Each of the 4 values ​​is stored in thethe settingsRow.
Empty receive event(int how many) { Serial.println("I2C receive event"); char t[30]; inti =0;  during(Cable.available()) { t[I];i = i +1;}  intj =0; if(checkForData(t, howMany, settingsSize) ==NOT CORRECT)return;string =""; Pro(inti =0; I < how many; i++) {Stringstrom =line(t[I]);// look for x as a value separator if(currently !="x") {set = set + current;}anders{ the settings[j] Int();put ="";j++;} if(i == how many -1) { the settings[j] Int();}}}


1. TB6600 and Arduino - Wiring and demonstration
(Curious Scientist)
2. NEMA 23 | NEMA 17 | NEMA 34 with TB6600 stepper motor driver and Arduino - Wiring and demonstration
3. Stepper Motors with Arduino - Controlling Bipolar & Unipolar stepper motors
(DroneBot Workshop)
4. TB6600 Stepper Motor Driver with Arduino
5. TB6600 Stepper Driver + Arduino + NEMA Motor Wiring and Control (Uno Mega Nano)
(Henry Cawley)
6. How to wire a STEPPER motor and CONTROLLER
(Machining with Joe)


Top Articles
Latest Posts
Article information

Author: Rubie Ullrich

Last Updated: 01/10/2023

Views: 5938

Rating: 4.1 / 5 (52 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Rubie Ullrich

Birthday: 1998-02-02

Address: 743 Stoltenberg Center, Genovevaville, NJ 59925-3119

Phone: +2202978377583

Job: Administration Engineer

Hobby: Surfing, Sailing, Listening to music, Web surfing, Kitesurfing, Geocaching, Backpacking

Introduction: My name is Rubie Ullrich, I am a enthusiastic, perfect, tender, vivacious, talented, famous, delightful person who loves writing and wants to share my knowledge and understanding with you.