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