I’d like to report a bug and a bugfix for certain Microchip dsPIC30F devices, including but not limited to the dsPIC30F4011/4013 and 5011/5013 – these are the processors I have worked with.
Specifically, the freeRTOS can crash when entering critical sections or disabling interrupts due to errata within the chip’s interrupt controller if the interrupt being disabled happens exactly at the same time it is disabled. The port, whether in assembler or C, should correct for this issue by using the disi instruction before and after changing the interrupt privelege level or disabling interrupts.
My tools: FreeRTOSv8.2.1 official and MPLAB-X with XC-16r1.24.
Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/80453G.pdf (dsPIC30F5011/5013 Family Silicon Errata and Data Sheet Clarification) section 10, Interrupt Controller and section 11, disi instruction. see also XC-16 compiler switch -mErrata=all.
These sections state that the CPU can generate an address trap when an interrupt occurs exactly as it is disabled…whether through increasing the CPU IPL (interrupt privelege level) or by turning off its enable flag or its status bit…and that the disi xxx instruction (disable interrupts for xxx CPU cycles) will fail if it is executed exactly on the last disabled cycle.
The current (8.2.1) “official” portDISABLE_INTERRUPTS and portENTER_CRITICAL macros simply manipulate the IPL.
Microchip XC-16 compiler documentation recommends using a compiler-supplied macro,
SET_CPU_IPL( new privelege level)
instead of writing directly to the IPL bits in the CPU status register SR. This macro covers every conceivable case of setting the CPU IPL, including the case where the code changing the IPL is itself in a section with interrupts previously disabled.
In my own code, since I need very short critical sections only, typically for using buffers or testing and/or setting setting mutexes, I simply write:
__builtin_disi(0x3FF); // same as "asm disi 0x3ff", meaning // disable interrupts for some large number of cycles. SRbits.IPL=4; __builtin_disi(4); // required time when interrupt shouldn't happen
or, more often, something like:
__builtin_disi(0x3FF); IECxbits.U2TXIE=0; // disable UART 2 Transmitter interrupt... // IECxbits because x is not important here.. __builtin_disi(4); Serial_Xmit_Buffer[head++] =character_to_transmit; IECxbits.U2TXIE = 1;
I am a careful ordering of operations here can be used to avoid a critical section.