diff --git a/.gitignore b/.gitignore index 27eeea4..1766e34 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # # # Date Author Comments # # ========== ============================= =========================== # +# 2010-01-27 https://github.com/SV-Zanshin Ignores for Doxygen # # 2018-09-22 https://github.com/SV-Zanshin Ignores for MS VS 2017 # # 2018-06-24 https://github.com/SV-Zanshin Changed file # # # @@ -87,3 +88,8 @@ Release *.vcxproj *.filters *.user + +######################################################################## +# Files and directories from doxygen # +######################################################################## +html diff --git a/src/DSFamily.cpp b/src/DSFamily.cpp index e271394..938ac2a 100644 --- a/src/DSFamily.cpp +++ b/src/DSFamily.cpp @@ -1,608 +1,785 @@ -/******************************************************************************************************************* -** Class definition for the DSFamily class, see the class header for detailed comments as well as current version ** -** information. This file defines the methods and variables in the class ** -** ** -** This program is free software: you can redistribute it and/or modify it under the terms of the GNU General ** -** Public License as published by the Free Software Foundation, either version 3 of the License, or (at your ** -** option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY ** -** WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** -** GNU General Public License for more details. You should have received a copy of the GNU General Public License ** -** along with this program. If not, see . ** -** ** -*******************************************************************************************************************/ -#include "DSFamily.h" // Include the header definition // +/*! @file DSFamily.cpp + @section DSFamilycpp_intro_section Description + +Arduino Library class to access the DS Family of 1-wire temperature sensors\n\n +See main library header file for details +*/ +#include "DSFamily.h" // Include the header definition /******************************************************************************************************************* ** Declare constants used in the class, but ones that are not visible as public or private class components ** *******************************************************************************************************************/ -const uint8_t DS18B20_FAMILY = 0x28; // The Family byte for DS18B20 // -const uint8_t DS18S20_FAMILY = 0x10; // The family byte for DS18S20 // -const uint8_t DS28EA00_FAMILY = 0x42; // The family byte for DS28EA00 // -const uint8_t DS1825_FAMILY = 0x3B; // The family byte for DS1825 // -const uint8_t DS1822_FAMILY = 0x22; // The family byte for DS1822 // -const uint8_t DS_START_CONVERT = 0x44; // Command to start conversion // -const uint8_t DS_READ_SCRATCHPAD = 0xBE; // Command to read the temperature // -const uint8_t DS_READ_POWER_SUPPLY = 0xB4; // Command to read the temperature // -const uint8_t DS_WRITE_SCRATCHPAD = 0x4E; // Write to the DS scratchpad // -const uint8_t DS_COPY_SCRATCHPAD = 0x48; // Copy the contents of scratchpad // -const uint8_t DS_SKIP_ROM = 0xCC; // Skip the ROM address on 1-Wire // -const uint8_t DS_SELECT_ROM = 0x55; // Select the ROM address on 1-Wire // -const uint8_t DS_SEARCH = 0xF0; // Search the 1-Wire for devices // -const int16_t DS_BAD_TEMPERATURE = 0xFC90; // Bad measurement value, -55°C // -const uint8_t DS_MAX_NV_CYCLE_TIME = 100; // Max ms taken to write NV memory // -const uint8_t DS_USER_BYTE_1 = 2; // The 2nd scratchpad byte // -const uint8_t DS_USER_BYTE_2 = 3; // The 3rd scratchpad byte // -const uint8_t DS_CONFIG_BYTE = 4; // The 4th scratchpad byte // -const uint16_t DS_12b_CONVERSION_TIME = 750; // Max ms taken to convert @ 12bits // -const uint16_t DS_11b_CONVERSION_TIME = 375; // Max ms taken to convert @ 11bits // -const uint16_t DS_10b_CONVERSION_TIME = 188; // Max ms taken to convert @ 10bits // -const uint16_t DS_9b_CONVERSION_TIME = 94; // Max ms taken to convert @ 9bits // +const uint8_t DS18B20_FAMILY = 0x28; ///< The Family byte for DS18B20 +const uint8_t DS18S20_FAMILY = 0x10; ///< The family byte for DS18S20 +const uint8_t DS28EA00_FAMILY = 0x42; ///< The family byte for DS28EA00 +const uint8_t DS1825_FAMILY = 0x3B; ///< The family byte for DS1825 +const uint8_t DS1822_FAMILY = 0x22; ///< The family byte for DS1822 +const uint8_t DS_START_CONVERT = 0x44; ///< Command to start conversion +const uint8_t DS_READ_SCRATCHPAD = 0xBE; ///< Command to read the temperature +const uint8_t DS_READ_POWER_SUPPLY = 0xB4; ///< Command to read the temperature +const uint8_t DS_WRITE_SCRATCHPAD = 0x4E; ///< Write to the DS scratchpad +const uint8_t DS_COPY_SCRATCHPAD = 0x48; ///< Copy the contents of scratchpad +const uint8_t DS_SKIP_ROM = 0xCC; ///< Skip the ROM address on 1-Wire +const uint8_t DS_SELECT_ROM = 0x55; ///< Select the ROM address on 1-Wire +const uint8_t DS_SEARCH = 0xF0; ///< Search the 1-Wire for devices +const int16_t DS_BAD_TEMPERATURE = 0xFC90; ///< Bad measurement value, -55°C +const uint8_t DS_MAX_NV_CYCLE_TIME = 100; ///< Max ms taken to write NV memory +const uint8_t DS_USER_BYTE_1 = 2; ///< The 2nd scratchpad byte +const uint8_t DS_USER_BYTE_2 = 3; ///< The 3rd scratchpad byte +const uint8_t DS_CONFIG_BYTE = 4; ///< The 4th scratchpad byte +const uint16_t DS_12b_CONVERSION_TIME = 750; ///< Max ms taken to convert @ 12bits +const uint16_t DS_11b_CONVERSION_TIME = 375; ///< Max ms taken to convert @ 11bits +const uint16_t DS_10b_CONVERSION_TIME = 188; ///< Max ms taken to convert @ 10bits +const uint16_t DS_9b_CONVERSION_TIME = 94; ///< Max ms taken to convert @ 9bits -/******************************************************************************************************************* -** Class Constructor instantiates the class and uses the initializer list to also instantiate the 1-Wire microLAN ** -** on the defined pin and to set the maximum number of thermometers that the system can store in EEPROM. The ** -** latter is dynamic because it depends upon which Atmel processor is being used, as each one has different amount** -** off EEPROM space available ** -*******************************************************************************************************************/ -DSFamily_Class::DSFamily_Class(const uint8_t OneWirePin, // CONSTRUCTOR - Instantiate 1-Wire // - const uint8_t ReserveRom) // protocol // - : ConversionMillis(DS_12b_CONVERSION_TIME), // Default conversion ms to maximum // - _MaxThermometers((E2END-ReserveRom)/8) { // // - pinMode(OneWirePin, INPUT); // Make the 1-Wire pin an input // - bitmask = PIN_TO_BITMASK(OneWirePin); // Set the bitmask // - baseReg = PIN_TO_BASEREG(OneWirePin); // Set the base register // - reset_search(); // Reset the search status // -} // of class constructor //----------------------------------// +/*! +* @brief Class constructor +* @details Class Constructor instantiates the class and uses the initializer list to also instantiate the 1-Wire +* microLAN on the defined pin and to set the maximum number of thermometers that the system can store in +* EEPROM. The latter is dynamic because it depends upon which Atmel processor is being used, as each one +* has different amount of EEPROM space available +* @param[in] OneWirePin 1-Wire microLAN pin number +* @param[in] ReserveRom (Optional) Number of bytes of ROM space to reserve, used to calculate _MaxThermometers +*/ +DSFamily_Class::DSFamily_Class(const uint8_t OneWirePin, const uint8_t ReserveRom) + : ConversionMillis(DS_12b_CONVERSION_TIME), _MaxThermometers((E2END-ReserveRom)/8) +{ + pinMode(OneWirePin, INPUT); // Make the 1-Wire pin an input + bitmask = PIN_TO_BITMASK(OneWirePin); // Set the bitmask + baseReg = PIN_TO_BASEREG(OneWirePin); // Set the base register + reset_search(); // Reset the search status +} // of class constructor -/******************************************************************************************************************* -** Class Destructor currently does nothing and is included for compatibility purposes ** -*******************************************************************************************************************/ -DSFamily_Class::~DSFamily_Class() { } // unused DESTRUCTOR // +/*! +* @brief Class destructor +* @details Currently empty and unused +*/ +DSFamily_Class::~DSFamily_Class() {} -/******************************************************************************************************************* -** Use the standardized 1-Wire microLAN search mechanism to discover all DS devices. Each device has a unique ** -** 8-byte ROM address, which is stored at the end of program's EEPROM. Since each Atmel chip has a different ** -** amount of memory, and the class constructor allows the user to specify a number of bytes to reserve at the the ** -** beginning of the EEPROM memory the maximum number of devices that can be processed by the class is variable. ** -** After each device is discovered the resolution is set to the maximum value and a conversion is initiated. This ** -** is done as soon as possible since a conversion at maximum resolution takes up to 750ms ** -*******************************************************************************************************************/ -uint8_t DSFamily_Class::ScanForDevices() { // // - uint8_t tempTherm[8]; // Temporary DS address array // - _LastCommandWasConvert = false; // Set switch to false // - reset_search(); // Reset the search status // - ThermometersFound = 0; // Reset the number of thermometers // - while (search(tempTherm)) { // Use the 1-Wire "search" method // - if (tempTherm[0]==DS18B20_FAMILY || tempTherm[0]==DS18S20_FAMILY || // Only add the recognized DS- // - tempTherm[0]==DS28EA00_FAMILY || tempTherm[0]==DS1822_FAMILY || // Family devices // - tempTherm[0]==DS1825_FAMILY ) { // // - EEPROM.put(E2END-((ThermometersFound+1)*8),tempTherm); // Write thermometer data to EEPROM // - SetDeviceResolution(ThermometersFound,12); // Set to maximum resolution // - if (ThermometersFound<=_MaxThermometers) ThermometersFound++; // Increment if we have room // - } // of if-then we have a DS18x20 family device // otherwise we just skip the device// - } // of while there are still new devices on the 1-Wire bus // // - reset(); // Reset the 1-Wire bus, then // - write_byte(DS_SKIP_ROM); // Send Skip ROM code // - write_byte(DS_READ_POWER_SUPPLY); // Send command to read power supply// - Parasitic = !read_bit(); // Read the power status from bus // - DeviceStartConvert(); // Start conversion for all devices // - return(ThermometersFound); // return number of devices detected// -} // of method ScanForDevices //----------------------------------// +/*! +* @brief Use the standardized 1-Wire microLAN search mechanism to discover all DS devices +* @details Each device has a unique 8-byte ROM address, which is stored at the end of program's EEPROM. Since each +* Atmel chip has a different amount of memory, and the class constructor allows the user to specify a +* number of bytes to reserve at the the beginning of the EEPROM memory the maximum number of devices that +* can be processed by the class is variable. After each device is discovered the resolution is set to the +* maximum value and a conversion is initiated. This is done as soon as possible since a conversion at +* maximum resolution takes up to 750ms +* @return number of devices found +*/ +uint8_t DSFamily_Class::ScanForDevices() +{ + uint8_t tempTherm[8]; + _LastCommandWasConvert = false; + reset_search(); // Reset the search status + ThermometersFound = 0; + while (search(tempTherm)) // Use the 1-Wire "search" method + { + if (tempTherm[0]==DS18B20_FAMILY || tempTherm[0]==DS18S20_FAMILY || tempTherm[0]==DS28EA00_FAMILY || + tempTherm[0]==DS1822_FAMILY || tempTherm[0]==DS1825_FAMILY ) + { + EEPROM.put(E2END-((ThermometersFound+1)*8),tempTherm); // Write thermometer data to EEPROM + SetDeviceResolution(ThermometersFound,12); // Set to maximum resolution + if (ThermometersFound<=_MaxThermometers) ThermometersFound++; // Increment if we have room + } // of if-then we have a DS18x20 family device + } // of while there are still new devices on the 1-Wire bus + reset(); // Reset the 1-Wire bus + write_byte(DS_SKIP_ROM); // Send Skip ROM code + write_byte(DS_READ_POWER_SUPPLY); // Send command to read power supply + Parasitic = !read_bit(); // Read the power status from bus + DeviceStartConvert(); // Start conversion for all devices + return(ThermometersFound); // return number of devices detected +} // of method ScanForDevices -/******************************************************************************************************************* -** Method Read1WireScratchpad() method to read the contents of a given DS devices scratchpad from the 1-Wire ** -** microLAN into local memory. ** -*******************************************************************************************************************/ -boolean DSFamily_Class::Read1WireScratchpad(const uint8_t deviceNumber, // // - uint8_t buffer[9]) { // // - _LastCommandWasConvert = false; // Set switch to false // - bool CRCStatus = false; // default to a bad reading // - uint8_t ErrorCounter = 0; // Count number of bad readings // - while (!CRCStatus && ErrorCounter++ < 10) { // Loop until good read or overflow // - SelectDevice(deviceNumber); // Reset the 1-wire, address device // - write_byte(DS_READ_SCRATCHPAD); // Request device send Scratchpad // - for (uint8_t i=0;i<9;i++) buffer[i] = read_byte(); // read all 9 bytes sent by DS // - CRCStatus = crc8(buffer,8) == buffer[8]; // Check to see if result is valid // - } // of loop until good read or number of errors exceeded // // - return(CRCStatus); // Return false if bad CRC checksum // -} // of method Read1WireScratchpad() //----------------------------------// +/*! +* @brief read the scratchpad contents from a given DS device +* @param[in] deviceNumber 1-Wire device number +* @param[in] buffer 8-byte scratchpad contents from device +* @return "true" if successful otherwise "false" +*/ +boolean DSFamily_Class::Read1WireScratchpad(const uint8_t deviceNumber, uint8_t buffer[9]) +{ + _LastCommandWasConvert = false; // Set switch to false + bool CRCStatus = false; // default to a bad reading + uint8_t ErrorCounter = 0; // Count number of bad readings + while (!CRCStatus && ErrorCounter++ < 10) // Loop until good read or overflow + { + SelectDevice(deviceNumber); // Reset the 1-wire, address device + write_byte(DS_READ_SCRATCHPAD); // Request device to send Scratchpad contents + for (uint8_t i = 0; i < 9; i++) + { + buffer[i] = read_byte(); + } // for-next read each scratchpad byte + CRCStatus = crc8(buffer,8) == buffer[8]; // Check to see if result is valid + } // of loop until good read or number of errors exceeded + return(CRCStatus); // Return false if bad CRC checksum +} // of method Read1WireScratchpad() -/******************************************************************************************************************* -** method ReadDeviceTemp() to return the current temperature value for a given device number. All devices except ** -** the DS18S20 return raw values in 0.0625°C increments, so the 0.5°C increments of the DS18S20 are converted to ** -** the same scale as the other devices. A check is done to see if there are still conversion(s) being done and a ** -** delay is made until any conversions have time to complete. We only store ony value for conversion start time, ** -** so the delay might be for another devices and might not be necessary, but the alternative is to store the ** -** conversion times for each device which would potentially consume a lot of available memory ** -*******************************************************************************************************************/ -int16_t DSFamily_Class::ReadDeviceTemp(const uint8_t deviceNumber, // // - const bool raw) { // // - uint8_t dsBuffer[9]; // Buffer to hold scratchpad return // - int16_t temperature = DS_BAD_TEMPERATURE; // Holds return value // - if (Parasitic || !_LastCommandWasConvert) { // Wait a fixed time in parasite or // - if ((_ConvStartTime+ConversionMillis)>millis()) // If a conversion is still running // - delay(millis()-(_ConvStartTime+ConversionMillis)); // then wait until it is finished // - } else if (_LastCommandWasConvert) while(read_bit()==0); // bit high when conversion finished// - if ( deviceNumber < ThermometersFound && // on a successful read from the // - Read1WireScratchpad(deviceNumber,dsBuffer)) { // device scratchpad // - if (ROM_NO[0]==DS1822_FAMILY) { // If DS1822 then temp is different // - temperature = ((dsBuffer[1] << 8) | dsBuffer[0])<<3; // get the raw reading and apply // - temperature = (temperature & 0xFFF0) + 12 - dsBuffer[6]; // value from "count remain" byte // - } else temperature = (dsBuffer[1]<<8)|dsBuffer[0]; // Results come in 2s complement // - if ((dsBuffer[2]^dsBuffer[3]) == 0xFF && // Apply any calibration offset // - !raw) temperature += (int8_t)dsBuffer[2]; // if raw is not true // - } // of if-then the read was successful // of if-then-else the DS18x20 read // - return(temperature); // Return our computed reading // -} // of method ReadDeviceTemp() //----------------------------------// +/*! +* @brief return the current temperature value for a given device number +* @details All devices except the DS18S20 return raw values in 0.0625°C increments, so the 0.5°C increments of the +* DS18S20 are converted to the same scale as the other devices. A check is done to see if there are still +* conversion(s) being done and a delay is made until any conversions have time to complete. We only store +* the value for conversion start time, so the delay might be for another devices and might not be necessary, +* but the alternative is to store the conversion times for each device which would potentially consume a lot +* of available memory +* @param[in] deviceNumber 1-Wire device number +* @param[in] raw (Optional, default "false") If set to "true" then the raw reading is returned, otherwise the + compensated calibrated value is returned +* @return Temperature reading in device units +*/ +int16_t DSFamily_Class::ReadDeviceTemp(const uint8_t deviceNumber, const bool raw) +{ + uint8_t dsBuffer[9]; + int16_t temperature = DS_BAD_TEMPERATURE; // Default return is error value + if (Parasitic || !_LastCommandWasConvert) // Wait a fixed time in parasite or + { + if ((_ConvStartTime + ConversionMillis) > millis()) + { + delay(millis() - (_ConvStartTime + ConversionMillis)); // Wait for conversion to finish + } // if-then-else conversion is active + } + else + { + if (_LastCommandWasConvert) + { + while (read_bit() == 0); // Loop until bit goes high after conversion has finished + } // if-then last command was conversion + } // if-then-else parasitic or conversion active + if ( deviceNumber < ThermometersFound && Read1WireScratchpad(deviceNumber,dsBuffer)) // Successful read from device + { + if (ROM_NO[0]==DS1822_FAMILY) // If DS1822 then temp is different + { + temperature = ((dsBuffer[1] << 8) | dsBuffer[0])<<3; // get the raw reading and apply + temperature = (temperature & 0xFFF0) + 12 - dsBuffer[6]; // value from "count remain" byte + } + else + { + temperature = (dsBuffer[1] << 8) | dsBuffer[0]; // Results come in 2s complement + } // if-then-else a DS1822 + if ((dsBuffer[2] ^ dsBuffer[3]) == 0xFF && !raw) // Apply any calibration offset if raw is not true + { + temperature += (int8_t)dsBuffer[2]; + } // if-then calibrated device and raw is not true + } // of if-then the read was successful + return(temperature); +} // of method ReadDeviceTemp() -/******************************************************************************************************************* -** method DeviceStartConvert() to start the sampling and conversion on a device. At maximum resolution this ** -** conversion can take 750ms. If the optional deviceNumber is not specified then all device conversions are ** -** started at the same time. If the optional WaitSwitch parameter is set to "true" then call doesn't return until ** -** the conversion has completed ** -*******************************************************************************************************************/ -void DSFamily_Class::DeviceStartConvert(const uint8_t deviceNumber, // // - const bool WaitSwitch ) { // // - ParasiticWait(); // Wait for conversion if necessary // - if (deviceNumber==UINT8_MAX) { // If no parameter specified, use // - reset(); // Reset 1-wire network // - write_byte(DS_SKIP_ROM); // Tell all devices to listen // - } else SelectDevice(deviceNumber); // of if-then all devices or just one // // - write_byte(DS_START_CONVERT); // Initiate temperature conversion // - _ConvStartTime = millis(); // Store start time of conversion // - _LastCommandWasConvert = true; // Set switch to true // - if (WaitSwitch) // Don't return until finished // +/*! +* @brief Start the sampling and conversion on a device +* @details At maximum resolution this conversion can take 750ms. If the optional deviceNumber is not specified then +* all device conversions are started at the same time. If the optional WaitSwitch parameter is set to +* "true" then call doesn't return until the conversion has completed +* @param[in] deviceNumber 1-Wire device number +* @param[in] WaitSwitch (Optional, default "false"). When "true" the call doesn't return until measurements have +* completed +*/ +void DSFamily_Class::DeviceStartConvert(const uint8_t deviceNumber, const bool WaitSwitch ) +{ + ParasiticWait(); // Wait for conversion to complete if necessary + if (deviceNumber==UINT8_MAX) // if default for all devices + { + reset(); // Reset 1-wire network + write_byte(DS_SKIP_ROM); // Tell all devices to listen + } + else + { + SelectDevice(deviceNumber); + } // if-then-else all devices or just one + write_byte(DS_START_CONVERT); // Initiate temperature conversion + _ConvStartTime = millis(); // Store start time of conversion + _LastCommandWasConvert = true; // Set switch to true + if (WaitSwitch) // Don't return until finished { if (Parasitic) { - ParasiticWait(); // wait a fixed period when parasite// + ParasiticWait(); // wait a fixed period when in parasite mode } else { - while (read_bit() == 0); // Read bit goes high when finished // + while (read_bit() == 0); // Wait until read bit goes high when conversion finished } // if-then-else Parasitic } // if-then Waitswitch set -} // of method DeviceStartConvert //----------------------------------// +} // of method DeviceStartConvert -/******************************************************************************************************************* -** CalibrateDSFamily() method to calibrate all thermometers. Each DS has a persistent 2 user bytes which can be ** -** both read and updated. These can be used for triggering alarms, but in this application we are going to set ** -** these to provide a calibration offset value so that all DS devices on the 1-wire can be set to accurately ** -** show the same temperature. This only works when all of the thermometers are at the same temperature, which can ** -** be done by various methods. What temperature is used for the calibration is unimportant, although a calibration** -** at typical operating temperatures makes the most sense. ** -** ** -** The calibration method used here is quite simple and straightforward. First, all devices are measured for a ** -** period of time defined in MEASUREMENT_ITERATIONS. The average of all readings is computed and that value is ** -** assumed to be the correct and accurate temperature reading. Each thermometer's offset to this standard value ** -** is computed and is written to the two user bytes. ** -** ** -** In order to ensure that the correct values are used at runtime, the values are written to the two user bytes ** -** so that XOR'ing them together always results in a value of 0xFF. ** -** ** -** The CalTemp optional parameter specifies the calibration temperature that all thermometers are to be adjusted ** -** to. This temperature is a signed integer in hectodegrees Celsius, so a temperature of "28.12" would be "2812". ** -** ** -*******************************************************************************************************************/ -void DSFamily_Class::Calibrate(const uint8_t iterations, // // - const int16_t CalTemp ) { // // - const uint8_t DS_MAX_THERMOMETERS = 32; // Specify a maximum number here // - int64_t stats1[DS_MAX_THERMOMETERS] = {0}; // store statistics per device // - int64_t tempSum = 0; // Stores interim values // - int8_t offset = 0; // Stores the computed offset value // - uint8_t ThermometersUsed = min(DS_MAX_THERMOMETERS,ThermometersFound); // Use the lower of the 2 values // - _LastCommandWasConvert = false; // Set switch to false // - for (uint8_t i=0;imaximumTemp) maximumTemp=deviceTemp; // if greater than minimum reset // - } // of for-next each thermometer // // - return(maximumTemp); // // -} // of method MaxTemperature //----------------------------------// +/*! +* @brief Return the calibration setting from the device +* @details If the calibration register 1 is equal to the XOR value of register 2 then it is assumed that a valid +* calibration has been set and that is returned +* @param[in] deviceNumber 1-Wire device number +* @return calibration offset. If the calibration offset is invalid then INT8_MIN is returned +*/ +int8_t DSFamily_Class::GetDeviceCalibration(const uint8_t deviceNumber) +{ + int8_t offset = INT8_MIN; // Default to an invalid value + uint8_t dsBuffer[9]; // Temporary scratchpad buffer + _LastCommandWasConvert = false; // Set switch to false + Read1WireScratchpad(deviceNumber,dsBuffer); // Read from the device scratchpad + if ((dsBuffer[2] ^ dsBuffer[3]) == 0xFF) + { + offset = (int8_t)dsBuffer[2]; + } // if-then a valid calibration + return (offset); +} // of method GetDeviceCalibration() -/******************************************************************************************************************* -** Method AvgTemperature reads all current device temperatures and returns the average value. If the optional ** -** skipDeviceNumber is specified then that device number is skipped; this is used when one of the thermometers is ** -** out-of-band ** -*******************************************************************************************************************/ -int16_t DSFamily_Class::AvgTemperature(const uint8_t skipDeviceNumber){ // Average temperature of devices// - int16_t AverageTemp = 0; // return value starts at 0 // - for (uint8_t i=0;i12) resolution = 12; // Default to full resolution // - switch (resolution) { // Now select which action to do // - case 12: // When 12 bits of precision used // - ConversionMillis = DS_12b_CONVERSION_TIME; // Set the conversion time // - break; //----------------------------------// - case 11: // When 12 bits of precision used // - ConversionMillis = DS_11b_CONVERSION_TIME; // Set the conversion time // - break; //----------------------------------// - case 10: // When 12 bits of precision used // - ConversionMillis = DS_10b_CONVERSION_TIME; // Set the conversion time // - break; //----------------------------------// - case 9: // When 12 bits of precision used // - ConversionMillis = DS_9b_CONVERSION_TIME; // Set the conversion time // - break; //----------------------------------// - default: // Otherwise // - ConversionMillis = DS_12b_CONVERSION_TIME; // Set the conversion time // - } // of switch statement for precision //----------------------------------// - resolution = (resolution-9)<<5; // Shift resolution bits over // - Read1WireScratchpad(deviceNumber,dsBuffer); // Read device scratchpad // - SelectDevice(deviceNumber); // Reset 1-wire, address device // - write_byte(DS_WRITE_SCRATCHPAD); // Write scratchpad, send 3 bytes // - write_byte(dsBuffer[DS_USER_BYTE_1]); // Restore the old user byte 1 // - write_byte(dsBuffer[DS_USER_BYTE_2]); // Restore the old user byte 2 // - write_byte(resolution); // Set configuration register // - write_byte(DS_COPY_SCRATCHPAD); // Copy scratchpad to NV memory // - delay(DS_MAX_NV_CYCLE_TIME); // Give the DS18x20 time to process // -} // of method SetDeviceResolution //----------------------------------// +/*! +* @brief return the 8-byte ROM address buffer +* @param[in] deviceNumber 1-Wire device number +* @param[out] ROMBuffer 8-byte ROM address buffer of device +*/ +void DSFamily_Class::GetDeviceROM(const uint8_t deviceNumber, uint8_t ROMBuffer[8]) +{ + _LastCommandWasConvert = false; + for (uint8_t i = 0; i < 8; i++) + { + ROMBuffer[i] = EEPROM.read(i + E2END - ((deviceNumber + 1) * 8)); + } // for-next each byte in the buffer +} // of method GetDeviceROM() -/******************************************************************************************************************* -** Method SetUserBytes to set the user bytes 1 and 2 to the calibration computed ** -*******************************************************************************************************************/ -uint8_t DSFamily_Class::GetDeviceResolution(const uint8_t deviceNumber) { // // - uint8_t resolution; // Store the return value // - uint8_t dsBuffer[9]; // Hold scratchpad return values // - _LastCommandWasConvert = false; // Set switch to false // - Read1WireScratchpad(deviceNumber,dsBuffer); // Read from the device scratchpad // - resolution = (dsBuffer[DS_CONFIG_BYTE]>>5)+9; // get bits 6&7 from the config byte// - return(resolution); // // -} // of method GetDeviceResolution() //----------------------------------// +/*! +* @brief reads all current device temperatures and returns the lowest value +* @details If the optional skipDeviceNumber is specified then that device number is skipped; this is used when +* one of the thermometers is out-of-band - i.e. if it is attached to an evaporator plate and reads much +* lower than the others. +* @param[in] skipDeviceNumber Device number to skip, defaults to no skipped device +* @return Minimum temperature +*/ +int16_t DSFamily_Class::MinTemperature(uint8_t skipDeviceNumber) +{ + int16_t deviceTemp; + int16_t minimumTemp = INT16_MAX; // Starts at highest possible value + for (uint8_t i=0;i maximumTemp) + { + maximumTemp = deviceTemp; + } // if-then not skipped device and greater than current max + } // of for-next each thermometer + return(maximumTemp); +} // of method MaxTemperature -/******************************************************************************************************************* -** 1-Wire: Method reset_search(). You need to use this function to start a search again from the beginning. You ** -** do not need to do it for the first search, though you could. ** -*******************************************************************************************************************/ -void DSFamily_Class::reset_search() { // // - LastDiscrepancy = 0; // // - LastDeviceFlag = FALSE; // // - LastFamilyDiscrepancy = 0; // // - for(int i = 7; ; i--) { // // - ROM_NO[i] = 0; // // - if ( i == 0) break; // // - } // of for-next each ROM byte // // -} // of method reset_search //----------------------------------// +/*! +* @brief reads all current device temperatures and returns the average value +* @details If the optional skipDeviceNumber is specified then that device number is skipped; this is used when +* one of the thermometers is out-of-band - i.e. if it is attached to a heat source or evaporator plate +* and reads much lower or higher than the others. +* @param[in] skipDeviceNumber Device number to skip, defaults to no skipped device +* @return Maximum temperature +*/ +int16_t DSFamily_Class::AvgTemperature(const uint8_t skipDeviceNumber) +{ + int16_t AverageTemp = 0; + for (uint8_t i=0;i12) resolution = 12; // Default to full resolution + switch (resolution) + { + case 12: + ConversionMillis = DS_12b_CONVERSION_TIME; + break; + case 11: + ConversionMillis = DS_11b_CONVERSION_TIME; + break; + case 10: + ConversionMillis = DS_10b_CONVERSION_TIME; + break; + case 9: + ConversionMillis = DS_9b_CONVERSION_TIME; + break; + default: + ConversionMillis = DS_12b_CONVERSION_TIME; + } // of switch statement for precision + resolution = (resolution-9)<<5; // Shift resolution bits over + Read1WireScratchpad(deviceNumber,dsBuffer); // Read device scratchpad + SelectDevice(deviceNumber); // Reset 1-wire, address device + write_byte(DS_WRITE_SCRATCHPAD); // Write scratchpad, send 3 bytes + write_byte(dsBuffer[DS_USER_BYTE_1]); // Restore the old user byte 1 + write_byte(dsBuffer[DS_USER_BYTE_2]); // Restore the old user byte 2 + write_byte(resolution); // Set configuration register + write_byte(DS_COPY_SCRATCHPAD); // Copy scratchpad to NV memory + delay(DS_MAX_NV_CYCLE_TIME); // Give the DS18x20 time to process +} // of method SetDeviceResolution -/******************************************************************************************************************* -** 1-Wire: Method read_bit(). Read a bit. Port & bit is used to cut lookup time and provide more certain timing. ** -*******************************************************************************************************************/ -uint8_t DSFamily_Class::read_bit(void) { // // - IO_REG_TYPE mask=bitmask; // Register mask // - volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; // Register // - uint8_t r; // Return bit // - noInterrupts(); // Disable interrupts for now // - DIRECT_MODE_OUTPUT(reg, mask); // // - DIRECT_WRITE_LOW(reg, mask); // // - delayMicroseconds(3); // Wait // - DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise// - delayMicroseconds(10); // Wait // - r = DIRECT_READ(reg, mask); // // - interrupts(); // Enable interrupts again // - delayMicroseconds(53); // Wait // - return r; // Return result // -} // of method read_bit() //----------------------------------// +/*! +* @brief Get the device resolution +* @param[in] deviceNumber 1-Wire device number +* @return number of bits resolution (9, 10, 11 or 12) +*/ +uint8_t DSFamily_Class::GetDeviceResolution(const uint8_t deviceNumber) +{ + uint8_t resolution, dsBuffer[9]; + _LastCommandWasConvert = false; // Set switch to false + Read1WireScratchpad(deviceNumber,dsBuffer); // Read from the device scratchpad + resolution = (dsBuffer[DS_CONFIG_BYTE]>>5)+9; // get bits 6&7 from the config byte + return(resolution); +} // of method GetDeviceResolution() -/******************************************************************************************************************* -** 1-Wire: Method write_byte(). Write a byte. The writing code uses the active drivers to raise the pin high, if ** -** you need power after the write (e.g. DS18S20 in parasite power mode) then set 'power' to 1, otherwise the pin ** -** will go tri-state at the end of the write to avoid heating in a short or other mishap. ** -*******************************************************************************************************************/ -void DSFamily_Class::write_byte(uint8_t v, uint8_t power) { // // - uint8_t bitMask; // Bit mask // - for (bitMask = 0x01; bitMask; bitMask <<= 1) write_bit( (bitMask & v)?1:0); // Write bits until empty // - if ( !power) { // Set pin in parasite mode // - noInterrupts(); // Disable interrupts for now // - DIRECT_MODE_INPUT(baseReg, bitmask); // // - DIRECT_WRITE_LOW(baseReg, bitmask); // // - interrupts(); // Enable interrupts again // - } // of if-then we have parasite mode // // -} // of method write_byte() //----------------------------------// +/*! +* @brief reads all current device temperatures and returns the standard deviation +* @details If the optional skipDeviceNumber is specified then that device number is skipped; this is used when +* one of the thermometers is out-of-band and should be ignored +* @param[in] skipDeviceNumber Device number to skip, defaults to no skipped device +* @return Floating point standard deviation +*/ +float DSFamily_Class::StdDevTemperature(const uint8_t skipDeviceNumber) +{ + float StdDev = 0; + int16_t AverageTemp = AvgTemperature(skipDeviceNumber); // Compute the average + for (uint8_t i=0;i0); // // - else // if equal to last pick then 1, if // - search_direction = (id_bit_number == LastDiscrepancy); // not then pick 0 // - if (search_direction == 0) { // if 0 was picked then record its // - last_zero = id_bit_number; // position in LastZero // - if (last_zero < 9) LastFamilyDiscrepancy = last_zero; // check for Last discrepancy // - } // of if-then search direction is 0 // // - } // of if-then-else all devices have 0 or 1 // // - if (search_direction == 1) // set or clear the bit in the ROM // - ROM_NO[rom_byte_number] |= rom_byte_mask; // byte rom_byte_number with mask // - else // rom_byte_mask // - ROM_NO[rom_byte_number] &= ~rom_byte_mask; // // - write_bit(search_direction); // serial number search direction // - id_bit_number++; // increment the byte counter // - rom_byte_mask <<= 1; // id_bit_number&shift rom_byte_mask// - if (rom_byte_mask == 0) { // if the mask is 0 then go to new // - rom_byte_number++; // SerialNum byte rom_byte_number // - rom_byte_mask = 1; // and reset mask // - } // of if-then mask is 0 // // - } // of if-then-else device still found // // - } // of loop until all devices found // // - while(rom_byte_number < 8); // loop until through ROM bytes 0-7 // - if (!(id_bit_number < 65)) { // if the search was successful then// - LastDiscrepancy = last_zero; // search successful so set // - if (LastDiscrepancy == 0) LastDeviceFlag = TRUE; // check for last device // - search_result = TRUE; // // - } // of if-then search was successful // // - } // of if-then there are still devices to be found // // - if (!search_result || !ROM_NO[0]) { // if no device found then reset // - LastDiscrepancy = 0; // counters so next 'search' will // - LastDeviceFlag = FALSE; // be like a first // - LastFamilyDiscrepancy = 0; // // - search_result = FALSE; // // - } // of if-then no device found // // - for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; // Copy result buffer // - return search_result; // Return results // -} // of method search() //----------------------------------// +/*! +* @brief Write a bit to 1-wire +* @details Port & bit is used to cut lookup time and provide more certain timing +* @param[in] v Only the LSB is used as the bit to write to 1-Wire +*/ +void DSFamily_Class::write_bit(uint8_t v) +{ + IO_REG_TYPE mask=bitmask; // Register mask + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; // Register + if (v & 1) // If writing a "1" + { + noInterrupts(); // Disable interrupts for now + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(10); // Wait + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); // Enable interrupts again + delayMicroseconds(55); // Wait + } + else + { + noInterrupts(); // Disable interrupts for now + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(65); // Wait + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); // Enable interrupts again + delayMicroseconds(5); // Wait + } // of if-then we have a "true" to write +} // of method write_bit() -/******************************************************************************************************************* -** 1-Wire: Method crc8(). Compute the 8 bit crc of the returned buffer. This method uses the iterative method, ** -** which is slower than the default 1-Wire table lookup but that uses 255 bytes of program memory. ** -*******************************************************************************************************************/ -uint8_t DSFamily_Class::crc8(const uint8_t *addr, uint8_t len) { // // - uint8_t crc = 0; // Return crc value // - while (len--) { // // - uint8_t inbyte = *addr++; // // - for (uint8_t i = 8; i; i--) { // // - uint8_t mix = (crc ^ inbyte) & 0x01; // // - crc >>= 1; // // - if (mix) crc ^= 0x8C; // // - inbyte >>= 1; // // - } // of for-next each byte in the ROM // // - } // of while data still be to computed // // - return crc; // return computed CRC value // -} // of method crc8() //----------------------------------// +/*! +* @brief Read a bit from 1-wire +* @details Port & bit is used to cut lookup time and provide more certain timing +* @return single bit where only the LSB is used as the bit that was read +*/ +uint8_t DSFamily_Class::read_bit(void) +{ + IO_REG_TYPE mask=bitmask; // Register mask + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; // Register + uint8_t r; // Return bit + noInterrupts(); // Disable interrupts for now + DIRECT_MODE_OUTPUT(reg, mask); + DIRECT_WRITE_LOW(reg, mask); + delayMicroseconds(3); // Wait + DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise it up again + delayMicroseconds(10); // Wait + r = DIRECT_READ(reg, mask); + interrupts(); // Enable interrupts again + delayMicroseconds(53); // Wait + return r; // Return result +} // of method read_bit() -/******************************************************************************************************************* -** Method ParasiticWait(). Any parasitically device needs to have a strong power pullup on the 1-Wire data line ** -** during conversion. This means that the whole 1-Wire microLAN is effectively blocked during the rather lengthy ** -** conversion time; since using the bus would cause the parasitically powered device to abort conversion. There- ** -** this function will wait until the last conversion request has had enough time to complete. The wait time might ** -** be unnecessary, but since we only track the last conversion start rather than track each device independently ** -** this is the best we can do. ** -*******************************************************************************************************************/ -void DSFamily_Class::ParasiticWait() { // // - if (Parasitic && ((_ConvStartTime+ConversionMillis)>millis())) { // If parasitic & active conversion // - delay((_ConvStartTime+ConversionMillis)-millis()); // then wait until it is finished // - } // of if-then we have a parasitic device on the 1-Wire // // -} // of method ParasiticWait() //----------------------------------// +/*! +* @brief Write a byte to 1-wire +* @details The writing code uses the active drivers to raise the pin high, if you need power after the write +* (e.g. DS18S20 in parasite power mode) then set 'power' to 1, otherwise the pin will go tri-state at +* the end of the write to avoid heating in a short or other mishap +* @param[in] v Byte to write +* @param[in] power Boolean switch set to "true" when powered and "false" in parasite mode +*/ +void DSFamily_Class::write_byte(uint8_t v, uint8_t power) +{ + uint8_t bitMask; + for (bitMask = 0x01; bitMask; bitMask <<= 1) + { + write_bit((bitMask & v) ? 1 : 0); // Write bits until empty + } // for-next each bit + if ( !power) + { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } // of if-then we have parasite mode +} // of method write_byte() + +/*! +* @brief Read a byte from 1-wire +* @return byte read from 1-Wire bus +*/ +uint8_t DSFamily_Class::read_byte() +{ + uint8_t bitMask; + uint8_t r = 0; + for (bitMask = 0x01; bitMask; bitMask <<= 1) + { + if ( read_bit()) r |= bitMask; // For each bit in the byte read bit into correct position + } // of for-next each bit in the byte + return r; +} // of method read_byte() + +/*! +* @brief Do a 1-Wire ROM Select +* @param[in] 8-Byte ROM buffer +*/ +void DSFamily_Class::select(const uint8_t rom[8]) +{ + write_byte(DS_SELECT_ROM); // Send Select ROM code + for (uint8_t i = 0; i < 8; i++) + { + write_byte(rom[i]); // Send the ROM address bytes + } // for-next each byte in ROM buffer +} // of method select() + +/*! +* @brief Search the 1-Wire microLAN using the Dallas Semiconductor search algorith and code +* @details Perform a search. If this function returns a '1' then it has enumerated the next device and you may +* retrieve the ROM from the OneWire::address variable. If there are no devices, no further devices, or +* something horrible happens in the middle of the enumeration then a 0 is returned. If a new device is +* found then its address is copied to newAddr. Use DSFamily_Class::reset_search() to start over. ** +** Return +* @param[in] newAddr 8-Byte ROM Buffer +* @return TRUE - device found, ROM number in ROM_NO buffer, FALSE - device not found, end of search +*/ +uint8_t DSFamily_Class::search(uint8_t *newAddr) +{ + uint8_t id_bit_number, last_zero, rom_byte_number, search_result, id_bit, cmp_id_bit; + unsigned char rom_byte_mask, search_direction; + id_bit_number = 1; // initialize values for searching + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + if (!LastDeviceFlag) // if last call was not the last one + { + if (!reset()) + { + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + return false; + } // of if-then we have a reset + write_byte(DS_SEARCH); // issue the search command + do // loop to do the search + { + id_bit = read_bit(); // read a bit + cmp_id_bit = read_bit(); // and then the complement + if ((id_bit == 1) && (cmp_id_bit == 1)) break; // check for no devices on 1-wire + else + { + if (id_bit != cmp_id_bit) search_direction = id_bit; // all devices coupled have 0 or 1 + else + { + // bit write value for search if this discrepancy is before the Last Discrepancy on a previous next + // then pick the same as last time + if (id_bit_number < LastDiscrepancy) + { + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + } + else + { + // if equal to last pick then 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + } // if-then-else + if (search_direction == 0) // if 0 was picked then record its position in LastZero + { + last_zero = id_bit_number; + if (last_zero < 9) LastFamilyDiscrepancy = last_zero; + } // of if-then search direction is 0 + } // of if-then-else all devices have 0 or 1 + if (search_direction == 1)// set or clear the bit in the ROM + { + ROM_NO[rom_byte_number] |= rom_byte_mask; // byte rom_byte_number with mask + } + else + { + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + } + write_bit(search_direction); // serial number search direction + id_bit_number++; // increment the byte counter + rom_byte_mask <<= 1; // id_bit_number & shift rom_byte_mask + if (rom_byte_mask == 0) // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + { + rom_byte_number++; + rom_byte_mask = 1; + } // of if-then mask is 0 + } // of if-then-else device still found + } // of loop until all devices found + while(rom_byte_number < 8); // loop until through ROM bytes 0-7 + + if (!(id_bit_number < 65)) // if the search was successful then search successful so set + { + LastDiscrepancy = last_zero; + if (LastDiscrepancy == 0) LastDeviceFlag = true; // check for last device + search_result = true; + } // of if-then search was successful + } // of if-then there are still devices to be found + if (!search_result || !ROM_NO[0]) // if the search was successful then + { + LastDiscrepancy = 0; // Set counters so next 'search' will be like a first + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + search_result = false; + } // of if-then no device found + for (int i = 0; i < 8; i++) + newAddr[i] = ROM_NO[i]; // Copy result buffer + // for-next each byte in buffer + return search_result; +} // of method search() + +/*! +* @brief Compute the 8 bit crc of the returned buffer +* @details This method uses the iterative method, which is slower than the default 1-Wire table lookup but that +* uses 255 bytes of scant program memory. +* @param[in] addr Pointer to buffer +* @param[in] len Length of buffer +* @return computed crc8 +*/ +uint8_t DSFamily_Class::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + while (len--) + { + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) + { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } // of for-next each byte in the ROM + } // of while data still be to computed + return crc; +} // of method crc8() + +/*! +* @brief Wait when parasitically powered devices are converting +* @details Any parasitically device needs to have a strong power pullup on the 1-Wire data line during conversion. +* This means that the whole 1-Wire microLAN is effectively blocked during the rather lengthy conversion +* time; since using the bus would cause the parasitically powered device to abort conversion. Therefore +* this function will wait until the last conversion request has had enough time to complete. The wait +* time might be unnecessary, but since we only track the last conversion start rather than track each +* device independently this is the best we can do. +*/ +void DSFamily_Class::ParasiticWait() +{ + if (Parasitic && ((_ConvStartTime+ConversionMillis)>millis())) + { + delay((_ConvStartTime+ConversionMillis)-millis()); // then delay until it is finished + } // of if-then we have a parasitic device on the 1-Wire +} // of method ParasiticWait() diff --git a/src/DSFamily.h b/src/DSFamily.h index b7d7cdd..6897383 100644 --- a/src/DSFamily.h +++ b/src/DSFamily.h @@ -1,181 +1,187 @@ -/******************************************************************************************************************* -** Class definition header for the DSFamily class. Defines the methods and variables in the class ** -** ** -** The goal of this class is to simplify using multiple DSFamily 1-Wire thermometers from an Arduino. Particularly** -** when the number of attached devices is unknown, quite a bit of valuable variable memory is consumed when ** -** allocating an array of {n} times the 8 Byte (64bit) Unique address; e.g. 128Bytes of storage would normally ** -** be reserved if there can be up to 16 1-Wire devices. ** -** ** -** This class stores this address information at the end of the EEPROM memory, optionally reserving space at the ** -** beginning of EEPROM for other applications. Thus the maximum number of devices that can be processed depends ** -** upon the space reserved and the space available on the given Atmel processor. While the number of write-cycles ** -** to EEPROM should be limited to 10,000; the order of the 1-Wire devices is deterministic and the EEPROM.h ** -** library calls will check to make sure that no writes are performed if the data hasn't changed, so using EEPROM ** -** for this functionality is not an issue as few, if any, writes are done after the program has been run with a ** -** given configuration. ** -** ** -** Access to the devices is done with an index number rather than the 64-Bit unique address, simplifying using ** -** the device. Several methods are built into the library to simplify basic operations on multiple thermometers, ** -** including allowing one of the thermometer readings to be ignored - important if one of the devices is placed ** -** elsewhere - i.e. one thermometer is on the board to measure ambient temperature, or one thermometer is placed ** -** directly on the evaporator plate in a refrigerator or freezer and is thus much colder than the others. ** -** ** -** While the DS Family of thermometers are quite accurate, there can still be significant variations between ** -** readings. The class contains a calibration routine which assumes that all of the devices are at the same ** -** temperature and will use the DS-Family's 2 User-definable bytes to store offset calibration information which ** -** ensures a significant improvement in accuracy. ** -** ** -** The Maxim DSFamily of thermometers use the 1-Wire microLAN protocol. There is an excellent library for 1-Wire, ** -** written by Paul Stoffregen and located at http://www.pjrc.com/teensy/td_libs_OneWire.html. There is also an ** -** informative page at http://playground.arduino.cc/Learning/OneWire describing how to use the library. As there ** -** are parts of the code that are unnecessary for this DS implementation and in order to make this library self- ** -** sufficient, the code from version 2.0 (extracted 2016-11-23), has been included in this library. ** -** ** -** Although programming for the Arduino and in c/c++ is new to me, I'm a professional programmer and have learned,** -** over the years, that it is much easier to ignore superfluous comments than it is to decipher non-existent ones;** -** so both my comments and variable names tend to be verbose. The code is written to fit in the first 80 spaces ** -** and the comments start after that and go to column 117 - allowing the code to be printed in A4 landscape mode. ** -** ** -** GNU General Public License ** -** ========================== ** -** This program is free software: you can redistribute it and/or modify it under the terms of the GNU General ** -** Public License as published by the Free Software Foundation, either version 3 of the License, or (at your ** -** option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY ** -** WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** -** GNU General Public License for more details. You should have received a copy of the GNU General Public License ** -** along with this program. If not, see . ** -** ** -** Vers. Date Developer Comments ** -** ====== ========== ============================= ============================================================== ** -** 1.0.7 2018-06-26 https://github.com/SV-Zanshin Packaging and documentation changes, optimiyed EEPROM ** -** 1.0.6 2018-06-25 https://github.com/SV-Zanshin Documentation changes ** -** 1.0.5 2017-07-31 https://github.com/SV-Zanshin Only function prototypes may have default values as this ** -** may cause compiler errors. ** -** 1.0.4 2016-12-29 https://github.com/SV-Zanshin Added error loop to Read1WireScratchpad(), corrected ** -** DS18S20 call in ReadDeviceTemp() to avoid false temps ** -** 1.0.3 2016-12-16 https://github.com/SV-Zanshin Added optional CalibrationTemp to Calibrate function ** -** 1.0.2 2016-12-03 https://github.com/SV-Zanshin Added optional ReadDeviceTemp "WaitSwitch", minimized waits ** -** 1.0.1 2016-12-02 https://github.com/SV-Zanshin Added delays for ReadDeviceTemp() and when a parasitic ** -** device is present ** -** 1.0.0 2016-12-01 https://github.com/SV-Zanshin Initial release ** -** 1.0.b5 2016-11-30 https://github.com/SV-Zanshin Moved 1-Wire calls to private, refactored some of the calls ** -** 1.0.b4 2016-11-29 https://github.com/SV-Zanshin Included sections of the 1-Wire library, see above ** -** 1.0.b3 2016-11-23 https://github.com/SV-Zanshin Refactored class naming DS18B20 to support multiple types ** -** 1.0.b2 2016-11-14 https://github.com/SV-Zanshin Made ScanForDevices return the number of devices found ** -** 1.0.b1 2016-11-10 https://github.com/SV-Zanshin Added SRAM template functions to read/write efficiently ** -*******************************************************************************************************************/ -#include "Arduino.h" // Arduino data type definitions // -#include // Access the EEPROM memory // -#if ARDUINO >= 100 // Include depending on version // - #include "Arduino.h" // delayMicroseconds, // -#else // digitalPinToBitMask, etc. // - #include "WProgram.h" // for delayMicroseconds // - #include "pins_arduino.h" // for digitalPinToBitMask, etc // -#endif // // -#define FALSE 0 // FALSE and TRUE used by 1-Wire // -#define TRUE 1 // library code // -#if defined(__AVR__) // Platform specific I/O definitions// - #define PIN_TO_BASEREG(OneWirePin) (portInputRegister(digitalPinToPort(OneWirePin)))// // - #define PIN_TO_BITMASK(OneWirePin) (digitalPinToBitMask(OneWirePin)) // // - #define IO_REG_TYPE uint8_t // // - #define IO_REG_ASM asm("r30") // // - #define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) // // - #define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) // // - #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) // // - #define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) // // - #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) // // -#elif defined(__MK20DX128__) // // - #define PIN_TO_BASEREG(OneWirePin) (portOutputRegister(OneWirePin)) // // - #define PIN_TO_BITMASK(OneWirePin) (1) // // - #define IO_REG_TYPE uint8_t // // - #define IO_REG_ASM // // - #define DIRECT_READ(base, mask) (*((base)+512)) // // - #define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) // // - #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) // // - #define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) // // - #define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) // // -#elif defined(__SAM3X8E__) // // +/*! @file DSFamily.h + + @mainpage Arduino Library Header to access the DS Family of 1-wire temperature sensors + + @section intro_section Description + +The goal of this class is to simplify using multiple DSFamily 1-Wire thermometers from an Arduino. Particularly +when the number of attached devices is unknown, quite a bit of valuable variable memory is consumed when +allocating an array of {n} times the 8 Byte (64bit) Unique address; e.g. 128Bytes of storage would normally +be reserved if there can be up to 16 1-Wire devices.\n\n + +This class stores this address information at the end of the EEPROM memory, optionally reserving space at the +beginning of EEPROM for other applications. Thus the maximum number of devices that can be processed depends upon +the space reserved and the space available on the given Atmel processor. While the number of write-cycles to +EEPROM should be limited to 10,000; the order of the 1-Wire devices is deterministic and the EEPROM.h library +calls will check to make sure that no writes are performed if the data hasn't changed, so using EEPROM for this +functionality is not an issue as few, if any, writes are done after the program has been run with a given +configuration.\n\n + +Access to the devices is done with an index number rather than the 64-Bit unique address, simplifying using the +device. Several methods are built into the library to simplify basic operations on multiple thermometers, +including allowing one of the thermometer readings to be ignored - important if one of the devices is placed +elsewhere - i.e. one thermometer is on the board to measure ambient temperature, or one thermometer is placed +directly on the evaporator plate in a refrigerator or freezer and is thus much colder than the others.\n\n + +While the DS Family of thermometers are quite accurate, there can still be significant variations between readings. +The class contains a calibration routine which assumes that all of the devices are at the same temperature and +will use the DS-Family's 2 User-definable bytes to store offset calibration information which ensures a significant +improvement in accuracy.\n\n + +The Maxim DSFamily of thermometers use the 1-Wire microLAN protocol. There is an excellent library for 1-Wire, +written by Paul Stoffregen and located at http://www.pjrc.com/teensy/td_libs_OneWire.html. There is also an +informative page at http://playground.arduino.cc/Learning/OneWire describing how to use the library. As there +are parts of the code that are unnecessary for this DS implementation and in order to make this library self- +sufficient, the code from version 2.0 (extracted 2016-11-23), has been included in this library. + +@section license GNU General Public License v3.0 +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General +Public License as published by the Free Software Foundation, either version 3 of the License, or (at your +option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. You should have received a copy of the GNU General Public License +along with this program. If not, see . + +@section author Author + +Written by Arnd\@SV-Zanshin + +@section versions Changelog + +Version| Date | Developer | Comments +------ | ---------- | ----------------------------- | -------- +1.0.5 | 2019-01-26 | https://github.com/SV-Zanshin | Issue #4 - converted documentation to doxygen + +1.0.8 | 2019-01-27 | https://github.com/SV-Zanshin | Issue #4 - Convert to Doxygen format +1.0.7 | 2018-06-26 | https://github.com/SV-Zanshin | Packaging and documentation changes, optimized EEPROM +1.0.6 |2018-06-25 | https://github.com/SV-Zanshin | Documentation changes +1.0.5 | 2017-07-31 | https://github.com/SV-Zanshin | Only function prototypes may have default values as this + may cause compiler errors. +1.0.4 | 2016-12-29 | https://github.com/SV-Zanshin | Added error loop to Read1WireScratchpad(), corrected + DS18S20 call in ReadDeviceTemp() to avoid false temps +1.0.3 | 2016-12-16 | https://github.com/SV-Zanshin | Added optional CalibrationTemp to Calibrate function +1.0.2 | 2016-12-03 | https://github.com/SV-Zanshin | Added optional ReadDeviceTemp "WaitSwitch", minimized waits +1.0.1 | 2016-12-02 | https://github.com/SV-Zanshin | Added delays for ReadDeviceTemp() and when a parasitic device + is present +1.0.0 | 2016-12-01 | https://github.com/SV-Zanshin | Initial release +1.0.b5 | 2016-11-30 | https://github.com/SV-Zanshin | Moved 1-Wire calls to private, refactored some of the calls +1.0.b4 | 2016-11-29 | https://github.com/SV-Zanshin | Included sections of the 1-Wire library, see above +1.0.b3 | 2016-11-23 | https://github.com/SV-Zanshin | Refactored class naming DS18B20 to support multiple types +1.0.b2 | 2016-11-14 | https://github.com/SV-Zanshin | Made ScanForDevices return the number of devices found +1.0.b1 | 2016-11-10 | https://github.com/SV-Zanshin | Added SRAM template functions to read/write efficiently +*/ + +#include // Access the AVR EEPROM memory +#if ARDUINO >= 100 // Include depending on version + #include "Arduino.h" +#else + #include "WProgram.h" + #include "pins_arduino.h" // for digitalPinToBitMask, etc. +#endif +#if defined(__AVR__) // Platform specific I/O definitions + #define PIN_TO_BASEREG(OneWirePin) (portInputRegister(digitalPinToPort(OneWirePin))) + #define PIN_TO_BITMASK(OneWirePin) (digitalPinToBitMask(OneWirePin)) + #define IO_REG_TYPE uint8_t + #define IO_REG_ASM asm("r30") + #define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) + #define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) + #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) + #define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) + #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) +#elif defined(__MK20DX128__) + #define PIN_TO_BASEREG(OneWirePin) (portOutputRegister(OneWirePin)) + #define PIN_TO_BITMASK(OneWirePin) (1) + #define IO_REG_TYPE uint8_t + #define IO_REG_ASM + #define DIRECT_READ(base, mask) (*((base)+512)) + #define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) + #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) + #define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) + #define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) +#elif defined(__SAM3X8E__) /***************************************************************************************************************** ** Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. If you have trouble with OneWire on the ** ** Arduino Due, please check the status of delayMicroseconds() before reporting a bug in OneWire! See URL ** ** http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 for details ** *****************************************************************************************************************/ - #define PIN_TO_BASEREG(OneWirePin) (&(digitalPinToPort(OneWirePin)->PIO_PER)) // // - #define PIN_TO_BITMASK(OneWirePin) (digitalPinToBitMask(OneWirePin)) // // - #define IO_REG_TYPE uint32_t // // - #define IO_REG_ASM // // - #define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) // // - #define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) // // - #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) // // - #define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) // // - #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) // // - #ifndef PROGMEM // // - #define PROGMEM // // - #endif // // - #ifndef pgm_read_byte // // - #define pgm_read_byte(addr) (*(const uint8_t *)(addr)) // // - #endif // // -#elif defined(__PIC32MX__) // // - #define PIN_TO_BASEREG(OneWirePin) (portModeRegister(digitalPinToPort(OneWirePin)))// // - #define PIN_TO_BITMASK(OneWirePin) (digitalPinToBitMask(OneWirePin)) // // - #define IO_REG_TYPE uint32_t // // - #define IO_REG_ASM // // - #define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) // PORTX + 0x10 // - #define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) // TRISXSET + 0x08 // - #define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) // TRISXCLR + 0x04 // - #define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) // LATXCLR + 0x24 // - #define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) // LATXSET + 0x28 // -#else // // - #error "Please define I/O register types here" // // -#endif // // -#ifndef DSFamily_h // Guard code definition // - #define DSFamily_h // Define the name inside guard code// - class DSFamily_Class { // Class definition // - public: // Publicly visible methods // - DSFamily_Class(const uint8_t OneWirePin, const uint8_t ReserveRom = 0 );// Class Constructor // - ~DSFamily_Class(); // Class destructor // - uint8_t ThermometersFound = 0; // Number of Devices discovered // - uint16_t ConversionMillis; // Current conversion milliseconds // - bool Parasitic = true; // If one or more parasitic devices // - uint8_t ScanForDevices (); // Scan/rescan the 1-Wire microLAN // - int16_t ReadDeviceTemp (const uint8_t deviceNumber, // Return the temperature // - const bool raw=false); // optionally using the raw value // - void DeviceStartConvert (const uint8_t deviceNumber=UINT8_MAX, // Start conversion on device // - const bool WaitSwitch=false); // optionally wait for it to finish // - void Calibrate (const uint8_t iterations=30, // Calibrate thermometers to read // - const int16_t CalTemp=INT16_MAX); // identically // - int8_t GetDeviceCalibration(const uint8_t deviceNumber); // Get the device's calibration // - void SetDeviceCalibration(const uint8_t deviceNumber, // Set calibration bytes 1 & 2 // - const int8_t offset); // // - int16_t MinTemperature (const uint8_t skipDeviceNumber=UINT8_MAX);// Min of devices, optional skip // - int16_t MaxTemperature (const uint8_t skipDeviceNumber=UINT8_MAX);// Max of devices, optional skip // - int16_t AvgTemperature (const uint8_t skipDeviceNumber=UINT8_MAX);// Avg of devices, optional skip // - float StdDevTemperature (const uint8_t skipDeviceNumber=UINT8_MAX);// Standard deviation, optional skip// - void SetDeviceResolution (const uint8_t deviceNumber,uint8_t res); // Set resolution 9,10,11 or 12 bits// - uint8_t GetDeviceResolution (const uint8_t deviceNumber); // Retrieve the device resolution // - void GetDeviceROM (const uint8_t deviceNumber, // Return the physical ROM values // - uint8_t ROMBuffer[8]); // // - uint8_t crc8 (const uint8_t *addr, uint8_t len); // Compute 1-Wire 8bit CRC // - private: // Private variables and methods // - boolean Read1WireScratchpad (const uint8_t deviceNumber,uint8_t bf[9]);// Read device's scratchpad contents// - void SelectDevice (const uint8_t deviceNumber); // Reset 1-Wire & select device // - void ParasiticWait(); // Wait for conversion if parasitic // - uint8_t _MaxThermometers; // Devices fit (EEPROM-ReserveRom) // - uint32_t _ConvStartTime; // Conversion start time // - bool _LastCommandWasConvert=false; // Unset when other commands issued // - IO_REG_TYPE bitmask; // // - volatile IO_REG_TYPE *baseReg; // // - unsigned char ROM_NO[8]; // global search state // - uint8_t LastDiscrepancy; // // - uint8_t LastFamilyDiscrepancy; // // - uint8_t LastDeviceFlag; // // - void reset_search(); // 1-Wire reset search status // - uint8_t reset(void); // Reset the 1-Wire microLAN state // - void write_bit(uint8_t v); // Write 1 bit on 1-Wire microLAN // - uint8_t read_bit(void); // Read 1 bit from 1-Wire microLAN // - void write_byte(uint8_t v, uint8_t power = 0 ); // Write a byte to 1-Wire microLAN // - uint8_t read_byte(); // Read a byte from 1-Wire microLAN // - void select(const uint8_t rom[8]); // Select a give 1-Wire device // - uint8_t search(uint8_t *newAddr); // Search 1-Wire for next device // - }; // of DSFamily class definition // // -#endif //----------------------------------// + #define PIN_TO_BASEREG(OneWirePin) (&(digitalPinToPort(OneWirePin)->PIO_PER)) + #define PIN_TO_BITMASK(OneWirePin) (digitalPinToBitMask(OneWirePin)) + #define IO_REG_TYPE uint32_t + #define IO_REG_ASM + #define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) + #define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) + #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) + #define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) + #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) + #ifndef PROGMEM + #define PROGMEM + #endif + #ifndef pgm_read_byte + #define pgm_read_byte(addr) (*(const uint8_t *)(addr)) + #endif +#elif defined(__PIC32MX__) + #define PIN_TO_BASEREG(OneWirePin) (portModeRegister(digitalPinToPort(OneWirePin))) + #define PIN_TO_BITMASK(OneWirePin) (digitalPinToBitMask(OneWirePin)) + #define IO_REG_TYPE uint32_t + #define IO_REG_ASM + #define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) // PORTX + 0x10 + #define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) // TRISXSET + 0x08 + #define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) // TRISXCLR + 0x04 + #define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) // LATXCLR + 0x24 + #define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) // LATXSET + 0x28 +#else + #error "Please define I/O register types here" +#endif +#ifndef DSFamily_h + /** @brief Guard code to prevent multiple definitions */ + #define DSFamily_h +/*! +* @class DSFamily_Class +* @brief Access the available DS-Family devices on the 1-Wire bus +*/ class DSFamily_Class + { + public: + DSFamily_Class(const uint8_t OneWirePin, const uint8_t ReserveRom = 0 ); + ~DSFamily_Class(); + uint16_t ConversionMillis; ///< Current conversion milliseconds + uint8_t ThermometersFound = 0; ///< Number of Devices discovered + bool Parasitic = true; ///< One or more parasitic devices present + + uint8_t ScanForDevices (); + int16_t ReadDeviceTemp (const uint8_t deviceNumber, const bool raw=false); + void DeviceStartConvert (const uint8_t deviceNumber=UINT8_MAX, const bool WaitSwitch=false); + void Calibrate (const uint8_t iterations=30, const int16_t CalTemp=INT16_MAX); + int8_t GetDeviceCalibration(const uint8_t deviceNumber); + void SetDeviceCalibration(const uint8_t deviceNumber, const int8_t offset); + int16_t MinTemperature (const uint8_t skipDeviceNumber=UINT8_MAX); + int16_t MaxTemperature (const uint8_t skipDeviceNumber=UINT8_MAX); + int16_t AvgTemperature (const uint8_t skipDeviceNumber=UINT8_MAX); + float StdDevTemperature (const uint8_t skipDeviceNumber=UINT8_MAX); + void SetDeviceResolution (const uint8_t deviceNumber, uint8_t resolution); + uint8_t GetDeviceResolution (const uint8_t deviceNumber); + void GetDeviceROM (const uint8_t deviceNumber, uint8_t ROMBuffer[8]); + uint8_t crc8 (const uint8_t *addr, uint8_t len); + private: + uint8_t _MaxThermometers; ///< Number of devices found/stord + uint32_t _ConvStartTime; ///< Conversion start time + bool _LastCommandWasConvert=false; ///< Unset when other commands issued + IO_REG_TYPE bitmask; ///< Bitmask for 1-Wire IO + volatile IO_REG_TYPE *baseReg; ///< Base register + unsigned char ROM_NO[8]; ///< global search state array + uint8_t LastDiscrepancy; ///< 1-Wire internal value + uint8_t LastFamilyDiscrepancy; ///< 1-Wire internal value + uint8_t LastDeviceFlag; ///< 1-Wire internal value + + boolean Read1WireScratchpad(const uint8_t deviceNumber, uint8_t bf[9]); + void SelectDevice(const uint8_t deviceNumber); + void ParasiticWait(); + void reset_search(); + uint8_t reset(void); + void write_bit(uint8_t v); + uint8_t read_bit(void); + void write_byte(uint8_t v, uint8_t power = 0 ); + uint8_t read_byte(); + void select(const uint8_t rom[8]); + uint8_t search(uint8_t *newAddr); + }; // of DSFamily class definition +#endif