Tutorial 8 : DELAY_SEC Macros Demystified - sudeshmoreyos/Morey_os-demo-1.0 GitHub Wiki

Home <<Prev 8 Next>>

In this tutorial we wil explore little bit more about DELAY_SEC() and DELAY_SEC_PRECISE() Macros.

1. Morey_os Pre-Built Macros

Macros in C language are basically preprocessor which are expanded to some code blocks. By convention all pre-built Macros in Morey_os are in Capital Letters only. Macros help Morey_os code look more readable and hide complex code blocks from the user. In a standard Morey_os code typical prebuilt Morey_os Macros are :

  1. TASK_CREATE
  2. TASK_AUTOSTART
  3. TASK_RUN
  4. BEGIN
  5. END
  6. DELAY_SEC
  7. DELAY_SEC_PRECISE

TASK_CREATE Macro initializes a task, TASK_AUTOSTART auto-start a task at boot time, TASK_RUN declares the runtime operation of a task, BEGIN initializes the code inside a task after variable creation, END ends a task, DELAY_SEC gives delay in seconds with a resolution of 0.001 second and Finally DELAY_SEC_PRECISE is same as DELAY_SEC with only difference that delay it gives is precise and synchronized.

2. Task switching in Morey_os

Morey_os is based on cooperative threads. Threads here means task in Morey_os which runs independently from other tasks. It means that by creating multiple tasks we can implement multi-tasking in our code. However in practice most embedded controller has only one core, so they cannot implement multi-tasking. Rather they simply switch between different tasks so fast that we get the illusion of multi-tasking. This task switching can be done either by Cooperative threads or pre-emptive thread. A cooperative thread must return its control to OS, OS itself cannot take control from the tasks. While Pre-emptive threads allow OS to take control back to itself without asking. Comparing both, Pre-emptive thread has advantage over cooperative thread but it is complex to implement and takes more memory footprint. Hence to begin with Morey_os currently implements cooperative threads to save memory footprint. This Multi-tasking functionality is provided by Morey_os kernel which is a modified trimmed down version of Popular Contiki Operating system. So that it can fit even in controller with minimum memory footprint of 8KB Flash and 1KB RAM.

Let us understand how this task switching is implemented in Morey_os. Let assume we have two independent tasks implementing simple LED blinking.


Task-1 :

TASK_RUN(led1)

{

BEGIN();

while(1)

{

Digital.write(pin13,HIGH);

DELAY_SEC(1);

Digital.write(pin13,LOW);

DELAY_SEC(1);

}

END();

}

Task-2 :

TASK_RUN(led2)

{

BEGIN();

while(1)

{

Digital.write(pin12,HIGH);

DELAY_SEC(0.1);

Digital.write(pin12,LOW);

DELAY_SEC(0.1);

}

END();

}


Assuming LEDs are connected to both pin12 and pin13 of Arduino Uno, task-1 blinks LED at pin13 after very 1 second delay, while task-2 blinks LED at pin12 after every 0.1 second delay. Since both LEDs blinks simultaneously, we get an illusion that both tasks are running in parallel. In practice, Morey_os implements multi-tasking in following steps :

  1. Controller/Board power-ups
  2. setup function runs
  3. OS initiates
  4. Task-1 is initialized till BEGIN macro ( typically variables are initialized )
  5. Control is returned back to OS by BEGIN Macro
  6. Task-2 is initialized till BEGIN macro ( typically variables are initialized )
  7. Control is returned back to OS by BEGIN Macro
  8. Task-1 enters while loop, sets pin13 as HIGH.
  9. DELAY_SEC(1) Macro, schedules delay for 1 second in OS scheduler and returns back control to OS
  10. Task-2 enters while loop, sets pin12 as HIGH.
  11. DELAY_SEC(0.1) Macro, schedules delay for 0.1 second in OS scheduler and returns back control to OS
  12. Assuming all the above steps completed in negligible time, Next task for OS is to wake up Task-2 after 0.1 seconds.
  13. Since OS has no work to do till next 0.1 seconds it will go to sleep.
  14. After 0.1 Seconds OS will wake up itself and then wakes up Task-2.
  15. Task-2 sets pin12 LOW.
  16. DELAY_SEC(0.1) Macro, schedules delay for 0.1 second in OS scheduler and returns back control to OS
  17. Since OS has no work to do till next 0.1 seconds it will go to sleep again.
  18. After 0.1 Seconds OS will wake up itself and then wakes up Task-2.
  19. Task-2 complete one loop of While(1) and goes back to start of the loop. Again sets pin12 HIGH.
  20. DELAY_SEC(0.1) Macro, schedules delay for 0.1 second in OS scheduler and returns back control to OS
  21. Process is repeated till 1 Second completes.
  22. After 1 seconds OS has to wake up both Task-1 and Task-2. Since Task-1 is created first it has higher priority over Task-2, hence task-1 will wake up first.
  23. Task-1 sets pin13 LOW.
  24. DELAY_SEC(1) Macro, schedules delay for 1 second in OS scheduler and returns back control to OS
  25. OS wakes up Task-2.
  26. Task-2 sets pin12 HIGH.
  27. DELAY_SEC(0.1) Macro, schedules delay for 0.1 second in OS scheduler and returns back control to OS
  28. Similarly Process continues till 2 seconds.
  29. After 2 seconds OS again wakes up Task-1.
  30. Task-1 completes one loop of While(1) and goes back to start of the loop. Again sets pin13 HIGH.
  31. DELAY_SEC(1) Macro, schedules delay for 1 second in OS scheduler and returns back control to OS
  32. Similarly process continues

