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
Code-Repository
- Arduino Nano .ino Sketch file for BVM Ventilator Covid
- Fritzing .fzz file with breadboard and schematic views
Primer
AccelStepper librarycreated byMike McCauleyand serviced byPatrick Wespe(Waspinator-Github-Konto)
DroneBot Workshop YouTube Channel: Large Stepper Motors with Arduino
(Video) Nema23 Stepper Motor and TB6600 Arduino (Quick tutorial for beginners)If you prefer to write your Arduino code, let's sayVisual Studio-Code
Installing additional Arduino libraries
(Video) Big Stepper Motors with Arduino
Importing libraries, assigning values to constants and variables
We need theCable
Library 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 knownAccelStepper
Library 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
,isStop
are control variables to detect the current operation in which the stepper motor is involved.stepperParams
have a magnitude of 3 (steps to move, steps per second to inhale, steps per second to exhale).maxBVMVolume
is used to determine the relationship between the received volume setting and this maximum possible volume.
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 speed
is for safety anddefaultSpeed
is for movements where steps per second are not specified.the settings
andprevioussettings
are used to track the data sent by the master ESP32 card.istClockWise
registers 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 thereceive
Event. In line 13 we open the relay, in lines 16-17 we set the maximum and default speed for theStepper
instance ofAccelStepper
Class.
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 ifisStop
and we call the helper functionStopStepper
again and again until this controlled variable isNOT CORRECT
.
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. ifnewStepperParams
We 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
moveStepper
is 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();}
StopStepper
is responsible for stopping all previous commands and bringing the motor to the “zero” position. When its job is done, it sets theisStop
controlled variable tooNOT CORRECT
to 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 homing
sets the "zero" position with the help of the push button sensor. Again, it's the attitudeisHoming
controlled variable tooNOT CORRECT
to 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();}
computeNewParams
just 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 event
is 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 ax
value separator. Each of the 4 values is stored in thethe settings
Row.
Empty receive event(int how many) { Serial.println("I2C receive event"); char t[30]; inti =0; during(Cable.available()) { t[I] =Cable.read();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] =set.to Int();put ="";j++;} if(i == how many -1) { the settings[j] =set.to Int();}}}