My observations here are specific to Microchip PIC 8-bit data path controllers and tend to go against common style in C programming.
These types of PIC controllers have fixed call stack depths from 2 to 32 nested calls, and no hardware to implement a data stack.
For these reasons C compilers like XC8 are quite complex when trying to produce efficient code from C source using modular programming style and encapsulation.
In general terms it is a bad idea to call a function from an interrupt handler on these types of PIC controllers.
The implementation choices of the Microchip compiler writers for XC8 have made creating re-entrant code hard to realize.
- Interrupt handlers not call functions at all.
- Never allow interrupts to nest.
- Keep the interrupt handler code execution time short.
- Never ever use a delay loop in an interurpt handler.
A specific comment on your code:
return (uint16_t) (TMR3H << 8) | TMR3L;
#define TIMER3_GET_VALUE() (uint16_t) (TMR3H << 8) | TMR3L
These code fragments appear to logically do the same thing, but there is an issue when used for reading the count registers of PIC timers.
This problem comes from the PIC architecture using an 8-bit data bus. This prevents the movement of objects larger than 8-bits a single instruction cycle.
The most efficient assembly language generated from your code for reading TIMER3 is:
There are 2 instruction cycles between the reading of TMR3H and TMR3L. When TIMER3 is clocked at a rate equal to (or higher) than the instruction clock it is possible to read the wrong value for the high 8-bits of the TIMER3 count register.
Using the prescaler does not prevent this from happening but it will make it less likely to cause a problem.
Recient PIC controllers have timers implemented with a 16-bit read/write mode. Read the data sheet for your controller and look on the Microchip web site for example code.