CCS News

Tech Note: How to Block Critical Code from Interrupts

Tuesday 09 June, 2020

It is quite common for an interrupt handler to save or alter data that can be accessed from your non-interrupt code. Doing this does require some care, otherwise the interrupt code might alter the data in the middle of the non-interrupt code dealing with the data. Look at following example:

WORD g_Timer1;

#int_timer1
void isr_timer1(void)
{
g_Timer1++;
}

int1 check_timer(void)
{
int1 ret = 0;
if (g_Timer1 > 500)
{
g_Timer1 = 0;
ret = 1;
}
return ret;
}


WORD is a type that takes multiple instructions of the architecture to modify, for example an int16 on an 8-bit architecture. If the Timer1 interrupt fired in the middle of check_timer() clearing g_Timer1, would result in a state where the interrupt would increment a half cleared g_Timer1. A common solution for this problem is to pause interrupts while handling this data. Here is an example of check_timer() updated to temporarily pause interrupts while handling data that could be modified in the ISR:

int1 check_timer(void)
{
int1 ret = 0;
disable_interrupts(GLOBAL);
If (g_Timer1 > 500)
{
g_Timer1 = 0;
ret = 1;
}
enable_interrupts(GLOBAL);
return ret;
}


Unfortunately, the solution presented in the above modification to check_timer() can fail in a few situations:

* If check_timer() is called in another interrupt, where interrupts have been disabled by the hardware, then it will re-enable the interrupt while inside an interrupt. This can cause a disaster where an interrupt interrupts an interrupt, not something all microcontroller architectures can handle.

* If check_timer() is called when interrupts were disabled, then check_timer() would accidentally enable the interrupt when it is not wanted to be enabled. This can easily happen if check_timer() is used in a library shared in several projects, including projects where interrupts are not used.

* If check_timer() is called when interrupts were already paused for another series of instructions, check_timer() would then re-enable them before the calling function would have finished their operations expecting interrupts to be disabled.


CCS has a library for pausing interrupts that solves all three of the above problems: critical.h. Here is an example of using critical.h with the previous example:

#include <critical.h>

int1 check_timer(void)
{
int1 ret = 0;
CRITICAL_SECTION_ENTER();
If (g_Timer1 > 500)
{
g_Timer1 = 0;
ret = 1;
}
CRITICAL_SECTION_EXIT();
return ret;
}


CRITICAL_SECTION_ENTER() saves the previous state of the global interrupt enable and is restored by the CRITICAL_SECTION_EXIT(). That means if CRITICAL_SECTION_ENTER() is called when interrupts were not enabled, then CRITICAL_SECTION_EXIT() does not have enable interrupts. The CCS C Compiler does something similar internally to prevent recursion on architectures that do not allow recursion. When this occurs, a warning that states "Interrupts disabled to prevent recursion." Now this method can be applied to critical sections of code where data is being modified by the interrupts that also need access to outside of the interrupt.


Like us on Facebook. Follow us on Twitter.

About CCS:

CCS is a leading worldwide supplier of embedded software development tools that enable companies to develop premium products based on Microchip PIC® MCU and dsPIC® DSC devices. Complete proven tool chains from CCS include a code optimizing C compiler, application specific hardware platforms and software development kits. CCS' products accelerate development of energy saving industrial automation, wireless and wired communication, automotive, medical device and consumer product applications. Established in 1992, CCS is a Microchip Premier 3rd Party Partner. For more information, please visit http://www.ccsinfo.com.

PIC® MCU, MPLAB® IDE, MPLAB® ICD2, MPLAB® ICD3 and dsPIC® are registered trademarks of Microchip Technology Inc. in the U.S. and other countries.