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