NEXTION HMI with Industruino

Touch screen over RS485

Tom

NEXTION is a popular HMI (human machine interface) for the Arduino environment, as it is relatively good value, easy to connect over Serial, and comes with a decent GUI editor. However, Nextion (a branch of Itead) also receives a lot of criticism, so please note this blog post is by no means an endorsement by Industruino, we just want to show you how to connect a popular HMI to our platform.

HMI are typically LED displays with touch screen functionality, so the user can interact by pressing buttons on a screen, navigating between different pages to read&control settings. In this example we use a 3.2'' Nextion display with resistive touch, which sells for around USD 22. It needs 5V with a recommended 500mA PSU, and the interface is Serial TTL. There are many getting started tutorials for Nextion, e.g. this one is well made.

GUI DESIGN

The display has a powerful 48MHz MCU and 4MB of flash memory, and runs all the graphics and interaction locally. The Serial connection only exchanges events e.g. 'show page 1', 'button CH1 is pressed', 'slider CH2 is moved to 50' etc.

The GUI design process is completely separate from the Arduino IDE; we need to install the Nextion Editor on a Windows computer to create pages with objects such as text fields, buttons, sliders etc. All objects have an ID; we need to use this ID in our Industruino sketch to control the objects and behaviour of the display. Here we used the latest v0.59 of the editor.

The GUI design is saved in .HMI format, and needs to be compiled in the Editor to a .tft file for the display. The easiest way to move the .tft file is to put it on a microSD, insert the SD into the display and it will automatically upload the new display code and store it in its flash memory.

HARDWARE CONNECTIONS

While connecting a Nextion display to an Arduino is relatively straight forward (5V and TTL available), in a typical Industruino IND.I/O setup, including a communication module such as Ethernet or GSM/GPRS, we do not have 5V available, and no Serial TTL.

In case you are not using Ethernet or GSM/GPRS, or you are using the Industruino PROTO, you could connect the Nextion display to the 14-pin IDC port (2 hardware serials available: Serial and Serial1), or to the screw terminals of the PROTO. See this blog post.

However, this example uses an IND.I/O powered by a 24V PSU. We use the RS485 port which is a serial protocol popular in industrial noisy environments; it works well over long distances as it uses a differential signal.

The test setup shown on the left has 4 wires coming from the Industruino (on the right, not shown):

24V: V+/V-

RS485: A and B

We use a simple DC/DC converter (adjustable LM2596 module) to bring the 24V down to a stable 5V. This 5V powers the display, and the RS485 to TTL converter. In above picture, this is the module on the top.

The RS485 to TTL converter (link to ebay) is going to convert our bi-directional TTL communication to RS485. Traditionally, with chips like the MAX485, this needed a TxEnablePin to toggle the converter between sending and receiving, but luckily this model does not need this pin because that would make it a lot more complicated.

To test the connections you can use the TEST SKETCH at the bottom of this page; it sends the raw commands over RS485 and displays the reply from the Nextion in HEX bytes as in the protocol documentation. Use the Arduino IDE's Serial Monitor top box to send the commands to the Nextion, e.g. 'rest' for reset should return 0 0 0 FF FF FF 88 FF FF FF indicating 'Nextion Startup' and 'Nextion Ready'.

 

VIDEO DEMO

This video shows the demo sketch in action: the Industruino is receiving signals on 4 digital input channels (a 4 bit counter), and 2 analog input channels.

In the mean time, the output channels can be controlled with the digital buttons and analog sliders.

You see the red LEDs on the RS485-TTL-converter blinking whenever an channel changed state is sent from the Industruino, or an event from the display.

The RS485 allows for long wire distances; in this setup i have wires of around 4 meters, so the display can be far away from the Industruino, even in a noisy industrial environment.

LIBRARY

The official Nextion library has not been updated for over 3 years. This is quite strange as Nextion seems to be releasing new products, and new versions of its GUI Editor. It may well be that most people are writing their own low level interfaces to the Nextion displays, using the instruction set documentation. Also there seems to be an active user community around this unofficial forum

Luckily there is also the 'Enhanced-Nextion-library' which we will use here as a starting point.

It allows configuration for different platforms (Arduino, ESP8266 etc): hardware or software serial, debug serial. The default works on Arduino UNO. Unfortunately no support for RS485.

We just need several small modifications to 2 of the libary files to make it work with the IND.I/O RS485 (you can download the full modified library in the link at the bottom):

In NexConfig.h:

uncomment this line:

//#define NEX_SOFTWARE_SERIAL

include this block:

// include this block for Industruino INDIO D21G with RS485
#define NEX_INDIO_RS485
#ifdef NEX_INDIO_RS485
#define nexSerial Serial  // already mentioned above
#define RS485TxEnablePin 9
//#define NEX_DEBUG_SERIAL_ENABLE
//#define dbSerial SerialUSB
#endif
// end of Industruino block

In NexHardware.cpp we need to toggle the IND.I/O's TxEnablePin before sending anything over RS485 using 'Serial'. The IND.I/O uses the MAX485 which will only send out data if the TxEnablePin (D9 on Industruino) is HIGH. The default state is low = receiving = listening for events from the display.

Look for the sendCommand() function and replace with this:

