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.
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):
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 ofcp
variable will not be destroyed by the compiler when the function returns since we declare it as static. Thecp
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 calledperiodic
. When it is called from the scheduler this parameter must be set to 1, and 0 for the calls other than the scheduler.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.Approach 2 - Create Hardware Timer Based Timebase
Approach 3 Put the Processor to Sleep for 1ms Timebase
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
Scheduler for Time Checking Approach