is there a way to wait for something without interrupt the execution of other code?

512 views Asked by At

i have to write a function that, by calling it only a single time, have to:

  • turn on an output pin

  • the pin stays high for 200mS

  • at the end of the timer the pin need to be low again.

  • the pin stays low for 200mS

  • at the end of the timer the function can be called again.

to turn on and off an output pin I already have wrote and tested the funcions:

outOn(pin_id);

outOff(pin_id);

now, i am trying to write the function that does the above mentioned actions and this is what l've come out with so far:

void outOnT02(enum e_outs ou){
    
    outOn(ou);
    gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2; 
    
    if(gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer) {
        outOff(ou);
    }
}

the function is named outOnT02 because:

  • it is an output;
  • after calling it, the pin became high;
  • T02 because the pin stays high for 0.2 Seconds.

outOn(ou); makes the pin go high,

outOff(ou); makes the pin go low,

gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2; starts a 200mS timer,

and gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer is true when the timer has run out.

it works but, as you can tell, I have to put it in a cycle otherwise gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer will never be true and so,the pin will stay high forever.

this is where i am stuck. i can't use a SLEEP(200); because i can't interrupt the execution of the code.

the language is C, the ide is MPLAB X IDE v6.00, the compiler is XC8 v2.31 and the cpu is a PIC16F15355.

1

There are 1 answers

0
Kozmotronik On

This post is a little old but it is worth to answer since it is both a good question and a common problem. Now this problem is very common in embedded world when we develop applications that has to run on only one CPU. Hence there is no real parallelism in the workflow. Also since the application will not run on top of any OS, there will be no scheduler, no timers, no threads etc. Especially in small scaled microcontrollers there is no way to run many of the true RTOSs.
But this shouldn't be an obstacle for developing applications that runs tasks concurrently. We can develop an application using some tricks so that it runs the tasks concurrently and behave as a small OS. Running concurrently means that no task blocks the CPU using busy waiting checks or something alike but we block a task that needs to wait some event to occur.
When we block a task, the specific data and the next execution point on that task must be preserved so that it can continue from where it should in the next execution. Knowing what we need to preserve helps us to create a thread-like structures that executes until it has to wait some event to occur (eg. time delay). When it has to wait (means that it will be blocked) the next state of it must be preserved and it exits to give the control to the CPU so that it executes other tasks.
When we need to deal with periodic tasks as in the question, it is relatively easier to implement without blocking the CPU execution and meanwhile handle other tasks. Moreover no interrupt usage needed for this type of tasks unless the tasks are extremely time sensitive.
Well, enough with the story part, let's get into it. I will base the examples on the OP's output flashing problem. However the same techniques can be applied for other situations like I/O events, hardware events etc.
Let's sum up the requirement briefly, we have a task that runs atomically. That is, when it is called it must run to completion so that it can be called again (this is what I understand from the OP's requirement):

  • Turns on an output pin for 200ms
  • Then turns off the pin for 200ms
  • Once turned off and 200ms time has elapsed it can be executed again.

Note Some functions in this example are not implemented since they can be application or microcontroller specific.

Task-like Functions

Let's assume we want to schedule the following two task-like functions each of which keeps track of its execution continuation points.
The static cp variables are declared in each function so that they remember where to continue whenever they are called. The content of cp variable will not be destroyed by the compiler when the function returns since we declare it as static. The cp needs to be updated upon the expected events occur in order to proceed to the next step whenever it is called.
Note that in outputTask, the call source must be known to control its atomic behaviour. Since the requirement for this task is that once it triggered or called, it must run to completion. So we have to know where the task is called from, in order it to decide what to do on each call. If it has been triggered from another task, it can't be triggered anymore until it completes its flashing prosess. If it is called from the scheduler (main loop) it knows it is a periodic call and will keep track of the time. This control is achieved using a parameter called periodic. When it is called from the scheduler this parameter must be set to 1, and 0 for the calls other than the scheduler.

/*
 * This task-like function performs what the OP wants to achieve
 */
