Trying To Use a Nextion 3.5 Display With a MEGA 2560

I'm trying to make an automated greenhouse and everything works except when trying to use sliders on the Nextion to change pH and Nutrient setpoints.

I've looked at Perry Bebbington's page on making it work without the library, but I'm at a loss.

Here's the Arduino code, but I'm pretty sure I'm messing up in the Nextion Editor:

#include <DHT.h>
#include <PID_v1_bc.h>
#include <Nextion.h>

#define DHTPIN 2
#define DHTTYPE DHT22
#define PH_PIN A0       // Pin connected to Po on the pH sensor
#define TDS_PIN A1
#define TEMP_PIN A2     // Pin connected to T1 or T2 for water temperature

#define PUMP_PH_UP_PIN 8
#define PUMP_PH_DOWN_PIN 9
#define PUMP_NUTRIENT_PIN 10

DHT dht(DHTPIN, DHTTYPE);

// PID parameters
double Setpoint_pH, Input_pH, Output_pH;
double Setpoint_TDS, Input_TDS, Output_TDS;

double Kp_pH = 2.0, Ki_pH = 5.0, Kd_pH = 1.0;
double Kp_TDS = 2.0, Ki_TDS = 5.0, Kd_TDS = 1.0;

// Create PID controllers
PID pH_PID(&Input_pH, &Output_pH, &Setpoint_pH, Kp_pH, Ki_pH, Kd_pH, DIRECT);
PID TDS_PID(&Input_TDS, &Output_TDS, &Setpoint_TDS, Kp_TDS, Ki_TDS, Kd_TDS, DIRECT);

// Timing variables for delays
unsigned long lastPHCorrectionTime = 0;
unsigned long lastTDSCorrectionTime = 0;
const unsigned long correctionDelay = 120000; // 2 minutes in milliseconds

void setup() {
  Serial.begin(9600);   // For debugging
  Serial1.begin(9600);  // For Nextion communication

  dht.begin();

  pinMode(PUMP_PH_UP_PIN, OUTPUT);
  pinMode(PUMP_PH_DOWN_PIN, OUTPUT);
  pinMode(PUMP_NUTRIENT_PIN, OUTPUT);

  // Initialize PID controllers
  Setpoint_pH = 6.5;  // Desired pH setpoint
  Setpoint_TDS = 1200; // Desired TDS setpoint

  pH_PID.SetMode(AUTOMATIC);
  TDS_PID.SetMode(AUTOMATIC);

  // Debug message to confirm setup
  Serial.println("Starting sensor data transmission to Nextion display...");
}

