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.22 $
    8 // Date:    $Date: 2021/04/26 15:13:51 $
    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 https://doc.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 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 == 16000000
  189   // 16 MHz
  190   // + 0.75us ( W=val, Call )
  191   for ( ;; )
  192   {         // loop time
  193     nop2(); // 0.50us
  194     nop2(); // 1.00us
  195     nop2(); // 1.50us
  196     nop2(); // 2.00us
  197     nop2(); // 2.50us
  198     nop2(); // 3.00us
  199     nop();  // 3.25us
  200     if ( --val == 0 ) // + 0.75us (W--, BTFS ) 
  201       return;         // + 0.25us 
  202     nop2(); // 4.50us
  203   }         // 5.00us (Goto)
  204 #else
  205 #error Unsupported oscillator frequency
  206 #endif
  207 }
  208 //############################################################################################
  209 
  210 #define OneWireData0()  { OneWire_TRIS = 0; }     // 0.5us @ 16MHz
  211 #define OneWireData1()  { OneWire_TRIS = 1; }     // 0.5us @ 16MHz
  212 
  213 //############################################################################################
  214 void OneWireWriteBit( bit value )
  215 //############################################################################################
  216 {
  217   // Next sequence is time precision critical
  218   GIE = FALSE;
  219 
  220   OneWireData0();
  221   nop2();           // 1 us [0.5 us]
  222 #if F_OSC == 16000000
  223   nop2();           // [1.0 us]
  224 #endif
  225   if ( value )  // 2.5 us [1.75us]
  226     OneWireData1();
  227 
  228   // End of time precision critical sequence
  229   GIE = TRUE;
  230 
  231   // 60us minimum in total, does not have to be precise
  232   Delay5us( ( 60 - 3 ) / 5 + 1 );
  233   OneWireData1();
  234 }
  235 
  236 //############################################################################################
  237 void OneWireWriteByte( uns8 byte )
  238 //############################################################################################
  239 {
  240   uns8 bitLoop = 8;
  241   do
  242   {
  243     byte = rr( byte );
  244     OneWireWriteBit( Carry );
  245   } while ( --bitLoop != 0 );
  246 }
  247 
  248 //############################################################################################
  249 bit OneWireReadBit()
  250 //############################################################################################
  251 {
  252   // Next sequence is time precision critical
  253   GIE = FALSE;
  254 
  255   OneWireData0();
  256   nop2();           // 1 us [0.5 us]
  257 #if F_OSC == 16000000
  258   nop2();           // [1.0 us]
  259 #endif
  260   OneWireData1();           // 2 us [1.5 us]
  261   Delay5us( 15 / 5 );       // 17 us [16.5 us]
  262 
  263   nop();                // 17.5 us [16.75 us]
  264   if ( OneWire_IO_IN )  // 18.5 us [ 17.25 us]
  265   {
  266     GIE = TRUE;
  267     // 60us minimum in total, does not have to be precise
  268     Delay5us( ( 60 - 20 ) / 5 + 1 );
  269     return TRUE;
  270   }
  271   else
  272   {
  273     GIE = TRUE;
  274     // 60us minimum in total, does not have to be precise
  275     Delay5us( ( 60 - 20 ) / 5 + 1 );
  276     return FALSE;
  277   }
  278 }
  279 
  280 //############################################################################################
  281 uns8 OneWireReadByte()
  282 //############################################################################################
  283 {
  284   uns8 result;
  285   uns8 bitLoop = 8;
  286   do
  287   {
  288     // Read one bit
  289     Carry = OneWireReadBit();
  290     result = rr( result );
  291   } while ( --bitLoop != 0 );
  292 
  293   return result;
  294 }
  295 
  296 //############################################################################################
  297 bit OneWireResetAndCmd( uns8 ROM, uns8 cmd )
  298 //############################################################################################
  299 {
  300   // Setting the pin once to low is enough
  301   OneWire_IO_OUT = 0;
  302   // Reset pulse
  303   OneWireData0();
  304   Delay5us( 500 / 5 );
  305   // Reset pulse end
  306   OneWireData1();
  307   // Next sequence is time precision critical
  308   GIE = FALSE;
  309   // Wait for presence pulse
  310   Delay5us( 70 / 5 );
  311   // End of time precision critical sequence
  312   GIE = TRUE;
  313   // Presence pulse?
  314   if ( OneWire_IO_IN )
  315   {
  316     // No presence, finish initialization sequence
  317     Delay5us( ( 500 - 70 ) / 5 );
  318     return FALSE;
  319   }
  320   else
  321   {
  322     // Presence OK, finish initialization sequence
  323     Delay5us( ( 500 - 70 ) / 5 );
  324     switch ( ROM )
  325     {
  326       case ROM_SKIP:
  327         // OneWire: Skip ROM
  328         OneWireWriteByte( CMD_SKIPROM );
  329         break;
  330 
  331       case ROM_MATCH:
  332         // OneWire: Match ROM (must not be used during search because usage o FSR0)
  333         OneWireWriteByte( CMD_MATCHROM );
  334         FSR0 = _DpaMessage.Request.PData;
  335         do
  336         {
  337           OneWireWriteByte( *FSR0++ );
  338         } while ( FSR0L != ( &_DpaMessage.Request.PData[8] & 0xFF ) );
  339         break;
  340 
  341       case ROM_none:
  342         break;
  343     }
  344 
  345     OneWireWriteByte( cmd );
  346     return TRUE;
  347   }
  348 }
  349 
  350 //############################################################################################
  351 
  352 // One Wire CRC accumulator
  353 static uns8 OneWireCrc;
  354 
  355 //############################################################################################
  356 void UpdateOneWireCrc( uns8 value @ W )
  357 //############################################################################################
  358 {
  359   // Based on http://www.dattalo.com/technical/software/pic/crc_8bit.c
  360   OneWireCrc ^= W;
  361 #pragma updateBank 0 /* OFF */  
  362   value = 0;
  363   if ( OneWireCrc.0 ) // 1 instruction
  364     value ^= 0x5e;    // 1 instruction
  365   if ( OneWireCrc.1 ) // 1 instruction
  366     value ^= 0xbc;    // ...
  367   if ( OneWireCrc.2 )
  368     value ^= 0x61;  // ...
  369   if ( OneWireCrc.3 )
  370     value ^= 0xc2;  // ...
  371   if ( OneWireCrc.4 )
  372     value ^= 0x9d;  // ...
  373   if ( OneWireCrc.5 )
  374     value ^= 0x23;  // ...
  375   if ( OneWireCrc.6 )
  376     value ^= 0x46;  // ...
  377   if ( OneWireCrc.7 )
  378     value ^= 0x8c;  // 0x8C is reverse polynomial representation (normal is 0x31)
  379   OneWireCrc = value;
  380 #pragma updateBank 1 /* ON */
  381 }
  382 
  383 
  384 //############################################################################################
  385 uns16 Ds18B20GetTemp()
  386 //############################################################################################
  387 {
  388   // OneWire: Convert T
  389   if ( OneWireResetAndCmd( ROM_MATCH, CMD_CONVERTTEMP ) )
  390   {
  391     // Wait for conversion to finish
  392     startCapture();
  393     for ( ;; )
  394     {
  395       // 1s timeout
  396       captureTicks();
  397       if ( param3.low8 > ( 1000 / 10 ) )
  398       {
  399         // Timeout
  400         Carry = FALSE;
  401         break;
  402       }
  403 
  404       if ( OneWireReadByte() == 0xff )
  405       {
  406         // Temperature ready to be read
  407         Carry = TRUE;
  408         break;
  409       }
  410     }
  411 
  412     // OneWire: Read Scratchpad
  413     if ( Carry && OneWireResetAndCmd( ROM_MATCH, CMD_RSCRATCHPAD ) )
  414     {
  415       // Read Scratchpad bytes
  416       uns16 temperature;
  417 
  418       // Initialize CRC
  419       OneWireCrc = 0;
  420       // Will read later rest of scratchpad bytes and update CRC (initialization is here to avoid MOVLB later)
  421       uns8 byteLoop = 9 - 2;
  422 
  423       // Temperature LSB into result & CRC
  424       UpdateOneWireCrc( temperature.low8 = OneWireReadByte() );
  425       // Temperature MSB into result & CRC
  426       UpdateOneWireCrc( temperature.high8 = OneWireReadByte() );
  427 
  428       // Read rest of Scratchpad 
  429       do
  430       {
  431         UpdateOneWireCrc( OneWireReadByte() );
  432       } while ( --byteLoop != 0 );
  433 
  434       // Check correct CRC
  435       if ( OneWireCrc == 0 )
  436         return temperature;
  437     }
  438   }
  439   // Some error occurred
  440   return DS18B20_ERROR_TEMPERATURE;
  441 }
  442 
  443 //############################################################################################
  444 bit Ds18B20WriteConfig( uns8 value )
  445 //############################################################################################
  446 {
  447   // OneWire: Write Scratchpad
  448   if ( OneWireResetAndCmd( ROM_SKIP, CMD_WSCRATCHPAD ) )
  449   {
  450     // Write TL = ? (we dot not care the value)
  451     OneWireWriteByte( W );
  452     // Write TH = ? (we dot not care the value)
  453     OneWireWriteByte( W );
  454     // Write configuration byte
  455     OneWireWriteByte( value );
  456 
  457     // OneWire: Copy Scratchpad
  458     if ( OneWireResetAndCmd( ROM_SKIP, CMD_CPYSCRATCHPAD ) )
  459       return TRUE;
  460   }
  461   return FALSE;
  462 }
  463 
  464 //############################################################################################
  465 uns8 OneWireSearch()
  466 //############################################################################################
  467 {
  468   // Based on Maxim Integrated APPLICATION NOTE 187 
  469 
  470   // reset the search state
  471   uns8 LastDiscrepancy = 0;
  472   // FSR0 points to the current ROM id
  473   FSR0 = _DpaMessage.Response.PData;
  474   // FSR1 points to the last read ROM id
  475   // Start loop only if Search command is confirmed
  476   for ( FSR1 = FSR0; OneWireResetAndCmd( ROM_none, CMD_SEARCHROM ); )
  477   {
  478     // initialize for search
  479     uns8 rom_byte_number = 0;
  480     uns8 last_zero = 0;
  481     OneWireCrc = 0;
  482     uns8 id_bit_number = 1;
  483     uns8 rom_byte_mask = 0x01;
  484 
  485     // loop to do the search
  486     do
  487     {
  488       // read a bit and its complement
  489       bit id_bit = OneWireReadBit();
  490       bit cmp_id_bit = OneWireReadBit();
  491 
  492       // check for no devices on 1-wire
  493       if ( id_bit && cmp_id_bit )
  494         break;
  495       else
  496       {
  497         bit search_direction;
  498 
  499         // all devices coupled have 0 or 1
  500         if ( id_bit != cmp_id_bit )
  501           search_direction = id_bit;  // bit write value for search
  502         else
  503         {
  504           // if this discrepancy is before the Last Discrepancy on a previous next then pick the same as last time
  505           if ( id_bit_number < LastDiscrepancy )
  506             search_direction = ( *FSR1 & rom_byte_mask ) != 0;
  507           else
  508             // if equal last pick 1, if not then pick 0
  509             search_direction = id_bit_number == LastDiscrepancy;
  510 
  511           // if 0 was picked then record its position in LastZero
  512           if ( !search_direction )
  513             last_zero = id_bit_number;
  514         }
  515 
  516         // set or clear the bit in the ROM byte rom_byte_number with mask rom_byte_mask
  517         if ( search_direction )
  518           setINDF0( *FSR0 | rom_byte_mask );
  519         else
  520           setINDF0( *FSR0 & ~rom_byte_mask );
  521 
  522         // serial number search direction write bit
  523         OneWireWriteBit( search_direction );
  524 
  525         // increment the byte counter id_bit_number
  526         id_bit_number++;
  527         // and shift the mask rom_byte_mask
  528         rom_byte_mask <<= 1;
  529 
  530         // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
  531         if ( rom_byte_mask == 0 )
  532         {
  533           // Check for result overflow
  534           if ( FSR0L == ( &_DpaMessage.Response.PData[sizeof( _DpaMessage.Response.PData )] & 0xff ) )
  535             break;
  536 
  537           // compute CRC, move pointers, increment index and reset byte mask
  538           UpdateOneWireCrc( *FSR0++ );
  539           FSR1++;
  540           rom_byte_number++;
  541           rom_byte_mask.0 = 1;
  542         }
  543       }
  544     } while ( !rom_byte_number.3 /* !=8 */ );  // loop until through all ROM bytes 0-7 
  545 
  546     // if the search was successful then
  547     if ( rom_byte_number.3 /* ==8 */ && OneWireCrc == 0 )
  548     {
  549       // search successful so set LastDiscrepancy
  550       LastDiscrepancy = last_zero;
  551 
  552       // check for last device
  553       if ( LastDiscrepancy == 0 )
  554         break;
  555     }
  556     else
  557       break;
  558 
  559     // FSR1 points to the last ROM ID read (optimized against FSR1 = FSR0 - 8)
  560     FSR1 = FSR0;
  561     FSR1 -= 8;
  562   }
  563 
  564   // return length of read bytes
  565   return FSR0L - ( &_DpaMessage.Response.PData[0] & 0xff );
  566 }
  567 
  568 //############################################################################################
  569 // 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) 
  570 #include "DPAcustomHandler.h"
  571 //############################################################################################