Start a new topic

Serial - The bottom line!

Hi Everyone,


I wrote a short sketch to do some hardcore testing of the Arduino Serial.print() function, and see how Nextion copes on the receiving end.

I ran this on ESP8266 but if anyone's interested we can modify for Arduino boards that don't have access to Serial.prinf().


Simply we have an HMI with 96 dual state buttons. State 0 is a White colour, state 1 is Black.


So just how much data can you output from the Serial.print() function to your Nextion???


Even I can't quite work out exactly what is going on here, so I welcome other testers and some healthy discussion.


What I do know is that the hardware serial modules on a lot of MCU's are separate devices to the main core and have a special status. 


Read through my sketch and watch the video, what do you think is going on here??? 


What I will say is that this code totally max's out Serial.print() yet nothing breaks!


Note the print to the serial monitor in the background, max function run is 7 times per second. About what I expect for  ~1300 character print @ 115200 baud inc overheads.


Video here

https://www.youtube.com/watch?v=nEYBIVRihlA&feature=youtu.be


Questions welcome........

uint32_t startTime;
uint32_t startTime2;
uint8_t funcCount = 0;

uint32_t interval = 300;
uint8_t switchBit = 1;


void setup () {
  Serial.begin (115200);
  Serial1.begin (115200);
  //Serial.println ("Starting ...");
  startTime = millis();
  startTime2 = millis();
}

void printNexSwitch () {

  funcCount ++;

  for (uint8_t i = 0; i < 96; i++) {
    Serial1.printf("bt%d.val=%d\xFF\xFF\xFF", i, switchBit);
  }
  Serial1.printf("xstr 180,160,150,150,1,RED,BLACK,1,1,1,\"%d\"\xFF\xFF\xFF", interval);
  if (interval > 50) interval --;
  startTime = millis();
  if (switchBit == 0) {
    switchBit = 1;
  }
  else {
    switchBit = 0;
  }
}

void loop () {

  if (millis() - startTime == interval) {

    printNexSwitch();

  }

  if (millis() - startTime2 > 999) {

    Serial.printf("Func Hz: %d\n", funcCount);
    funcCount = 0;
    startTime2 = millis();
  }
}

  

HMI
(242 KB)

One thing that doesn't make sense is that as the millisecond interval gets incremented down, as we get near to target (50 ms) there is no way the printNexSwitch() can get done 20 times per second.

This is borne out by the fact that funcCount only gets to a max print of around 7 cycles per second.

However if you watch the interval count on Nextion in the video, every step is displayed without jumps toward the end. 

It's almost as if the for loop is jumped over if the last iteration is still printing....

If you comment that section out and print to a serial monitor then we do get the full 20 cycles per second as expected, just printing xstr 180,160,150,150,RED,BLACK,1,1,1....along with the Hz count.


Is the code really that smart as to not just crash the stack or buffers at that point???


Comments please...


MCU to Nextion for the dualstates is 1238 bytes per loop

MCU to Nextion for the xstr is ~46/47 bytes per loop

MCU to Serial monitor is 11 bytes after ~1 sec elapses.


Printing out how many millis() passed would reveal a bit more

- but we know that 6 iterations passed before it entered the seventh

- we know that after iterating through the seventh more than 999 millis() passed.


So max is 7 loops for 8995 bytes + 11 bytes = 9006 * 10.bits

   90060 bits ... but we can not quite say per second

If there was a millis() count out at time of Freq AND for 10 sec > 9999 millis()

we could perhaps get a better handle on how many bits per second by average


We also have to determine bkcmd Nextion Return Data.

 - power on default should be no return unless fail.

 - I didn't catch failures in the video first couple times viewing.


So as it stands now, I'm counting roughly 90060 bits of 115200 ~ 78.2%


Performance issues - each iteration causes more than just serial

Each Iteration also causes screen to do a full repaint.

There is a bit of time lost in oversized font drawing for xstr.

  - can increase performance at 80x80 and reduced the fontsize

    so that font just includes numbers 0 to 9 and space

    optimize xstr 180,160,80,80,1,37268,0,0,0,3,<number>

  - Ultimate xstr 180,160,16,16,1,37268,0,0,0,3,<number> with smaller numbers

  - two digit needs program to adjust for faster start.


I'll think a bit more on this

