1 // *********************************************************************
    2 //   Custom DPA Handler - Bridge using SPI                             *
    3 // *********************************************************************
    4 // Copyright (c) MICRORISC s.r.o.
    5 //
    6 // File:    $RCSfile: CustomDpaHandler-Bridge-SPI.c,v $
    7 // Version: $Revision: 1.13 $
    8 // Date:    $Date: 2021/11/05 10:25:14 $
    9 //
   10 // Revision history:
   11 //   2019/03/07  Release for DPA 4.01
   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 #if defined( TR7xG )
   27 #error This example is not intended for TR7xG
   28 #endif
   29 
   30 //############################################################################################
   31 /*
   32 This handler forwards [1] DpaEvent_DpaRequest and [2] DpaEvent_FrcValue events via SPI to the external device.
   33 The handler behaves as the SPI Slave while the external device is SPI Master.
   34 The external device is responsible for preparing the proper responses to these events. It sends the response back to the handler via SPI.
   35 Handler then responses with the response prepared by the external device back to the IQRF network.
   36 
   37 There is an example CustomDpaHandler-Bridge-SPI.ino for Arduino that shows how the external device prepares the response. In this case the device behaves as Standard IQRF Sensor.
   38 Use an IQRF Breakout board IQRF-BB-01 or IQRF-SHIELD-02 containing the level shifters to connect TR module (3.3 V logic) to the Arduino board (5 V logic):
   39 #IQRF-BB-01    Arduino (Uno)
   40  GND           GND
   41  +3V3/+5V      3.3V
   42  SS            Digital 7
   43  MOSI          Digital 11 (MOSI)
   44  MISO          Digital 12 (MISO)
   45  SCK           Digital 13 (SCK)
   46 
   47 The Handler (SPI Slave) sets SS low to signal SPI Master (external device) that the SPI transaction starts.
   48 It keep SS low during the whole transaction. SS signal can be used to wake up the external SPI master device from sleep state.
   49 
   50 Please restart the TR module every time after the Arduino sketch is uploaded.
   51 
   52 This handler and the external device exchange data (packets) using the HDLC protocol the same way the DPA UART Interface except the CRC is not used.
   53 Please see https://www.iqrf.org/DpaTechGuide/#2.3.2%20UART for details. SPI master recognizes the end of the packet sent from SPI slave by detecting the
   54 closing HDLC_FRM_FLAG_SEQUENCE 0x7e byte. The it prepares the response and sends it back to the SPI slave using the same HDLC coding.
   55 
   56 The handler and the external device pack a different set of information to the packet depending on the event.
   57 
   58 [1] DpaEvent_DpaRequest
   59 The packet (data part) sent to the device has the following structure:
   60 #Byte  Content         Notes
   61  0     0               = DpaEvent_DpaRequest
   62  1     _DpaDataLength
   63  2     NADR
   64  3     NADRhigh
   65  4     PNUM
   66  5     PCMD
   67  6     HWPID.low8
   68  7     HWPID.high8
   69  8...  PDATA           Optional DPA Request data.
   70 
   71 Please note that _DpaDataLength does not have to equal (in case of enumeration DPA Requests) length of PDATA.
   72 The handler waits maximum RESPONSE_TIMEOUT ms for the response from the external device otherwise ERROR_FAIL is returned.
   73 
   74 The device responses with a packet of the following structure:
   75 #Byte  Content         Notes
   76  0     see Notes       = DpaEvent_DpaRequest with ORed optional flags. When bit7 set, the handler returns TRUE otherwise returns FALSE. When bit6 is set, the handler return DPA error specified at byte #2 i.e. PDATA[0]
   77  1     _DpaDataLength
   78  2...  PDATA           Optional DPA Response data.
   79 
   80 [2] DpaEvent_FrcValue
   81 The 32 bytes long packet (data part) sent to the device has the following structure:
   82 #Byte   Content        Notes
   83  0      10             = DpaEvent_FrcValue
   84  1      RFC Command
   85  2...31 FRC user data
   86 
   87 The handler waits maximum FRC_RESPONSE_TIMEOUT ms for the response from the external device otherwise the FRC is not handled and the default DPA value (bit0=1) is returned.
   88 
   89 The device responses with a 5 bytes long packet of the following structure:
   90 #Byte  Content         Notes
   91  0     10              DpaEvent_DpaRequest
   92  1...4 FRC value       Content will be written to the responseFRCvalue, responseFRCvalue2B, responseFRCvalue4B variables. Coded using Little Endian.
   93 */
   94 //############################################################################################
   95 
   96 
   97 // _SS_ pin assignment (C5 = PORTA.5)
   98 // When SS is set low by this Slave, then SPI master wakes up and know that SPI transaction is active while SS is low
   99 #define SS_PIN                    LATA.5
  100 
  101 // DPA response timeout from external device
  102 #define RESPONSE_TIMEOUT          ( 30 / 10 )
  103 // FRC value response timeout from external device
  104 #define FRC_RESPONSE_TIMEOUT      ( 20 / 10 )
  105 // DPA reported fixed FRC response time
  106 #define _FRC_RESPONSE_TIME_FIXED  _FRC_RESPONSE_TIME_40_MS
  107 
  108 //############################################################################################
  109 
  110 // Division macro with rounding
  111 #define DIV(Dividend,Divisor) (((Dividend+((Divisor)>>1))/(Divisor)))
  112 // PIC baud register computation
  113 #define UART_SPBRG_VALUE( Baud )  ( DIV( F_OSC, ( ( ( uns24 )4  ) * ( Baud ) ) ) - 1 )
  114 
  115 // HDLC byte stuffing bytes
  116 // Flag Sequence
  117 #define   HDLC_FRM_FLAG_SEQUENCE    0x7e
  118 // Asynchronous Control Escape
  119 #define   HDLC_FRM_CONTROL_ESCAPE   0x7d
  120 // Asynchronous transparency modifier
  121 #define   HDLC_FRM_ESCAPE_BIT       0x20
  122 
  123 // Flag to DpaEvent_DpaRequest event value to indicate return TRUE not FALSE
  124 #define EVENT_RETURN_TRUE           0x80
  125 // Flag to DpaEvent_DpaRequest event value to report error, error value is the 1st data byte
  126 #define EVENT_RESPONSE_ERROR        0x40
  127 
  128 //############################################################################################
  129 
  130 // Buffer used for exchange data with external device
  131 #define RxBuffer  bufferCOM
  132 // Sends byte to the external device
  133 uns8 TxByte( uns8 data );
  134 // Sends HDLC byte to the external device
  135 void TxHdlcByte( uns8 data );
  136 // Receives data from external device, returns length, 0 if timeout occurred
  137 uns8 RxPacket( uns8 timeout );
  138 // Indicates start/stop of the SPI transaction to the master
  139 void StartSpiTransaction();
  140 void StopSpiTransaction();
  141 // Initialization
  142 void Init();
  143 
  144 //############################################################################################
  145 bit CustomDpaHandler()
  146 //############################################################################################
  147 {
  148   // Handler presence mark
  149   clrwdt();
  150 
  151   // Detect DPA event to handle (unused event handlers can be commented out or even deleted)
  152   switch ( GetDpaEvent() )
  153   {
  154     // -------------------------------------------------
  155     // Handler these unused events as fast as possible
  156     case DpaEvent_Idle:
  157       Init();
  158 
  159     case DpaEvent_Interrupt:
  160       return Carry;
  161 
  162       // -------------------------------------------------
  163     case DpaEvent_FrcValue:
  164       // Called to get FRC value
  165 
  166       // Initialize HW if needed
  167       Init();
  168       // Start measuring timeout for RxPacket() function
  169       startCapture();
  170 
  171       // Start packet
  172       StartSpiTransaction();
  173       TxByte( HDLC_FRM_FLAG_SEQUENCE );
  174       // Send event value
  175       TxHdlcByte( DpaEvent_FrcValue );
  176       // Send FRC command
  177       TxHdlcByte( _PCMD );
  178       // Now send all FRC user data
  179       uns8 loop = sizeof( DataOutBeforeResponseFRC );
  180       FSR0 = &DataOutBeforeResponseFRC[0];
  181       do
  182       {
  183         TxHdlcByte( *FSR0++ );
  184       } while ( --loop != 0 );
  185       // Stop packet
  186       TxByte( HDLC_FRM_FLAG_SEQUENCE );
  187 
  188       // Receive the FRC value from the device via UART, length must equal to the event value + 4 FRC value bytes
  189       if ( RxPacket( FRC_RESPONSE_TIMEOUT ) == ( sizeof( uns8 ) + sizeof( uns32 ) ) && RxBuffer[0] == DpaEvent_FrcValue )
  190       {
  191         // Return FRC values to DPA
  192 #if IQRFOS >= 403
  193         responseFRCvalue4B.low8 = RxBuffer[1];
  194         responseFRCvalue4B.midL8 = RxBuffer[2];
  195         responseFRCvalue4B.midH8 = RxBuffer[3];
  196         responseFRCvalue4B.high8 = RxBuffer[4];
  197 #else
  198         responseFRCvalue = RxBuffer[1];
  199         responseFRCvalue2B.low8 = RxBuffer[1];
  200         responseFRCvalue2B.high8 = RxBuffer[2];
  201 #endif
  202       }
  203 
  204       StopSpiTransaction();
  205       break;
  206 
  207       // -------------------------------------------------
  208     case DpaEvent_FrcResponseTime:
  209       // Called to get FRC response time
  210       // ToDo - Improve, make value dynamic?
  211       responseFRCvalue = _FRC_RESPONSE_TIME_FIXED;
  212       break;
  213 
  214       // -------------------------------------------------
  215     case DpaEvent_DpaRequest:
  216       // Called to interpret DPA request for peripherals
  217 
  218       // Initialize HW if needed
  219       Init();
  220       // Pass the request to the connected device via UART (must not modify FSR0 till it is used)
  221       // Start packet
  222       StartSpiTransaction();
  223       TxByte( HDLC_FRM_FLAG_SEQUENCE );
  224       // Send event value
  225       TxHdlcByte( DpaEvent_DpaRequest );
  226       // Send DPA variable data length (must not equal to the actual data length sent)
  227       TxHdlcByte( _DpaDataLength );
  228       // Send DPA message fields
  229       TxHdlcByte( _NADR );
  230       TxHdlcByte( _NADRhigh );
  231       TxHdlcByte( _PNUM );
  232       TxHdlcByte( _PCMD );
  233       TxHdlcByte( _HWPID.low8 );
  234       TxHdlcByte( _HWPID.high8 );
  235 
  236       // How much data to pass to the device?
  237       uns8 dataLength;
  238       if ( IsDpaEnumPeripheralsRequest() )
  239         dataLength = sizeof( _DpaMessage.EnumPeripheralsAnswer );
  240       else if ( IsDpaPeripheralInfoRequest() )
  241         dataLength = sizeof( _DpaMessage.PeripheralInfoAnswer );
  242       else
  243         // Same amount as available
  244         dataLength = _DpaDataLength;
  245 
  246       // FSRx might have been destroyed by Init()
  247       setFSR01( _FSR_RF, _FSR_RF );
  248       // Now send the data byte by byte
  249       for ( ; dataLength != 0; dataLength-- )
  250         TxHdlcByte( *FSR0++ );
  251       // Stop packet
  252       TxByte( HDLC_FRM_FLAG_SEQUENCE );
  253 
  254       // Start measuring timeout for RxPacket() function
  255       startCapture();
  256 
  257       // Receive the response from the device via UART
  258       dataLength = RxPacket( RESPONSE_TIMEOUT );
  259 
  260       StopSpiTransaction();
  261 
  262       // Check for timeout and correct event
  263       if ( dataLength == 0 || ( RxBuffer[0] & ~( EVENT_RETURN_TRUE | EVENT_RESPONSE_ERROR ) ) != DpaEvent_DpaRequest )
  264         DpaApiReturnPeripheralError( ERROR_FAIL );
  265 
  266       // Report DPA error?
  267       if ( ( RxBuffer[0] & EVENT_RESPONSE_ERROR ) != 0 )
  268         DpaApiReturnPeripheralError( RxBuffer[2] );
  269 
  270       // Get DPA data length field
  271       _DpaDataLength = RxBuffer[1];
  272       // Copy DPA response data (all data minus event value and _DpaDataLength value)
  273       copyMemoryBlock( &RxBuffer[2], &_DpaMessage.Response.PData[0], dataLength - 2 );
  274 
  275       // Return TRUE or FALSE
  276 #if EVENT_RETURN_TRUE != 0x80
  277 #error Cannot optimize
  278 #endif
  279       // Carry = TRUE of FALSE to return, got from EVENT_RETURN_TRUE part of the 1st byte which is header
  280       W = rl( RxBuffer[0] );
  281       return Carry;
  282   }
  283 
  284   return FALSE;
  285 }
  286 
  287 //############################################################################################
  288 uns8 RxByteValue;
  289 bit RxByte()
  290 //############################################################################################
  291 {
  292   // SPI byte ready?
  293   if ( !BF )
  294     return FALSE;
  295   // Get the byte
  296   RxByteValue = SSPBUF;
  297   // This value make sure HDLC machine will reset
  298   SSPBUF = HDLC_FRM_FLAG_SEQUENCE;
  299   return TRUE;
  300 }
  301 
  302 //############################################################################################
  303 uns8 RxPacket( uns8 timeout )
  304 //############################################################################################
  305 {
  306 #define MIN_RX_PACKET_DATA_LENGTH  1
  307 #define MAX_RX_PACKET_DATA_LENGTH  sizeof( RxBuffer )
  308 
  309   typedef enum { RXstateWaitHead, RXstatePacket, RXstateEscape } TState;
  310 
  311   TState state = RXstateWaitHead;
  312   uns8 rxLength;
  313   for ( ; ; )
  314   {
  315     clrwdt();
  316     // Timeout?
  317     captureTicks();   // Note: must not modify FSR0
  318     if ( param3 > timeout )
  319       return 0;
  320 
  321     // If anything received via SPI
  322     if ( RxByte() )
  323     {
  324       // HDLC machine
  325       skip( (uns8)state );
  326 #pragma computedGoto 1
  327       goto _RXstateWaitHead;
  328       goto _RXstatePacket;
  329       goto _RXstateEscape;
  330 #pragma computedGoto 0
  331       ;
  332       // ---------------------------
  333 _RXstateEscape:
  334       switch ( RxByteValue )
  335       {
  336         case HDLC_FRM_FLAG_SEQUENCE:
  337           goto _SetRXstatePacket;
  338 
  339         case HDLC_FRM_CONTROL_ESCAPE:
  340 _SetRXstateWaitHead:
  341           state = RXstateWaitHead;
  342           continue;
  343       }
  344 
  345       state--; // RXstatePacket
  346       RxByteValue ^= HDLC_FRM_ESCAPE_BIT;
  347       goto _StoreByte;
  348 
  349       // ---------------------------
  350 _RXstatePacket:
  351       switch ( RxByteValue )
  352       {
  353         case HDLC_FRM_CONTROL_ESCAPE:
  354           // RXstateEscape
  355 _NextState:
  356           state++;
  357 
  358         case HDLC_FRM_FLAG_SEQUENCE:
  359         {
  360           if ( rxLength >= MIN_RX_PACKET_DATA_LENGTH )
  361             return rxLength;
  362 
  363           goto _SetRXstatePacket;
  364         }
  365       }
  366 
  367 _StoreByte:
  368       if ( rxLength == ( MAX_RX_PACKET_DATA_LENGTH + 2 ) )
  369         goto _SetRXstateWaitHead;
  370 
  371       setINDF0( RxByteValue );
  372       FSR0++;
  373       rxLength++;
  374       continue;
  375 
  376       // ---------------------------
  377 _RXstateWaitHead:
  378       if ( RxByteValue == HDLC_FRM_FLAG_SEQUENCE )
  379       {
  380 _SetRXstatePacket:
  381         rxLength = 0;
  382         FSR0 = RxBuffer;
  383         state = RXstatePacket;
  384       }
  385 
  386       continue;
  387     }
  388   }
  389 }
  390 
  391 //############################################################################################
  392 void StartSpiTransaction()
  393 //############################################################################################
  394 {
  395   // This value make sure HDLC machine will reset
  396   SSPBUF = HDLC_FRM_FLAG_SEQUENCE;
  397   // SPI transaction starts
  398   SS_PIN = 0;
  399   // Some delay to let master wake up (might be removed)
  400   waitMS( 1 );
  401 }
  402 
  403 //############################################################################################
  404 void StopSpiTransaction()
  405 //############################################################################################
  406 {
  407   // SPI transaction is over
  408   SS_PIN = 1;
  409   // This value make sure HDLC machine will reset
  410   SSPBUF = HDLC_FRM_FLAG_SEQUENCE;
  411 }
  412 
  413 //############################################################################################
  414 uns8 TxByte( uns8 data )
  415 //############################################################################################
  416 {
  417   SSPBUF = data;
  418   // WDT reset will handle the timeout
  419   while ( !BF );
  420   return SSPBUF;
  421 }
  422 
  423 //############################################################################################
  424 void TxHdlcByte( uns8 data )
  425 //############################################################################################
  426 {
  427   switch ( data )
  428   {
  429     default:
  430       TxByte( data );
  431       return;
  432 
  433     case HDLC_FRM_FLAG_SEQUENCE:
  434     case HDLC_FRM_CONTROL_ESCAPE:
  435     {
  436       TxByte( HDLC_FRM_CONTROL_ESCAPE );
  437       TxByte( data ^ HDLC_FRM_ESCAPE_BIT );
  438       return;
  439     }
  440   }
  441 }
  442 
  443 //############################################################################################
  444 void Init()
  445 //############################################################################################
  446 {
  447   // Initialize SPI
  448   if ( !SSPEN )
  449   {
  450     // Initialize PINs to inputs and outputs
  451     TRISC.5 = 0;        // RC5 as output SDO (C8)
  452     TRISC.4 = 1;        // RC4 as input SDI (C7)
  453     TRISC.3 = 1;        // RC3 as input SCK (C6)
  454     TRISA.5 = 0;        // RA5 as output SS (C5)
  455 
  456     // Set idle level of SS
  457     SS_PIN = 1;
  458 
  459     // TR module with connected pins?
  460     moduleInfo();
  461     if ( bufferINFO[5].7 == 0 )
  462     {
  463       // Yes
  464       TRISC.6 = 1;        // RC6 as input (connected to RA5 in parallel)
  465       TRISB.4 = 1;        // RB4 as input (connected to RA5 in parallel)
  466       TRISC.7 = 1;        // RC7 as input (connected to RC5 in parallel)
  467     }
  468 
  469     // Setup SPI
  470     // SSPSTAT: Transmit occurs on transition from Idle to active clock state
  471     SSPSTAT = 0;
  472     // No SPI interrupts
  473     SSPIE = 0;
  474     // SSPCON1: (SSPCON1 cannot be accessed directly due to OS restriction)
  475     //  SPI Slave mode, clock = SCK pin, SS pin control disabled, SS can be used as I/O pin
  476     //  Idle state for clock is a low level
  477     writeToRAM( &SSPCON1, 0b0.0.1.0.0101 );
  478   }
  479 }
  480 
  481 //############################################################################################
  482 // 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)
  483 #include "DPAcustomHandler.h"
  484 //############################################################################################