void sendCommand(const char* cmd)
{
    // empty in buffer for clean responce
    while (nexSerial.available())
    {
        nexSerial.read();
    }
    
    #ifdef NEX_INDIO_RS485
    digitalWrite(RS485TxEnablePin, HIGH);   // for RS485 sending
    #endif
    
    nexSerial.print(cmd);
    nexSerial.write(0xFF);
    nexSerial.write(0xFF);
    nexSerial.write(0xFF);
    dbSerialPrintln(cmd);
    
    #ifdef NEX_INDIO_RS485
    nexSerial.flush();                    // important to wait for Serial buffer to empty itself
    digitalWrite(RS485TxEnablePin, LOW);  // for RS485 receiving
    #endif

}

You see that we set the TxEnablePin HIGH before writing to the Serial/RS485, and LOW after the writing is done. The flush() command is very important, it waits until the data have been written out to Serial from its buffer, otherwise we would be switching the RS485 back to listening before the writing is completed.

There are 2 more send functions in the same file: sendRawData() and sendRawByte(); you can add the 2 above blocks in bold to those functions, but i'm not sure they are used for normal operation.

INDUSTRUINO SKETCH

The sketch is easy to understand, it is largely based on examples of the library.

On startup, we have a page 0 that shows a welcome message.

The sketch then moves to page 1 showing the 8 digital channels: 

CH1-4 are OUTPUTS, so we can press the button to change their state

CH5-6 are INPUTS, they only show their state (LOW = grey, HIGH = green)

There is a button to move to page 2 showing the analog I/O channels:

CH1-2 OUTPUTS can be controlled by slider and show % numbers

CH1-4 INPUTS show the % numbers and bars 0-100%

In the sketch we first need to declare all Nextion objects that we want to interact with; they are identified by their page number and object id. In this example we use Buttons, Text, Sliders, ProgressBars, and 3 pages.

OUTPUTS

The we register the objects that we need to listen for: the 4 digital output buttons and the 2 analog output sliders.

These 6 objects each have their own callback function: when the user clicks on the object on the display, the library handles the event with the relevant callback function. The callback function get the value from the display, and updates the output channel. 

This events-based system works quite well with the library.

INPUTS

However, for the INPUTS, we need to make sure the input channel states remain in sync with the display. In the loop() we read all input channels, and we only update the display when a channel state has changed, otherwise we would overload the Serial. However, it is important here that the updates are successful, otherwise we might not be showing the true state of the input channels on the display. Here the library is less useful in my experience, because the Set_background_color_bco() and setText() functions often fail, maybe because they include a 'refresh' command that is not necessary for the latest displays. I opted for a more low level approach, with a function sendCmd() sending the instruction and checking for the success reply from the display (4 bytes: 0x01 0xFF 0xFF 0xFF), and repeating this sendCmd() until it is successful. This needs more work, as it can of course block the sketch when it gets stuck in unsuccessful attempts. 

The screenshot on the left shows the feedback in the Arduino IDE Serial Monitor of the demo sketch. The callback functions are mentioned as [button action] and [slider action]. You can also see that the bco, pco, and txt instructions (to change the colours and values of the input CHx text boxes) often fail a few times before success.

A few tips for working with the Nextion display:

  • To get feedback (success or failure) of each sent instruction, we need to send command bkcmd=3 and check its success.
  • The display only accepts and returns integer values.
  • There is an xFloat type but it is not clear how to use it, no instruction info. We can use Text fields instead.
  • In the GUI Editor you need to generate and include a font before you can display any text.
  • If objects need to be updated while another page is active, these objects need to be defined with 'global' scope in the GUI Editor.
  • It makes sense to have all data objects of 'global' scope otherwise they do not retain their value when moving between pages.
  • The display retains values even over a reset, so better reset any outputs to off in the setup() to make sure the display is in sync with the sketch.
Here is a zip file of the modified library, and other relevant files: GUI  .HMI (editor) .zi (fonts) .png (blackground image) and .tft (display) and .ino (sketch)
You can open the .HMI file to edit it with the GUI Editor, and it will need the .zi and .png files also.
Or you can upload the .tft file straight if you have a NX4024T032 display.
/*
   Industruino INDIO RS485 serial demo   NEXTION TEST SKETCH
   INDIO has a half duplex RS485 port connected to hardware Serial
   TxEnablePin is D9

   NEXTION commands:
   rest       reset
   bkcmd=3    always get feedback (default is 2: only on failure)
   page 0     go to page 0
*/

// Industruino RS485 has transmission enable pin on D9
const int TxEnablePin = 9;

void setup() {

  Serial.begin(9600);    // RS485
  SerialUSB.begin(115200);
  delay(1000);
  SerialUSB.println("RS485 receiver and sender");
  pinMode(TxEnablePin, OUTPUT);
  digitalWrite(TxEnablePin, LOW);  // for receiving

}

void loop() {

  while (Serial.available()) {
    byte byte_received = Serial.read();
    SerialUSB.println(byte_received, HEX);
  }

  if (SerialUSB.available()) {
    String cmd = SerialUSB.readStringUntil('\n');
    SerialUSB.print("sending cmd= ");
    SerialUSB.println(cmd);
    digitalWrite(TxEnablePin, HIGH);  // for sending
    //delay(10);
    Serial.print(cmd);
    Serial.write(0xFF);
    Serial.write(0xFF);
    Serial.write(0xFF);
    Serial.flush();  // important to wait until the Serial buffer has emptied itself
    digitalWrite(TxEnablePin, LOW);   // for receiving
  }

}