void loop() {
  // Read sensors
  float airTemp = dht.readTemperature();
  float humidity = dht.readHumidity();
  float waterTemp = readWaterTemp();  // Read water temperature from T1 or T2

  if (isnan(airTemp) || isnan(humidity)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  Input_pH = readPHValue();                          // Read pH value from the sensor
  Input_TDS = analogRead(TDS_PIN) * (1500.0 / 1023.0); // Example calculation for TDS

  // Update setpoints from Nextion controls
  updateSetpointsFromNextion();

  // Compute PID and control pH
  computePHControl();

  // Compute PID and control TDS
  controlNutrientPump();

  // Send data to the Nextion display
  sendToNextion(airTemp, humidity, Input_pH, Input_TDS, Setpoint_pH, waterTemp, Setpoint_TDS);

  // Debug output
  Serial.print("Air Temp: ");
  Serial.println(airTemp);
  Serial.print("Humidity: ");
  Serial.println(humidity);
  Serial.print("Water Temp: ");
  Serial.println(waterTemp);
  Serial.print("pH: ");
  Serial.println(Input_pH);
  Serial.print("TDS: ");
  Serial.println(Input_TDS);

  delay(1000);  // Update every second
}

// Function to compute and control pH
void computePHControl() {
  pH_PID.Compute();

  // Deadband of ±0.2 around the setpoint
  double deadband = 0.2;

  // Calculate the difference from the setpoint
  double pH_difference = Setpoint_pH - Input_pH;

  // Get the current time
  unsigned long currentTime = millis();

  if (currentTime - lastPHCorrectionTime >= correctionDelay) {
    if (abs(pH_difference) <= deadband) {
      // Within acceptable range, turn off both pumps
      digitalWrite(PUMP_PH_UP_PIN, LOW);
      digitalWrite(PUMP_PH_DOWN_PIN, LOW);
    } else if (pH_difference > 0) {
      // pH is too low, turn on the pH up pump
      digitalWrite(PUMP_PH_UP_PIN, HIGH);
      digitalWrite(PUMP_PH_DOWN_PIN, LOW);
      lastPHCorrectionTime = currentTime; // Update the last correction time
    } else {
      // pH is too high, turn on the pH down pump
      digitalWrite(PUMP_PH_UP_PIN, LOW);
      digitalWrite(PUMP_PH_DOWN_PIN, HIGH);
      lastPHCorrectionTime = currentTime; // Update the last correction time
    }
  } else {
    // If within the delay period, ensure pumps are off
    digitalWrite(PUMP_PH_UP_PIN, LOW);
    digitalWrite(PUMP_PH_DOWN_PIN, LOW);
  }
}

// Function to control nutrient pump based on TDS PID output
void controlNutrientPump() {
  TDS_PID.Compute();

  // Deadband of ±100 around the setpoint
  double deadband = 100.0;

  // Calculate the difference from the setpoint
  double TDS_difference = Setpoint_TDS - Input_TDS;

  // Get the current time
  unsigned long currentTime = millis();

  if (currentTime - lastTDSCorrectionTime >= correctionDelay) {
    if (abs(TDS_difference) <= deadband) {
      // Within acceptable range, turn off the nutrient pump
      digitalWrite(PUMP_NUTRIENT_PIN, LOW);
    } else if (TDS_difference > 0) {
      // TDS is too low, turn on the nutrient pump
      digitalWrite(PUMP_NUTRIENT_PIN, HIGH);
      lastTDSCorrectionTime = currentTime; // Update the last correction time
    } else {
      // TDS is too high, turn off the nutrient pump
      digitalWrite(PUMP_NUTRIENT_PIN, LOW);
    }
  } else {
    // If within the delay period, ensure pump is off
    digitalWrite(PUMP_NUTRIENT_PIN, LOW);
  }
}

// Function to send data to the Nextion display
void sendToNextion(float airTemp, float humidity, float pH, float TDS, float pHSetpoint, float waterTemp, float TDSSetpoint) {
  char buffer[20];

  // Air temperature
  dtostrf(airTemp, 4, 1, buffer);
  sendNextionCommand("airTempTxt", buffer);

  // Humidity
  dtostrf(humidity, 4, 1, buffer);
  sendNextionCommand("humidityTxt", buffer);

  // pH
  dtostrf(pH, 4, 2, buffer);
  sendNextionCommand("pHTxt", buffer);

  // TDS
  dtostrf(TDS, 4, 0, buffer);
  sendNextionCommand("tdsTxt", buffer);

  // pH Setpoint
  dtostrf(pHSetpoint, 4, 2, buffer);
  sendNextionCommand("pHSet", buffer);

  // Water temperature
  dtostrf(waterTemp, 4, 1, buffer);
  sendNextionCommand("waterTempTxt", buffer);

  // TDS Setpoint
  dtostrf(TDSSetpoint, 4, 0, buffer);
  sendNextionCommand("tdsSet", buffer);
}

// Helper function to send commands to the Nextion display
void sendNextionCommand(const char* component, const char* value) {
  Serial1.print(component);
  Serial1.print(".txt=\"");
  Serial1.print(value);
  Serial1.print("\"");
  sendEndCommand();
}

// Function to send the end command for Nextion communication
void sendEndCommand() {
  Serial1.write(0xff);
  Serial1.write(0xff);
  Serial1.write(0xff);
}

// Function to read pH value from the sensor
float readPHValue() {
  int sensorValue = analogRead(PH_PIN);
  float voltage = sensorValue * (5.0 / 1023.0);  // Convert analog reading to voltage

  // Convert the voltage to pH value based on the sensor's characteristics
  return mapVoltageToPH(voltage);
}

// Function to map voltage to pH value
float mapVoltageToPH(float voltage) {
  // Example conversion based on a typical pH sensor
  // Adjust the mapping based on your sensor's datasheet
  return 7.0 + ((2.5 - voltage) * 3.0);
}

// Function to read water temperature from T1 or T2 pin
float readWaterTemp() {
  int sensorValue = analogRead(TEMP_PIN);
  float voltage = sensorValue * (5.0 / 1023.0);  // Convert analog reading to voltage

  // Example conversion for typical temperature sensor (e.g., TMP36)
  // Adjust this formula based on your specific sensor.
  float temperatureC = (voltage - 0.5) * 100.0;
  return temperatureC;
}

// Function to update setpoints from Nextion controls
void updateSetpointsFromNextion() {
  // Read pH setpoint from phCtrl
  Serial1.print("get phCtrl.val");
  sendEndCommand();
  while (Serial1.available() == 0); // Wait for response
  Setpoint_pH = Serial1.parseInt(); // Read the integer value
  updateTextField("pHSet", Setpoint_pH);

  // Read TDS setpoint from tdsCtrl
  Serial1.print("get tdsCtrl.val");
  sendEndCommand();
  while (Serial1.available() == 0); // Wait for response
  Setpoint_TDS = Serial1.parseInt(); // Read the integer value
  updateTextField("tdsSet", Setpoint_TDS);
}

// Helper function to update a text field on the Nextion display
void updateTextField(const char* component, int value) {
  char buffer[10];
  itoa(value, buffer, 10); // Convert integer to string
  sendNextionCommand(component, buffer);

    if (nexSerial.available()) {
        String receivedData = nexSerial.readStringUntil('\n');  // Adjust the delimiter if necessary
        
        // Handle pH slider input
        if (receivedData.startsWith("phSlider.val=")) {
            Setpoint_pH = receivedData.substring(13).toFloat();  // Extract the value after the equals sign and convert it to float
            // Update the display or take other actions with the new Setpoint_pH value
        }
        
        // Handle TDS slider input
        if (receivedData.startsWith("tdsSlider.val=")) {
            Setpoint_TDS = receivedData.substring(14).toFloat();  // Extract the value after the equals sign and convert it to float
            // Update the display or take other actions with the new Setpoint_TDS value
        }
    }
    
}

#include <Nextion.h>

Regarding the syntax of communication to the Nextion there have been many reported issues with using "Nextion.h".

It would be better to use either the methods of Perry Bebbington documented in

https://forum.arduino.cc/t/using-nextion-displays-with-arduino/580289

or alternatively the EasyNextionLibrary by Seithan which is available through the library manager.

You will not find much support on this Arduino forum for using the ITEAD Nextion.h library.

I personally use the EasyNextionLibrary and find the trigger functions very helpful for sending values from the Nextion to the Arduino.

So basically you are saying that the manufacturer's libraries are trash.

This matches up with what I have read online so far. It seems like the handling of data isn't up to par with the two devices. It's almost like the Nextion developers just phoned it in when making this POS.

Now if you just want to read values on a screen, this works very well.

Meanwhile Perry Bebbington's Clock is just ticking away.

What help do you need?

Plus everything @cattledog said, I agree with every word.

Basically getting the sliders for 'TDS' and 'pH' to read the current setpoint, display it on the Nextion and then display and write when the slider is changed.

I have two pages. Page 0 displays all the sensor data with a button to change setpoints.(Page 1).


I'm not certain I understand. What is the issues with the sliders and the display of their values on both page0 and page1? This should all be unrelated to communication with the Arduino.

The sliders set the setpoints they don't read them except from their positional value. With global fields, they should display their values in the pHset and TdsSet fields of page0 as well as on page1. They should hold their values when you switch pages.

Please explain more if you are having issues with the sliders on the Nextion side of things.

and write when the slider is changed.

Yes, writing the slider value to the Arduino when the position is changed it where I thought you were having the problem.

If you were using the Easy Nextion Library you could set a trigger function on the touch release event for the slider. The Arduino code would look like this

#include "EasyNextionLibrary.h" // Include EasyNextionLibrary 
EasyNex myNex(Serial1);

void setup() {
  Serial.begin(115200);//monitor
  Serial.println("Starting");
  myNex.begin(9600); //Begin the object with a baud rate of 9600 on Serial1
  delay(500);        // Wait for Nextion to start
}

void loop() {
  myNex.NextionListen(); //NE listen for triggers coming from Nextion
}

void trigger1( )
{
  Serial.println("Trigger1");
  uint8_t sliderValue = myNex.readNumber("n0.val");//number field holding slider value
  if (sliderValue != 777777)
    Serial.println(sliderValue);
  //do whatever with the sliderValue
 //I sometimes write the slider value back to the Nextion field to confirm that all went well.
}

If you want to stick with library free code, I think that Perry Bebbington has a slider example in what he has posted in his tutorial.

There is, yes. If you need help with it please ask.

My approach is that every time the slider is moved the new value is sent to the Arduino. The Arduino receives the value and sends it back as text to be displayed. Working this way requires that all the code be written to be non blocking.

Yes, there is a user interface and Arduino program design to be made. Does the slider value need to be dynamically updated on the Arduino as well as the Nextion, or does the Arduino just need to know the value when it stops moving?

If you are providing a value to a "setpoint", it seems reasonable to only send the value to the Arduino when you have set what you want.

If you were trying to ramp a motor like a fan, you'd want the alternate approach of dynamically sending/reading from the slider.

Define 'stops moving'. That would require a timeout. No issues with sending on move, even the slowest processor can keep up as long as there's no blocking code.

I can't remember exactly how Nextion decides to send the data, I'm not home at the moment so can't check.

Repeatedly sending the data as the slider moves allows for the display to be updated as it moves, providing feedback on the setting as it's changed.

When you remove your finger from the bar. The positional value is sent as a Touch Release Event.

Repeatedly sending the data as the slider moves allows for the display to be updated as it moves, providing feedback on the setting as it's changed.

With a Nextion, the display updates and feedback on the setting can take place on the Nextion, using its own processor, without any involvement of the Arduino.

Now I'm back at my PC I have had a look at the editor, I'd forgotten that bit. I've done both, send the value on move and send the value on release.

It can, and that's one way to do things. It's not my preferred way, mine is as commented, send the values as the slider is moved, send back the values as text for display. Doing that means the Arduino (or whatever) always knows the current value. No issue with doing it a different way, but I can't help with that. This is for the OP to decide what they think is best for them.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.