1 // ********************************************************************************************
    2 //   Custom DPA Handler code example - User peripheral implementation - Multiple Dallas 18B20 *
    3 // ********************************************************************************************
    4 // Copyright (c) MICRORISC s.r.o.
    5 //
    6 // File:    $RCSfile: CustomDpaHandler-UserPeripheral-18B20-Multiple.c,v $
    7 // Version: $Revision: 1.29 $
    8 // Date:    $Date: 2022/02/25 09:41:25 $
    9 //
   10 // Revision history:
   11 //   2022/02/24  Release for DPA 4.17
   12 //   2017/03/13  Release for DPA 3.00
   13 //   2016/09/12  Release for DPA 2.28
   14 //   2015/12/16  Release for DPA 2.24
   15 //
   16 // *********************************************************************
   17 
   18 // Online DPA documentation https://doc.iqrf.org/DpaTechGuide/
   19 
   20 // This example implements the user peripheral reading temperature from one Dallas 18B20 temperature sensor
   21 // There are more such sensors connected to the 1-Wire bus
   22 // * PNUM = 0x20, PCMD = 0 and PDATA="8 byte ROM code" of the sensor, returns 2 bytes with temperature value read from the selected Dallas 18B20
   23 // * PNUM = 0x20, PCMD = 1 returns list of up to seven "8 byte ROM codes" of connected 1-Wire devices
   24 // Dallas 18B20 sensors are connected to MCU pin (see below) with 10k pull-up resistor and not using parasite power
   25 
   26 // Default IQRF include (modify the path according to your setup)
   27 #include "IQRF.h"
   28 
   29 // Default DPA header (modify the path according to your setup)
   30 #include "DPA.h"
   31 // Default Custom DPA Handler header (modify the path according to your setup)
   32 #include "DPAcustomHandler.h"
   33 
   34 //############################################################################################
   35 
   36 // Special temperature value to indicate a sensor error
   37 #define DS18B20_ERROR_TEMPERATURE 0xF800
   38 
   39 // Sensor connected to PORT C.3 (compatible with multiple DDC-SE-01)
   40 #define OneWire_TRIS         TRISC.3
   41 #define OneWire_IO_IN        PORTC.3
   42 #define OneWire_IO_OUT       LATC.3
   43 
   44 // Reads temperature from sensor
   45 uns16 Ds18B20GetTemp();
   46 // Writes sensor configuration (resolution)
   47 bit Ds18B20WriteConfig( uns8 value );
   48 // Searches 1-Wire bus device IDs
   49 uns8 OneWireSearch();
   50 
   51 // DS18B20 commands
   52 #define CMD_READROM       0x33
   53 #define CMD_CONVERTTEMP   0x44
   54 #define CMD_CPYSCRATCHPAD 0x48
   55 #define CMD_WSCRATCHPAD   0x4e
   56 #define CMD_MATCHROM      0x55
   57 #define CMD_RPWRSUPPLY    0xb4
   58 #define CMD_RECEEPROM     0xb8
   59 #define CMD_RSCRATCHPAD   0xbe
   60 #define CMD_SKIPROM       0xcc
   61 #define CMD_ALARMSEARCH   0xec
   62 #define CMD_SEARCHROM     0xf0
   63 
   64 // What to send to 1-Wire bus after Reset condition and before sending command
   65 #define ROM_SKIP  0
   66 #define ROM_MATCH 1
   67 #define ROM_none  2
   68 
   69 // Must be the 1st defined function in the source code in order to be placed at the correct FLASH location!
   70 //############################################################################################
   71 bit CustomDpaHandler()
   72 //############################################################################################
   73 {
   74   // Handler presence mark
   75   clrwdt();
   76 
   77   // Detect DPA event to handle
   78   switch ( GetDpaEvent() )
   79   {
   80     // -------------------------------------------------
   81     case DpaEvent_Interrupt:
   82       // Do an extra quick background interrupt work
   83       // ! 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.
   84       // ! 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.
   85       // ! 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.
   86       // ! Only global variables or local ones marked by static keyword can be used to allow reentrancy.
   87       // ! Make sure race condition does not occur when accessing those variables at other places.
   88       // ! 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.
   89       // ! Do not call any OS functions except setINDFx().
   90       // ! Do not use any OS variables especially for writing access.
   91       // ! 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.
   92 
   93 DpaHandleReturnTRUE:
   94       return TRUE;
   95 
   96       // -------------------------------------------------
   97     case DpaEvent_Init:
   98       // Do a one time initialization before main loop starts
   99 
  100       // Setup DS18B20 for 9bit precision, conversion takes 94ms (see datasheet)
  101       Ds18B20WriteConfig( 0b0.00.00000 );
  102 
  103       //// Setup DS18B20 for 12bit precision, conversion takes 750ms (see datasheet)
  104       //Ds18B20WriteConfig( 0b0.11.00000 );
  105 
  106       break;
  107 
  108       // -------------------------------------------------
  109     case DpaEvent_DpaRequest:
  110       // Called to interpret DPA request for peripherals
  111       // -------------------------------------------------
  112       // Peripheral enumeration
  113       if ( IsDpaEnumPeripheralsRequest() )
  114       {
  115         // We implement 1 user peripheral
  116         _DpaMessage.EnumPeripheralsAnswer.UserPerNr |= 1;
  117         FlagUserPer( _DpaMessage.EnumPeripheralsAnswer.UserPer, PNUM_USER + 0 );
  118         _DpaMessage.EnumPeripheralsAnswer.HWPID |= 0x000F;
  119         _DpaMessage.EnumPeripheralsAnswer.HWPIDver |= 0xaabb;
  120 
  121         goto DpaHandleReturnTRUE;
  122       }
  123       // -------------------------------------------------
  124       // Get information about peripheral
  125       else if ( IsDpaPeripheralInfoRequest() )
  126       {
  127         if ( _PNUM == PNUM_USER + 0 )
  128         {
  129           _DpaMessage.PeripheralInfoAnswer.PerT = PERIPHERAL_TYPE_USER_AREA;
  130           _DpaMessage.PeripheralInfoAnswer.PerTE = PERIPHERAL_TYPE_EXTENDED_READ;
  131           goto DpaHandleReturnTRUE;
  132         }
  133 
  134         break;
  135       }
  136       // -------------------------------------------------
  137       else
  138       {
  139         // Handle peripheral command
  140         if ( _PNUM == PNUM_USER + 0 )
  141         {
  142           // Check command
  143           switch ( _PCMD )
  144           {
  145             case 0:
  146               // -------------------------------------------------
  147               // Read temperature
  148               if ( _DpaDataLength != 8 )
  149                 DpaApiReturnPeripheralError( ERROR_DATA_LEN );
  150 
  151               uns16 temperature @ _DpaMessage.Response.PData;
  152               temperature = Ds18B20GetTemp();
  153               if ( temperature == DS18B20_ERROR_TEMPERATURE )
  154                 DpaApiReturnPeripheralError( ERROR_FAIL );
  155 
  156               _DpaDataLength = sizeof( temperature );
  157               goto DpaHandleReturnTRUE;
  158 
  159             case 1:
  160               // -------------------------------------------------
  161               // Read 1-Wire ROM codes
  162               if ( _DpaDataLength != 0 )
  163                 DpaApiReturnPeripheralError( ERROR_DATA_LEN );
  164 
  165               _DpaDataLength = OneWireSearch();
  166               goto DpaHandleReturnTRUE;
  167           }
  168         }
  169       }
  170   }
  171 
  172   return FALSE;
  173 }
  174 
  175 //############################################################################################
  176 // OneWire and Dallas 18B20 routines
  177 // If there is an interrupt running that consumes a lot of MCU time (>450us once per 1ms) then use GIE=1/GIE=0 at whole OneWireResetAndCmd() for sure
  178 //############################################################################################
  179 
  180 //############################################################################################
  181 void Delay5us( uns8 val @ W ) // Absolutely precise timing but val != 0
  182 //############################################################################################
  183 {
  184 #if F_OSC == 16000000
  185   // 16 MHz
  186   // + 0.75us ( W=val, Call )
  187   for ( ;; )
  188   {         // loop time
  189     nop2(); // 0.50us
  190     nop2(); // 1.00us
  191     nop2(); // 1.50us
  192     nop2(); // 2.00us
  193     nop2(); // 2.50us
  194     nop2(); // 3.00us
  195     nop();  // 3.25us
  196     if ( --val == 0 ) // + 0.75us (W--, BTFS )
  197       return;         // + 0.25us
  198     nop2(); // 4.50us
  199   }         // 5.00us (Goto)
  200 #else
  201 #error Unsupported oscillator frequency
  202 #endif
  203 }
  204 //############################################################################################
  205 
  206 #define OneWireData0()  { OneWire_TRIS = 0; }     // 0.5us @ 16MHz
  207 #define OneWireData1()  { OneWire_TRIS = 1; }     // 0.5us @ 16MHz
  208 
  209 //############################################################################################
  210 void OneWireWriteBit( bit value )
  211 //############################################################################################
  212 {
  213   // Next sequence is time precision critical
  214   GIE = FALSE;
  215 
  216   OneWireData0();
  217   nop2();           // 1 us [0.5 us]
  218 #if F_OSC == 16000000
  219   nop2();           // [1.0 us]
  220 #endif
  221   if ( value )  // 2.5 us [1.75us]
  222     OneWireData1();
  223 
  224   // End of time precision critical sequence
  225   GIE = TRUE;
  226 
  227   // 60us minimum in total, does not have to be precise
  228   Delay5us( ( 60 - 3 ) / 5 + 1 );
  229   OneWireData1();
  230 }
  231 
  232 //############################################################################################
  233 void OneWireWriteByte( uns8 byte )
  234 //############################################################################################
  235 {
  236   uns8 bitLoop = 8;
  237   do
  238   {
  239     byte = rr( byte );
  240     OneWireWriteBit( Carry );
  241   } while ( --bitLoop != 0 );
  242 }
  243 
  244 //############################################################################################
  245 bit OneWireReadBit()
  246 //############################################################################################
  247 {
  248   // Next sequence is time precision critical
  249   GIE = FALSE;
  250 
  251   OneWireData0();
  252   nop2();           // 1 us [0.5 us]
  253 #if F_OSC == 16000000
  254   nop2();           // [1.0 us]
  255 #endif
  256   OneWireData1();           // 2 us [1.5 us]
  257   Delay5us( 15 / 5 );       // 17 us [16.5 us]
  258 
  259   nop();                // 17.5 us [16.75 us]
  260   if ( OneWire_IO_IN )  // 18.5 us [ 17.25 us]
  261   {
  262     GIE = TRUE;
  263     // 60us minimum in total, does not have to be precise
  264     Delay5us( ( 60 - 20 ) / 5 + 1 );
  265     return TRUE;
  266   }
  267   else
  268   {
  269     GIE = TRUE;
  270     // 60us minimum in total, does not have to be precise
  271     Delay5us( ( 60 - 20 ) / 5 + 1 );
  272     return FALSE;
  273   }
  274 }
  275 
  276 //############################################################################################
  277 uns8 OneWireReadByte()
  278 //############################################################################################
  279 {
  280   uns8 result;
  281   uns8 bitLoop = 8;
  282   do
  283   {
  284     // Read one bit
  285     Carry = OneWireReadBit();
  286     result = rr( result );
  287   } while ( --bitLoop != 0 );
  288 
  289   return result;
  290 }
  291 
  292 //############################################################################################
  293 bit OneWireResetAndCmd( uns8 ROM, uns8 cmd )
  294 //############################################################################################
  295 {
  296   // Setting the pin once to low is enough
  297   OneWire_IO_OUT = 0;
  298   // Reset pulse
  299   OneWireData0();
  300   Delay5us( 500 / 5 );
  301   // Reset pulse end
  302   OneWireData1();
  303   // Next sequence is time precision critical
  304   GIE = FALSE;
  305   // Wait for presence pulse
  306   Delay5us( 70 / 5 );
  307   // End of time precision critical sequence
  308   GIE = TRUE;
  309   // Presence pulse?
  310   if ( OneWire_IO_IN )
  311   {
  312     // No presence, finish initialization sequence
  313     Delay5us( ( 500 - 70 ) / 5 );
  314     return FALSE;
  315   }
  316   else
  317   {
  318     // Presence OK, finish initialization sequence
  319     Delay5us( ( 500 - 70 ) / 5 );
  320     switch ( ROM )
  321     {
  322       case ROM_SKIP:
  323         // OneWire: Skip ROM
  324         OneWireWriteByte( CMD_SKIPROM );
  325         break;
  326 
  327       case ROM_MATCH:
  328         // OneWire: Match ROM (must not be used during search because usage o FSR0)
  329         OneWireWriteByte( CMD_MATCHROM );
  330         FSR0 = _DpaMessage.Request.PData;
  331         do
  332         {
  333           OneWireWriteByte( *FSR0++ );
  334         } while ( FSR0L != ( &_DpaMessage.Request.PData[8] & 0xFF ) );
  335         break;
  336 
  337       case ROM_none:
  338         break;
  339     }
  340 
  341     OneWireWriteByte( cmd );
  342     return TRUE;
  343   }
  344 }
  345 
  346 //############################################################################################
  347 
  348 // One Wire CRC accumulator
  349 uns8 OneWireCrc;
  350 
  351 //############################################################################################
  352 void UpdateOneWireCrc( uns8 value )
  353 //############################################################################################
  354 {
  355   uns8 _FSR1H = FSR1H;
  356   OneWireCrc = DpaApiCrc8( OneWireCrc, value );
  357   FSR1H = _FSR1H;
  358 }
  359 
  360 //############################################################################################
  361 uns16 Ds18B20GetTemp()
  362 //############################################################################################
  363 {
  364   // OneWire: Convert T
  365   if ( OneWireResetAndCmd( ROM_MATCH, CMD_CONVERTTEMP ) )
  366   {
  367     // Wait for conversion to finish
  368     startCapture();
  369     for ( ;; )
  370     {
  371       // 1s timeout
  372       captureTicks();
  373       if ( param3.low8 > ( 1000 / 10 ) )
  374       {
  375         // Timeout
  376         Carry = FALSE;
  377         break;
  378       }
  379 
  380       if ( OneWireReadByte() == 0xff )
  381       {
  382         // Temperature ready to be read
  383         Carry = TRUE;
  384         break;
  385       }
  386     }
  387 
  388     // OneWire: Read Scratchpad
  389     if ( Carry && OneWireResetAndCmd( ROM_MATCH, CMD_RSCRATCHPAD ) )
  390     {
  391       // Read Scratchpad bytes
  392       uns16 temperature;
  393 
  394       // Initialize CRC
  395       OneWireCrc = 0;
  396       // Will read later rest of scratchpad bytes and update CRC (initialization is here to avoid MOVLB later)
  397       uns8 byteLoop = 9 - 2;
  398 
  399       // Temperature LSB into result & CRC
  400       UpdateOneWireCrc( temperature.low8 = OneWireReadByte() );
  401       // Temperature MSB into result & CRC
  402       UpdateOneWireCrc( temperature.high8 = OneWireReadByte() );
  403 
  404       // Read rest of Scratchpad
  405       do
  406       {
  407         UpdateOneWireCrc( OneWireReadByte() );
  408       } while ( --byteLoop != 0 );
  409 
  410       // Check correct CRC
  411       if ( OneWireCrc == 0 )
  412         return temperature;
  413     }
  414   }
  415   // Some error occurred
  416   return DS18B20_ERROR_TEMPERATURE;
  417 }
  418 
  419 //############################################################################################
  420 bit Ds18B20WriteConfig( uns8 value )
  421 //############################################################################################
  422 {
  423   // OneWire: Write Scratchpad
  424   if ( OneWireResetAndCmd( ROM_SKIP, CMD_WSCRATCHPAD ) )
  425   {
  426     // Write TL = ? (we dot not care the value)
  427     OneWireWriteByte( W );
  428     // Write TH = ? (we dot not care the value)
  429     OneWireWriteByte( W );
  430     // Write configuration byte
  431     OneWireWriteByte( value );
  432 
  433     // OneWire: Copy Scratchpad
  434     if ( OneWireResetAndCmd( ROM_SKIP, CMD_CPYSCRATCHPAD ) )
  435       return TRUE;
  436   }
  437   return FALSE;
  438 }
  439 
  440 //############################################################################################
  441 uns8 OneWireSearch()
  442 //############################################################################################
  443 {
  444   // Based on Maxim Integrated APPLICATION NOTE 187
  445 
  446   // reset the search state
  447   uns8 LastDiscrepancy = 0;
  448   // FSR0 points to the current ROM id
  449   FSR0 = _DpaMessage.Response.PData;
  450   // FSR1 points to the last read ROM id
  451   // Start loop only if Search command is confirmed
  452   for ( FSR1 = FSR0; OneWireResetAndCmd( ROM_none, CMD_SEARCHROM ); )
  453   {
  454     // initialize for search
  455     uns8 rom_byte_number = 0;
  456     uns8 last_zero = 0;
  457     OneWireCrc = 0;
  458     uns8 id_bit_number = 1;
  459     uns8 rom_byte_mask = 0x01;
  460 
  461     // loop to do the search
  462     do
  463     {
  464       // read a bit and its complement
  465       bit id_bit = OneWireReadBit();
  466       bit cmp_id_bit = OneWireReadBit();
  467 
  468       // check for no devices on 1-wire
  469       if ( id_bit && cmp_id_bit )
  470         break;
  471       else
  472       {
  473         bit search_direction;
  474 
  475         // all devices coupled have 0 or 1
  476         if ( id_bit != cmp_id_bit )
  477           search_direction = id_bit;  // bit write value for search
  478         else
  479         {
  480           // if this discrepancy is before the Last Discrepancy on a previous next then pick the same as last time
  481           if ( id_bit_number < LastDiscrepancy )
  482             search_direction = ( *FSR1 & rom_byte_mask ) != 0;
  483           else
  484             // if equal last pick 1, if not then pick 0
  485             search_direction = id_bit_number == LastDiscrepancy;
  486 
  487           // if 0 was picked then record its position in LastZero
  488           if ( !search_direction )
  489             last_zero = id_bit_number;
  490         }
  491 
  492         // set or clear the bit in the ROM byte rom_byte_number with mask rom_byte_mask
  493         if ( search_direction )
  494           setINDF0( *FSR0 | rom_byte_mask );
  495         else
  496           setINDF0( *FSR0 & ~rom_byte_mask );
  497 
  498         // serial number search direction write bit
  499         OneWireWriteBit( search_direction );
  500 
  501         // increment the byte counter id_bit_number
  502         id_bit_number++;
  503         // and shift the mask rom_byte_mask
  504         rom_byte_mask <<= 1;
  505 
  506         // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
  507         if ( rom_byte_mask == 0 )
  508         {
  509           // Check for result overflow
  510           if ( FSR0L == ( &_DpaMessage.Response.PData[sizeof( _DpaMessage.Response.PData )] & 0xff ) )
  511             break;
  512 
  513           // compute CRC, move pointers, increment index and reset byte mask
  514           UpdateOneWireCrc( *FSR0++ );
  515           FSR1++;
  516           rom_byte_number++;
  517           rom_byte_mask.0 = 1;
  518         }
  519       }
  520     } while ( !rom_byte_number.3 /* !=8 */ );  // loop until through all ROM bytes 0-7
  521 
  522     // if the search was successful then
  523     if ( rom_byte_number.3 /* ==8 */ && OneWireCrc == 0 )
  524     {
  525       // search successful so set LastDiscrepancy
  526       LastDiscrepancy = last_zero;
  527 
  528       // check for last device
  529       if ( LastDiscrepancy == 0 )
  530         break;
  531     }
  532     else
  533       break;
  534 
  535     // FSR1 points to the last ROM ID read (optimized against FSR1 = FSR0 - 8)
  536     FSR1 = FSR0;
  537     FSR1 -= 8;
  538   }
  539 
  540   // return length of read bytes
  541   return FSR0L - ( &_DpaMessage.Response.PData[0] & 0xff );
  542 }
  543 
  544 //############################################################################################
  545 // 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)
  546 #include "DPAcustomHandler.h"
  547 //############################################################################################