Hi and welcome @dotdash32!
Your observation is pretty much spot on I think. Nextion does have a non-negligible delay. On top of that the delay is somewhat variable depending on what the display is doing, so as soon as you’re putting a decent load on the serial bus you need to work with acknowledges (which Nextion thankfully supports) and - ideally - non-blocking communication.
The acknowledge part is easy, and apparently already in use judging by your LA screenshot. The non-blocking part may be more tricky depending on your requirements. F.ex. if you need to know if a specific command executed correctly or failed, you need to keep track of the outgoing commands and associate Nextions return replies with them and communicate this back to the code that originally emitted the command. Using an RTOS may provide some tools that simplify the task significantly (some allow you to write code in a “blocking” way but without actually blocking. In case you don’t know what I mean, if you have a waiting loop, you add a function call to the loop that will tell the task scheduler ‘hey, I’m currently waiting, check other pending tasks and come back to me afterwards’ - on Arduino - if available - this function is called
yield() I think).
If however a 2 priority system is all you need (time critical stuff like PID loops vs everything else), I’d suggest running the time critical stuff by one or more timer interrupts, and keep everything else blocking as it is. Hardware interrupts are not blocked by loops so your time critical stuff is guaranteed to work properly. Especially when there’s no experience with any RTOS and/or a lot of code that would have to be adapted this can be a simple solution.
Again, note for readers who have less experience with hardware interrupts: since they block everything else, you should be extra careful to keep the stuff they do as short and fast as possible. If they take too long, you’ll see “weird” issues like
Serial.read/print/write not working properly (they all depend on interrupts that’ll be blocked/delayed by yours if it’s too long).
This aside, I also agree with @paulvk’s point. In my personal experience it is often - not always - a good idea to cross the “serial line” as rarely as possible. This helps keeping the UI separate from the “actual” software, reducing code complexity, dependency and delays where one processor is waiting for the other one to receive or respond.
I said most of the time because there are cases where Nextion’s so limited that it makes more sense to send stuff to the microcontroller, get it processed there, and send the result back (the lack of math functions would be one example).
In all other cases however, especially when Nextion does have good support for what you need, I strongly suggest to use it. Button press measurement can be done with literally five lines on Nextion:
// Touch Press Event
// Reset counter variable and start timer to measure button
// press duration.
// Touch Release Event
// Stop timer, read elapsed time from the counter variable.
// on Nextion.
// Do stuff based on timeMS.val
// Timer Event
// NOTE: you could hardcode the resolution, or have a counter
// that only increases by 1 each time, but the following code
// is much cleaner IMO and flexible: you can change the timer
// resolution without changing anything else and you don't have
// to think about converting between "ticks" and milliseconds
// anywhere else.
// Note that I personally like to add the unit to the name of
// variables - especially useful if you have different units
// across your code. Just one of those little habits that can
// prevent stupid errors in the future.
This works for any number of buttons because Nextion doesn’t have multi-touch support. Note that the code above only measures the delay, it can’t do anything until the user actually releases the button (since only the release event decides what action shall be taken).
This can easily be changed by adding the “release” code to the timer event. Again, since there can only be one button pressed at the same time it’s perfectly fine to have many timers, for each button action one (even if you have 20 timers, they’re not going to run simultaneously, so no risk of overloading).
This was an extensive example, but there are more things you can do:
- I found it much more efficient to not use the Nextion Instruction Set for sending data from Nextion to the MCU. This allows me to send multiple values together in a single serial packet.
- Also, personally, I prefer Nextion sending stuff to the MCU whenever it changes instead of the MCU polling for changes. Considering the overhead of sending a request to Nextion and waiting for a reply, this can save you a lot of time.
- Getting rid of the NIS for communication towards Nextion is possible (
recmod / protocol reparse mode), but a pain in the ass because you have to run it off of timers, basically polling the serial buffer continuously - and copy that code to every page because there’s no option for global code. There are cases where it makes sense but you do lose a lot of comfort. If you go for it, it makes probably more sense to have fixed, periodic updates of variables. Put a bunch of them together in one packet, such that you have as little different packets as possible to distinguish and parse on the Nextion side. Send that packet 1-3 times per second and check for it every 100ms on the Nextion side (rough values I’d start with). The lower the update rate, the stupider the format can be.
- The MCU doesn’t need to know about every button press and release. Often enough, certain buttons are only there to navigate through the UI without actually having any effect on the device they control. UI navigation can be done rather easily on Nextion, so there’s potential here to get rid of some serial messages.
Hope this helps!