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