1 // ********************************************************************* 2 // Custom DPA Handler code example - Using calibrated TMR6 MCU timer * 3 // ********************************************************************* 4 // Copyright (c) MICRORISC s.r.o. 5 // 6 // File: $RCSfile: CustomDpaHandler-TimerCalibrated.c,v $ 7 // Version: $Revision: 1.21 $ 8 // Date: $Date: 2023/03/07 08:03:13 $ 9 // 10 // Revision history: 11 // 2023/03/07 Release for DPA 4.30 12 // 2022/02/24 Release for DPA 4.17 13 // 2018/10/25 Release for DPA 3.03 14 // 15 // ********************************************************************* 16 17 // Online DPA documentation https://doc.iqrf.org/DpaTechGuide/ 18 19 // Default IQRF include (modify the path according to your setup) 20 #include "IQRF.h" 21 22 // Default DPA header (modify the path according to your setup) 23 #include "DPA.h" 24 // Default Custom DPA Handler header (modify the path according to your setup) 25 #include "DPAcustomHandler.h" 26 27 // This example initializes and calibrates TMR6 timer. 28 // TR7xD: 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). 29 // TR7xG: User DPA Menu item #1 switches between calibrated and uncalibrated timer 30 // The example code pulses GPIO pin PA.0 every 1000 timer interrupts. 31 32 // This example works only at STD mode, not at LP mode 33 34 //############################################################################################ 35 36 // Timer prescaler value. Can be one of 1, 4, 16, 64 (1, 2, ..., 64, 128 for G modules). Postscaler cannot be used. 37 #define TMR_PRESCALER 16 38 39 // PR6 register value minus 1. This value is used for further timer division from the PIC MCU fOsc/4/Prescaler value. 40 // 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 41 #define PR_VALUE ( 250 - 1 ) 42 43 // Calibration time [1 ms]. Calibration time must be multiple of 10. Longer calibration time provides higher calibration accuracy. 44 // Calibration accuracy is 1 / ( ( PR6 + 1 ) * NumberOfTimerTicksPerCalibration ). 45 // 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 46 // 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. 47 // The maximum number is checked automatically. The calibration function measures the number of prescaled timer ticks per the calibration period precisely measured by the Xtal driven IQRS OS ticks. 48 // Then Bresenham algorithm is used to let the timer interrupt generate the expected number of interrupts during the calibration time while the PIC timer regularly counts the measured number of timer ticks. 49 #define CALIB_TIME_1ms 200 50 51 // Type of the timer interrupt counter variable. Can be uns8 or uns16. It is also used for calibration. 52 #define TIMER_COUNTER_TYPE uns8 53 54 //############################################################################################ 55 56 // Calibration time must be multiple of 10 57 #if CALIB_TIME_1ms % 10 != 0 58 #error Calibration time must by multiple of 10! 59 #endif 60 61 // Timer tick time [1 ms] 62 #define TICK_TIME_1ms ( 1000.0 / ( F_OSC / 4.0 / TMR_PRESCALER / ( PR_VALUE + 1 ) ) ) 63 64 // Number of timer ticks per calibration 65 #define TICK_PER_CALIBf ( CALIB_TIME_1ms / TICK_TIME_1ms ) 66 67 // Calibration time must be multiple of Tick time 68 #if TICK_PER_CALIBf - (uns16)TICK_PER_CALIBf != 0 69 #error Calibration time must be multiple of Tick time 70 #endif 71 72 // Check number of timer ticks per calibration 73 #if TICK_PER_CALIBf > (TIMER_COUNTER_TYPE)( 0.98 * (TIMER_COUNTER_TYPE)-1 ) 74 #error Too many timer ticks per calibration! 75 #endif 76 77 #define TICK_PER_CALIB ((TIMER_COUNTER_TYPE)TICK_PER_CALIBf) 78 79 // Define timer 6 initialization register value according to the prescaler. Postscaler cannot be used. 80 #if defined( TR7xG ) 81 #if TMR_PRESCALER == 1 82 #define T6CONvalue 0b1.000.0000; 83 #elif TMR_PRESCALER == 2 84 #define T6CONvalue 0b1.001.0000; 85 #elif TMR_PRESCALER == 4 86 #define T6CONvalue 0b1.010.0000; 87 #elif TMR_PRESCALER == 8 88 #define T6CONvalue 0b1.011.0000; 89 #elif TMR_PRESCALER == 16 90 #define T6CONvalue 0b1.100.0000; 91 #elif TMR_PRESCALER == 32 92 #define T6CONvalue 0b1.101.0000; 93 #elif TMR_PRESCALER == 64 94 #define T6CONvalue 0b1.110.0000; 95 #elif TMR_PRESCALER == 128 96 #define T6CONvalue 0b1.111.0000; 97 #else 98 #error Invalid timer prescaler value! 99 #endif 100 #else 101 #if TMR_PRESCALER == 1 102 #define T6CONvalue 0b0.0000.1.00; 103 #elif TMR_PRESCALER == 4 104 #define T6CONvalue 0b0.0000.1.01; 105 #elif TMR_PRESCALER == 16 106 #define T6CONvalue 0b0.0000.1.10; 107 #elif TMR_PRESCALER == 64 108 #define T6CONvalue 0b0.0000.1.11; 109 #else 110 #error Invalid timer prescaler value! 111 #endif 112 #endif 113 114 //############################################################################################ 115 116 // Timer interrupt ticks counter 117 TIMER_COUNTER_TYPE timerTicks; 118 // Normal calibrated PR6 value. Value +1 is used to slow down the timer when Bresenham algorithm underflow when subtracting BresenhamSmaller occurs. 119 uns8 PRcalib; 120 // Bresenham algorithm variable to subtract from the sum variable (smaller "line" size) 121 TIMER_COUNTER_TYPE BresenhamSmaller; 122 123 //############################################################################################ 124 125 void CalibrateTimer(); 126 void SwitchCalibUncalib(); 127 128 // Must be the 1st defined function in the source code in order to be placed at the correct FLASH location! 129 //############################################################################################ 130 bit CustomDpaHandler() 131 //############################################################################################ 132 { 133 // Handler presence mark 134 clrwdt(); 135 136 // Detect DPA event to handle 137 switch ( GetDpaEvent() ) 138 { 139 // ------------------------------------------------- 140 case DpaEvent_Interrupt: 141 // Do an extra quick background interrupt work 142 // ! 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. 143 // ! 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. 144 // ! 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. 145 // ! Only global variables or local ones marked by static keyword can be used to allow reentrancy. 146 // ! Make sure race condition does not occur when accessing those variables at other places. 147 // ! 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. 148 // ! Do not call any OS functions except setINDFx(). 149 // ! Do not use any OS variables especially for writing access. 150 // ! 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. 151 // https://doc.iqrf.org/DpaTechGuide/pages/EventInterrupt.html 152 { 153 // Timer6 interrupt occurred? 154 if ( !TMR6IF ) 155 // No, do not handle the timer interrupt 156 return Carry; // Note: Optimization (avoid slow "goto to return") 157 158 // Reset the timer interrupt flag 159 TMR6IF = 0; 160 161 // Add next timer interrupt tick 162 timerTicks++; 163 164 // Bresenham algorithm sum variable 165 static TIMER_COUNTER_TYPE bresenhamSum; 166 // Subtract smaller Bresenham value from the sum variable 167 bresenhamSum -= BresenhamSmaller; 168 // Prepare normal optimized PR6 value to the W in case the sum is not negative 169 W = PRcalib; // Note: must not modify Carry 170 // If the previous subtraction result was negative (underflow) 171 if ( !Carry ) 172 { 173 // Add longer "line" size value to the Bresenham sum variable 174 bresenhamSum += TICK_PER_CALIB; 175 // Use +1 PR6 value to slow down the timer 176 W = PRcalib + 1; 177 } 178 // Set PR6 value 179 _PR6 = W; 180 181 // Demo output = make pulse every 1000 timer ticks 182 static int16 demoCounter; 183 // Time to pulse? 184 if ( --demoCounter < 0 ) 185 { 186 // Reset demo counter 187 demoCounter = 1000 - 1; 188 189 // Make a GPIO output pulse 190 LATA.0 = !LATA.0; 191 // Make a LED pulse 192 _LEDG = !_LEDG; 193 } 194 195 return Carry; 196 } 197 198 #ifndef DpaEvent_MenuActivated 199 // ------------------------------------------------- 200 case DpaEvent_Idle: 201 // Do a quick background work when RF packet is not received 202 // https://doc.iqrf.org/DpaTechGuide/pages/idle.html 203 { 204 // Variable to save last 8 states of the IQRF button to avoid "pressing" button at CK-USB-04x or a button bouncing in general 205 static uns8 lastButton; 206 // bit.0 will hold the current button state, so let's make a room for it 207 lastButton <<= 1; 208 // Set bit.0 if the button is pressed 209 if ( buttonPressed ) 210 lastButton |= 1; 211 212 // Was the button kept pressed for the last 7 Idle events? 213 if ( lastButton == ( (uns8)-1 >> 1 ) ) 214 SwitchCalibUncalib(); 215 216 break; 217 } 218 #else 219 // ------------------------------------------------- 220 case DpaEvent_MenuActivated: 221 // Called to customize DPA menu 222 // https://doc.iqrf.org/DpaTechGuide/pages/menuactivated.html 223 224 switch ( userReg1 ) 225 { 226 case DMENU_Online: 227 // Customize Online DPA Menu 228 userReg1 = DMENU_Item_Implemented_User1; 229 return TRUE; 230 } 231 break; 232 233 // ------------------------------------------------- 234 case DpaEvent_MenuItemSelected: 235 // Called to indicate "OK" or "Error" for selected menu item 236 // https://doc.iqrf.org/DpaTechGuide/pages/menuitemselected.html 237 238 switch ( userReg1 ) 239 { 240 case MakeDMenuAndItem( DMENU_Online, DMENU_Item_User1 ): 241 SwitchCalibUncalib(); 242 return TRUE; 243 } 244 break; 245 #endif 246 247 // ------------------------------------------------- 248 case DpaEvent_Init: 249 // Do a one time initialization before main loop starts 250 // https://doc.iqrf.org/DpaTechGuide/pages/init.html 251 252 // Setup digital output 253 TRISA.0 = 0; 254 255 // Initialize the timer 256 #if defined( TR7xG ) 257 TMR6MD = 0; 258 // Timer2/4/6 Clock Select bits = FOSC/4 259 T6CLKCON |= 0b0000.0001; 260 #endif 261 T6CON = T6CONvalue; 262 TMR6IE = TRUE; 263 264 // Calibrate the timer 265 CalibrateTimer(); 266 break; 267 268 // ------------------------------------------------- 269 case DpaEvent_AfterSleep: 270 // Called after woken up after sleep 271 // https://doc.iqrf.org/DpaTechGuide/pages/aftersleep.html 272 TMR6IE = TRUE; 273 _TMR6ON = TRUE; 274 break; 275 276 // ------------------------------------------------- 277 case DpaEvent_BeforeSleep: 278 // Called before going to sleep ( here the same handling as DpaEvent_DisableInterrupts event) 279 // https://doc.iqrf.org/DpaTechGuide/pages/beforesleep.html 280 // To minimize the power consumption, no MCU pin must be left as a digital input without defined input level value. 281 // So, unused pins in given hardware should be set as outputs. 282 // See https://www.iqrf.org/DpaTechGuide/302/examples/CustomDpaHandler-Timer.c.html for an example. 283 284 // ------------------------------------------------- 285 case DpaEvent_DisableInterrupts: 286 // Called when device needs all hardware interrupts to be disabled (before Reset, Restart, LoadCode, Remove bond and run RFPGM) 287 // https://doc.iqrf.org/DpaTechGuide/pages/eventDisableInterrupts.html 288 // Must not use TMR6 any more 289 _TMR6ON = FALSE; 290 TMR6IE = FALSE; 291 break; 292 } 293 294 return FALSE; 295 } 296 297 //############################################################################################ 298 void SwitchCalibUncalib() 299 //############################################################################################ 300 { 301 // Is timer calibrated? 302 if ( BresenhamSmaller != 0 ) 303 // UNcalibrate the timer 304 BresenhamSmaller = 0; 305 else 306 // Calibrate the timer 307 CalibrateTimer(); 308 } 309 310 //############################################################################################ 311 // Calibrates the timer. Assumes previous RF activity or explicit previous setRFready() call in order to have OS ticks connected to the RF Xtal. 312 void CalibrateTimer() 313 //############################################################################################ 314 { 315 // Switch off the timer 316 _TMR6ON = FALSE; 317 // Set default uncalibrated PR6 318 _PR6 = PRcalib = PR_VALUE; 319 // Reset timer, prescaler and write PR6 double buffered value 320 _TMR6 = 0; 321 322 // Reset timer counter with a little optimization in case the counter is already 16 bit wide 323 #if (TIMER_COUNTER_TYPE)-1 == 0xFF 324 timerTicks = 0; 325 #else 326 timerTicks = -TICK_PER_CALIB; 327 #endif 328 329 // Disable Bresenham algorithm to use constant and default uncalibrated PR6 value 330 BresenhamSmaller = 0; 331 332 // Synchronize OS ticks already "connected" to the RF Xtal 333 waitNewTick(); 334 // Start timer 335 _TMR6ON = TRUE; 336 LATA.0 = 1; 337 // Wait calibration time precisely 338 waitDelay( CALIB_TIME_1ms / 10 ); 339 // Switch off the timer 340 _TMR6ON = FALSE; 341 LATA.0 = 0; 342 343 // Variable tmr6 will store number of HW ticks (after prescaler ticks) above/below the ideal state at the end of the computation. 344 // Compute a signed difference from ideal number of timer ticks per calibration time 345 #if (TIMER_COUNTER_TYPE)-1 == 0xFF 346 // When timer counter is 8 bit we must declare and use another 16 bit variable. 347 int16 tmr6 = (int16)timerTicks - TICK_PER_CALIB; 348 #else 349 // When timer counter is 16 bit we can use it as a computation variable that already was initialized to -TICK_PER_CALIB above. 350 int16 tmr6 @ timerTicks; 351 #endif 352 // Multiply value by the PR6 value to get number of HW timer ticks above/below the ideal state 353 tmr6 *= (uns16)( PR_VALUE + 1 ); 354 // And add more HW ticks that were above 355 tmr6 += _TMR6; 356 357 // Number of HW ticks must not be negative 358 while ( tmr6 < 0 ) 359 { 360 // When negative, add number of timer ticks per calibration 361 tmr6 += TICK_PER_CALIB; 362 // Decrease PR6 to make timer faster (timer was slower as the number of ticks was negative) 363 PRcalib--; 364 } 365 366 // 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) 367 while ( tmr6 >= TICK_PER_CALIB ) 368 { 369 // Subtract number of HW timer ticks per calibration 370 tmr6 -= TICK_PER_CALIB; 371 // Make timer slower 372 PRcalib++; 373 } 374 375 // Smaller Bresenham value. The algorithm actually spreads fixed number of timer ticks into precisely measured number of HW timer ticks 376 #if (TIMER_COUNTER_TYPE)-1 == 0xFF 377 BresenhamSmaller = tmr6.low8; 378 #else 379 BresenhamSmaller = tmr6; 380 #endif 381 382 _TMR6ON = TRUE; 383 } 384 385 //############################################################################################ 386 // Default Custom DPA Handler header; 2nd include implementing a Code bumper to detect too long code of the Custom DPA Handler (modify the path according to your setup) 387 #include "DPAcustomHandler.h" 388 //############################################################################################