LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

Xmodem calls do not yield to system and UI events

Need to make a correction in one of my above posts ---

 

BTW... I already looked at the multi-threading examples you suggested ... 

Actually, I looked at the examples that came with the CVi developer ... not the links posted.  

I found one of the links very useful ......  http://zone.ni.com/devzone/cda/tut/p/id/3663#toc2

The other links, not

0 Kudos
Message 11 of 22
(1,784 Views)

Need to make a correction in one of my above posts ---

 

BTW... I already looked at the multi-threading examples you suggested ... 

Actually, I looked at the examples that came with the CVi developer ... not the links posted.  

I found one of the links very useful ......  http://zone.ni.com/devzone/cda/tut/p/id/3663#toc2

The other links, not so

0 Kudos
Message 12 of 22
(1,785 Views)

Need to make a correction in one of my above posts ---

 

BTW... I already looked at the multi-threading examples you suggested ... 

Actually, I looked at the examples that came with the CVi developer ... not the links posted.  

I found one of the links very useful ......  http://zone.ni.com/devzone/cda/tut/p/id/3663#toc2

The other links, not so much

0 Kudos
Message 13 of 22
(1,788 Views)
I apologize for the many extraneous posts (these were somehow created by my 3 year old son banging on my keyboard while I was off getting a cup of brew... )  There doesn't seem to be a way to remove them... if any one knows how, please feel free to zap them or let me know how to do it... I'm a bit new to this forum.
 
To the matter at hand, I was able to get the thread calls working using the example provided by Mert and by referencing the information links provided by Jervin....   the file transfer time increased by apx 120 seconds (I'm uploading a 2 meg file to my target).  But the extra time is to be expected. 
 
Strange enough, I now seem to be unable to pass variables into the function (without using globals, which seems like another problem waiting to happen) .. I did see the information about locking variables and such... but I'd rather not have to create globals to pass variables into a private function....   and... I'm also having to set a global when I exit the thread because the thread does not seem to return the value I would expect (i.e. error codes) ....  
 
I did not see any information on how to pick up the return code.  You can get the gist of what I am doing in the code extract below. I got around the issue (for now) by using globals... but, as I mentioned, I'd rather not do that....
 
Cheers
 
 
 

double X_Modem_UI_RW (int com_port_ref, char *upload_file_name, int size_of_packet)
      int    threadFuncID;
 
 
      //setting globals from the function's passed variables here
      X_Modem_Com_Port = com_port_ref; 
      packet_size      = size_of_packet;
      X_Modem_File_Name= upload_file_name;

    
      CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE, SendFileThreadFunc, 0, &threadFuncID);
      x_error = CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE,threadFuncID,    OPT_TP_PROCESS_EVENTS_WHILE_WAITING);

      return (x_error);
}
 

int CVICALLBACK SendFileThreadFunc(void *functionData)  
{
   
 // calls to xmodem here - just onle line of code here for clarity

      X_Modem_Err = XModemSend (X_Modem_Com_Port, X_Modem_File_Name);

      return (X_Modem_Err);
      // the return value does not get passed back
      // to the caller "CmtWaitForThreadPoolFunctionCompletion"
      // what happens to the return value ?
}
0 Kudos
Message 14 of 22
(1,769 Views)
To pass data into your thread function, you have to define a struct that stores it all, then pass a pointer to that struct as the callbackData parameter of the thread scheduling function (i.e. CmtScheduleThreadPoolFunction). Like this:

typedef struct
{
  int port;
  int packet_size;
  char *filename;
} XModemThreadData;

...
XModemThreadData cbData = { com_port_ref, size_of_packet, upload_file_name };
CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE, SendFileThreadFunc, &cbData, &threadFuncID);
...

Like most functions, CmtWaitForThreadPoolFunctionCompletion returns a code regarding its own status, not the function that it's waiting for. To get the return value of the thread function, you use CmtGetThreadPoolFunctionAttribute and pass ATTR_TP_FUNCTION_RETURN_VALUE.

Also, as a side note, after you're done getting the return value
, you should also call CmtReleaseThreadPoolFunctionID. If you don't, each time you schedule a thread pool function, you'll leak a little memory that the library uses for bookkeeping (e.g. return value, etc) for that thread function.

Mert A.
National Instruments
0 Kudos
Message 15 of 22
(1,756 Views)
Hello Mert    this was pretty easy to implement ...   I have the return codes now and included the release of the thread.  I think I'm all set for now. Thanks for the advice.
 
Cheers ! Smiley Wink
 
 
0 Kudos
Message 16 of 22
(1,750 Views)
I found a new snafu .....   the thread is still not processing other events. 
 
For instance, on all of our P/S GUI screens, there is a timer which looks at the voltage and current during the automation tests.  This is so that if the UUT starts drawing too much current, too little current, has voltage variances, or if even the operator drops a screwdriver between the power rails..   the timer task would kill the power supply. (of course we have current limits set in the programmable supplies for protection, but that's not the point here ...    the point is that the P/S timers (and other GUI timers) are part of a safety mechanism... we can't have them running at arbitrary times...   and we sure can't sit for five to seven minutes without knowing what the power supply is doing...  sure, it will current limit, but, in this case, the unit could be drawing an amp or more, cook itself, and still never turn off during the upload process. These are units that have never been tested and the first active test, after a cursory P/S check, is to program the parts.... it's more than a possibility that parts will fail during this time....
 
On the xmodem upload screen I placed a "progress bar" (slide numeric) and simply have a timer increment the value every second...  for all practical purposes, it's just an indication that "something" is happening so the operators don't think the system locked up.... however, even though the thread says to process events, the timer is not running and is not updating the progress bar....
 
No timers in any part of any GUI that I have seem to be operating when the xmodem upload is running ...   but, of course, if I continually move the mouse, everything starts ticking (but very slowly and  not at the correct speed) ...    the problem is that I can't even force the screens to update ... even when other GUI screens are running timers that tell the CVI to process system and draw events...  
 
So, I'm back to the original problem ...   the xmodem is not giving up time to other GIU processes....  or at least, not enough time.
 
Suggestions ?
 
 
 
 


Message Edited by San Jose Test Engineering on 06-02-2008 01:19 PM
0 Kudos
Message 17 of 22
(1,717 Views)
I'm not sure I understand your program structure very well. Typically a program's main thread controls all the UI elements by 1) loading or creating the panel(s), 2) displaying them, and 3) running a message processing loop (RunUserInterface, or a loop with ProcessSystemEvents). As long as control/timer callbacks do not take too long before returning, the UI should be responsive and update well. You say "other GUI screens are running timers that tell the CVI to process system and draw events," which suggests a potentially problematic program structure. You really don't want to be calling ProcessSystemEvents from timer (or other control) callbacks, because it can result in reentrancy and quirky behavior.

Basically, you want to have your main (UI) program thread always processing events. "Processing events" consists of calling callback functions as they are triggered. These callback functions should be short, and return back to the processing loop quickly. If they need to perform a time-consuming task (like an xmodem file transfer), they spawn a separate thread to do that work and either return back to the processing loop without waiting for the thread to complete, or wait for the thread with CmtWaitForThreadPoolFunctionCompletion which can process events while it waits.

I'm attaching a simple program that has a timer firing every .1 sec and incrementing a numeric control. There is a button that spawns a thread that just does a 5 second busy wait (Delay). I would try to use this structure as a model.

Mert A.
National Instruments
0 Kudos
Message 18 of 22
(1,709 Views)
Ok..   too much complex thinking  here...   let me see if I can make this a bit more obvious : .
 
 
In a SINGLE GUI Panel...    (no other panels, no additional code, and I'm running the P/S manually...   so the only code that is executing is the main (having executed RunUserInterface and having already loaded and displayed the panel. The "program" is  event driven by clicking a command button ... (i.e. nothing happens until a commit event)
 
The command button calls a function (X_Modem_UI_RW)  and passes a few variables (the actual function is an EXTERN as we want other things to call the function from other GUIs (not going into details as to why here)
 
X_Modem_UI_RW does this
  • sets a desired max value to a slider (the slider is used as a progress bar)
  • starts a timer which ticks every second (to increase the slider bar on each tick)
  • sets up and calls the threaded code which begins the xmodem upload
  • the threaded code is supposed to process system events here while waiting to complete so the timer should tick
  • after the thread returns, the timer is stopped
  • the thread return code is passed back to the caller

end of X_Modem_UI_RW

 

The Timer simply ticks every second and does this :

  • Gets the current value of the slider
  • Gets the max value of the slider
  • if the current value of the slider == max value, set slider value to zero

end of timer

When running the panel, and after clicking the command button, the timer starts, the xmodem starts uploading.. as soon as the upload starts, the timer stops updating the slider. When the upload finishes, the timer will start again (but then in the next millisecond, resets to 0 and is disabled - so no use there).....   and if, while the upload is in progress, the mouse or any other "non CVI" program is used, then the timer does update the slider values, but not each second... more like hit and miss...   the more events I manually generate by moving the mouse, the better the tick resolution...  if the mouse is not moved at all, the timer simply does not update the slider...   this is "proved" (using the word loosely here) by seting a breakpoint at the processing section of the timer loop...  

I set up the break point, run the program, and stop moving the mouse... the upload starts and continues, the timer never hits the break point....    so I'm pretty well satisfied that the timer is not being called (for whatever reason) .....

As far as i can the code is simple, straightforward, and there is no reason the timer should not be ticking (causing events)....

 

here's the relevant code..... it looks a little funny because of the HTML formatting .. sorry.
 
 
 
double X_Modem_UI_RW (int com_port_ref, char *upload_file_name, int size_of_packet, int ticks)
 int     threadFuncID     = 0;
 int   Thread_Ret_Value = -9999; // dummy return value set as a default for debug purposes
 
 
 SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM, ATTR_MAX_VALUE, ticks);
 SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_TIMER_Count_Down, ATTR_ENABLED, 1);
 
 X_Modem_Com_Port = com_port_ref;
 packet_size      = size_of_packet;
 X_Modem_File_Name= upload_file_name;
   
 CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE, SendFileThreadFunc, 0, &threadFuncID);
 CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE,threadFuncID, OPT_TP_PROCESS_EVENTS_WHILE_WAITING);
 CmtGetThreadPoolFunctionAttribute (DEFAULT_THREAD_POOL_HANDLE, threadFuncID,ATTR_TP_FUNCTION_RETURN_VALUE, &Thread_Ret_Value);  
 CmtReleaseThreadPoolFunctionID (DEFAULT_THREAD_POOL_HANDLE, threadFuncID);

 SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_TIMER_Count_Down, ATTR_ENABLED,  0 ); 
 SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM,     ATTR_CTRL_VAL, 0 ); 
  
 return (Thread_Ret_Value);
}
 
 
 
int CVICALLBACK CB_Count_Down (int panel, int control, int event,
  void *callbackData, int eventData1, int eventData2)
{
 
 static int current_tick;
 static int max_tick;
  switch (event)
  {
   case EVENT_TIMER_TICK:
     GetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM, ATTR_CTRL_VAL,  &current_tick);   
     GetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM, ATTR_MAX_VALUE, &max_tick);
   
     if ( current_tick < max_tick )
     {
      SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM, ATTR_CTRL_VAL, (current_tick+1) );     
     }
     else
     {
      SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM, ATTR_CTRL_VAL, 0 );     
     }
   
   break;
  }
 
 return 0;
}
 
 
0 Kudos
Message 19 of 22
(1,704 Views)
After doing a little digging, I've been able to confirm what you're seeing. It turns out this is a problem that was previously reported on this forum, and is already fixed in the development build of CVI which is on my computer. That's explains why the sample I posted works for me, but surely doesn't for you.  You can refer to the linked discussion for details, but basically the OPT_TP_PROCESS_EVENTS_WHILE_WAITING is not equivalent to ProcessSystemEvents and actually relies on Windows events to trigger processing. RS-232 and most UI events have associated Window messages, so those events get processed regularly, but the timer events do not unless another trigger (moving the mouse) initiates processing.

So, until this is fixed properly in the next release, you have a couple options for workarounds.

1. Asynchronous Timers.
In the toolbox directory, you'll find asynctmr.fp, which is a library for Asynchronous Timers. These timers are similar to the standard UI library timers, except that they basically process their own events. On a regular interval, they call your registered callback function from a separate thread (which is implicitly created/destroyed along with the timer). To use async timers, just add asynctmr.fp to your project. You can use an async timer instead of your regular UI timer, and all should work well:

double X_Modem_UI_RW (int com_port_ref, char *upload_file_name, int size_of_packet, int ticks)
{
    ...
    int asyncTimerID;
    SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM, ATTR_MAX_VALUE, ticks);
    asyncTimerID
= NewAsyncTimer(1.0, -1, 1, CB_Count_Down, NULL);
    ... /* Thread creation/waiting code */
    DiscardAsyncTimer(asyncTimerID);
    SetCtrlAttribute (Panel_X_Modem, UI_X_Modem_NUM_SLIDE_XM,     ATTR_CTRL_VAL, 0 );
    ...
}

2. ProcessSystemEvents loop.
Another option is to do exactly what I said earlier to avoid. 🙂 You can basically simulate CmtWaitForThreadPoolFunctionCompletion by doing this:

static int gFileXferComplete = 0;

double X_Modem_UI_RW (int com_port_ref, char *upload_file_name, int size_of_packet, int ticks)
{
    ...
    gFileXferComplete = 1;
    return (Thread_Ret_Value);
}


double X_Modem_UI_RW (int com_port_ref, char *upload_file_name, int size_of_packet, int ticks)
{
    ...

    gFileXferComplete = 0;
    CmtScheduleThreadPoolFunction(DEFAULT_THREAD_POOL_HANDLE, SendFileThreadFunc, 0, &threadFuncID);
    while (!gFileXferComplete)
       ProcessSystemEvents();
   
/* Note the following call is still necessary, because it ensures that getting the return value will succeed. */
    /* There is a race condition that could otherwise cause
CmtGetThreadPoolFunctionAttribute to be called before the thread function exits. */
    CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE,threadFuncID, 0);
    CmtGetThreadPoolFunctionAttribute (DEFAULT_THREAD_POOL_HANDLE, threadFuncID,ATTR_TP_FUNCTION_RETURN_VALUE, &Thread_Ret_Value);  
    CmtReleaseThreadPoolFunctionID (DEFAULT_THREAD_POOL_HANDLE, threadFuncID);
    ...
}

Again, I'm sorry for the bumps along the way. If you still have questions, let me know.

Mert A.
National Instruments

0 Kudos
Message 20 of 22
(1,698 Views)