Start a new topic

Arduino+Nextion. Simultaneous send to UART problem.

Situation: sensor connected to Arduino measures constatly changing value and I need to view this value on display. It means Arduino keeps UART busy sending this value to display. (Say i have filter that sends value to display once per 500ms).
And also I have button on same screen  ("Exit" for example, that must open previous page).

Problem: if I press button while arduino sends sensor value, this press event is not recieved by arduino (or not sent by display, not sure).  If I press button between arduino transmissions it is ok.
Question: how to avoid this situation? Another words how to queue events in UART for their execution after UART gets free. 


After all I'm pretty sure that popup event is not recieved by arduino because in exit button events i tried "com_stop" on press with "com_start" on release, which means display stops execute commands from UART, sends button ID (checkbox send ID is checked of course), then continues execution. So problem seems to be on arduino side.

@Ivan,


I've done lots of tests with the Arduino/Nextion combo and can assure you it is quite capable of executing the task you describe. The problem is most likely your code, and if you would like to ask for help in that area, then it would help us enormously if you posted that code, with an HMI file if possible :)  

This is a user design decision.


Arduino is single threaded single core, sequential execution with a few interrupts. 

setup() {
   code line a
   code line b
}

loop() {
   code line c
   code line d
   code line e
   serialEvent()
}

Sequence: a, b, c, d, e, sE, c, d, e, sE c, d, e, sE, c, d, e, sE c, d, e, sE, c, d, e, sE ...

Only one line can be being executed at a single time.


What you are describing a collision, you want two things to execute at a time.

So this is not possible on a single core, single threaded sequential application.


At 9600 bps, a char/byte arrives in roughly 1/960s.  On a 16MHz MCU (assuming)

  each bit arrives every 1666.6 cycles, or ~104.1 microseconds.

  10 bits needed per byte, 16,666.6 cycles, or ~1.041 milliseconds.

A touch event of 7 bytes (0x65 0x03 0x02 0x01 0xFF 0xFF 0xFF)

   is at least ~ 7.291 milliseconds from start to end of transmission


Each of your pieces of code could be timed in such manner millis() and micros().

You as the designer have to decide what is of important to your project

  - and then make code provisions to protect that importance

  - if receiving is important, then you need to ensure time is allotted to get bytes


There is always scenarios where two items are going to occur at the same time

 - or nearly the same time, usually one occurs a bit before the other

   leaving them sequential.  Such fractions of microseconds are hard for both

   to actually occur at exactly the same time.

- sometimes, it is not worth chasing the collision scenarios

- other times, it is more evident, you must decide and provide for.


But this is a user design decision.

What you described would be correct in application where all actions can be predicted. But it is impossible to predict exact moment when user presses a button. And it would be unpleasant surprise for user if he presses DSbutton, sees its state changed but nothing happens after. So what I am trying to do now is to monitor pause between arduino transmissions on Nextion side. But I cant find correct instruments for it. 

For example: I want display to ask arduino about state of variable which is displayed in NexText object on current page every 1 second. On page I create local numeric variable "poll", and local enabled  timer "tm" with period 1000ms. In timer event i write:   

 

printh 65 01 13 00 ff ff ff //timer pop event
poll.val=1 //start of query
tm.en=0 //timer stops after one cycle and display waits for response 

 

 Next is on arduino side we have function attached to pop event of timer which contains:  

 

//--send to NexText part--//
poll.setValue(0); //transmission-is-over-variable
tm.enable(); // start timer till next query

 Now all I need is to tell user-pressed-button to wait till poll var becomes 0. And here i stuck.In button press event i need something like

 

if(poll.val==1){
please wait till it becomes 0 and then send your ID to UART
}

 but I cant figure out how to do it. If this is possible, it would solve the problem. Maybe you could give me a hint about it? 


Thank you for your reply.

Try this very simple example on your Arduino.


Rename the Serial if necessary.


The code just increments a number by 1 each cycle. It is set to do that at ~10ms intervals or 100 times a second. Every cycle sends a new string to nextion to update n0.val.


Your mileage may vary - I'm using a 3.5" Enhanced Nextion 


Start/Stop the count by hitting the big start stop button. 

uint32_t startTime = 0;
uint32_t myLong = 0;
bool soTrue;


void setup() {
  
  Serial.begin(115200);

  soTrue = false;
}

void loop() {

  if (soTrue) {
    if (millis() - startTime > 9) {
      Serial.print("n0.val=");
      Serial.print(myLong);
      Serial.write(0xFF);
      Serial.write(0xFF);
      Serial.write(0xFF); 
      myLong ++;
      startTime = millis();
    }
  }

  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '0') {
      soTrue = !soTrue;
    } 
  }
}

 

HMI

So how does it work?


The Nextion touch panel upon user interaction, will interrupt the currently running thread and get it's intructions run instead. In this case print a '0' across the serial to Arduino. Arduino is only busy for a few milliseconds of the 10 between each print routine. So has plenty of loop cycles (thousands) to look out for a '0' coming in on serial. On receipt it's told to flip the bool so stopping or starting the count. Simples:)

What I described was not some ideal situation but how your Arduino works - sequential

When you wait for 1000ms at 9600 bps, 960 chars already could go by.


