It seems that sprinf is a blocking function? what to do?

Status
Not open for further replies.

laptophead

Well-known member
I am building a robot with 8 motors and I am sending to a PC where I have a GUI all kind of data. 112500 baud,

8 encoders, 8 current readings and some other variables. I was counting on my 3.6 to be fast enough to do it, but then I read that serial print or write is blocking...

In order not to block my processes I send 3 strings at 3 different times.

Code:
  if (millis() - Transmission_Millis > 100)
  { Transmission_Millis = millis();

    TransInc++;  // transmission incrementor

    if (TransInc == 1)
    { sprintf(data_out, "%s  Step: %d   Compl A: %d Compl B: %d   Send Once:%d   First: %c   Show Gaps:%d ",
              ModeStr, StepNo, Completed_A, Completed_B, SendOnce, First, Show_Gaps);
     Serial5.println(data_out);
    }

    else if (TransInc == 2)
    { sprintf(data_enc, "Encs: M1:%.1f,  %.1f,  %.1f,   %.1f,  B:  M11:%.1f,  %.1f,  %.1f,  %.1f,  ",
              Mot_Ang_Read[1], Mot_Ang_Read[2], Mot_Ang_Read[3],  Mot_Ang_Read[4],  Mot_Ang_Read[11], Mot_Ang_Read[12],
              Mot_Ang_Read[13], Mot_Ang_Read[14]);
     Serial5.println(data_enc);
    }


    else if (TransInc == 3)
    { float TotalCurrent = ( Mot_Curr[1] + Mot_Curr[2] + Mot_Curr[3] + Mot_Curr[4] + Mot_Curr[11] + Mot_Curr[12] + Mot_Curr[13] + Mot_Curr[14]);
      sprintf(data_currents, "M.Curr: M1:%.1fA  %.1fA  %.1fA  %.1fA   B: M11:%.1fA  %.1fA  %.1fA  %.1fA, Volts:%.1fA   %.1fA Total,",
              Mot_Curr[1], Mot_Curr[2], Mot_Curr[3], Mot_Curr[4] , Mot_Curr[11], Mot_Curr[12], Mot_Curr[13], Mot_Curr[14], Volts_B, TotalCurrent );
      Serial5.println(data_currents);
      Serial.println(data_currents);
      TransInc = 0;
    }


In the background, I have a routine that is reading encoders every 5 mS. I need it in order to know what the robot is doing. I need it fast in order to balance it, shift weight from one leg to another.... etc

I am discovering that my sprintf is blocking my reads and some other periodic processes.
The scope shows a gap in my readings. The gap disappears if I suspend the Print,

OSC.jpg

The encoders are read on other Serials, (2 and 3).

What is a wiser way to handle this?
Thanks

I only included the main file, there are another 5 full of functions...
 

Attachments

  • RS485_Command_20_Enc_Curr.ino.ino
    14.3 KB · Views: 47
Can you find a pin to set HIGH on _isr() entry and set low on exit - and show that on the scope?

This code for the isr() looks like it will sit 1 ms up to 14+ms on a motor stop? And doing printing from the _isr() too:
Code:
void Run_Pause_Mode_ISR()
{ delay (1); // for debouncing
  Serial.println (ModeStr);
  if (digitalReadFast (WaypMode_Pin) == 0) // Manual Mode ON _ Calibration and ARM manual move are HERE
  { Mode = 0;
    StepNo = 0;
    for (int i = 1; i <= 13; i++) {
      Motor_Stop(i); // stop 8 motors for chanel A
      delayMicroseconds(1050);
    }
    Serial.println ("everybody Stop");
    ModeStr = "WAY P";
  }
  else if (digitalReadFast (RunMode_Pin) == 0) // run
  { Mode = 2;
    //StepNo = 10;
    ModeStr = "RUN";
  }
  else  // This is Pause both pins are 1s
  { Mode = 1;
    StepNo = 0;
    ModeStr = "PAUSE";
  }

}
 
I am doubting that sprintf is blocking function.

HOWEVER Serial.println or Serial5.println can be.
That is these functions will wait until they can place the last part of your print data into their output queue. So if the queues are full it will wait until there is room.


Note: with many cases here you are doing this in two steps when it could be one...
That is:
Code:
{ sprintf(data_out, "%s  Step: %d   Compl A: %d Compl B: %d   Send Once:%d   First: %c   Show Gaps:%d ",
              ModeStr, StepNo, Completed_A, Completed_B, SendOnce, First, Show_Gaps);
     Serial5.println(data_out);
could be the same thing as:
Code:
{ Serial5.printf("%s  Step: %d   Compl A: %d Compl B: %d   Send Once:%d   First: %c   Show Gaps:%d ",
              ModeStr, StepNo, Completed_A, Completed_B, SendOnce, First, Show_Gaps);
However in your case you might be able to use it to your advantage.

That is you could do your sprintf to your variable data_out. You could then get the strlen of it and save it away.

You can then use the method availableForWrite(), like:
Code:
int count_out = Serial5.availableForWrite();
And only output at most that number of bytes this time through.

Then if you saved these counts and data away, on the next call, you can check if you still have more bytes to output, and if so again see how many bytes you can output this time, and then
call Serial5.write(ptr, cnt); that many bytes and again remember where you are in the output array and how many more bytes to go...

Hope that makes sense.
 
Dear defragster

The ISR is rarely used, it is a switch that I flip sometimes. The blocking happens regardless...

Kurt

I tried Serial5.printf but it won't print out on ser 5 . it works for the serial monitor,
Or maybe is not terminated properly. How do I do that? NL or CR at the end?

My Processing is receiving like this:

IncomingStr = BTPort.readStringUntil(lf);

Thanks

Interestingly:

Code:
  if (TransInc == 1)
    { Serial5.printf(data_out, "%s  Step: %d   Compl A: %d Compl B: %d   Send Once:%d   First: %c   Show Gaps:%d ",
              ModeStr, StepNo, Completed_A, Completed_B, SendOnce, First, Show_Gaps);
      //Serial5.println(data_out);
      Serial.printf(data_out, "%s  Step: %d   Compl A: %d Compl B: %d   Send Once:%d   First: %c   Show Gaps:%d ",
              ModeStr, StepNo, Completed_A, Completed_B, SendOnce, First, Show_Gaps);
    }

When I do this , not even the serial monitor gets the message.
If I only do the Serial.printf(etc. I get the data, but not Line feed terminated.

?!?!?!
Thanks
 
What is printed is what is provided with .printf - no 'implied' <CR> or <LF> will be sent. Using .prinln() appends one of each.

\n or \r must be added as needed to the .printf("%u \n ",xxx)
 
never mind , I figured it. Silly me. I had a long day, sorry.

for everyone:

Serial5.printf( "%s Step: %d Compl A: %d Compl B: %d Send Once:%d First: %c Show Gaps:%d \n",
ModeStr, StepNo, Completed_A, Completed_B, SendOnce, First, Show_Gaps);
 
So does that resolve the apparent blocking? Or just make the prints get processed?

KurtE said what I was thinking - that's why I pointed out the _isr() - no apparent reason for sprint() to block - but a full 115Kbaud Serial5 will on printing.
 
Hi laptophead,

Your longest output string is "M.Curr: M1:%.1fA %.1fA %.1fA %.1fA B: M11:%.1fA %.1fA %.1fA %.1fA, Volts:%.1fA %.1fA Total," which is about 100 characters long (depending a bit on the number of digits required for the motor current values that you are formatting).

At 115200 baud one bit is 8.7μs long (1000000/115200). I guess you are running with no parity and 1 stop bit, that makes 1 start bit plus 8 data bits plus 1 stop bit = 10 bits per character sent on the serial line, so ~90μs. Your 100 characters above therefore need about 9ms to be transmitted.

In Arduino/hardware/teensy/avr/cores/teensy3/serial5.c you find:
Code:
#define SERIAL5_TX_BUFFER_SIZE     40 // number of outgoing bytes to buffer

This means that when you call Serial5.println() with a 100 character string, the println() function must busy wait for all the bits of the first 60 characters of the string to be pushed one by one onto the wire, such that then the remaining 40 fit into the transmit buffer and the function can return, then continuing to empty the transmit buffer onto the wire by interrupts in the background. So it will block 5.4ms and then return.

If your routine reading the encoders every 5ms is not triggered by interrupts but rather by time passing from the main loop, then indeed it will be compromised by the Serial5 output above.

You can address this in multiple ways. For one you could limit the output to the Serial5 transmit buffer size and rather perform the output more often, considering that a full 40 character transmit buffer is emptied at 115200 baud in about 4ms. You could also increase the transmit buffer size in the Arduino low-level code. Or you could build your own buffer and feed Serial5 from it when becoming availableForWrite. You could also perform your crucial encoder reading in an interrupt routine.

Kind regards,
Sebastian
 
Last edited:
My longest string looks like this
94 lenght
M.Curr: M1:1.6A 0.7A 0.5A 0.0A B: M11:1.8A 0.2A 0.2A 0.0A, Volts:15.8A 4.9A Total,

so you're right Sebastian, it is longer than 40.

So I went and used
#define SERIAL5_TX_BUFFER_SIZE 120

I am also using Serial5.printf instead of sprinf and serial print

Things got better but not perfect. You can see on the scope how there is a delay occasionally.

About triggering my encoder readings.

it is done like this:

HTML:
 if (millis() - ReadEncMillis > 3) {
    ReadEncMillis = millis();

    if ( ReadENC_Master == 1  )

        Read_All_Enc();
    }
then we go:

   if (EncPair_Incr == 1)
    { Read_Enc (1);// on Ser 2
      Read_Enc (11); // on Ser 3

    }

    else if (EncPair_Incr == 2)
    { Read_Enc (2);
      Read_Enc (12);
      // IMU.readSensor();
    }

    else if (EncPair_Incr == 3)
    { Read_Enc (3);
      Read_Enc (13);
    }

    else if (EncPair_Incr == 4)
    { Read_Enc (4);
      Read_Enc (14);

    
      
      if (DispEnc == 1)
        Show_Encs();
    }


  } // end of else


The read encoder function is composing a hex string

void Read_Enc ( uint8_t  motor_id) // chapter 6

{
  if (motor_id <= 10)
  {
    //16383 steps per turn
    byte cmd_send_buf[5];
    cmd_send_buf[0] = 0x3E;  //header
    cmd_send_buf[1] = 0x92;   // cmd_id-- Read enc MULTI TURN IS 92 - SINGLE TURN IS 90
    cmd_send_buf[2] = motor_id;
    cmd_send_buf[3] = 0x00;      // data lenght
    cmd_send_buf[4] = cmd_send_buf[0] + cmd_send_buf[1] + cmd_send_buf[2] + cmd_send_buf[3]; // check sum

    Serial2.write (cmd_send_buf, 5);  ///ch A


  }
  else  // this is for ch B, Serial3
  {
    byte cmd_send_buf[5];
    cmd_send_buf[0] = 0x3E;  //header
    cmd_send_buf[1] = 0x92;   // cmd_id-- Read enc
    cmd_send_buf[2] = motor_id - 10;
    cmd_send_buf[3] = 0x00;      // data lenght
    cmd_send_buf[4] = cmd_send_buf[0] + cmd_send_buf[1] + cmd_send_buf[2] + cmd_send_buf[3]; // check sum
    Serial3.write (cmd_send_buf, 5);
    //SentTime = millis();
    EncPair_Incr++;
    if (EncPair_Incr == 5)
      EncPair_Incr = 1;
    //Serial.println (EncPair_Incr);

  }


}

Then I decode the response and generate angles, etc

Is there a smarter way to read those?

Is there a software interrupt available on teensy?

Thanks a lot everyone.
 

Attachments

  • Scope.jpg
    Scope.jpg
    130.5 KB · Views: 69
Hi laptophead,

the TX buffer size increase should now avoid the TX buffer ever filling up completely and therefore Serial5.write() should not take longer anymore than the copying into the TX buffer takes. This should therefore not lead to significant main loop stutter anymore.

But there may be other places in your code where some processing triggered from the main loop takes longer than a few microseconds and thereby introduces the jitter that you see ever so often on the scope.

One of these places might well be the encoder reading code. Unfortunately the excerpt above shows only the sending of the read command to the encoder, but not the reading of the reply which might well be spinning waiting for the answer.

You see, your code is partially event driven but then again in big parts sequential even in places where busy waiting (in library code) is looming, and this is in conflict with your quasy-real-time needs.

I guess what would help you to understand what's going on is if you would print to Serial in the main loop the amount of time the diverse processing that you perform takes, for instance Read_All_Enc.

Now, you can workaround using software interrupts via the IntervalTimer functions of Paul (see Arduino/hardware/teensy/avr/cores/teensy3/IntervalTimer.h), and then get into the business of synchronising access to shared volatile variables etc. But if I was you I would not do that, and rather attempt to write the code fully event driven, using little state machines to avoid each and every busy wait. Anyway, both approaches will learn you a lot.

Kind regards,
Sebastian
 
Status
Not open for further replies.
Back
Top