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