1 // ********************************************************************* 2 // Custom DPA Handler code example - Using calibrated TMR6 MCU timer * 3 // ********************************************************************* 4 // Copyright (c) IQRF Tech s.r.o. 5 // 6 // File: $RCSfile: CustomDpaHandler-TimerCalibrated.c,v $ 7 // Version: $Revision: 1.9 $ 8 // Date: $Date: 2019/05/06 07:13:14 $ 9 // 10 // Revision history: 11 // 2018/10/25 Release for DPA 3.03 12 // 13 // ********************************************************************* 14 15 // Online DPA documentation http://www.iqrf.org/DpaTechGuide/ 16 17 // Default IQRF include (modify the path according to your setup) 18 #include "IQRF.h" 19 20 // Default DPA header (modify the path according to your setup) 21 #include "DPA.h" 22 // Default Custom DPA Handler header (modify the path according to your setup) 23 #include "DPAcustomHandler.h" 24 25 // This example initializes and calibrates TMR6 timer. 26 // By pressing the standard IQRF button connected to GPIO pin PB.4 (e.g. at DK-EVAL-04x) the timer can be switched between calibrated and uncalibrated states to see the difference (use logic analyzer to measure the pulse at the demo pin). 27 // The example code pulses GPIO pin PA.0 every 1000 timer interrupts. At the same time it pulses Green LED when timer is calibrated or Red LED when timer is not calibrated respectively. 28 29 // This example works only at STD mode, not at LP mode 30 31 //############################################################################################ 32 33 // Timer prescaler value. Can be one of 1, 4, 16, 64. Postscaler cannot be use. 34 #define TMR_PRESCALER 16 35 36 // PR6 register value minus 1. This value is used for further timer division from the PIC MCU fOsc/4 value. 37 // E.g. fOsc=16 MHz, Prescaler=16, PR6=249 gives timer frequency 16_000_000 / 4 / 16 / ( 249 + 1 ) = 1000 Hz and period 1 ms 38 #define PR_VALUE ( 250 - 1 ) 39 40 // Calibration time [1 ms]. Calibration time must be multiple of 10. Longer calibration time provides higher calibration accuracy. 41 // Calibration accuracy is 1 / ( ( PR6 + 1 ) * NumberOfTimerTicksPerCalibration ). 42 // I.e. for the previous settings and 100 ms calibration period the accuracy is 1 / ( 250 * 100 ) = 0.00004 = 0.004 % = 0.04 ‰ = 40 ppm 43 // Number of timer periods per calibration time must not be above 98 % of 0xFF if variable "timerTicks" is type of "uns8". If the variable is "uns16" then 98 % of 0xFfFf is maximum. The maximum number is checked automatically. 44 // The calibration function measures the number of prescaled timer ticks per the calibration period precisely measured by the Xtal driven IQRS OS ticks. Then Bresenham algorithm is used to let the timer interrupt generate the expected 45 // number of interrupts during the calibration time while the PIC timer regularly counts the measured number of timer ticks. 46 #define CALIB_TIME_1ms 100 47 48 // Type of the timer interrupt counter variable. Can be uns8 or uns16. It is also used for calibration. 49 #define TIMER_COUNTER_TYPE uns8 50 51 //############################################################################################ 52 53 // Timer tick time [1 ms] 54 #define TICK_TIME_1ms ( 1000.0 / ( F_OSC / 4.0 / TMR_PRESCALER / ( PR_VALUE + 1 ) ) ) 55 56 // Define timer 6 initialization register value according to the prescaler. Postscaler cannot be used. 57 #if TMR_PRESCALER == 1 58 #define T6CONvalue 0b0.0000.1.00; 59 #elif TMR_PRESCALER == 4 60 #define T6CONvalue 0b0.0000.1.01; 61 #elif TMR_PRESCALER == 16 62 #define T6CONvalue 0b0.0000.1.10; 63 #elif TMR_PRESCALER == 64 64 #define T6CONvalue 0b0.0000.1.11; 65 #else 66 #error Invalid timer prescaler value! 67 #endif 68 69 // Calibration time must be multiple of 10 70 #if CALIB_TIME_1ms % 10 != 0 71 #error Calibration time must by multiple of 10! 72 #endif 73 74 // Check number of timer ticks per calibration 75 #if ( ( CALIB_TIME_1ms + TICK_TIME_1ms / 2 ) / TICK_TIME_1ms ) > (TIMER_COUNTER_TYPE)( 0.98 * ( (TIMER_COUNTER_TYPE)-1 ) ) 76 #error Too many timer ticks per calibration! 77 #endif 78 79 // Number of timer ticks per calibration 80 #define TICK_PER_CALIB (TIMER_COUNTER_TYPE)( ( CALIB_TIME_1ms + TICK_TIME_1ms / 2 ) / TICK_TIME_1ms ) 81 82 //############################################################################################ 83 84 // Timer interrupt ticks counter 85 TIMER_COUNTER_TYPE timerTicks; 86 // Normal calibrated PR6 value. Value +1 is used to slow down the timer when Bresenham algorithm underflow when subtracting BresenhamSmaller occurs. 87 uns8 PRcalib; 88 // Bresenham algorithm variable to subtract from the sum variable (smaller "line" size) 89 uns8 BresenhamSmaller; 90 91 //############################################################################################ 92 93 void CalibrateTimer(); 94 95 // Must be the 1st defined function in the source code in order to be placed at the correct FLASH location! 96 //############################################################################################ 97 bit CustomDpaHandler() 98 //############################################################################################ 99 { 100 // Handler presence mark 101 clrwdt(); 102 103 // Detect DPA event to handle 104 switch ( GetDpaEvent() ) 105 { 106 // ------------------------------------------------- 107 case DpaEvent_Interrupt: 108 // Do an extra quick background interrupt work 109 // ! The time spent handling this event is critical.If there is no interrupt to handle return immediately otherwise keep the code as fast as possible. 110 // ! Make sure the event is the 1st case in the main switch statement at the handler routine.This ensures that the event is handled as the 1st one. 111 // ! It is desirable that this event is handled with immediate return even if it is not used by the custom handler because the Interrupt event is raised on every MCU interrupt and the “empty” return handler ensures the shortest possible interrupt routine response time. 112 // ! Only global variables or local ones marked by static keyword can be used to allow reentrancy. 113 // ! Make sure race condition does not occur when accessing those variables at other places. 114 // ! Make sure( inspect.lst file generated by C compiler ) compiler does not create any hidden temporary local variable( occurs when using division, multiplication or bit shifts ) at the event handler code.The name of such variable is usually Cnumbercnt. 115 // ! Do not call any OS functions except setINDFx(). 116 // ! Do not use any OS variables especially for writing access. 117 // ! All above rules apply also to any other function being called from the event handler code, although calling any function from Interrupt event is not recommended because of additional MCU stack usage. 118 119 { 120 // Timer6 interrupt occurred? 121 if ( !TMR6IF ) 122 // No, do not handle the timer interrupt 123 return Carry; // Note: Optimization (avoid slow "goto to return") 124 125 // Reset the timer interrupt flag 126 TMR6IF = 0; 127 128 // Add next timer interrupt tick 129 timerTicks++; 130 131 // Bresenham algorithm sum variable 132 static uns8 bresenhamSum; 133 // Subtract smaller Bresenham value from the sum variable 134 bresenhamSum -= BresenhamSmaller; 135 // Prepare normal optimized PR6 value to the W in case the sum is not negative 136 W = PRcalib; // Note: must not modify Carry 137 // If the previous subtraction result was negative (underflow) 138 if ( !Carry ) 139 { 140 // Add longer "line" size value to the Bresenham sum variable 141 bresenhamSum += TICK_PER_CALIB; 142 // Use +1 PR6 value to slow down the timer 143 W = PRcalib + 1; 144 } 145 // Set PR6 value 146 PR6 = W; 147 148 // Demo output = make pulse every 1000 timer ticks 149 static uns16 demoCounter; 150 // Time to pulse? 151 if ( ++demoCounter == 1000 ) 152 { 153 // Reset demo counter 154 demoCounter = 0; 155 // Make a GPIO output pulse 156 LATA.0 = !LATA.0; 157 // Make a LED pulse 158 if ( BresenhamSmaller != 0 ) 159 { 160 // Green pulse when timer is calibrated 161 _LEDG = !_LEDG; 162 _LEDR = 0; 163 } 164 else 165 { 166 // Red pulse when timer is NOT calibrated 167 _LEDR = !_LEDR; 168 _LEDG = 0; 169 } 170 } 171 172 return Carry; 173 } 174 175 // ------------------------------------------------- 176 case DpaEvent_Idle: 177 // Do a quick background work when RF packet is not received 178 { 179 // Variable to save last 8 states of the IQRF button to avoid "pressing" button at CK-USB-04x or a button bouncing in general 180 static uns8 lastButton; 181 // bit.0 will hold the current button state, so let's make a room for it 182 lastButton <<= 1; 183 // Set bit.0 if the button is pressed 184 if ( buttonPressed ) 185 lastButton |= 1; 186 187 // Was the button kept pressed for the last 7 Idle events? 188 if ( lastButton == ( (uns8)-1 >> 1 ) ) 189 { 190 // Is timer calibrated? 191 if ( BresenhamSmaller != 0 ) 192 // UNcalibrate the timer 193 BresenhamSmaller = 0; 194 else 195 // Calibrate the timer 196 CalibrateTimer(); 197 } 198 break; 199 } 200 201 // ------------------------------------------------- 202 case DpaEvent_Init: 203 // Do a one time initialization work before main loop starts 204 205 // Setup digital output 206 TRISA.0 = 0; 207 208 // Initialize the timer 209 T6CON = T6CONvalue; 210 TMR6IE = TRUE; 211 212 // Calibrate the timer 213 CalibrateTimer(); 214 215 break; 216 217 // ------------------------------------------------- 218 case DpaEvent_AfterSleep: 219 // Called on wake-up from sleep 220 TMR6IE = TRUE; 221 TMR6ON = TRUE; 222 break; 223 224 // ------------------------------------------------- 225 case DpaEvent_BeforeSleep: 226 // Called before going to sleep (the same handling as DpaEvent_DisableInterrupts event) 227 // To minimize the power consumption, no MCU pin must be left as a digital input without defined input level value. 228 // So, unused pins in given hardware should be set as outputs. 229 // See https://www.iqrf.org/DpaTechGuide/302/examples/CustomDpaHandler-Timer.c.html for an example. 230 231 // ------------------------------------------------- 232 case DpaEvent_DisableInterrupts: 233 // Called when device needs all hardware interrupts to be disabled (before Reset, Restart, LoadCode, Remove bond, and Run RFPGM) 234 // Must not use TMR6 any more 235 TMR6ON = FALSE; 236 TMR6IE = FALSE; 237 break; 238 } 239 240 return FALSE; 241 } 242 243 //############################################################################################ 244 // Calibrates the timer. Assumes previous RF activity or explicit previous setRFready() call in order to have OS ticks connected to the RF Xtal. 245 void CalibrateTimer() 246 //############################################################################################ 247 { 248 // Switch off the timer 249 TMR6ON = FALSE; 250 // Reset timer 251 TMR6 = 0; 252 253 // Reset timer counter with a little optimization in case the counter is already 16 bit wide 254 #if ( (TIMER_COUNTER_TYPE)-1) == 0xFF 255 timerTicks = 0; 256 #else 257 timerTicks = -TICK_PER_CALIB; 258 #endif 259 260 // Disable Bresenham algorithm to used constant and default uncalibrated PR6 value 261 BresenhamSmaller = 0; 262 263 // Synchronize OS ticks already "connected" to the RF Xtal 264 waitNewTick(); 265 // Set default uncalibrated PR6 266 PR6 = PRcalib = PR_VALUE; 267 // Start timer 268 LATA.0 = 1; 269 TMR6ON = TRUE; 270 // Wait calibration time precisely 271 waitDelay( CALIB_TIME_1ms / 10 ); 272 // Switch off the timer 273 TMR6ON = FALSE; 274 LATA.0 = 0; 275 276 // Variable tmr6 will store number of HW ticks (after prescaler ticks) above/below the ideal state at the end of the computation. 277 // Compute a signed difference from ideal number of timer ticks per calibration time 278 #if ( (TIMER_COUNTER_TYPE)-1) == 0xFF 279 // When timer counter is 8 bit we must declare and use another 16 bit variable. 280 int16 tmr6 = (int16)timerTicks - TICK_PER_CALIB; 281 #else 282 // When timer counter is 16 bit we can use it as a computation variable that already was initialized to -TICK_PER_CALIB above. 283 int16 tmr6 @ timerTicks; 284 #endif 285 // Multiply value by the PR6 value to get number of HW timer ticks above/below the ideal state 286 tmr6 *= (uns16)( PR_VALUE + 1 ); 287 // And add more HW ticks that were above 288 tmr6 += TMR6; 289 290 // Number of HW ticks must not be negative 291 while ( tmr6 < 0 ) 292 { 293 // When negative, add number of timer ticks per calibration 294 tmr6 += TICK_PER_CALIB; 295 // Decrease PR6 to make timer it faster (timer was slower as the number of ticks was negative) 296 PRcalib--; 297 } 298 299 // Final number of above HW timer ticks must be smaller (small Bresenham "line" value) than number of timer ticks per calibration (long Bresenham "line" value) 300 while ( tmr6 > TICK_PER_CALIB ) 301 { 302 // Subtract number of HW timer ticks per calibration 303 tmr6 -= TICK_PER_CALIB; 304 // Make timer slower 305 PRcalib++; 306 } 307 308 // Smaller Bresenham value. The algorithm actually spreads fixed number of timer ticks into precisely measured number of HW timer ticks 309 BresenhamSmaller = tmr6.low8; 310 // Enable timer again 311 TMR6ON = TRUE; 312 } 313 314 //############################################################################################ 315 // Default Custom DPA Handler header; 2nd include to implement Code bumper to detect too long code of the Custom DPA Handler (modify the path according to your setup) 316 #include "DPAcustomHandler.h" 317 //############################################################################################