void outputTask(unsigned char periodic) {

    static unsigned char cp = 0; // Continuation Point holder
    static unsigned char currentMillis;
    
    /*
     * Check whether it is a periodic call or a new output signal call.
     * If it is a periodic call and signalling has been initialized,
     * proceed for time keeping.
     * If it is a new signalling call and the task hasn't completed yet,
     * simply ignore and return.
     */
     if(!periodic && cp != 0) {
        return;
     }
    
    switch(cp) {
        case 0:
            outOn(pin_id); // Turn on the output
            cp = 1; // Next execution point
            currentMillis = 200; // Load the 200ms counter for time keeping
            break;
            
        case 1:
            currentMillis--;
            if(currentMillis == 0) {
                // 200ms time for output high has elapsed, proceed to next step
                outOff(pin_id); // Turn off the output
                currentMillis = 200; // Reload the counter value
                cp = 2; // Proceed to the next step
            }
            break;
            
        case 2:
            currentMillis--;
            if(currentMillis == 0) {
                // 200ms time for output low has elapsed, proceed to next step
                cp = 0; // Last step is done, reset the state for new calls
            }
            break;
            
        default:
            // For anything else, reset the task state to the initials
            cp = 0 // Reset the task state to zero so that it accepts new calls
    }
}


/*
 * Let's say this task will wait for a button press event and will
 * trigger the outputTask upon the event occurs
 */
void outputTriggerTask() {

    static unsigned char cp = 0;
    static unsigned char currentMillis;
    
    switch(cp) {
        case 0:
            if(isButtonPressed()) { // Platform specific function
                // A button press has been detected, debounce first
                currentMillis = 50;
                cp = 1; // Next step, check for the elapsed time
            }
            else {
                break;
            }
            
        case 1:
            currentMillis--;
            if(currentMillis == 0) {
                // Check whether the button press is consistent
                if(isButtonPressed()) {
                    // Yes still consistent, handle the button press by triggering the output task
                    outputTask(0); // Not a periodic call
                    cp = 2; // Next step is to check whether button is released
                }
                else {
                    cp = 0; // Reset the task state
                }
            }
            break;
            
        case 2:
            if(isButtonReleased()) { // Platform specific function
                currentMillis = 50; // Reload the time counter
                cp = 3;
            }
            else {
                break;
            }
            
        case 3:
            currentMillis--;
            if(currentMillis == 0) {
                // Check whether the button release is consistent
                if(isButtonReleased()) {
                    // Yes still consistent, handle the button release if needed
                    cp = 0; // Reset the task to its initial state
                }
            }
            break;
            
        default:
            cp = 0; // Reset to initials
    }
}

Scheduling Approches

The following approches are for non RTOS small embedded systems. They are suitable for wide range of 8-bit microcontrollers.

Approach 1 - Create Delay Based Timebase to Schedule Tasks

Scheduling using CPU blocking delay is suitable for hobby and educational purposes while it is not suitable for real projects. This example uses a platform specific delay_ms function (or can be a macro) to create a 1ms heartbeat for the application so that the tasks can keep track of time.

void main(void) {
    
    systemInit(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // The first thing we do is to create a 1ms timebase using delay.
        // This is the heartbeat for the application
        delay_ms(1000); // Platform specific function
        
        // 1ms has elapsed check the tasks
        outputTriggerTask(); // Check whether any button press event has occured
        outputTask(1); // It is a periodic call for the output task
        // Maybe more tasks go here...
    }
}

Approach 2 - Create Hardware Timer Based Timebase

void main(void) {
    
    systemInit(); // Platform specific function
    // Setup a hardware timer for 1ms overflow without interrupt
    initTimerForOneMs(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // Wait for the timer to overflow
        while(!isTimerOverflow()) // Platform specific function
            ;
        // Timer has overflowed, reload and check tasks
        reloadTimer(); // Platform specific function
        
        // 1ms has elapsed check the tasks
        outputTriggerTask(); // Check whether any button press event has occured
        outputTask(1); // It is a periodic call for the output task
        // Maybe more tasks go here...
    }
}

Approach 3 Put the Processor to Sleep for 1ms Timebase

