This library is used to control one or more stepper motors with a ESP 32 module. The motors are accelerated and decelerated as they travel to their destination. The library has been optimized for flexible control where speeds and positions can be changed while in-motion. Based on S.Reifels FlexyStepper library.
The library provides the following features:
- generating pulses for a connected stepper driver with a direction and step input
- connection of emergency switch to stop all motion immediately
- connection of limit switches / homing switches
- blocking and non blocking function calls possible
- callback functions to handle events like position reached, homing complete etc.
- can run in different modes:
- as a service / task in the background (so you can do whatever you want in the main loop of your sketch without interfering with the stepper motion)
- manually call the processMovement() function in the main loop (then you have to make sure your main loop completes quick enough to ensure smooth movement
- use the blocking movement functions, that take care of calling processMovement but block the main loop for the duration of the movement
Depending on your custom code and user case where you are using the ESP FlexyStepper library as a part, you might experience some jitter (unstable/uneven step signal frequency, short breaks in the movement or in general a not always smoothly running stepper motor). The reason for this is to be found in the basic principle how the library is implemented currently and the fact that the Arduino sketch is running as a task within the FreeRTOS operating system of the ESP32 and along with some other hose keeping tasks in "parallel". When you start the library as a service using the "startAsService(int coreNumber)" function, it will start a separate task on one of the two cores of the ESP32. It most cases it will not be the only task running on this core (no matter which one you chose) so it will need to share the available CPU time with at least one other task running on the same core. Usually the "setup" and "loop" function of an Arduino Framework based program will run on core 1 of the ESP. To my knowledge core 0 usually runs the Wifi/Bluetooth/BLE stack task.
What does this mean for you and how is it related to jitter you might ask? Since multiple tasks that are running on the same core, also need to share the CPU cycles (for more on this topic of how tasks are managed in FreeRTOS, and the mysterious role of a 'Tick' this might be a good starting point: https://www.freertos.org/implementation/a00011.html) you can get into trouble and see some jitter especially with higher step signal frequencies (higher stepper speeds). The result will be a noisy, bumpy stepper movement since the ESP FlexyStepper Task is interrupted by the Operating System every now and then and thus might not be able to send the step signal in time to the IO Pin.
What can I do now? When starting the service using the startAsService() function you can provide a parameter to define the core number on which to pin the task to. The default value is 1.
If you have lot of code in the loop() function of you program, you can start the stepper service on Core 0 of the ESP32, this way it will not interfere with some heavy load you might be generating in the loop function BUT if you are also using the WiFi/Bluetooth/BLE stack of the ESP32, then core 0 might not be a good idea either, since the task that manages the before managed stack is usually running on core 0 of the ESP32 and with a high priority. So if you move the motor and at the same time have a lot of Wifi traffic or BT/BLE communication going on, the OS will frequently stop the execution of the ESP FlexyStepper task to yield the way for the Wireless stack. You will see more jitter the more wireless communication is going on. If you rely on wireless communication in your project and at the same time you have some a lot of going on in the loop function (e.g. long running loops and other function calls), you might need to change your software architecture in general. The goal is to keep the time needed to execute the code in the loop function as short as possible as long as the ESP FlexyStepper library is running as a service on Core 1. You can move in the direction of a interrupt or event based design pattern rather than having long running loops or while statements that just wait for a response or change of a pin state for example.
Decision matrix:
| Scenario | Suggestion which core to use |
|---|---|
| You are using Wifi / Bluetooth or BLE in your project | if you send/receive data while the motor is moving: start the service on core 1 if you only send / receive data while the motor is not moving: you might get away with starting the service on core 0, if you experience jitter, start it on core 1 |
| You are NOT using Wifi / Bluetooth or BLE in your project | start the service on core 0 |
| You have a lot of code in your loop() function that takes a lot of time for each loop execution | if you are not using wireless communication: start the service on core 0 if you are using wireless communication, go to line one of this decision matrix, if this does not help you will most likely need to optimize your loop() function execution time or move to a event/interrupt based design. You can also try to increase the task priority of the ESP Flexy Stepper task in ESP_FlexyStepper.cpp in the xTaskCreatePinnedToCore(...) function call |
For more details on the topic you can also have a look at the discussion in issue #4: pkerspe#4
The following is an example of how to use the library as a service running in the "background" as a separate Task on the ESP32:
// IO pin assignments
const int MOTOR_STEP_PIN = 33;
const int MOTOR_DIRECTION_PIN = 25;
const int EMERGENCY_STOP_PIN = 13; //define the IO pin the emergency stop switch is connected to
const int LIMIT_SWITCH_PIN = 32; //define the IO pin where the limit switches are connected to (switches in series in normally closed setup against ground)
// Speed settings
const int DISTANCE_TO_TRAVEL_IN_STEPS = 2000;
const int SPEED_IN_STEPS_PER_SECOND = 300;
const int ACCELERATION_IN_STEPS_PER_SECOND = 800;
const int DECELERATION_IN_STEPS_PER_SECOND = 800;
// create the stepper motor object
ESP_FlexyStepper stepper;
int previousDirection = 1;
void setup()
{
Serial.begin(115200);
// connect and configure the stepper motor to its IO pins
stepper.connectToPins(MOTOR_STEP_PIN, MOTOR_DIRECTION_PIN);
// set the speed and acceleration rates for the stepper motor
stepper.setSpeedInStepsPerSecond(SPEED_IN_STEPS_PER_SECOND);
stepper.setAccelerationInStepsPerSecondPerSecond(ACCELERATION_IN_STEPS_PER_SECOND);
stepper.setDecelerationInStepsPerSecondPerSecond(DECELERATION_IN_STEPS_PER_SECOND);
// Not start the stepper instance as a service in the "background" as a separate task
// and the OS of the ESP will take care of invoking the processMovement() task regularly so you can do whatever you want in the loop function
stepper.startAsService(1);
}
void loop()
{
// just move the stepper back and forth in an endless loop
if (stepper.getDistanceToTargetSigned() == 0)
{
delay(5000);
previousDirection *= -1;
long relativeTargetPosition = DISTANCE_TO_TRAVEL_IN_STEPS * previousDirection;
Serial.printf("Moving stepper by %ld steps\n", relativeTargetPosition);
stepper.setTargetPositionRelativeInSteps(relativeTargetPosition);
}
// Notice that you can now do whatever you want in the loop function without the need to call processMovement().
// also you do not have to care if your loop processing times are too long (you might experience some jitter though if you do).
}