All the above mentioned steps may be visualized graphically in the diagram below :

3. Importance of DELAY_SEC Macros

So as explained above DELAY_SEC macro has two critical things to do:

  1. Schedules delay for desired time in seconds with 0.001 sec or 1 millisecond resolution.
  2. Return control to OS so that it can service other tasks as well.

So if by mistake we forget to add a delay inside an infinite loop, that task will stuck in that infinite loop and will never return control to OS. Effectively code will hung. To avoid this hung state Morey_os takes help of Watchdog timer. Based on controller, watchdog timer is set for 1 or 2 seconds. Which means if code is hung for more than 1 or 2 seconds, watchdog will reset the controller and it will restart. So if in case your Morey_os code restarts again and again, that means you have forgot to add DELAY_SEC or DELAY_SEC_PRECISE macro inside an infinite loop. Please not DELAY_SEC macros can only be used inside TASK_RUN and nowhere else. Also DELAY_SEC Macros cannot be used inside any sub-functions.

It is very important to note that while adding DELAY_SEC or DELAY_SEC_PRECISE macro inside an infinite loop, it must run at least once every loop run. Lets take an example code below :


TASK_RUN(test) {

static x = 0;

BEGIN();

while(1)

{

if(x==1)

{

// Do Something

DELAY_SEC(1);

}

}

END();

}


Now as you can see in above code, we did add DELAY_SEC macro but inside an if statement. if statement works on condition here which is x == 1. if this condition is not satisfied code will not enter if block, effectively DELAY_SEC macro wont run in every loop run. As a result at times code will stuck inside infinite loop and controller will restart after watchdog expires. A simple solution to it is as follows :


TASK_RUN(test) {

static x = 0;

BEGIN();

while(1)

{

if(x==1)

{

// Do Something

DELAY_SEC(1);

}

DELAY_SEC(0.05);

}

END();

}


So as shown above we added DELAY_SEC(0.05) which gives delay of 50 milli seconds in each loop. And this DELAY_SEC macro will always run in every loop run. So our code will never stuck in infinite loop. DELAY_SEC Macros support minimum delay of 1 milli second. However exact delay is not guaranteed every time. Hence it is advised not to give delay less than 50 milli seconds using DELAY_SEC or DELAY_SEC_PRECISE Macros.

4. DELAY_SEC vs DELAY_SEC_PRECISE

In this section we will explore the difference between DELAY_SEC and DELAY_SEC_PRECISE Macros. Let us take the example of task-1 discussed in point-2 above. In this example we are blinking led connected to pin13 after every one second. We have used DELAY_SEC macro in this code. If we replace these with DELAY_SEC_PRECISE macros, let us understand the difference as explained in the diagram below :

As shown above, there are code blocks. Left side code block uses DELAY_SEC Macro while right side block used DELAY_SEC_PRECISE. Let us assume that Digital.write function takes 1 milli second to set the output to HIGH or LOW ( It is assumption, in practice it is much lesser). So in between the codes I have shown the time stamp after execution of each line of the code.

Let us first understand the time-stamps for Task-1 using DELAY_SEC Macro :

  1. Task begins at Time = 0.000 sec
  2. It completes till BEGIN(); Macro at TIME = 0.000 sec
  3. Sets pin13 HIGH at TIME = 0.001 sec
  4. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 1.001
  5. Sets pin13 LOW, TIME = 1.002
  6. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 2.002
  7. while loop ends, so it goes to the top again.
  8. Sets pin13 HIGH again, TIME = 2.003 sec
  9. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 3.003
  10. Sets pin13 LOW, TIME = 3.004
  11. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 4.004
  12. And the process repeats.

As you see that, delay due to Digital.write function is added to delay due to DELAY_SEC macro. In most cases this delay will be very small, but in long run it may become significant.

Let us first understand the time-stamps for Task-1 using DELAY_SEC_PRECISE Macro :

Task begins at Time = 0.000 sec 2. It completes till BEGIN(); Macro at TIME = 0.000 sec 3. Sets pin13 HIGH at TIME = 0.001 sec 4. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 1.000 5. Sets pin13 LOW, TIME = 1.001 6. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 2.000 7. while loop ends, so it goes to the top again. 8. Sets pin13 HIGH again, TIME = 2.001 sec 9. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 3.000 10. Sets pin13 LOW, TIME = 3.001 11. Waits for 1 second. So at the end of DELAY_SEC execution time stamp is TIME = 4.000 12. And the process repeats.

As you see that, delay due to Digital.write function is not added to delay due to DELAY_SEC macro. DELAY_SEC_PRECISE consider all other delays and adjust itself to given exact delay from last DELAY_SEC_MACRO call. So this is advantage of DELAY_SEC_PRECISE over DELAY_SEC.

Home <<Prev 8 Next>>