void main(void) {
    
    systemInit(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // Put the Processor to sleep along with a watchdog timer to wake it up
        clearWatchdogTimer(); // Platform specific function
        sleep(); // Platform specific function
        
        // CPU slept for 1ms and woke up, handle the periodic tasks
        outputTriggerTask(); // Check whether any button press event has occured
        clearWatchdogTimer(); // Platform specific function
        outputTask(1); // It is a periodic call for the output task
        clearWatchdogTimer(); // Platform specific function
        // Maybe more tasks go here...
    }
}

And Last But not Least Time Checking Approach

In this approach the tasks will be keeping the time by checking better say comparing the elapsed time to the desired time to delay tasks without blocking the CPU. For this, we will need to use a free running timer. This will be like the millis function of the Arduino API.

Rewriting the Tasks for the Time Checking Approach

/*
 * This task-like function performs what the OP wants to achieve
 */
void outputTask(unsigned char periodic) {

    static unsigned char cp = 0; // Continuation Point holder
    static unsigned short currentMillis; // 16 bit millisecond holder
    
    /*
     * Check whether it is a periodic call or a new output signal call.
     * If it is a periodic call and signalling has been initialized,
     * proceed for time keeping.
     * If it is a new signalling call and the task hasn't completed yet,
     * simply ignore and return.
     */
     if(!periodic && cp != 0) {
        return;
     }
    
    switch(cp) {
        case 0:
            outOn(pin_id); // Turn on the output
            cp = 1; // Next execution point
            currentMillis = getCurrentMillis(); // Platform specific function
            break;
            
        case 1:
            if(getCurrentMillis() - currentMillis >= 200) {
                // 200ms time for output high has elapsed, proceed to next step
                outOff(pin_id); // Turn off the output
                currentMillis = getCurrentMillis(); // Reload the counter value
                cp = 2; // Proceed to the next step
            }
            break;
            
        case 2:
            if(getCurrentMillis() - currentMillis >= 200) {
                // 200ms time for output low has elapsed, proceed to next step
                cp = 0; // Last step is done, reset the state for new calls
            }
            break;
            
        default:
            // For anything else, reset the task state to the initials
            cp = 0 // Reset the task state to zero so that it accepts new calls
    }
}


/*
 * Let's say this task will wait for a button press event and will
 * trigger the outputTask upon the event occurs
 */
void outputTriggerTask() {

    static unsigned char cp = 0;
    static unsigned short currentMillis;
    
    switch(cp) {
        case 0:
            if(isButtonPressed()) { // Platform specific function
                // A button press has been detected, debounce first
                currentMillis = getCurrentMillis(); // Platform specific function
                cp = 1; // Next step, check for the elapsed time
            }
            else {
                break;
            }
            
        case 1:
            if(getCurrentMillis() - currentMillis >= 50) {
                // Check whether the button press is consistent
                if(isButtonPressed()) {
                    // Yes still consistent, handle the button press by triggering the output task
                    outputTask(0); // Not a periodic call
                    cp = 2; // Next step is to check whether button is released
                }
                else {
                    cp = 0; // Reset the task state
                }
            }
            break;
            
        case 2:
            if(isButtonReleased()) { // Platform specific function
                currentMillis = getCurrentMillis();
                cp = 3;
            }
            else {
                break;
            }
            
        case 3:
            if(getCurrentMillis() - currentMillis >= 50) {
                // Check whether the button release is consistent
                if(isButtonReleased()) {
                    // Yes still consistent, handle the button release if needed
                    cp = 0; // Reset the task to its initial state
                }
            }
            break;
            
        default:
            cp = 0; // Reset to initials
    }
}

Scheduler for Time Checking Approach

void main(void) {
    
    systemInit(); // Platform specific function
    initMillisTimerWithInterrupt(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // Now that we use a free running millis timer no need to block the CPU to create a timebase
        
        // Just call tasks sequentially. Each task will know what to do individually
        outputTriggerTask(); // Check whether any button press event has occured
        outputTask(1); // It is a periodic call for the output task
        // Maybe more tasks go here...
    }
}