1 // *********************************************************************
    2 //   IQD-RC4-01 Example
    3 // *********************************************************************
    4 // Copyright (c) MICRORISC s.r.o.
    5 //
    6 // File:    $RCSfile: CustomDpaHandler-IQD-RC4-01.c,v $
    7 // Version: $Revision: 1.9 $
    8 // Date:    $Date: 2025/06/27 15:47:48 $
    9 //
   10 // Revision history:
   11 //   2025/05/30  Release for DPA 4.33
   12 //   2024/04/17  Release for DPA 4.31
   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 is a template for the IQD-RC4-01 remote control. **
   27 // Please note that the LED indications described below are for development purposes and may not be retained in the final product.
   28 // In any device state, the DPA Menu can be opened by simultaneously pressing SW1 & SW4 (see AllowOpenDpaMenu). Then release one of the SW1 or SW4 buttons and use the other to control the DPA Menu.
   29 // * ReadyToBond state:
   30 //   Default behavior and content of the DPA Menu.
   31 // * Online state:
   32 //   The 1st item of the OnLine DPA Menu switches from the OnLine to the Beaming state.
   33 //   After 2 minutes of RF inactivity, the device switches from the OnLine to the Beaming state.
   34 // * Beaming state:
   35 //   Entering and leaving the Beaming state are signaled by the LEDR & LEDG pulse.
   36 //   The 3rd (user) DPA Beaming Menu item exits Beaming to OnLine state.
   37 //   This template allows you to record single and multiple button presses as well as their combination with an optional final long button press.
   38 //   Each button press is indicated by a very short LEDG flash.
   39 //   Pressing and releasing SW1 to SW4 pulses the LED(s) 1 to 4 times.
   40 //   Pressing and releasing SW2 & SE3 simultaneously flashes the LED(s) 5 times.
   41 //   If the press is long no more presses are recorded and then both LEDR & LEDG pulse, otherwise only LEDG pulses.
   42 //   The number of presses greater than 1 is finally indicated by the corresponding number of LEDR pulses.
   43 //
   44 //   Examples of button presses and corresponding LED indication:
   45 //     [a] short SW1                                => one LEDG pulse
   46 //     [b] short SW1, gap, short SW1                => one LEDG pulse and then two LEDR pulses
   47 //     [c] long SW4                                 => four LEDG & LEDR pulses
   48 //     [d] short SW2, gap, short SW2, gap, long SW2 => two LEDG & LEDR pulses and then three LEDR pulses
   49 //
   50 // * StandBy state:
   51 //   Default behavior and content of the DPA Menu.
   52 
   53 //############################################################################################
   54 
   55 #if defined( TR7xD )
   56 #error This example is intended for TR7xG
   57 #endif
   58 
   59 #define _HWPID_           0x321F
   60 #define _HWPIDver_        0xBabe
   61 
   62 // Timeout in seconds to start beaming in case of network inactivity (655 seconds is maximum)
   63 #define BEAMING_TIMEOUT   120
   64 
   65 // SW* GPIOs. Pressing any SW* also "presses" IQRF button at PORTB.4
   66 #define SW1               PORTC.7
   67 #define SW2               PORTC.6
   68 #define SW3               PORTA.0
   69 #define SW4               PORTC.2
   70 
   71 // Switch coding flags
   72 #define SW1flag           0b0.0001
   73 #define SW2flag           0b0.0010
   74 #define SW3flag           0b0.0100
   75 #define SW4flag           0b0.1000
   76 #define SWlongFlag        0b1.0000
   77 
   78 // Executes beaming mode
   79 void Beaming();
   80 // Checks the condition for DPA Menu to open
   81 bit AllowOpenDpaMenu();
   82 
   83 // Must be the 1st defined function in the source code in order to be placed at the correct FLASH location!
   84 //############################################################################################
   85 // https://doc.iqrf.org/DpaTechGuide/pages/custom-dpa-handler.html
   86 bit CustomDpaHandler()
   87 //############################################################################################
   88 {
   89   // TRUE to start beaming at Idle event
   90   static bit startBeamingAtIdle;
   91 
   92   // Handler presence mark
   93   clrwdt();
   94 
   95   // Detect DPA event to handle (unused event handlers can be commented out or even deleted)
   96   switch ( GetDpaEvent() )
   97   {
   98     // -------------------------------------------------
   99     case DpaEvent_Interrupt:
  100       // Do an extra quick background interrupt work
  101       // ! 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.
  102       // ! 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.
  103       // ! 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.
  104       // ! Only global variables or local ones marked by static keyword can be used to allow reentrancy.
  105       // ! Make sure race condition does not occur when accessing those variables at other places.
  106       // ! 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.
  107       // ! Do not call any OS functions except setINDFx().
  108       // ! Do not use any OS variables especially for writing access.
  109       // ! 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.
  110       // https://doc.iqrf.org/DpaTechGuide/pages/EventInterrupt.html
  111 
  112       // Note: IQRF OS does IOCBF = 0;
  113       return Carry;
  114 
  115       // -------------------------------------------------
  116     case DpaEvent_Idle:
  117       // Do a quick background work when RF packet is not received
  118       // https://doc.iqrf.org/DpaTechGuide/pages/idle.html
  119 
  120       if ( startBeamingAtIdle )
  121       {
  122         startBeamingAtIdle = FALSE;
  123         // Go to Beaming mode
  124         Beaming();
  125         goto _startCapture;
  126       }
  127 
  128       // Start beaming after a defined inactivity time
  129       captureTicks();
  130       if ( param3 > (uns16)BEAMING_TIMEOUT * 100 )
  131       {
  132         if ( ntwADDR != TEMPORARY_ADDRESS )
  133           Beaming();
  134         goto _startCapture;
  135       }
  136       break;
  137 
  138       // -------------------------------------------------
  139     case DpaEvent_Reset:
  140       // Called after module is reset
  141       // https://doc.iqrf.org/DpaTechGuide/pages/ResetEvent.html
  142 
  143       // Unused SPI pins set as output in low level
  144       // SS
  145       LATA &= 0b1101.1111;
  146       TRISA &= 0b1101.1111;
  147       // SCK+SDI+SDO
  148       LATC &= 0b1100.0111;
  149       TRISC &= 0b1100.0111;
  150 
  151       break;
  152 
  153       // -------------------------------------------------
  154     case DpaEvent_Init:
  155       // Do a one time initialization before main loop starts
  156       // https://doc.iqrf.org/DpaTechGuide/pages/init.html
  157 
  158       goto _startCapture;
  159 
  160       // -------------------------------------------------
  161     case DpaEvent_ReceiveDpaRequest:
  162       // Called after DPA request was received
  163       // https://doc.iqrf.org/DpaTechGuide/pages/receivedparequest.html
  164 
  165       if ( _ROUTEF )
  166       {
  167         // Start inactivity timer
  168 _startCapture:
  169         startCapture();
  170       }
  171       break;
  172 
  173       // -------------------------------------------------
  174     case DpaEvent_MenuActivated:
  175       // Called to customize DPA menu
  176       // https://doc.iqrf.org/DpaTechGuide/pages/menuactivated.html
  177 
  178       // Do not open DPA Menu?
  179       if ( !AllowOpenDpaMenu() )
  180       {
  181         // Yes, do not open event the IQRF button at RB4 is low
  182         userReg1 = DMENU_Ext_DoNotOpen;
  183         goto DpaHandleReturnFALSE;
  184       }
  185 
  186       // Only OnLine menu is customized, exit the event at others
  187       if ( userReg1 != DMENU_Online )
  188         goto DpaHandleReturnFALSE;
  189 
  190       // Customize OnLine DPA Menu
  191       userReg1 = DMENU_Item_Implemented_Beaming;
  192       goto DpaHandleReturnTRUE;
  193 
  194       // -------------------------------------------------
  195     case DpaEvent_MenuItemSelected:
  196       // Called to indicate "OK" or "Error" for selected menu item
  197       // https://doc.iqrf.org/DpaTechGuide/pages/menuitemselected.html
  198 
  199       // Which menu item?
  200       switch ( userReg1 )
  201       {
  202         // Start beaming?
  203         case MakeDMenuAndItem( DMENU_Online, DMENU_Item_Beaming ):
  204           if ( amIBonded() )
  205           {
  206             startBeamingAtIdle = TRUE;
  207             goto DpaHandleReturnTRUE; // return TRUE to indicate "OK" for menu item specified by userReg1, otherwise to indicate Error
  208           }
  209           break;
  210       }
  211       break;
  212 
  213       // -------------------------------------------------
  214     case DpaEvent_DpaRequest:
  215       // Called to interpret DPA request for peripherals
  216       // https://doc.iqrf.org/DpaTechGuide/pages/EventDpaRequest.html
  217       if ( IsDpaEnumPeripheralsRequest() )
  218       {
  219         // -------------------------------------------------
  220         // Peripheral enumeration
  221         // https://doc.iqrf.org/DpaTechGuide/pages/enumerate-peripherals.html
  222 
  223         _DpaMessage.EnumPeripheralsAnswer.HWPID |= _HWPID_;
  224         _DpaMessage.EnumPeripheralsAnswer.HWPIDver |= _HWPIDver_;
  225 
  226 DpaHandleReturnTRUE:
  227         return TRUE;
  228       }
  229       break;
  230   }
  231 
  232 DpaHandleReturnFALSE:
  233   return FALSE;
  234 }
  235 
  236 //############################################################################################
  237 uns8 GetButtons()
  238 //############################################################################################
  239 {
  240   uns8 btns @ W;  // Note: optimization using
  241   btns = 0;
  242   if ( SW1 )  // Note: must not modify W
  243     btns |= SW1flag;
  244   if ( SW2 )  // Note: must not modify W
  245     btns |= SW2flag;
  246   if ( SW3 )  // Note: must not modify W
  247     btns |= SW3flag;
  248   if ( SW4 )  // Note: must not modify W
  249     btns |= SW4flag;
  250 
  251   return btns;
  252 }
  253 
  254 //############################################################################################
  255 void ReleaseButtons()
  256 //############################################################################################
  257 {
  258   // Works only at beaming mode because of using DpaApiSleep
  259   do
  260   {
  261     _DpaApiDeepSleep( WDTCON_2ms );
  262   } while ( GetButtons() );
  263 }
  264 
  265 //############################################################################################
  266 void Beaming()
  267 //############################################################################################
  268 {
  269   // Indicate start of beaming
  270   setLEDG();
  271   setLEDR();
  272   // Longer LEDs
  273   waitMS( 200 );
  274 
  275   FirstDpaApiSleep = TRUE;
  276   for ( ;; )
  277   {
  278     // Check for the rare lock of the Spirit1 RF chip
  279     if ( wasRFICrestarted() )
  280       _DpaApiSetRfDefaults();
  281 
  282     // Setup IOC for waking up from the next sleep by a "IQRF button" at RB4
  283     IOCBN.4 = TRUE;
  284     // Note: is cleared by OS at iqrf[Deep]Sleep
  285     IOCIE = TRUE;
  286     // Deep sleep until button is pressed
  287     _DpaApiDeepSleep( DpaApiSleep_WdtOff );
  288     // Disables port on change settings
  289     IOCIE = FALSE;
  290     // Clears port on change
  291     IOCBF = 0;
  292 
  293     // No SW* pressed, so we just woke up after 256 s?
  294     if ( GetButtons() == 0 )
  295       // Then keep sleeping...
  296       continue;
  297 
  298     // A gap for the other optional buttons to be pressed by user
  299     _DpaApiDeepSleep( WDTCON_64ms );
  300 
  301     // Condition to open DPA Beaming Menu is met?
  302     if ( AllowOpenDpaMenu() )
  303     {
  304       // Open Beaming DPA Menu
  305       uns8 menuAndItem = DpaApiMenu( DMENU_Beaming, DMENU_Item_Implemented_User1 );
  306       // Which menu item was selected?
  307       switch ( menuAndItem )
  308       {
  309         // ConnectivityCheck - 1st item
  310         case MakeDMenuAndItem( DMENU_Beaming, DMENU_Item_ConnectivityCheck ):
  311           // Stop beaming sleep as we will be doing some RF stuff
  312           _DpaApiAfterSleep();
  313           setRFready();
  314           // Voluntary indication of the TestRange execution
  315           pulsingLEDR();
  316           // Bitmap of repeaters to test at bufferINFO
  317           clearBufferINFO();
  318           bufferINFO[0 / 8] = 0b1111.1110;   // Nodes 1...7
  319           // Do the test and result indication
  320           DpaApiMenuIndicateResult( DpaApiLocalFrc( FRC_Ping, TX_POWER_MAX ) );
  321           // Continue with the regular beaming sleep
  322           FirstDpaApiSleep = TRUE;
  323           break;
  324 
  325           // Abort beaming - 3rd user item
  326         case MakeDMenuAndItem( DMENU_Beaming, DMENU_Item_User1 ):
  327           // Abort beaming indication
  328           setLEDG();
  329           setLEDR();
  330           // Longer LEDs
  331           _DpaApiDeepSleep( WDTCON_256ms );
  332           // No more sleep
  333           _DpaApiAfterSleep();
  334           // Restore RF settings
  335           setRFready();
  336           _DpaApiSetRfDefaults();
  337           // Abort beaming (start inactivity timer at caller)
  338           return;
  339 
  340           // Other (default) menu items
  341         default:
  342           // Stop beaming sleep
  343           _DpaApiAfterSleep();
  344           // Execute the menu item
  345           DpaApiMenuExecute( menuAndItem );
  346           // Continue with the regular beaming sleep
  347           FirstDpaApiSleep = TRUE;
  348           break;
  349 
  350           // No menu item was selected
  351         case MakeDMenuAndItem( DMENU_Beaming, DMENU_Item_None ):
  352           break;
  353       }
  354     }
  355     else
  356     {
  357       // Beaming DPA Menu will not open, we might do the real beaming based on pressed buttons
  358 
  359       // Button press count
  360       uns8 pressCnt = 0;
  361       // Current active pressed button(s)
  362       uns8 btns = GetButtons();
  363 
  364 _Press:
  365       // Voluntary very short pulse button(s) press indication
  366       setLEDG();
  367       _DpaApiDeepSleep( WDTCON_2ms );
  368       stopLEDG();
  369 
  370       // Count the press
  371       pressCnt++;
  372       // Long 500 ms button timeout variable
  373       uns8 longPressTimeout = 500 / 8/*see delay below*/ + 0.5;
  374       do
  375       {
  376         _DpaApiDeepSleep( WDTCON_8ms );
  377         // Same button(s)?
  378         if ( GetButtons() != btns )
  379         {
  380           // No, test for 200 ms whether the same button(s) combination repeats
  381           uns8 againCnt = 200 / 8/*see delay below*/ + 0.5;
  382           do
  383           {
  384             _DpaApiDeepSleep( WDTCON_8ms );
  385             // Same press?
  386             if ( GetButtons() == btns )
  387               // Yes, repeated press, start the main button loop over
  388               goto _Press;
  389           } while ( --againCnt != 0 );
  390 
  391           // No more repeats, it is the last short press
  392           goto _ShortPress;
  393         }
  394       } while ( --longPressTimeout != 0 );
  395       // The long press
  396       btns |= SWlongFlag;
  397 
  398 _ShortPress:
  399       // Release button(s)
  400       ReleaseButtons();
  401       // Test the supported button(s) combination
  402       uns8 ledPulses;
  403       switch ( btns )
  404       {
  405         case SW1flag:
  406         case SW1flag | SWlongFlag:
  407           ledPulses = 1;
  408           break;
  409 
  410         case SW2flag:
  411         case SW2flag | SWlongFlag:
  412           ledPulses = 2;
  413           break;
  414 
  415         case SW3flag:
  416         case SW3flag | SWlongFlag:
  417           ledPulses = 3;
  418           break;
  419 
  420         case SW4flag:
  421         case SW4flag | SWlongFlag:
  422           ledPulses = 4;
  423           break;
  424 
  425         case SW2flag | SW3flag:
  426         case SW2flag | SW3flag | SWlongFlag:
  427           ledPulses = 5;
  428           break;
  429 
  430         default:
  431           ledPulses = 0;
  432           break;
  433       }
  434 
  435       // Do the LED action if any supported SW? combination was pressed
  436       if ( ledPulses != 0 )
  437       {
  438         // Indicate SW? by LEDG (& LEDR) pulse(s)
  439         do
  440         {
  441           setLEDG();
  442           // Was long press?
  443           if ( btns & SWlongFlag )
  444             setLEDR();
  445 
  446           _DpaApiDeepSleep( WDTCON_128ms );
  447           stopLEDG();
  448           stopLEDR();
  449           _DpaApiDeepSleep( WDTCON_256ms );
  450         } while ( --ledPulses != 0 );
  451 
  452         // Indicate number of presses from 2 up by LEDR pulses
  453         if ( pressCnt > 1 )
  454           do
  455           {
  456             _DpaApiDeepSleep( WDTCON_256ms );
  457             setLEDR();
  458             _DpaApiDeepSleep( WDTCON_128ms );
  459             stopLEDR();
  460           } while ( --pressCnt != 0 );
  461       }
  462 
  463       // Wait for button(s) to be released
  464       ReleaseButtons();
  465     }
  466   }
  467 }
  468 
  469 //############################################################################################
  470 bit AllowOpenDpaMenu()
  471 //############################################################################################
  472 {
  473   return GetButtons() == ( SW1flag | SW4flag );
  474 }
  475 
  476 //############################################################################################
  477 // 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)
  478 #include "DPAcustomHandler.h"
  479 //############################################################################################