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 //############################################################################################