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