I am working on a coal loader for my model railroad. It uses 3 servos to control its operation. It has been programmed using 3 push buttons and a 4-line LCD display running on a NANO. Everything works well until I wanted to eliminate the LCD display and pushbuttons and convert the display to a Nextion display with buttons for control.
I programmed the Nextion display to control the servos as the pushbuttons did. All was well, and the servos moved as they should until data was written back to the Nextion display. At that point, the servos act like they received erroneous data and moved erratically.
The movement of the servos has to operate in a sequence. Meaning servo 1 has to finish its movement before 2 can move, and so on for 3. In the sketch, I am using the dreaded delay() statement for all the movements just to get it operating with the pushbuttons.
So I have added up all the delays in the program to a sum on 8 seconds. I have been studying the use of Millis() to replace all the delay() statements, as I think that may be causing my servo jitter when writing to the display.
At this point, while the servo is in motion, nothing else can happen until it is finished. So, using Millis() to be able to do other things doesn't seem to be useful here. I have included the file I'm currently using to see if you can see improvements to make it work with the Nextion display. Thank you for any help you can provide.
In the Arduino IDE, use CtrlT or CMDT to format your code then copy the complete sketch. Use the < CODE / > icon from the ‘posting menu’ to attach the copied sketch.
There are a lot of examples of non-blocking timing here, use the search feature.
#include <Servo.h>
#include <SoftwareSerial.h>
SoftwareSerial Serial2(2, 3); // RX, TX Setup for second serial port for the ITEAD display
//End charactor string to send to the new display
String endChar = String(char(0xff)) + String(char(0xff)) + String(char(0xff));
unsigned long currentMillis; //hold the current board time
Servo servo[3]; //code used for attaching upto 3 servos
int n = 0;
int pos = 0; // variable to store the servo position
int ldrLights = 13; // Pin 13 to control lights
// Servo angles for Chute up/down, Lower gate closed/open, Upper gate closed/open
byte minAngle[] = { 0, 5, 0 }; // min angle for each servo
byte maxAngle[] = { 60, 90, 95 }; // max angle for each servo
enum { //Setup veriables to represent the servos
Chute,
lowerGate,
upperGate
};
enum { // Setup variables for chute position
Lower = 0,
Raise = 1
};
enum { // Setup variables for gate position
Open = 1,
Close = 0
};
//Servo control pins
const byte servoPin[] = { 10, 11, 12 };
void setup() {
Serial.begin(9600);
Serial2.begin(9600);
Serial.println("Hello");
// Attach all servos to their drive pins
for (int n = 0; n < 3; n++) {
servo[n].attach(servoPin[n]);
}
//Home all Servos
for (int x = 0; x < 2; x++) {
servo[x].write(minAngle[x]);
}
//Add pin 13 to control the loading lights
pinMode(ldrLights, OUTPUT);
initDisplay();
}
void loop() {
currentMillis = millis(); // read the system system time in ms
// dfd = Data From Display
if (Serial2.available()) {
String dfd = "";
delay(30);
while (Serial2.available()) {
dfd += char(Serial2.read());
}
Serial2.print(dfd);
Serial.println(dfd);
sendData(dfd);
}
}
void sendData(String dfd) { // The eon & eoff text does turn the LED on and off
if (dfd == "eon") {
digitalWrite(13, HIGH);
}
if (dfd == "eoff") {
digitalWrite(13, LOW);
}
if (dfd == "load") { // received the 'load' command from the display
Serial2.print(endChar); // Send 3 x char string (0xff) to clear buffer
Serial2.print("t1.pco=63488" + endChar); // Turn textbox text red
// Start by calling the Load routine Here
load();
// delay(4000); //Simulat the loading procedure not needed now
//Serial2.print(endChar); // Send 3 x char string (0xff)
Serial2.print("t1.pco=0" + endChar); //Change the text back to black
Serial2.print("t1.txt=\" Car is done loading. Move to the next car.\"" + endChar);
Serial2.print("bt2.val=0" + endChar);
Serial2.print("bt2.bco=50712" + endChar); // This does not change the color on my display.
}
}
void load() {
// START HERE TO CREATE THE AUTOMATED LOADING PROCEEDURE
//Systen is enabled and Loading process started
chutePos(Chute, Lower); // lower the chute
delay(500); // wait 1/2 sec
// Open lower gate control
gatePos(lowerGate, Open); // open lower gate
delay(1500); // wait 1 sec
gatePos(lowerGate, Close); // close the lower gate
delay(500); // wait 1/2 sec
chutePos(Chute, Raise); //Raise chute
Serial.println("Car loading complete. Move to the next car.");
//Open the upper gate to fill the lower bin
gatePos(upperGate, Close); //open the upper gate - reversed for servo placement
delay(4000); // 4 sec delay used for reloading the lower bin
gatePos(upperGate, Open); // close the upper gate - reversed for servo placement
delay(500); // wait 1/2 sec
}
// Controlling the chute - Up and Down
void chutePos(int i, int d) { // i selects chute servo and d selects up or down
if (d == 0) {
Serial.println("Lowering chute");
for (pos = minAngle[i]; pos <= maxAngle[i]; pos += 1) {
servo[i].write(pos); // Move servo[i] to max angle, speed
delay(20);
}
} else {
Serial.println("Raising chute");
for (pos = maxAngle[i]; pos >= minAngle[i]; pos -= 1) {
servo[i].write(pos); // Move servo[i] to max angle, speed
delay(20);
}
}
}
// Controlling the opening and closing of the gates
// i selects the servo control ( 0 = chute 1 = lower gate 2 = upper gate)
// p selects the gate open/close (1 = open gate 0 = close gate)
void gatePos(int i, int p) {
if (p == 1) {
Serial.print("Opening ");
} else {
Serial.print("Closing ");
}
if (i == 1) {
Serial.println("Lower gate"); //Opening lower gate
} else {
Serial.println("Upper gate");
}
switch (p) {
case 1:
for (pos = minAngle[i]; pos <= maxAngle[i]; pos += 1) {
servo[i].write(pos); // Move servo[i] to max angle, speed - open gate
delay(20);
}
break;
case 0:
for (pos = maxAngle[i]; pos >= minAngle[i]; pos -= 1) {
servo[i].write(pos); // Move servo[i] to max angle, speed - close gate
delay(20);
}
break;
}
}
void initDisplay() {
//resets Nextion display to power on status
Serial2.print("page 0" + endChar);
Serial2.print("rest" + endChar);
}
Since there are Nextion issues, it will be important to also post the .hmi file. That file will need to be zipped and posted as an upload as the ide does not allow for a .hmi file upload.
There may be a conflict between SoftwareSerial and Servo, because they both rely heavily on interrupts.
< edit > SoftwareSerial disables interrupts while sending data, so it would cause the interrupts for Servo to be delayed, producing jitter. Either use an Arduino with two or more hardware serial ports, or eliminate the use of Serial to communicate over USB and use that port for the display.
There is something corrupted with the .zip file and I can not extract the ,hmi file.
Can you please try again.
As a stated by @david_2018 the use of software serial is likely to create issues. A processor with an additional hardware serial port like the Arduino Mega or a Nano Every is a far better choice for Nextion applications.
EDIT: Don't bother with the .zip file. I managed to extract it with 7-Zip. .
These delays can be accommodated in a state machine, no need for blocking delay(. . .)
void load() {
// START HERE TO CREATE THE AUTOMATED LOADING PROCEEDURE
//Systen is enabled and Loading process started
chutePos(Chute, Lower); // lower the chute
delay(500); // wait 1/2 sec
// Open lower gate control
gatePos(lowerGate, Open); // open lower gate
delay(1500); // wait 1 sec
gatePos(lowerGate, Close); // close the lower gate
delay(500); // wait 1/2 sec
chutePos(Chute, Raise); //Raise chute
Serial.println("Car loading complete. Move to the next car.");
//Open the upper gate to fill the lower bin
gatePos(upperGate, Close); //open the upper gate - reversed for servo placement
delay(4000); // 4 sec delay used for reloading the lower bin
gatePos(upperGate, Open); // close the upper gate - reversed for servo placement
delay(500); // wait 1/2 sec
}
david_2018, Thank you for that information! That was the problem. I went in and removed all the calls to the serial port for the print statement and moved the serial connection to pins 0 & 1. No more problems!
LarryD, the delay(1500) and the delay(4000) are values I have determined for the time the gates need to be open to empty the lower bin and refill the upper bin. the two delay(500) statements are not really needed. They are more of an effect of the loading process before it moves to the next step.
A Nano based on the Atmega328PB processor is easily available, and doesn't cost any more than it would with an Atmega328P. That would give you a second UART (TX and RX would be on D11 and D12). That conflicts with the SPI port, but if you need one of those, there's also a second one among the analog pins.
Of course, if the issue is that one part of the program is turning interrupts off, and you need to have interrupts in order to (for instance) read the serial port, you're stuck. You'd need to find some replacement for that routine, which isn't so arrogant about blocking interrupts.
LarryD, once I followed david_2018's thoughts, I removed all the Serial.print & Serial.println statements, and everything works as it did with the pushbuttons. So it was a problem using USB serial port for debugging, along with the second serial port for the Nextion display.
Thank you, LarryD and gcjr! I copied both of your code examples down so I can improve my coding experience. I am always learning something new when visiting this forum with a question!