I have a TFT screen, buttons and two Easy Drivers wired up with Nema 17 motors (See Frit Wiring - note the input was changed to buttons instead of a rotary encoder). With all of this said, I have created a program that has a menu system that allows the user to select the Moulding Ratio, speed and Time. The program then goes ahead and mould the part - with some extra features such as an emergency switch etc.
My big question is, when you look at the video, there is a slight PAUSE or JUTTER each second that makes the motion juttery and not flow as well as I would like. I have been working on different methods to reduce the amount of lines the processor has to run while it is moulding (checks for the end time, so when it reaches a certain time it then stops moulding) however, upon refining this, the biggest issue is the TFT refresh (updating the time on the screen every second).
My question is, is there a way to reduce this jutter? I can just take out the TFT statement, however, this is an important part as it showcases how long is left. Is there another way?
I have attached the entire CODE however, the part I am focused on, is in the Rotary class and is the Update/UpdateHandler methods - I attempted to add it as a snippet but it was flagged as being too large! sorrry
I understand this could be quite confusing as I am yet to go through and refine it (take out all the debug aspects) so please bear with me! If you do need any additional information, please feel free to drop a comment and I will happily let you know! Thank you kindly!
To save you having to download the whole program, here is the ROTARY class. The Rotary.UpdateHandler runs every loop when the user presses GO in the menu to begin rotary moulding. This then manager the emergency stop, timing and of course moving the steppers!
void Rotary::update()
{
for (int x = 0; x < 1000; x++) //Loop the forward stepping enough times for motion to be visible
{
for (int i = 0; i < OuterRatio; i++)
{ // ratio is (outer : inner) or 1 to 1 + ratio so to get a 1:4 the for loop would be 5
StepOuterCore(MouldSpeed); // Calculates Inner speed based on core ratio
}
for (int i = 0; i < (InnerRatio + 1); i++)
{ // ratio is (outer : inner) or 1 to 1 + ratio so to get a 1:4 the for loop would be 5
StepInnerCore((MouldSpeed * OuterRatio) / (InnerRatio + 1)); // Calculates Inner speed based on core ratio
}
}
}
int Rotary::StepInnerCore(int Delay)
{
digitalWrite(stp, HIGH);
delayMicroseconds(Delay);
digitalWrite(stp, LOW);
delayMicroseconds(Delay);
}
int Rotary::StepOuterCore(int Delay)
{
digitalWrite(sstp, HIGH);
delayMicroseconds(Delay);
digitalWrite(sstp, LOW);
delayMicroseconds(Delay);
}
void Rotary::initialize()
{
Tft.fillScreen(0x0000);
MouldTime = EEPROM.read(MouldTimeAddress);
//USER pressed GO to start moulding - RUN ONCE
StartTime = millis(); // StartTime
EndTime = StartTime + (MouldTime * 60000); // convert minutes to miliseconds. Calculate the end time.
Serial.println("");
Serial.print("Start Time Set: ");
Serial.println(StartTime);
Serial.print("End Time Set: ");
Serial.println(EndTime);
Tft_drawCenteredString("Begin", 16, 4, WHITE);
Tft_drawCenteredString("Press EMERGENCY to stop", 50, 1, WHITE);
//set change
ScreenChange = 1;
//Set FLAG to start moulding
Moulding = true;
}
void Rotary::UpdateHandler()
{
Serial.println("MOULDING");
//Get current time this LOOP
CurrentTime = millis();
//Calculate END TIME----------------------------------------------------------
if ( CurrentTime > EndTime and state == false) {
//MouldTime is OVER
if (Moulding == true) {
Tft.fillScreen(0x0000);
}
Moulding = false;
Tft_drawCenteredString("FINISH", 16, 4, WHITE);
Tft_drawCenteredString("Take your mould out", 50, 1, WHITE);
Tft_drawCenteredString("Press A to Exit", 60, 1, WHITE);
if (controller.AButtonPressed) {
Reset();
Tft.fillScreen(0x0000);
}
}
//UPDATE Rotary Position-----------------------------------------------------
if (Moulding) {
//Display TIME-----------------------------------------------------------
TimeLeft = EndTime - CurrentTime; // in miliseconds
TimeLeftMinutes = (TimeLeft / 1000) / 60; // convert to minutes
TimeLeftSeconds = (TimeLeft / 1000) - (int(TimeLeftMinutes) * 60); // convert to TOTAL seconds
/*
Serial.println(CurrentTime);
Serial.println(TimeLeft);
Serial.println(TimeLeftSeconds);
*/
Serial.println(TimeLeftMinutes);
Serial.println("");
Serial.println("");
Serial.print("Start Time Set: ");
Serial.println(StartTime);
Serial.print("End Time Set: ");
Serial.println(EndTime);
if (state == false and (millis() - LastPrint) > 1000) {//if no emergency switch pressed and its been a second, update the time.
TimeString = " " + String(int(TimeLeftMinutes)) + " Min & " + String(int(TimeLeftSeconds)) + " Sec" + " ";
Tft_drawCenteredString(TimeString, 80, 1, WHITE);
LastPrint = millis();
}
// no emergency skip
if (state) { //If pressed DO NOT UPDATE
//MEANS ITS BEEN PAUSED
if (Flag) { //Save current milis position
PauseTime = millis();
Tft.fillScreen(0x0000);
Flag = 0;
Tft_drawCenteredString("PAUSED", 16, 4, WHITE);
Tft_drawCenteredString("Press A to Exit", 50, 1, WHITE);
} // do not change value, this is the time it was first paused.
ScreenChange = 1; // The screen has changed
//If A Button is pressed, then EXIT
if (controller.AButtonPressed) {
Reset();
Tft.fillScreen(0x0000);
}
}
else {
//RUN THIS EACH LOOP IF NO EMERGENCY
//If not, then carry on
if (ScreenChange) {//screen changed therefore update
ScreenChange = 0; // reset back
// JUST come back from pause pos so update END TIME
//End time plus current time minus pause time so add on the time we were paused
if (Flag == 0) {
EndTime = EndTime + (millis() - PauseTime);
}
Tft.fillScreen(0x0000);
Tft_drawCenteredString("Mould", 16, 4, WHITE);
Tft_drawCenteredString("Press EMERGENCY to stop", 50, 1, WHITE);
Serial.println("TITLE UPDATED");
}
//Set flag back to 1
Flag = 1;
update();
}
}
}
void Rotary::Reset() {
Create.currentSelection = OptCreateList; // Will therefore revert back
controller.position.y = 1;
state = 0; // reset state
Tft.fillScreen(0x0000);
StartTime = 0;
EndTime = 0;
TimeLeft = 0;
TimeLeftSeconds = 0;
TimeLeftMinutes = 0;
PauseTime = 0;
CurrentTime = 0;
}
Few people here will open a zip file. If your code is < 9000 lines, then post it according to the "Read this before posting a programming question". Invariably a programming problem is usually not in the snippet provided.
Also, Fritzing pictures are NOT SCHEMATICS. But I doubt if the circuit would be where the problem lies, so I won't rag on you any more about not providing a schematic.
Does the TFT refresh coincide with the jutter? If so, then put the TFT on a separate processor. Sent the TFT processor the data by serial of I2C and then your loop won't be hesitating while the TFT is updating.
You could also use a dual-core ESP32 board and a Real-Time OS, but I have used neither.
You might try a different type of display, the LCDs are inherently slow on the Arduinos especially with the I2C interface. A schematic would make this much easier to follow then the frizzy picture.
Tft.fillScreen(0x0000);
Flag = 0;
Tft_drawCenteredString("PAUSED", 16, 4, WHITE);
Tft_drawCenteredString("Press A to Exit", 50, 1, WHITE);
You only need to overwrite the part with the text, not the whole screen. fillScreen is slowing you down.
Where is the code for the steppers ? Have you considered using a timer to handle their movement ?
Also, but on a different matter.TimeString = " " + String(int(TimeLeftMinutes)) + " Min & " + String(int(TimeLeftSeconds)) + " Sec" + " ";How have you declared TimeString ? is it a global variable ? if so, it is going to cause issues.
Your millis() values should all be 'unsigned long', not 'float'.
You should not calculate 'EndTime' as an absolute time. If you do that you will get a glitch every 47-odd days when the millisecond clock rolls over (sooner if you use 'float'?). You should only compare elapsed time (currentTime - StartTime) to a time interval.
Try changing:
EndTime = StartTime + (MouldTime * 60000); // convert minutes to miliseconds. Calculate the end time.
to
RunTime = MouldTime * 60000UL; // convert minutes to miliseconds. Calculate the run time.
and
if ( CurrentTime > EndTime and state == false) {
to
if ( CurrentTime - StartTime > RunTime and state == false) {
Check out my tutorial on Multi-tasking in Arduino
which includes a detailed tutorial on how to make steppers move smoothly using AccelStepper library.
Having said that updating the LCD can be slow and that will probably been your main problem.
The Multi-tasking in Arduino includes a loopTimer to help you debug how slow your loop in running and which parts are actually consuming the time.