diff --git a/esp32/include/ControllerConfiguration.h b/esp32/include/ControllerConfiguration.h index f9ba6bb..0917159 100644 --- a/esp32/include/ControllerConfiguration.h +++ b/esp32/include/ControllerConfiguration.h @@ -53,8 +53,9 @@ #define MOIST_SENSOR_MAX_ADC (85 * 4095 / 100) #define MOIST_SENSOR_MIN_ADC (25 * 4095 / 100) -#define SOLAR_VOLT(adc) ADC_TO_VOLT_WITH_MULTI(adc, 4.0306) /**< 100k and 33k voltage dividor */ -#define ADC_5V_TO_3V3(adc) ADC_TO_VOLT_WITH_MULTI(adc, 1.69) /**< 33k and 47k8 voltage dividor */ +#define SOLAR_VOLT_FACTOR 2 +#define BATTSENSOR_INDEX_SOLAR 0 +#define BATTSENSOR_INDEX_BATTERY 1 #define MS_TO_S 1000 #define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */ diff --git a/esp32/include/DS2438.h b/esp32/include/DS2438.h new file mode 100644 index 0000000..0809015 --- /dev/null +++ b/esp32/include/DS2438.h @@ -0,0 +1,112 @@ +/* + * DS2438.h + * + * by Joe Bechter + * + * (C) 2012, bechter.com + * + * All files, software, schematics and designs are provided as-is with no warranty. + * All files, software, schematics and designs are for experimental/hobby use. + * Under no circumstances should any part be used for critical systems where safety, + * life or property depends upon it. You are responsible for all use. + * You are free to use, modify, derive or otherwise extend for your own non-commercial purposes provided + * 1. No part of this software or design may be used to cause injury or death to humans or animals. + * 2. Use is non-commercial. + * 3. Credit is given to the author (i.e. portions © bechter.com), and provide a link to the original source. + * + */ + +#ifndef DS2438_h +#define DS2438_h + +#include +#include + +#define DS2438_TEMPERATURE_CONVERSION_COMMAND 0x44 +#define DS2438_VOLTAGE_CONVERSION_COMMAND 0xb4 +#define DS2438_WRITE_SCRATCHPAD_COMMAND 0x4e +#define DS2438_COPY_SCRATCHPAD_COMMAND 0x48 +#define DS2438_READ_SCRATCHPAD_COMMAND 0xbe +#define DS2438_RECALL_MEMORY_COMMAND 0xb8 + +#define PAGE_MIN 0 +#define PAGE_MAX 7 + +#define DS2438_CHA 0 +#define DS2438_CHB 1 + +#define DS2438_MODE_CHA 0x01 +#define DS2438_MODE_CHB 0x02 +#define DS2438_MODE_TEMPERATURE 0x04 + +#define DS2438_TEMPERATURE_DELAY 10 +#define DS2438_VOLTAGE_CONVERSION_DELAY 8 + +#define DEFAULT_PAGE0(var) uint8_t var[8] { \ + 0b00001011 /* X, ADB=0, NVB=0, TB=0, AD=1, EE=0, CA=1, IAD=1 */, \ + 0, /* Temperatur */ \ + 0, /* Temperatur */ \ + 0, /* Voltage */ \ + 0, /* Voltage */ \ + 0, /* Current */ \ + 0, /* Current */ \ + 0 /* Threashold */ \ +} + +typedef struct PageOne { + uint8_t eleapsedTimerByte0; /**< LSB of timestamp */ + uint8_t eleapsedTimerByte1; + uint8_t eleapsedTimerByte2; + uint8_t eleapsedTimerByte3; /**< MSB of timestamp */ + uint8_t ICA; /**< Integrated Current Accumulator (current flowing into and out of the battery) */ + uint8_t offsetRegisterByte0; /**< Offset for ADC calibdation */ + uint8_t offsetRegisterByte1; /**< Offset for ADC calibdation */ + uint8_t reserved; +} PageOne_t; + +typedef struct PageSeven { + uint8_t userByte0; + uint8_t userByte1; + uint8_t userByte2; + uint8_t userByte3; + uint8_t CCA0; /**< Charging Current Accumulator (CCA) */ + uint8_t CCA1; /**< Charging Current Accumulator (CCA) */ + uint8_t DCA0; /**< Discharge Current Accumulator (DCA) */ + uint8_t DCA1; /**< Discharge Current Accumulator (DCA) */ +} PageSeven_t; + +typedef uint8_t DeviceAddress[8]; + +class DS2438 { + public: + DS2438(OneWire *ow, float currentShunt); + DS2438(OneWire *ow, uint8_t *address); + + void begin(); + void update(); + double getTemperature(); + float getVoltage(int channel=DS2438_CHA); + float getCurrent(); + boolean isError(); + boolean isFound(); + private: + bool validAddress(const uint8_t*); + bool validFamily(const uint8_t* deviceAddress); + + bool deviceFound = false; + OneWire *_ow; + DeviceAddress _address; + uint8_t _mode; + double _temperature; + float _voltageA; + float _voltageB; + float _current; + float _currentShunt; + boolean _error; + boolean startConversion(int channel, boolean doTemperature); + boolean selectChannel(int channel); + void writePage(int page, uint8_t *data); + boolean readPage(int page, uint8_t *data); +}; + +#endif \ No newline at end of file diff --git a/esp32/src/DS2438.cpp b/esp32/src/DS2438.cpp new file mode 100644 index 0000000..791b76d --- /dev/null +++ b/esp32/src/DS2438.cpp @@ -0,0 +1,286 @@ +/* + * DS2438.cpp + * + * by Joe Bechter + * + * (C) 2012, bechter.com + * + * All files, software, schematics and designs are provided as-is with no warranty. + * All files, software, schematics and designs are for experimental/hobby use. + * Under no circumstances should any part be used for critical systems where safety, + * life or property depends upon it. You are responsible for all use. + * You are free to use, modify, derive or otherwise extend for your own non-commercial purposes provided + * 1. No part of this software or design may be used to cause injury or death to humans or animals. + * 2. Use is non-commercial. + * 3. Credit is given to the author (i.e. portions © bechter.com), and provide a link to the original source. + * + */ + +#include "DS2438.h" + +// DSROM FIELDS +#define DSROM_FAMILY 0 +#define DSROM_CRC 7 + +#define DS2438MODEL 0x26 + +DS2438::DS2438(OneWire *ow, float currentShunt = 1.0f) { + _ow = ow; + _currentShunt = currentShunt; +}; + +void DS2438::begin(){ + DeviceAddress searchDeviceAddress; + + _ow->reset_search(); + memset(searchDeviceAddress,0, 8); + _temperature = 0; + _voltageA = 0.0; + _voltageB = 0.0; + _error = true; + _mode = (DS2438_MODE_CHA | DS2438_MODE_CHB | DS2438_MODE_TEMPERATURE); + + deviceFound = false; // Reset the number of devices when we enumerate wire devices + + while (_ow->search(searchDeviceAddress)) { + if (validAddress(searchDeviceAddress)) { + if (validFamily(searchDeviceAddress)) { + memcpy(_address,searchDeviceAddress,8); + DEFAULT_PAGE0(defaultConfig); + writePage(0, defaultConfig); + deviceFound = true; + } + } + } +} + +bool DS2438::isFound(){ + return deviceFound; +} + +bool DS2438::validAddress(const uint8_t* deviceAddress) { + return (_ow->crc8(deviceAddress, 7) == deviceAddress[DSROM_CRC]); +} + +bool DS2438::validFamily(const uint8_t* deviceAddress) { + switch (deviceAddress[DSROM_FAMILY]) { + case DS2438MODEL: + return true; + default: + return false; + } +} + +void DS2438::update() { + uint8_t data[9]; + + _error = true; + if(!isFound()){ + return; + } + + if (_mode & DS2438_MODE_CHA || _mode == DS2438_MODE_TEMPERATURE) { + boolean doTemperature = _mode & DS2438_MODE_TEMPERATURE; + if (!startConversion(DS2438_CHA, doTemperature)) { + Serial.println("Error starting temp conversion ds2438 channel a"); + return; + } + if (!readPage(0, data)){ + + Serial.println("Error reading zero page ds2438 channel a"); + return; + } + Serial.print(data[0],16); + Serial.print(" "); + Serial.print(data[1],16); + Serial.print(" "); + Serial.print(data[2],16); + Serial.print(" "); + Serial.print(data[3],16); + Serial.print(" "); + Serial.print(data[4],16); + Serial.print(" "); + Serial.print(data[5],16); + Serial.print(" "); + Serial.print(data[6],16); + Serial.print(" "); + Serial.println(data[7],16); + + + if (doTemperature) { + _temperature = (double)(((((int16_t)data[2]) << 8) | (data[1] & 0x0ff)) >> 3) * 0.03125; + } + if (_mode & DS2438_MODE_CHA) { + _voltageA = (((data[4] << 8) & 0x00300) | (data[3] & 0x0ff)) / 100.0; + } + } + if (_mode & DS2438_MODE_CHB) { + boolean doTemperature = _mode & DS2438_MODE_TEMPERATURE && !(_mode & DS2438_MODE_CHA); + if (!startConversion(DS2438_CHB, doTemperature)) { + Serial.println("Error starting temp conversion channel b ds2438"); + return; + } + if (!readPage(0, data)){ + Serial.println("Error reading zero page ds2438 channel b"); + return; + } + if (doTemperature) { + int16_t upperByte = ((int16_t)data[2]) << 8; + int16_t lowerByte = data[1] >> 3; + int16_t fullByte = (upperByte | lowerByte); + _temperature = ((double)fullByte) * 0.03125; + } + _voltageB = (((data[4] << 8) & 0x00300) | (data[3] & 0x0ff)) / 100.0; + } + + int16_t upperByte = ((int16_t)data[6]) << 8; + int16_t lowerByte = data[5]; + int16_t fullByte = (int16_t)(upperByte | lowerByte); + float fullByteb = fullByte; + _current = (fullByteb) / ((4096.0f * _currentShunt)); + _error = false; + Serial.print(data[0],16); + Serial.print(" "); + Serial.print(data[1],16); + Serial.print(" "); + Serial.print(data[2],16); + Serial.print(" "); + Serial.print(data[3],16); + Serial.print(" "); + Serial.print(data[4],16); + Serial.print(" "); + Serial.print(data[5],16); + Serial.print(" "); + Serial.print(data[6],16); + Serial.print(" "); + Serial.println(data[7],16); + Serial.println("-"); + + + + uint16_t ICA = 0; + if (readPage(1, data)){ + PageOne_t *pOne = (PageOne_t *) data; + Serial.println(pOne->ICA); + float Ah = pOne->ICA / (2048.0f * _currentShunt); + Serial.print("Ah="); + Serial.println(Ah); + ICA = pOne->ICA; + } + + + + + if (readPage(7, data)){ + PageSeven_t *pSeven = (PageSeven_t *) data; + int16_t CCA = pSeven->CCA0 | ((int16_t) pSeven->CCA1) << 8; + int16_t DCA = pSeven->DCA0 | ((int16_t) pSeven->DCA1) << 8; + Serial.println("ICA, DCA, CCA"); + Serial.print(ICA); + Serial.print(", "); + Serial.print(DCA); + Serial.print(", "); + Serial.println(CCA); + } + +} + +double DS2438::getTemperature() { + return _temperature; +} + +float DS2438::getVoltage(int channel) { + if (channel == DS2438_CHA) { + return _voltageA; + } else if (channel == DS2438_CHB) { + return _voltageB; + } else { + return 0.0; + } +} + +float DS2438::getCurrent() { + return _current; +} + +boolean DS2438::isError() { + return _error; +} + +boolean DS2438::startConversion(int channel, boolean doTemperature) { + if(!isFound()){ + return false; + } + if (!selectChannel(channel)){ + return false; + } + _ow->reset(); + _ow->select(_address); + if (doTemperature) { + _ow->write(DS2438_TEMPERATURE_CONVERSION_COMMAND, 0); + delay(DS2438_TEMPERATURE_DELAY); + _ow->reset(); + _ow->select(_address); + } + _ow->write(DS2438_VOLTAGE_CONVERSION_COMMAND, 0); + delay(DS2438_VOLTAGE_CONVERSION_DELAY); + return true; +} + +boolean DS2438::selectChannel(int channel) { + if(!isFound()){ + return false; + } + uint8_t data[9]; + if (readPage(0, data)) { + if (channel == DS2438_CHB){ + data[0] = data[0] | 0x08; + } + else { + data[0] = data[0] & 0xf7; + } + writePage(0, data); + return true; + } + Serial.println("Could not read page zero data"); + return false; +} + +void DS2438::writePage(int page, uint8_t *data) { + _ow->reset(); + _ow->select(_address); + _ow->write(DS2438_WRITE_SCRATCHPAD_COMMAND, 0); + if ((page >= PAGE_MIN) && (page <= PAGE_MAX)) { + _ow->write(page, 0); + } else { + return; + } + for (int i = 0; i < 8; i++){ + _ow->write(data[i], 0); + } + _ow->reset(); + _ow->select(_address); + _ow->write(DS2438_COPY_SCRATCHPAD_COMMAND, 0); + _ow->write(page, 0); +} + +boolean DS2438::readPage(int page, uint8_t *data) { + //TODO if all data is 0 0 is a valid crc, but most likly not as intended + _ow->reset(); + _ow->select(_address); + _ow->write(DS2438_RECALL_MEMORY_COMMAND, 0); + if ((page >= PAGE_MIN) && (page <= PAGE_MAX)) { + _ow->write(page, 0); + } else { + return false; + } + _ow->reset(); + _ow->select(_address); + _ow->write(DS2438_READ_SCRATCHPAD_COMMAND, 0); + _ow->write(page, 0); + for (int i = 0; i < 9; i++){ + data[i] = _ow->read(); + } + return _ow->crc8(data, 8) == data[8]; +} + diff --git a/esp32/src/main.cpp b/esp32/src/main.cpp index 76edb13..e6ce79a 100644 --- a/esp32/src/main.cpp +++ b/esp32/src/main.cpp @@ -25,6 +25,7 @@ #include #include #include +#include "DS2438.h" /****************************************************************************** * DEFINES @@ -91,9 +92,15 @@ RunningMedian solarRawSensor = RunningMedian(VOLT_SENSOR_MEASURE_SERIES); RunningMedian waterRawSensor = RunningMedian(5); RunningMedian lipoTempSensor = RunningMedian(TEMP_SENSOR_MEASURE_SERIES); RunningMedian waterTempSensor = RunningMedian(TEMP_SENSOR_MEASURE_SERIES); +float mBatteryVoltage = 0.0f; +float mSolarVoltage = 0.0f; +float mChipTemp = 0.0f; + +/*************************** Hardware abstraction *****************************/ OneWire oneWire(SENSOR_DS18B20); DallasTemperature sensors(&oneWire); +DS2438 battery(&oneWire,0.1f); Plant mPlants[MAX_PLANTS] = { Plant(SENSOR_PLANT0, OUTPUT_PUMP0, 0, &plant0, &mSetting0), @@ -108,16 +115,6 @@ Plant mPlants[MAX_PLANTS] = { * LOCAL FUNCTIONS ******************************************************************************/ -float getBatteryVoltage() -{ - return ADC_5V_TO_3V3(lipoRawSensor.getAverage()); -} - -float getSolarVoltage() -{ - return SOLAR_VOLT(solarRawSensor.getAverage()); -} - void setMoistureTrigger(int plantId, long value) { if ((plantId >= 0) && (plantId < MAX_PLANTS)) @@ -176,17 +173,52 @@ long getDistance() } /** - * @brief Read Voltage + * @brief Read Voltage and Temperatur * Read the battery voltage and the current voltage, provided by the solar panel */ void readSystemSensors() { + int timeoutTemp = millis() + TEMPERATUR_TIMEOUT; + int sensorCount = 0; + + rtcLastLipoTemp = lipoTempSensor.getAverage(); + rtcLastWaterTemp = waterTempSensor.getAverage(); + + /* Required to read the temperature at least once */ + while (sensorCount == 0 && millis() < timeoutTemp) + { + sensors.begin(); + battery.begin(); + sensorCount = sensors.getDeviceCount(); + Serial << "Waitloop: One wire count: " << sensorCount << endl; + delay(200); + } + + Serial << "One wire count: " << sensorCount << endl; + /* Measure temperature */ + if (sensorCount > 0) + { + sensors.requestTemperatures(); + } + + for (int i = 0; i < sensorCount; i++) + { + Serial << "OnwWire sensor " << i << " has value " << sensors.getTempCByIndex(i) << endl; + } + + // Update battery chip data + battery.update(); + mSolarVoltage = battery.getVoltage(BATTSENSOR_INDEX_SOLAR) * SOLAR_VOLT_FACTOR; + mBatteryVoltage = battery.getVoltage(BATTSENSOR_INDEX_BATTERY); + mChipTemp = battery.getTemperature(); for (int i = 0; i < VOLT_SENSOR_MEASURE_SERIES; i++) { lipoRawSensor.add(analogRead(SENSOR_LIPO)); solarRawSensor.add(analogRead(SENSOR_SOLAR)); } - Serial << "Lipo " << lipoRawSensor.getAverage() << " -> " << getBatteryVoltage() << endl; + Serial << "Lipo " << lipoRawSensor.getAverage() << " -> " << mBatteryVoltage << endl; + rtcLastBatteryVoltage = mBatteryVoltage; + rtcLastSolarVoltage = mSolarVoltage; } long getCurrentTime() @@ -293,9 +325,9 @@ void mode2MQTT() lastWaterValue = waterRawSensor.getAverage(); sensorLipo.setProperty("percent").send(String(100 * lipoRawSensor.getAverage() / 4095)); - sensorLipo.setProperty("volt").send(String(getBatteryVoltage())); + sensorLipo.setProperty("volt").send(String(mBatteryVoltage)); sensorSolar.setProperty("percent").send(String((100 * solarRawSensor.getAverage()) / 4095)); - sensorSolar.setProperty("volt").send(String(getSolarVoltage())); + sensorSolar.setProperty("volt").send(String(mSolarVoltage)); startupReason.setProperty("startupReason").send(String(wakeUpReason)); rtcLipoTempIndex = lipoSensorIndex.get(); @@ -330,6 +362,8 @@ void mode2MQTT() delay(100); sensors.begin(); Serial << "Reset 1-Wire Bus" << endl; + // Setup Battery sensor DS2438 + battery.begin(); } for(j=0; j < TEMP_SENSOR_MEASURE_SERIES && isnan(lipoTempCurrent); j++) { @@ -394,10 +428,11 @@ void mode2MQTT() } if (lastPumpRunning == -1 || !hasWater) { - if (getSolarVoltage() < SOLAR_CHARGE_MIN_VOLTAGE) + if (mSolarVoltage < SOLAR_CHARGE_MIN_VOLTAGE) { gotoMode2AfterThisTimestamp = getCurrentTime() + maxTimeBetweenMQTTUpdates.get(); - Serial.println("No pumps to activate and low light, deepSleepNight"); + Serial.print(mSolarVoltage); + Serial.println("V! No pumps to activate and low light, deepSleepNight"); espDeepSleepFor(deepSleepNightTime.get()); rtcDeepSleepTime = deepSleepNightTime.get(); } @@ -530,8 +565,6 @@ int readTemp() { bool readSensors() { bool leaveMode1 = false; - int timeoutTemp = millis() + TEMPERATUR_TIMEOUT; - int sensorCount = 0; Serial << "Read Sensors" << endl; @@ -567,62 +600,33 @@ bool readSensors() } } - if (abs(getBatteryVoltage() - rtcLastBatteryVoltage) > LIPO_DELTA_VOLT_ADC) + if (abs(mBatteryVoltage - rtcLastBatteryVoltage) > LIPO_DELTA_VOLT_ADC) { wakeUpReason = WAKEUP_REASON_BATTERY_CHANGE; leaveMode1 = true; } - if (abs(getSolarVoltage() - rtcLastSolarVoltage) > SOLAR_DELTA_VOLT_ADC) + if (abs(mSolarVoltage - rtcLastSolarVoltage) > SOLAR_DELTA_VOLT_ADC) { wakeUpReason = WAKEUP_REASON_SOLAR_CHANGE; leaveMode1 = true; } - rtcLastLipoTemp = lipoTempSensor.getAverage(); - rtcLastWaterTemp = waterTempSensor.getAverage(); - rtcLastBatteryVoltage = getBatteryVoltage(); - rtcLastSolarVoltage = getSolarVoltage(); - - /* Required to read the temperature at least once */ - while (sensorCount == 0 && millis() < timeoutTemp) - { - sensors.begin(); - sensorCount = sensors.getDeviceCount(); - Serial << "Waitloop: One wire count: " << sensorCount << endl; - delay(200); - } - - Serial << "One wire count: " << sensorCount << endl; - /* Measure temperature */ - if (sensorCount > 0) - { - sensors.requestTemperatures(); - } - /* Read the distance and give the temperature sensors some time */ readDistance(); Serial << "Distance sensor " << waterRawSensor.getAverage() << " cm" << endl; - /* Retreive temperatures */ - if (sensorCount > 0) + // check if chip needs to start into full operational mode + leaveMode1 |= readTemp(); + + if (abs(lipoTempSensor.getAverage() - rtcLastLipoTemp) > TEMPERATURE_DELTA_TRIGGER_IN_C) { - leaveMode1 |= readTemp(); - - for (int i = 0; i < sensorCount; i++) - { - Serial << "OnwWire sensor " << i << " has value " << sensors.getTempCByIndex(i) << endl; - } - - if (abs(lipoTempSensor.getAverage() - rtcLastLipoTemp) > TEMPERATURE_DELTA_TRIGGER_IN_C) - { - leaveMode1 = true; - wakeUpReason = WAKEUP_REASON_TEMP1_CHANGE; - } - if (abs(waterTempSensor.getAverage() - rtcLastWaterTemp) > TEMPERATURE_DELTA_TRIGGER_IN_C) - { - wakeUpReason = WAKEUP_REASON_TEMP2_CHANGE; - leaveMode1 = true; - } + leaveMode1 = true; + wakeUpReason = WAKEUP_REASON_TEMP1_CHANGE; + } + if (abs(waterTempSensor.getAverage() - rtcLastWaterTemp) > TEMPERATURE_DELTA_TRIGGER_IN_C) + { + wakeUpReason = WAKEUP_REASON_TEMP2_CHANGE; + leaveMode1 = true; } /* deactivate the sensors */ @@ -679,8 +683,7 @@ void onHomieEvent(const HomieEvent &event) int determineNextPump() { - float solarValue = getSolarVoltage(); - bool isLowLight = (solarValue > SOLAR_CHARGE_MIN_VOLTAGE || solarValue < SOLAR_CHARGE_MAX_VOLTAGE); + bool isLowLight = (mSolarVoltage > SOLAR_CHARGE_MIN_VOLTAGE || mSolarVoltage < SOLAR_CHARGE_MAX_VOLTAGE); //FIXME instead of for, use sorted by last activation index to ensure equal runtime? @@ -937,10 +940,10 @@ void setup() // Big TODO use here the settings in RTC_Memory //Panik mode, the Lipo is empty, sleep a long long time: - if ((getBatteryVoltage() < MINIMUM_LIPO_VOLT) && - (getBatteryVoltage() > NO_LIPO_VOLT)) + if ((mBatteryVoltage < MINIMUM_LIPO_VOLT) && + (mBatteryVoltage > NO_LIPO_VOLT)) { - Serial << PANIK_MODE_DEEPSLEEP << " s lipo " << getBatteryVoltage() << "V" << endl; + Serial << PANIK_MODE_DEEPSLEEP << " s lipo " << mBatteryVoltage << "V" << endl; esp_sleep_enable_timer_wakeup(PANIK_MODE_DEEPSLEEP_US); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);