Shop OBEX P1 Docs P2 Docs Learn Events
Is there a thread-safe printf to use from LMM? — Parallax Forums

Is there a thread-safe printf to use from LMM?

mindrobotsmindrobots Posts: 6,506
edited 2012-06-28 23:58 in Propeller 1
Playing with more blinky tutorials (picking up where I left off when the real world interrupted!)

This code works like a champ on my C3 as it is:
#include <stdio.h> #include <propeller.h>              
 #include "pin.h" 
   // propeller-load variable patching based on *.cfg content - see blinky2.c for details
  // int _cfg_led_pin = -1;          // The value will default to -1 if not patched by the loader
 _cfg_led_pin = 15;
  // setting up the stack for the cogstart demo - cogstart is the feature being explained in this demo
  #define MINIMUM_LMM_STACK 160   // MINIMUM stack in bytes to accomodate thread control structure (40 LONG)
 #define COG1_LOCAL_VARS 0      // number of local variable in LMM thread (4 bytes / Interger) 
 //#define COG1_LOCAL_VARS 20  // used to give stack space when testing printf within do_blink                               
  static int cog1_stack[(MINIMUM_LMM_STACK + (COG1_LOCAL_VARS*4))/4];  // allocate space for the stack - you need to set up a 
                                     // stack for each Cog you plan on starting  
  volatile int wait_time;         // a global variable we will change from Cog0 to pass
                                 // delay times to cog1
  /* The do_blink function will be started in it's own Cog and toggle the LED at whatever 
  rate is set by the starting Cog - this is the main code from blink1 and blink2 moved to
  it's own function so it can be run in it's own LMM kernel
  The only difference is that it picks up the delay from the global wait_time variable.
 */
 void do_blink(void *par)  // needs to be defined like this to pass to cogstart
                             // the dummy parameter isn't and can't be used for anything
 {
     int pin = (int)(void *)par;
     // printf("pin: %d\n",pin);
     pinOutput(pin); 
     while (1) {                         
       pinHigh(pin);              
       waitcnt(wait_time + CNT);                                      
       pinLow(pin);                
       waitcnt(wait_time + CNT);          
     }
 }
 /* main runs in Cog0 - it starts a second cog and then enters a loop to change the wait time between 
 the LED toggles. do_toggle picks up this changing value and uses it fo it's waitcnt
 */                                
 int main (void)
 {
     int cog_id;                 // we get the Cog ID (or error) back from the cogstart
                                 // we don't really do anything with it in this program but
                                 // good practice to keep it around
         
                
     wait_time = CLKFREQ / 32;   // initial wait time between toggles
     waitcnt((CLKFREQ/2)+CNT);   // give the terminal time to start
      cog_id = cogstart(do_blink, (void *) _cfg_led_pin, cog1_stack, sizeof(cog1_stack));  // startup do_blink function on a new Cog
                                                                         // NULL - no arguments are being passed
                                                                         // cog1_stack - stack area for the new function
                                                                         // size_of(cog1_stack) - size of stack in BYTES
      // if cog_id is -1, then there was a problem starting the cog
     // just sit in a loop if that happens - otherwise, cog_id is
     // the cog number that was started and both Cogs can continue
     // with their work.
      if (cog_id < 0) {
         printf("Unable to start Cog. Status %d returned.\n", cog_id);
         while (1) {
             }
         }
     else {
         printf("Blink Cog %d has started.\n", cog_id);
          // a little looping to show how the original Cog can change the global
         // wait_time variable and it can then be read by the new Cog doing 
         // the blinking
         int i;
         while (1) {       
             printf("Entering Blink Loop.\n");                  
             for (i=1;i<5;i++){   
                 waitcnt((CLKFREQ * 2)+ CNT);
                 wait_time = wait_time << 1;        
                 if (i == 4) {
                     wait_time = CLKFREQ / 32;
                 }
             }          

         }
     }                        
 }
While debugging, I thought I tried to put a printf inside the do_blink function (this is the function I cogstart). I also increased the stack by 20 longs (100 at one point) to see if it was a stack issue. With the printf in the started function, it printed garbage and probably ran off someplace to hang one or both Cogs. (you can uncomment the printf and the 2nd #define for COG1_LOCAL_VARS if you want to recreate failure)

So the question is can I use stdio from a started LMM cog? It would be nice to be handy to at least be able to print debug info from the COGs you start.

If its not thread-safe, maybe a way to close stdout on the first COG and open it on the started COG?

Of course, then there are follow on questions about how this would work with pthreads in LMM and pthreads in XMM.

Maybe I just really don't want to do something like this! :lol:

Comments

  • jazzedjazzed Posts: 11,803
    edited 2012-06-28 15:52
    Hi Rick,

    If you printf from two or more cogs you must use locks to keep the output of one cog from interfering with the output of another. The same happens in spin, but the locks are hidden in the fullduplex serial object - probably a good example to follow.

    Using pthreads gives more flexibility because the locks are handled for you to a point. The only thing with the pthreads implementation is you can't mix waitcnt with printf. Instead you'll have to use sleep and usleep with printf. Of course with pthreads one has to yield to make sure the COGs aren't being hogged.

    Either way this is an advanced topic, but either should work applying the right practice.
  • mindrobotsmindrobots Posts: 6,506
    edited 2012-06-28 16:15
    Thanks Steve!

    This does sound a bit advanced for tutorial #4 or 5!

    But a good topic!
  • ersmithersmith Posts: 6,097
    edited 2012-06-28 19:32
    Actually the stdio functions (including regular printf, but not the smaller _simple_printf) already have lock handling built in. The main issue is that when printing with the default simple serial driver you end up with two different cogs trying to manipulate the same serial pins, which is a recipe for disaster. The work-around, as Steve suggested, is to use the full duplex serial driver instead, which uses a separate cog to run the serial. This will happen automatically if you link with the pthreads library (add -lpthread to the link command line). Adding -lpthread is probably a good idea for any threaded application, not just ones using pthreads.

    Eric
  • mindrobotsmindrobots Posts: 6,506
    edited 2012-06-28 23:58
    Eric, thanks for the additional info. There's always several ways to skin the same cat!

    This certainly leads to some interesting fodder for demos and tutorials and good underlying info to document.
Sign In or Register to comment.