When the user presses the button Nextion side your MCU can be notified in ~7.3ms later (9600bps, or ~608us at 115200) when this is received by your Arduino.  But first you have to read and interpret the bytes coming in from Nextion --- sequential execution.  This means your loop() code needs to be able to complete quick enough to get back to where your bytes are being read in.


Polling is usually a bad method - waiting on this code line until such and such happens. When your code sits for too long on a particular line number, you're missing the opportunity to read in those bytes.  This of course depends on how you implement such in your code but generally consumes way way more cycles than necessary.


HMI is Human Machine Interfacing. 

  - Two main components.  User requests, and machine statuses.

When a user presses a component - is this intended then intended that the machine (MCU) must now carry this out?  Then carry out if it is proper to do so and followed up by a status if needed back to the users HMI screen.  If such can not be carried out. perhaps reset the component to represent the actual state on the HMI screen (status).

 - The code running in your loop() may be taking too long to catch incoming serial (refer to MCU and compiler documentations for buffer sizes, etc).  If such is the case, you have to adjust your approach - but many times a few simple tests will tell you this is or is not even a problem. 


Don't get caught up in too many theoreticals as all the code for all these theoretical mechanisms can end up being 3/4s of what is causing your delays.  Your MCU is most often more capable and such collisions wont often happen if your focus remains on ensuring you are receiving and processing your bytes.


1 person likes this

There is no need for a DS Button in my code either, if you modify the Serial.read section you can emulate the same and it's always in sync with Arduino True/False Stop/Start Red/Green whatever...

What Nextion 'knows' is irrelevant, it's just a dumb display  

  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '0') {
      soTrue = !soTrue;
      if (soTrue) Serial.print("b0.bco=2016\xff\xff\xff");
      else Serial.print("b0.bco=63488\xff\xff\xff");
    }
  }

 

I'll note that the Iteadlib Arduino Nextion Library takes care of reading your serial in NexLoop()

 - when it receives your 0x65 return code, it iterates through your list of components from nex_listen_list

 - if you have defined .attachPop or .attachPush event, it will trigger your function


At this point this is dealing with touch events from Nextion as they arrive

- the only thing that would prevent such from functioning - is user code taking too much time


So you now have a bit more information from myself and Steve (indev2)
it now mostly boils down to .... user design decision. =)

Go the whole hog and switch the labels while you're at it....:) 

  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '0') {
      soTrue = !soTrue;
      if (soTrue) {
        Serial.print("b0.bco=2016\xff\xff\xff");
        Serial.print("b0.txt=\"Run\"\xff\xff\xff");
      }
      else {
        Serial.print("b0.bco=63488\xff\xff\xff");
        Serial.print("b0.txt=\"Stop\"\xff\xff\xff");
      }
    }
  }

 

I am really astonished by the way you answer. Really, thanks for such explanations. But now I have to make conclusions out of all above. And they are:

1. Arduino recieves 1 byte per cycle, and the shorter cycle is the faster interpretation of byte sequence recieved form UART will occur.

2. Polling is not good (however modbus protocol for example is worldwide standart and uses this method)

3. Yes, I use arduino library and i looked through source code of commands and I know that it already monitors serial on incoming commands (like 0x65), so I am not really sure why to use this method one more time only with 0 instead of 65. 1 char insted of 2 ? 

4. And the most important conclusion, in my opinion, as you said, is too much time taken by user code. My code now occupies 71% of dynamic memory of Arduino Mega (and it is not finished yet) and up to 3800 lines long, so I believe this is the problem.. But "too much" is not a measure. Is there any method to make sure ? Maybe i should measure average cycle time? But what is the proper value for it then?

 

For now I'll try to imitate my current situation only with few lines of code and hope it will tell me a bit more about my problem.

 

Thanks again.


PS: Where is "Correct comment" button in this forum?)

Aaaand no. It is not program size. I cut off everything except one page and its functionality. And it is simple. I press button, relay switches, green lamp on screen lights up. Here is video. Value from sensor changes every 100 ms. And not every button touch is recognized(you can hear as relay clicks along with lamp goes green and grey). So here it is. https://youtu.be/dCjJ-FCEm40

MCUs do as you have programmed them to do, so for #1.  There may be a difference or "receiving" 1 byte per cycle and "processing" 1 byte per cycle.  Depends on how you have implemented such in your code.


If (Serial.available() > 0) {

  inbyte = Serial.read();

}


is different then


while (Serial.available() > 0 {

  inbyte = Serial.read();

}


#2) Polling is indeed used in many places

#3) Steve's example is an example to show a technique. 0x65 is 1 8-bit byte as '0' is 1 8-bit char.  But Steve's example is not meant to be Nextion ready code for copy, but for learning about cycles.

#4) Size of code and the length of time code takes are not the same.  You must measure to know length of time.  Example is short code:

  delay(2000);

This will not take up much space at all, but will possibly be the cause of losing data to buffer overflows.


Yes, learning, thats what I am doing right now. I tried Steve's example with my sensor changing values. Works perfectly.. All presses recognized. So the only reason is my code, as you said. And can you tell me where can I read about serial buffer organization? I'm not sure that I have correct preception about it. How does it work exactly? Maybe proper understanding of this aspect would make easier all other steps?

UPD: Found this: http://www.gammon.com.au/serial .. Quite useful

Login or Signup to post a comment