zi
(8.67 KB)
zi
(432 Bytes)

Spotted some issues in the sketch.


Moving the timer set to before the printNexSwitch() and allowing an overrun in 


if (millis() - startTime > interval)


gives much better through put, Hz counter now tops out at 9. Of course it's taking much longer than 50ms to run the print but even at this maxed out speed nothing breaks...


A testament to both devices that they can work at the full 115200 baud so well...cool 

uint32_t startTime;
uint32_t startTime2;
uint8_t funcCount = 0;

uint32_t interval = 300;
uint8_t switchBit = 1;


void setup () {
  Serial.begin (115200);
  Serial1.begin (115200);
  //Serial.println ("Starting ...");
  startTime = millis();
  startTime2 = millis();
}

void printNexSwitch () {

  funcCount ++;

  for (uint8_t i = 0; i < 96; i++) {
    Serial1.printf("bt%d.val=%d\xFF\xFF\xFF", i, switchBit);
  }
  Serial1.printf("xstr 180,160,150,150,1,RED,BLACK,1,1,1,\"%d\"\xFF\xFF\xFF", interval);

  if (switchBit == 0) {
    switchBit = 1;
  }
  else {
    switchBit = 0;
  }
}

void loop () {

  if (millis() - startTime > interval) {

    if (interval > 50) interval --;
    startTime = millis();
    printNexSwitch();

  }

  if (millis() - startTime2 > 999) {

    Serial.printf("Func Hz: %d\n", funcCount);
    funcCount = 0;
    startTime2 = millis();
  }
}

 

Going back to if (millis() - startTime == interval) 


the program halts at about 107ms call interval. Just about right for 115200 baud.


My conclusion is that Arduino Serial.print() just wont go into overload condition under these conditions, and that all the the bytes sent to Nextion are dealt with in some way without crashing the device. Not sure Nextion would be able to do much by way of any other output. But in the real world we wouldn't be driving the input flat out..... 

Let's consider a few things about Hardware UART modules.

 ... and since we don't have the Nextion source to examine

we make a few assumptions based on good practices (assumed)


A Hardware UART module should be able to receive a byte independent by itself.

One of the first steps in configuring a hardware uart is setting the registers with

all the information needed: data bits per frame, parity, length of the stop bit, baud,

so right from the falling edge of the start bit, the timing is already known.  Most

take measures of averaging by oversampling to ensure against line noise.  This

is also needed to handle the variation between the two crystals used by each end

of the communications line so ensure some tolerance is built in.


If we dig into some data sheets, we see that there is a variance error rate that needs

to be addressed when the baud rate isn't an evenly divisible into the mcu's clock.

So, indeed this variance should be already taken into consideration by any mature

microprocessor manufacture.  So when receiving the bits of a byte, it isn't until the

entire byte has been loaded into the register that the interrupt occurs to notify the

MCU that the byte is now available in the RX register.  We know how many bits were

to be coming - it is part of the agreed upon protocol to connect, and so part of the

configuration we set up in our programs.


Let's consider the timing of such.  The ESP is usually running with 80 MHz clock,

and the GD32F103 is assumed configured for a 108MHz clock. Once the interrupt is

triggered, the only task it needs to do is get the byte out of the Register and into the

software buffer.  At 115200 baud, you have a bit width from the stop bit, it's the start

bit, before data would begin to be corrupted by the next incoming bit.  But just assuming

only one bit width, the ESP's 80MHz clock allows around 694 clock cycles to get this

task done.  On the GD32 at 108MHz that's around 937 clock cycles.  So plenty of time

to get it from the Register and into the buffer.  We can also consider that the next byte

will not arrive until another 10 bits go by so 6940 clock cycles between interrupts.


One thought on trying to overrun another buffer on the other side - we are restricted

by the hardware uart on our side.  We can not fill the outbound TX register until the

TX register is empty.  This too is at the set predefined timings of the agreed upon

serial communications - in this case 115200 N81.  To begin to overtake the buffer on

the other side to cause an overrun, the task assigned to the other side has to be long

enough to fill the other buffer. 


Consider the Nextion command delay=500.  At 500ms and 115200 bps, that should be

enough time to allow 5760 bytes to be sent while the Nextion has been told to stand still.

Login or Signup to post a comment