Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 11 of 11

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

  1. #1
    Member
    Join Date
    Dec 2016
    Location
    Buena Park ca
    Posts
    97

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

    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,

    Click image for larger version. 

Name:	OSC.jpg 
Views:	2 
Size:	141.7 KB 
ID:	19885

    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...
    Attached Files Attached Files

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,826
    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";
      }
    
    }

  3. #3
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,949
    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.

  4. #4
    Member
    Join Date
    Dec 2016
    Location
    Buena Park ca
    Posts
    97
    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

  5. #5
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,826
    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)

  6. #6
    Member
    Join Date
    Dec 2016
    Location
    Buena Park ca
    Posts
    97
    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);

  7. #7
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,826
    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.

  8. #8
    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 by wangnick; 04-28-2020 at 03:04 PM.

  9. #9
    Member
    Join Date
    Dec 2016
    Location
    Buena Park ca
    Posts
    97
    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 Code:
     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.
    Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	Scope.jpg 
Views:	1 
Size:	130.5 KB 
ID:	19893  

  10. #10
    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

  11. #11
    Member
    Join Date
    Dec 2016
    Location
    Buena Park ca
    Posts
    97
    Sebastian

    Thanks a lot, I will explore the IntervalTimer.h

    Here

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •