This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

For loop exiting before completing all tasks - how to diagnose?

Hi all,

I'm having an intermittent issue and wanted to get some help figuring out how to go about debugging. I've pasted the shortest extent of code I could to try and simplify the issue, so I imagine there will be some questions to help me figure this one out. Here's my setup:

TM4C123GXL connected to SIM900 GSM cell phone module via UART1. Programmed using Keil uv4

When a text message comes in, the UART1 interrupt handler increments counter msgCount.

The while loop (excerpt below) in my main program then triggers message processing. If the message is four bits and comes from the right sender, the program then toggles four relays on or off as appropriate.

I also have an ADC running as part of this while loop. Currently, the ADC is just printing the value to the screen, but eventually it will also be used to trigger actions with the relays.

In my testing, I am finding that occasionally when two or three messages come in short succession, the proper relay state is not applied. The first one or two relays states will be changed, but not the last ones. You can see I added an additional condition to check and make sure that the relay state matches the most recent message, but even still it fails. When I disable the ADC, I don't see this problem.

I believe at least one of these three things is somehow causing this problem:

First, receiving and processing a messages takes a few seconds, due to slow response of GSM module, and error checking of the message.

Second, toggling relays also takes a bit of time, and I have to update relay state to EEPROM and an external display, which takes some time.

Finally, my ADC function includes a 250ms time delay in order to make the ADC value readable (the numbers flash too fast on the display to read them otherwise).

My hunch is that these time delays are somehow causing a problem with my code (as evidenced by the fact that when I take the time delay out of the ADC the problem goes away).

Here's the code:

while(1)
{
	// Process new messages - an interrupt will increment msgCount when a new message comes in
	if (msgCount > 0)
	{
		// Start working on the oldest message
		msgOpen = msgCount;
		msgCount--;
		
		// Process message for envelope and content
		GSMprocessMessage(msgOpen);
		
		// If message content is good, act on message. A "good message" is four characters, each
		// 0 or 1, and from a specific sender.
		if (strstr(msgSender,ctrlID) != NULL && strlen(msgContent) == 4) {
			for (ctr1=0;ctr1<4;ctr1++){
				if ( msgContent[ctr1] == '0' && relayStatus[ctr1] == 1 ) { relayOff(ctr1+1); }
				else if (msgContent[ctr1] == '1' && relayStatus[ctr1] == 0 ) { relayOn(ctr1+1); } 
			}
		}
	}
	// When there are no new messages, verify relays are in the right state
	else if ( msgCount == 0 ) {
		for (ctr1=0;ctr1<4;ctr1++){
			if ( msgContent[ctr1] == '0' && relayStatus[ctr1] == 1 ) { relayOff(ctr1+1); }
			else if (msgContent[ctr1] == '1' && relayStatus[ctr1] == 0 ) { relayOn(ctr1+1); } 
		}
	}
	// Run the ADC
	if ( testADC && msgCount == 0 ) { runADC(); }
}

Any help is greatly appreciated!

-- Lee

  • Does not KISS suggest that you simply test/verify your ADC code "free" from the (other) confounding influences?

    Your use of an ADC time delay - to make a display more readable - cannot result from your deep thought & planning.   (from your time here - we know you're far smarter than to "walk that sub-optimal" path...)  (i.e. don't poison the ADC's well - "freeze" the display for viewing by less brutal means!)

  • Yes, that's a good point... however I haven't yet verified if removing the ADC processing fixes the problem. I'm concerned because I always see the problem when the ADC is running, but of course it's harder to prove that it never happens when the ADC isn't running.

    As to the time delay in ADC code... I hadn't thought about it much, but I agree this is sub-optimal. Sadly, the code is from TIs example for single-ended ADC:

        while(1)
        {
            //
            // Trigger the ADC conversion.
            //
            ADCProcessorTrigger(ADC0_BASE, 3);
    
            //
            // Wait for conversion to be completed.
            //
            while(!ADCIntStatus(ADC0_BASE, 3, false))
            {
            }
    
            //
            // Clear the ADC interrupt flag.
            //
            ADCIntClear(ADC0_BASE, 3);
    
            //
            // Read ADC Value.
            //
            ADCSequenceDataGet(ADC0_BASE, 3, pui32ADC0Value);
    
            //
            // Display the AIN0 (PE7) digital value on the console.
            //
            UARTprintf("AIN0 = %4d\r", pui32ADC0Value[0]);
    
            //
            // This function provides a means of generating a constant length
            // delay.  The function delay (in cycles) = 3 * parameter.  Delay
            // 250ms arbitrarily.
            //
            SysCtlDelay(SysCtlClockGet() / 12);
        }

    I assume using a timer interrupt would be a preferable way to implement this? Or perhaps some sort of filter to only update the display for larger swings in value?

  • I figured out an easy solution... change that first if (msgCount>0) {...} to while(msgCount>0){...}. This prevents the ADC from running until there are no more new messages, which is the only time I care about it anyway.
  • You should almost certainly re-structure that so there is no need for explicit delays. This is a rather brittle piece of code.

    Robert
  • Hi Robert,

    Thanks for the feedback. Would you recommend running a timer? The main reason for the delay is to make the number readable - but lowering resolution (less digits) could also accomplish that.
  • That would be one way to start Lee, but don't approach it as a patch job.

    Better to redesign your approach I think.

    You currently have multiple relatively independent processes. I see

    • Serial I/O
    • Communication Protocol
    • Command Parsing
    • Relay driving
    • A/D running
    • Display

    Some of these could be combined or re-arranged.  Especially since I don't know your application.

    However, just to focus a bit. From what you've showed it appears that the A/D and display are independent of the rest. Quite likely that will change but for now it's a place to start.

    There is no reason the A/D and display update need to be synchronous with the communications and relay. So let's run them separately (I'll get to how later). Also given that your display is much slower than what the A/D capable of you do not necessarily need to run them at the same speed or indeed synchronously. If you filter the A/D results you actually improve the stability of the results (not necessarily the accuracy). If the A/D is enough faster than the display you may be able to run them independently since the difference between successive results is too small to matter.

    So alternative 1 for A/D and display

    A/D runs in it's own process along with filtering.  This filter puts it's results into a global location/mailbox to be read by other processes

    The display runs in an independent process and periodically reads the filtered values and displays them.

    Alternative 2 for A/D and display

    Slightly more complex, the A/D process is synchronized with the display process.

    The A/D runs in it's own process along with any filtering. The filter may make it's results available as previously but every n'th conversion it triggers the display process. Maybe passing the updated results through a queue.

    So how to run this as separate processes?

    You could use an RTOS. I'd suggest doing that at some point.

    There is a simpler way to start with. It's usually called a super-loop

    In pseudo code something like

    while(TRUE) {

         run_ad()

         run_display()

        ....

         }

    run_ad becomes something like

    if time() > last_ad_time + ad_period {

          do_conversion()

          filter_conversions()

          last_ad_time = time()

         }

    and run_display becomes something like

    if time > last_display_time + display_period {

        update_display()

        }

    There are multiple approaches that range from this simple method to a full RTOS, each applicable to its own area of use.

    Robert