diff --git a/esp32/PlantControl.code-workspace b/esp32/PlantControl.code-workspace index 83e0c1b..0f76eb0 100644 --- a/esp32/PlantControl.code-workspace +++ b/esp32/PlantControl.code-workspace @@ -25,6 +25,7 @@ "fstream": "cpp", "ostream": "cpp", "sstream": "cpp" + "system_error": "cpp" } } } diff --git a/esp32/include/ControllerConfiguration.h b/esp32/include/ControllerConfiguration.h index 17077d6..3d0ac5e 100644 --- a/esp32/include/ControllerConfiguration.h +++ b/esp32/include/ControllerConfiguration.h @@ -1,6 +1,51 @@ +/** + * @file ControllerConfiguration.h + * @author your name (you@domain.com) + * @brief + * @version 0.1 + * @date 2020-05-30 + * + * @copyright Copyright (c) 2020 + * + * \mainpage Configuration of the controller + * @{ + * Describe the used PINs of the controller + * + * @subpage Controller + * + * @subpage Homie + * + * @subpage Configuration + * + * There are several modes in the controller + * \dot + * digraph Operationmode { + * ranksep=.75; + * poweroff [ label="off" ]; + * mode1 [ label="Mode 1 - Sensor only", shape=box, width=2 ]; + * mode2 [ label="Mode 2 - Wifi enabled", shape=box ]; + * mode3 [ label="Mode 3 - Stay alive", shape=box ]; + * mode1 -> mode2 [ label="wakeup reason", fontsize=10 ]; + * mode1 -> mode2 [ label="Time duration", fontsize=10 ]; + * mode2 -> mode3 [ label="Over the Air Update", fontsize=10 ]; + * mode3 -> mode2 [ label="Over the Air Finished", fontsize=10 ]; + * mode3 -> mode2 [ label="Mqtt Command", fontsize=10 ]; + * mode2 -> mode3 [ label="Mqtt Command", fontsize=10 ]; + * poweroff -> mode1 [ label="deep sleep wakeup", fontsize=10 ]; + * mode1 -> poweroff [ label="enter deep sleep", fontsize=10 ]; + * mode2 -> poweroff [ label="Mqtt queue empty", fontsize=10 ]; + * } + * \enddot + * + * Before entering Deep sleep the controller is configured with an wakeup time. + * + * @} + */ #ifndef CONTROLLER_CONFIG_H #define CONTROLLER_CONFIG_H - +/** \addtogroup GPIO Settings + * @{ + */ #define SENSOR_PLANT0 GPIO_NUM_32 /**< GPIO 32 (ADC1) */ #define SENSOR_PLANT1 GPIO_NUM_33 /**< GPIO 33 (ADC1) */ #define SENSOR_PLANT2 GPIO_NUM_25 /**< GPIO 25 (ADC2) */ @@ -21,8 +66,8 @@ #define OUTPUT_ENABLE_PUMP GPIO_NUM_13 /**< GPIO 13 - Enable Pumps */ #define SENSOR_ONEWIRE GPIO_NUM_4 /**< GPIO 12 - Temperatur sensor, Battery and other cool onewire stuff */ -#define SHARED_SCL GPIO_NUM_16 /**< GPIO 16 - echo feedback of water sensor */ -#define SENSOR_TANK_SDA GPIO_NUM_17 /**< GPIO 17 - trigger for water sensor */ +#define SENSOR_TANK_SDA GPIO_NUM_16 /**< GPIO 16 - echo feedback of water sensor */ +#define SENSOR_TANK_SCL GPIO_NUM_17 /**< GPIO 17 - trigger for water sensor */ #define BUTTON GPIO_NUM_0 /**< GPIO 0 - Fix button of NodeMCU */ #define CUSTOM1_PIN1 GPIO_NUM_34 /** direct gpio */ @@ -32,20 +77,28 @@ #define I2C1_SDA GPIO_NUM_34 /**< GPIO 34 - I2C */ #define I2C1_SCL GPIO_NUM_35 /**< GPIO 35 - I2C */ +/* @} */ -#define FIRMWARE_VERSION "sw2.201 hw0.10b" +/** \addtogroup Configuration + * @{ + */ +#define CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE +#define CONFIG_COMPILER_CXX_EXCEPTIONS + + +#define FIRMWARE_VERSION "2.4 HW0.10b" #define TIMED_LIGHT_PIN CUSTOM1_PIN5 #define FLOWMETER_PIN CUSTOM1_PIN1 #ifdef FLOWMETER_PIN - #define FLOWMETER_FLOWFACTOR 23 /** F = 22 * Q;Q = L/min */ + #define FLOWMETER_PULSES_PER_ML 2.2 #endif -#define MOIST_SENSOR_MAX_FRQ 10000 // 10kHz (as never more then 3000 was measured) -#define MOIST_SENSOR_MIN_FRQ 1000 // 1kHz (500Hz margin) +#define MOIST_SENSOR_MAX_FRQ 60000 // 60kHz (500Hz margin) +#define MOIST_SENSOR_MIN_FRQ 500 // 0.5kHz (500Hz margin) -#define ANALOG_SENSOR_MAX_MV 4095 /**< Maximum according https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html */ -#define ANALOG_SENSOR_MIN_MV 100 +#define ANALOG_SENSOR_MAX_MV 1300 //successive approximation of good range +#define ANALOG_SENSOR_MIN_MV 300 //successive approximation of good range #define SOLAR_VOLT_FACTOR 11 #define BATTSENSOR_INDEX_SOLAR 0 @@ -65,11 +118,9 @@ #define TEMPERATUR_TIMEOUT 3000 /**< 3 Seconds timeout for the temperatur sensors */ #define DS18B20_RESOLUTION 9 /**< 9bit temperature resolution -> 0.5°C steps */ -#define PLANT_WITHOUT_TEMPSENSOR 100 - #define UTC_OFFSET_DE 3600 /* UTC offset in seconds for Germany */ #define UTF_OFFSET_DE_DST 3600 /* offset in seconds if daylight saving time is active */ /* @} */ -#endif \ No newline at end of file +#endif diff --git a/esp32/include/HomieConfiguration.h b/esp32/include/HomieConfiguration.h index 23dc690..7bceab7 100644 --- a/esp32/include/HomieConfiguration.h +++ b/esp32/include/HomieConfiguration.h @@ -88,6 +88,7 @@ HomieSetting ntpServer("ntpServer", "NTP server (pool.ntp.org as d HomieSetting timedLightStart("LightStart", "hour to start light"); HomieSetting timedLightEnd("LightEnd", "hour to end light"); HomieSetting timedLightOnlyWhenDark("LightOnlyDark", "only enable light, if solar is low"); + HomieSetting timedLightPowerLevel("LightPowerLevel", "0-255 power level"); #endif // TIMED_LIGHT_PIN @@ -106,11 +107,11 @@ HomieSetting ntpServer("ntpServer", "NTP server (pool.ntp.org as d HomieSetting mPumpAllowedHourRangeStart##plant = HomieSetting("hourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \ HomieSetting mPumpAllowedHourRangeEnd##plant = HomieSetting("hourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \ HomieSetting mPumpOnlyWhenLowLight##plant = HomieSetting("lowLight" strplant, "Plant" strplant " - Enable the Pump only, when there is no sunlight"); \ - HomieSetting mPumpCooldownInMinutes##plant = HomieSetting("delay" strplant, "Plant" strplant " - How long to wait until the pump is activated again (minutes)"); \ + HomieSetting mPumpCooldownInSeconds##plant = HomieSetting("delay" strplant, "Plant" strplant " - How long to wait until the pump is activated again (minutes)"); \ HomieSetting pPumpDuration##plant = HomieSetting("pumpDuration" strplant, "Plant" strplant " - time seconds to water when pump is active"); \ HomieSetting pPumpMl##plant = HomieSetting("pumpAmount" strplant, "Plant" strplant " - ml (if using flowmeter) to water when pump is active"); \ HomieSetting pPowerLevel##plant = HomieSetting("powerLevel" strplant, "Plant" strplant " - pwm duty cycle in percent"); \ - PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInMinutes##plant, &pPumpDuration##plant, &pPowerLevel##plant, &pPumpMl##plant}; \ + PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInSeconds##plant, &pPumpDuration##plant, &pPowerLevel##plant, &pPumpMl##plant}; \ /**< Generate all settings for one plant \ * \ * Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE} \ diff --git a/esp32/include/HomieTypes.h b/esp32/include/HomieTypes.h index 3105f2d..d39cd3b 100644 --- a/esp32/include/HomieTypes.h +++ b/esp32/include/HomieTypes.h @@ -22,8 +22,7 @@ #define FOREACH_SENSOR(SENSOR) \ SENSOR(NONE) \ SENSOR(CAPACITIVE_FREQUENCY) \ - SENSOR(ANALOG_RESISTANCE_PROBE) \ - SENSOR(SHT20) + SENSOR(ANALOG_RESISTANCE_PROBE) /** * @} @@ -46,6 +45,8 @@ static const char *SENSOR_STRING[] = { #define MISSING_SENSOR -2 //plant uses only cooldown and duration, moisture is measured but ignored, allowedHours is ignored (eg. make a 30min on 30min off cycle) #define HYDROPONIC_MODE -3 +//plant uses cooldown and duration and workhours, moisture is measured but ignored +#define TIMER_ONLY -4 typedef struct PlantSettings_t { diff --git a/esp32/include/LogDefines.h b/esp32/include/LogDefines.h index 4e6a9ba..dc6ff3c 100644 --- a/esp32/include/LogDefines.h +++ b/esp32/include/LogDefines.h @@ -36,4 +36,5 @@ #define LOG_SLEEP_NIGHT 100 #define LOG_SLEEP_DAY 101 #define LOG_SLEEP_CYCLE 102 -#define LOG_MISSING_PUMP -4 \ No newline at end of file +#define LOG_MISSING_PUMP -4 +#define LOG_BOOT_ERROR_DETECTION 10000 \ No newline at end of file diff --git a/esp32/include/PlantCtrl.h b/esp32/include/PlantCtrl.h index f052a47..332b1a7 100644 --- a/esp32/include/PlantCtrl.h +++ b/esp32/include/PlantCtrl.h @@ -75,6 +75,12 @@ public: return SENSOR_STRING[mode]; } + bool isTimerOnly() + { + long current = this->mSetting->pSensorDry->get(); + return equalish(current, TIMER_ONLY); + } + bool isHydroponic() { long current = this->mSetting->pSensorDry->get(); @@ -94,7 +100,7 @@ public: */ bool isPumpRequired() { - if (isHydroponic()) + if (isHydroponic() || isTimerOnly()) { // hydroponic only uses timer based controll return true; @@ -122,13 +128,6 @@ public: } } - float getCurrentTemperature(){ - if(mTemperature_degree.getCount() == 0){ - return PLANT_WITHOUT_TEMPSENSOR; - } - return mTemperature_degree.getMedian(); - } - float getCurrentMoisturePCT() { switch (getSensorMode()) @@ -138,9 +137,7 @@ public: case CAPACITIVE_FREQUENCY: return mapf(mMoisture_raw.getMedian(), MOIST_SENSOR_MAX_FRQ, MOIST_SENSOR_MIN_FRQ, 0, 100); case ANALOG_RESISTANCE_PROBE: - return mapf(mMoisture_raw.getMedian(), ANALOG_SENSOR_MIN_MV, ANALOG_SENSOR_MAX_MV, 0, 100); - case SHT20: - return mMoisture_raw.getMedian(); + return mapf(mMoisture_raw.getMedian(), ANALOG_SENSOR_MAX_MV, ANALOG_SENSOR_MIN_MV, 0, 100); } return MISSING_SENSOR; } @@ -210,6 +207,12 @@ public: { return this->mSetting->pPumpDuration->get(); } + long getPumpMl() + { + return this->mSetting->pPumpMl->get(); + } + + }; #endif diff --git a/esp32/include/ulp-pwm.h b/esp32/include/ulp-pwm.h new file mode 100644 index 0000000..778d202 --- /dev/null +++ b/esp32/include/ulp-pwm.h @@ -0,0 +1,188 @@ +#ifndef ULP_PWM_h +#define ILP_PWM_h + +#include +#include "driver/rtc_io.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/rtc.h" +#include "esp32/ulp.h" +#include "ControllerConfiguration.h" + +#define LBL_START 1 +#define LBL_DELAY_ON 2 +#define LBL_DELAY_OFF 3 +#define LBL_SKIP_ON 4 +#define LBL_SKIP_OFF 5 +#define REGISTER_DELAY_LOOP_COUNTER R0 +#define REGISTER_TICKS_ON R1 +#define REGISTER_TICKS_OFF R2 +#define TOTAL_TICKS_DELAY 255 +#define PIN TIMED_LIGHT_PIN + +//support 20 vars +const size_t ulp_var_offset = CONFIG_ULP_COPROC_RESERVE_MEM - 20; +//use the first for dimming +const size_t ulp_dimm_offset = ulp_var_offset + 1; +const size_t ulp_alive_offset = ulp_var_offset + 2; + +//see https://github.com/perseus086/ESP32-notes +const uint32_t rtc_bit[40] = { + 25, //gpio0 + 0, //gpio1 + 26, //gpio2 + 0, //gpio3 + 24, //gpio4 + 0, //gpio5 + 0, //gpio6 + 0, //gpio7 + 0, //gpio8 + 0, //gpio9 + 0, //gpio10 + 0, //gpio11 + 29, //gpio12 + 28, //gpio13 + 30, //gpio14 + 27, //gpio15 + 0, //gpio16 + 31, //gpio17 + 0, //gpio18 + 0, //gpio19 + 0, //gpio20 + 0, //gpio21 + 0, //gpio22 + 0, //gpio23 + 0, //gpio24 + 20, //gpio25 + 21, //gpio26 + 0, //gpio27 + 0, //gpio28 + 0, //gpio29 + 0, //gpio30 + 0, //gpio31 + 23, //gpio32 + 22, //gpio33 + 18, //gpio34 + 19, //gpio35 + 14, //gpio36 + 15, //gpio37 + 16, //gpio38 + 17 //gpio39 +}; + +static inline void ulp_internal_data_write(size_t offset, uint16_t value) +{ + if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset) + { + Serial.print("Invalid ULP offset detected, refusing write!"); + Serial.print(offset); + Serial.print("-"); + Serial.print(ulp_var_offset); + Serial.print("-"); + Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM); + return; + } + else + { + RTC_SLOW_MEM[offset] = value; + } +} + +static inline uint16_t ulp_internal_data_read(size_t offset) +{ + if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset) + { + Serial.print("Invalid ULP offset detected"); + Serial.print(offset); + Serial.print("-"); + Serial.print(ulp_var_offset); + Serial.print("-"); + Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM); + } + return RTC_SLOW_MEM[offset] & 0xffff; +} + +static inline uint32_t rtc_io_number_get(gpio_num_t gpio_num) +{ + assert(rtc_gpio_is_valid_gpio(gpio_num) && "Invalid GPIO for RTC"); + uint32_t bit = rtc_bit[gpio_num]; + Serial.print("Resolved GPIO "); + Serial.print(gpio_num); + Serial.print(" to rtc bit "); + Serial.println(bit); + return bit; +} + +void ulp_internal_start(void) +{ + rtc_gpio_init(PIN); + rtc_gpio_set_direction(PIN, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(PIN, 0); + const uint32_t rtc_gpio = rtc_io_number_get(PIN); + + // Define ULP program + const ulp_insn_t ulp_prog[] = { + M_LABEL(LBL_START), + + I_MOVI(REGISTER_DELAY_LOOP_COUNTER, 1), + I_MOVI(REGISTER_TICKS_ON, 0), + I_ST(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON, ulp_alive_offset), //store 1 with 0 offset into alive + + I_LD(REGISTER_TICKS_ON, REGISTER_TICKS_ON, ulp_dimm_offset), //REGISTER_TICKS_ON = RTC_DATA[0+ulp_dimm_offset] + //in total there is always 255 delay loop iterations, but in different duty cycle + I_MOVI(REGISTER_TICKS_OFF, TOTAL_TICKS_DELAY), + I_SUBR(REGISTER_TICKS_OFF, REGISTER_TICKS_OFF, REGISTER_TICKS_ON), + + //on phase + I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON), + M_BL(LBL_SKIP_ON, 1), //if never on, skip on phase + I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, HIGH), // on + M_LABEL(LBL_DELAY_ON), + I_DELAY(1), //wait 1 clock + I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER-- + M_BGE(LBL_DELAY_ON, 1), //if time left, goto start of on loop + M_LABEL(LBL_SKIP_ON), + + //off phase + I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_OFF), + + M_BL(LBL_SKIP_OFF, 1), //if never off, skip on phase + I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, LOW), // on + M_LABEL(3), + I_DELAY(1), //wait 1 clock + I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER-- + M_BGE(3, 1), //if time left, goto start of on loop + M_LABEL(LBL_SKIP_OFF), + + M_BX(LBL_START), + }; + // Run ULP program + size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t); + assert(size < ulp_var_offset && "ULP_DATA_OFFSET needs to be greater or equal to the program size"); + esp_err_t error = ulp_process_macros_and_load(0, ulp_prog, &size); + Serial.print("ULP bootstrap status "); + Serial.println(error); + + //allow glitchless start + ulp_internal_data_write(ulp_alive_offset, 0); + + error = ulp_run(0); + Serial.print("ULP start status "); + Serial.println(error); +} + +static inline void ulp_pwm_set_level(uint8_t level) +{ + ulp_internal_data_write(ulp_dimm_offset, level); +} + +static inline void ulp_pwm_init() +{ + ulp_internal_data_write(ulp_alive_offset, 0); + delay(10); + if (ulp_internal_data_read(ulp_alive_offset) == 0) + { + ulp_internal_start(); + } +} + +#endif \ No newline at end of file diff --git a/esp32/platformio.ini b/esp32/platformio.ini index 39ddff5..eaa7c40 100644 --- a/esp32/platformio.ini +++ b/esp32/platformio.ini @@ -13,13 +13,13 @@ platform = espressif32 board = esp32doit-devkit-v1 framework = arduino build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY - -DPLANT0_SENSORTYPE=SHT20 + -DPLANT0_SENSORTYPE=ANALOG_RESISTANCE_PROBE -DPLANT1_SENSORTYPE=ANALOG_RESISTANCE_PROBE - -DPLANT2_SENSORTYPE=CAPACITIVE_FREQUENCY - -DPLANT3_SENSORTYPE=CAPACITIVE_FREQUENCY - -DPLANT4_SENSORTYPE=CAPACITIVE_FREQUENCY - -DPLANT5_SENSORTYPE=CAPACITIVE_FREQUENCY - -DPLANT6_SENSORTYPE=CAPACITIVE_FREQUENCY + -DPLANT2_SENSORTYPE=ANALOG_RESISTANCE_PROBE + -DPLANT3_SENSORTYPE=ANALOG_RESISTANCE_PROBE + -DPLANT4_SENSORTYPE=ANALOG_RESISTANCE_PROBE + -DPLANT5_SENSORTYPE=ANALOG_RESISTANCE_PROBE + -DPLANT6_SENSORTYPE=ANALOG_RESISTANCE_PROBE board_build.partitions = defaultWithSmallerSpiffs.csv @@ -33,4 +33,4 @@ lib_deps = ArduinoJson@6.16.1 [platformio] -extra_configs = custom_platformio.ini \ No newline at end of file +extra_configs = custom_platformio.ini diff --git a/esp32/src/PlantCtrl.cpp b/esp32/src/PlantCtrl.cpp index a7d0e93..246d880 100644 --- a/esp32/src/PlantCtrl.cpp +++ b/esp32/src/PlantCtrl.cpp @@ -31,7 +31,7 @@ void Plant::init(void) /* Initialize Home Settings validator */ this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT); this->mSetting->pSensorDry->setValidator([](long candidate) - { return (((candidate >= 0.0) && (candidate <= 100.0)) || equalish(candidate, DEACTIVATED_PLANT) || equalish(candidate, HYDROPONIC_MODE)); }); + { return (((candidate >= 0.0) && (candidate <= 100.0)) || equalish(candidate, DEACTIVATED_PLANT) || equalish(candidate, HYDROPONIC_MODE) || equalish(candidate, TIMER_ONLY)); }); this->mSetting->pPumpAllowedHourRangeStart->setDefaultValue(8); // start at 8:00 this->mSetting->pPumpAllowedHourRangeStart->setValidator([](long candidate) @@ -39,7 +39,7 @@ void Plant::init(void) this->mSetting->pPumpAllowedHourRangeEnd->setDefaultValue(20); // stop pumps at 20:00 this->mSetting->pPumpAllowedHourRangeEnd->setValidator([](long candidate) { return ((candidate >= 0) && (candidate <= 23)); }); - this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true); + this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(false); this->mSetting->pPumpCooldownInSeconds->setDefaultValue(60 * 60); // 1 hour this->mSetting->pPumpCooldownInSeconds->setValidator([](long candidate) { return (candidate >= 0); }); @@ -47,7 +47,7 @@ void Plant::init(void) this->mSetting->pPumpDuration->setDefaultValue(30); this->mSetting->pPumpDuration->setValidator([](long candidate) { return ((candidate >= 0) && (candidate <= 1000)); }); - this->mSetting->pPumpMl->setDefaultValue(0); + this->mSetting->pPumpMl->setDefaultValue(1000); this->mSetting->pPumpMl->setValidator([](long candidate) { return ((candidate >= 0) && (candidate <= 5000)); }); this->mSetting->pPumpPowerLevel->setDefaultValue(100); @@ -91,7 +91,6 @@ void Plant::initSensors(void) adcAttachPin(this->mPinSensor); break; } - case SHT20: case NONE: { // do nothing @@ -113,34 +112,6 @@ void Plant::blockingMoistureMeasurement(void) } break; } - case SHT20: - { - - //do not assume valid i2c state, reinit - TwoWire sensorWire = TwoWire(0); - sensorWire.setPins(SENSOR_TANK_SDA, SHARED_SCL); - sht20.begin(&sensorWire); - sht20.reset(); - delay(100); - if(!sht20.isConnected()){ - Serial.println("SHT20 connection error"); - } - bool success = sht20.read(); - int error = sht20.getError(); - if (error) - { - log(LOG_LEVEL_ERROR, "Failure reading SHT20 " + String(error), LOG_SENSOR_MISSING); - } - if (!success || error) - { - this->mMoisture_raw.clear(); - this->mMoisture_raw.add(MISSING_SENSOR); - return; - } - mMoisture_raw.add(sht20.getHumidity()); - mTemperature_degree.add(sht20.getTemperature()); - break; - } case CAPACITIVE_FREQUENCY: case NONE: { @@ -160,7 +131,6 @@ void Plant::startMoistureMeasurement(void) pcnt_counter_resume(unit); break; } - case SHT20: case ANALOG_RESISTANCE_PROBE: case NONE: { @@ -192,7 +162,6 @@ void Plant::stopMoistureMeasurement(void) } break; } - case SHT20: case ANALOG_RESISTANCE_PROBE: case NONE: { diff --git a/esp32/src/main.cpp b/esp32/src/main.cpp index 80df651..369cbc5 100644 --- a/esp32/src/main.cpp +++ b/esp32/src/main.cpp @@ -1,18 +1,18 @@ /** \addtogroup Controller * @{ - * + * * @file main.cpp * @author Ollo * @brief PlantControl * @version 0.1 * @date 2020-05-01 - * + * * @copyright Copyright (c) 2020 */ /****************************************************************************** * INCLUDES -******************************************************************************/ + ******************************************************************************/ #include "LogDefines.h" #include "FileUtils.h" #include "TimeUtils.h" @@ -35,28 +35,32 @@ #include #include "driver/pcnt.h" #include "MQTTUtils.h" +#include "esp_ota_ops.h" +#if defined(TIMED_LIGHT_PIN) +#include "ulp-pwm.h" +#endif /****************************************************************************** * DEFINES -******************************************************************************/ + ******************************************************************************/ #define AMOUNT_SENOR_QUERYS 8 #define MAX_TANK_DEPTH 2000 +#define REBOOT_LOOP_DETECTION_ERROR 5 /****************************************************************************** * FUNCTION PROTOTYPES -******************************************************************************/ + ******************************************************************************/ int determineNextPump(bool lowLight); void plantcontrol(); void readPowerSwitchedSensors(); bool determineTimedLightState(bool lowLight); +bool otaRunning = false; /****************************************************************************** * NON VOLATILE VARIABLES in DEEP SLEEP -******************************************************************************/ - + ******************************************************************************/ #if defined(TIMED_LIGHT_PIN) -RTC_DATA_ATTR bool timedLightOn = false; /**< allow fast recovery after poweron */ RTC_DATA_ATTR bool timedLightLowVoltageTriggered = false; /**remember if it was shut down due to voltage level */ #endif // TIMED_LIGHT_PIN @@ -65,7 +69,7 @@ RTC_DATA_ATTR long consecutiveWateringPlant[MAX_PLANTS] = {0}; /****************************************************************************** * LOCAL VARIABLES -******************************************************************************/ + ******************************************************************************/ bool volatile mDownloadMode = false; /**< Controller must not sleep */ bool volatile mSensorsRead = false; /**< Sensors are read without Wifi or MQTT */ int volatile pumpToRun = -1; /** pump to run at the end of the cycle */ @@ -80,6 +84,7 @@ unsigned long setupFinishedTimestamp; bool pumpStarted = false; long pumpTarget = -1; +long pumpStartTime = 0; long lastSendPumpUpdate = 0; #ifdef FLOWMETER_PIN long pumpTargetMl = -1; @@ -125,13 +130,30 @@ Plant mPlants[MAX_PLANTS] = { /****************************************************************************** * LOCAL FUNCTIONS -******************************************************************************/ + ******************************************************************************/ + +void finsihedCycleSucessfully() +{ + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_ota_img_states_t ota_state; + if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) + { + log(LOG_LEVEL_INFO, "Get State Partition was Successfull", LOG_BOOT_ERROR_DETECTION); + if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) + { + log(LOG_LEVEL_INFO, "Diagnostics completed successfully! Marking as valid", LOG_BOOT_ERROR_DETECTION); + esp_ota_mark_app_valid_cancel_rollback(); + } + } +} void espDeepSleep(bool afterPump = false) { if (mDownloadMode) { log(LOG_LEVEL_DEBUG, "abort deepsleep, DownloadMode active", LOG_DEBUG_CODE); + // if we manage to get to the download mode, the device can be restored + finsihedCycleSucessfully(); return; } if (aliveWasRead()) @@ -154,17 +176,7 @@ void espDeepSleep(bool afterPump = false) } } - //allo hold for all digital pins - gpio_deep_sleep_hold_en(); - - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_ON); - esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_ON); - -#if defined(TIMED_LIGHT_PIN) - gpio_hold_en(TIMED_LIGHT_PIN); -#endif // TIMED_LIGHT_PIN + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); long secondsToSleep = -1; @@ -187,6 +199,7 @@ void espDeepSleep(bool afterPump = false) } } + finsihedCycleSucessfully(); esp_sleep_enable_timer_wakeup((secondsToSleep * 1000U * 1000U)); if (aliveWasRead()) { @@ -199,7 +212,7 @@ void espDeepSleep(bool afterPump = false) } } -//requires homie being started +// requires homie being started void readOneWireSensors() { for (uint8_t i = 0; i < sensors.getDeviceCount(); i++) @@ -227,7 +240,7 @@ void readOneWireSensors() if (!valid) { - //wrong family or crc errors on each retry + // wrong family or crc errors on each retry continue; } @@ -302,30 +315,22 @@ void readPowerSwitchedSensors() Plant plant = mPlants[i]; switch (plant.getSensorMode()) { - case CAPACITIVE_FREQUENCY: { - Serial << "Plant " << i << " measurement: " << mPlants[i].getCurrentMoistureRaw() << " hz " << mPlants[i].getCurrentMoisturePCT() << "%" << endl; - break; - } - case ANALOG_RESISTANCE_PROBE : { - Serial << "Plant " << i << " measurement: " << mPlants[i].getCurrentMoistureRaw() << " mV " << mPlants[i].getCurrentMoisturePCT() << "%" << endl; - break; - } - case SHT20:{ - Serial << "Plant " << i << " measurement: " << mPlants[i].getCurrentTemperature() << "°C " << mPlants[i].getCurrentMoisturePCT() << "rH%" << endl; - break; - } - - case NONE : { - - } + case CAPACITIVE_FREQUENCY: + { + Serial << "Plant " << i << " measurement: " << mPlants[i].getCurrentMoistureRaw() << " hz " << mPlants[i].getCurrentMoisturePCT() << "%" << endl; + break; + } + case ANALOG_RESISTANCE_PROBE: + { + Serial << "Plant " << i << " measurement: " << mPlants[i].getCurrentMoistureRaw() << " mV " << mPlants[i].getCurrentMoisturePCT() << "%" << endl; + break; + } + case NONE: + { + } } } - //do not assume valid i2c state, force release of hardware - Wire = TwoWire(0); - Wire.setPins(SENSOR_TANK_SDA, SHARED_SCL); - Wire.begin(); - waterRawSensor.clear(); tankSensor.setTimeout(500); long start = millis(); @@ -398,10 +403,7 @@ void onHomieEvent(const HomieEvent &event) { mPlants[i].deactivatePump(); } - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); - digitalWrite(OUTPUT_ENABLE_PUMP, HIGH); - delay(100); - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); + otaRunning = true; mDownloadMode = true; break; case HomieEventType::OTA_SUCCESSFUL: @@ -458,7 +460,7 @@ int determineNextPump(bool isLowLight) log(LOG_LEVEL_DEBUG, String(String(i) + " No pump required: due to light"), LOG_DEBUG_CODE); continue; } - if (!plant.isHydroponic()) + if (! (plant.isHydroponic() || plant.isTimerOnly())) { if (equalish(plant.getCurrentMoistureRaw(), MISSING_SENSOR)) { @@ -485,10 +487,17 @@ int determineNextPump(bool isLowLight) } else { - plant.publishState("active"); + if (mDownloadMode) + { + plant.publishState("active+supressed"); + } + else + { + plant.publishState("active"); + } } - if (!plant.isHydroponic()) + if (! (plant.isHydroponic() || plant.isTimerOnly())) { consecutiveWateringPlant[i]++; } @@ -514,7 +523,7 @@ int determineNextPump(bool isLowLight) else { plant.publishState("wet"); - //plant was detected as wet, remove consecutive count + // plant was detected as wet, remove consecutive count consecutiveWateringPlant[i] = 0; } } @@ -523,7 +532,7 @@ int determineNextPump(bool isLowLight) /** * @brief Handle Mqtt commands to keep controller alive - * + * * @param range multiple transmitted values (not used for this function) * @param value single value * @return true when the command was parsed and executed succuessfully @@ -600,12 +609,12 @@ bool switch7(const HomieRange &range, const String &value) void initPumpLogic() { - //set targets + // set targets #ifdef FLOWMETER_PIN - pumpTargetMl = mPlants[pumpToRun].getPumpDuration(); + pumpTargetMl = mPlants[pumpToRun].getPumpMl(); - //0-6 are used for moisture measurment + // 0-6 are used for moisture measurment pcnt_unit_t unit = (pcnt_unit_t)(PCNT_UNIT_7); pcnt_config_t pcnt_config = {}; // Instancia PCNT config pcnt_config.pulse_gpio_num = FLOWMETER_PIN; // Configura GPIO para entrada dos pulsos @@ -613,8 +622,8 @@ void initPumpLogic() pcnt_config.unit = unit; // Unidade de contagem PCNT - 0 pcnt_config.channel = PCNT_CHANNEL_0; // Canal de contagem PCNT - 0 pcnt_config.counter_h_lim = INT16_MAX; // Limite maximo de contagem - 20000 - pcnt_config.pos_mode = PCNT_COUNT_DIS; // Incrementa contagem na subida do pulso - pcnt_config.neg_mode = PCNT_COUNT_INC; // Incrementa contagem na descida do pulso + pcnt_config.pos_mode = PCNT_COUNT_INC; // Incrementa contagem na subida do pulso + pcnt_config.neg_mode = PCNT_COUNT_DIS; // Incrementa contagem na descida do pulso pcnt_config.lctrl_mode = PCNT_MODE_KEEP; // PCNT - modo lctrl desabilitado pcnt_config.hctrl_mode = PCNT_MODE_KEEP; // PCNT - modo hctrl - se HIGH conta incrementando pcnt_unit_config(&pcnt_config); // Configura o contador PCNT @@ -622,10 +631,15 @@ void initPumpLogic() pcnt_counter_clear(unit); // Zera o contador PCNT pcnt_counter_resume(unit); #endif + pumpStartTime = millis(); pumpTarget = millis() + (mPlants[pumpToRun].getPumpDuration() * 1000); +#ifdef FLOWMETER_PIN + log(LOG_LEVEL_INFO, "Starting pump " + String(pumpToRun) + " for " + String(mPlants[pumpToRun].getPumpDuration()) + "s or " + String(pumpTargetMl) + "ml", LOG_PUMP_STARTED_CODE); +#else log(LOG_LEVEL_INFO, "Starting pump " + String(pumpToRun) + " for " + String(mPlants[pumpToRun].getPumpDuration()) + "s", LOG_PUMP_STARTED_CODE); +#endif - //enable power + // enable power WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); digitalWrite(OUTPUT_ENABLE_PUMP, HIGH); delay(100); @@ -652,8 +666,8 @@ void pumpActiveLoop() mqttUpdateTick = true; } + long duration = millis() - pumpStartTime; #ifdef FLOWMETER_PIN - int16_t pulses; pcnt_unit_t unit = (pcnt_unit_t)(PCNT_UNIT_7); esp_err_t result = pcnt_get_counter_value(unit, &pulses); @@ -661,29 +675,27 @@ void pumpActiveLoop() { log(LOG_LEVEL_ERROR, LOG_HARDWARECOUNTER_ERROR_MESSAGE, LOG_HARDWARECOUNTER_ERROR_CODE); targetReached = true; + log(LOG_LEVEL_INFO, "Reached pump target ml " + String(pumpToRun), LOG_PUMP_STARTED_CODE); } else { - /**FLOWMETER_FLOWFACTOR * (L/Min) = F; - given 1L/min -> FLOWMETER_FLOWFACTOR*60 pulses per liter - -> 1000/result -> ml pro pulse; - -> result * pulses ->*/ - long pumped = (FLOWMETER_FLOWFACTOR * 60) * pulses / 1000; - if (pumped >= pumpTargetMl) + float mLPumped = pulses / FLOWMETER_PULSES_PER_ML; // mLperMs*duration; + if (mLPumped >= pumpTargetMl) { targetReached = true; pcnt_counter_pause(unit); - mPlants[pumpToRun].setProperty("waterusage").send(String(pumped)); + mPlants[pumpToRun].setProperty("pulses").send(String(pulses)); + mPlants[pumpToRun].setProperty("waterusage").send(String(mLPumped)); } + else if (mqttUpdateTick) { - mPlants[pumpToRun].setProperty("waterusage").send(String(pumped)); + mPlants[pumpToRun].setProperty("pulses").send(String(pulses)); + mPlants[pumpToRun].setProperty("waterusage").send(String(mLPumped)); } } #endif - long pumpStarted = pumpTarget - (mPlants[pumpToRun].getPumpDuration() * 1000); - long duration = millis() - pumpStarted; if (millis() > pumpTarget) { mPlants[pumpToRun].setProperty("watertime").send(String(duration)); @@ -696,15 +708,13 @@ void pumpActiveLoop() if (targetReached) { - //disable all + + // disable all digitalWrite(OUTPUT_ENABLE_PUMP, LOW); - for (int i = 0; i < MAX_PLANTS; i++) - { - mPlants[i].deactivatePump(); - } - //disable loop, to prevent multi processing + mPlants[pumpToRun].deactivatePump(); + // disable loop, to prevent multi processing pumpStarted = false; - //if runtime is larger than cooldown, else it would run continously + // if runtime is larger than cooldown, else it would run continously rtcLastWateringPlant[pumpToRun] = getCurrentTime(); espDeepSleep(true); } @@ -721,11 +731,9 @@ void safeSetup() WiFi.mode(WIFI_OFF); Serial.flush(); -//restore state before releasing pin, to prevent flickering +// restore state before releasing pin, to prevent flickering #if defined(TIMED_LIGHT_PIN) - pinMode(TIMED_LIGHT_PIN, OUTPUT); - digitalWrite(TIMED_LIGHT_PIN, timedLightOn); - gpio_hold_dis(TIMED_LIGHT_PIN); + ulp_pwm_init(); #endif // TIMED_LIGHT_PIN /* Intialize Plant */ @@ -743,26 +751,28 @@ void safeSetup() pinMode(OUTPUT_ENABLE_SENSOR, OUTPUT); + static_assert(HomieInternals::MAX_CONFIG_SETTING_SIZE >= MAX_CONFIG_SETTING_ITEMS, "Limits.hpp not adjusted MAX_CONFIG_SETTING_ITEMS"); if (HomieInternals::MAX_CONFIG_SETTING_SIZE < MAX_CONFIG_SETTING_ITEMS) { - //increase the config settings + // increase the config settings Serial << "Limits.hpp is not adjusted, please search for this string and increase" << endl; return; } + static_assert(HomieInternals::MAX_JSON_CONFIG_FILE_SIZE >= MAX_JSON_CONFIG_FILE_SIZE_CUSTOM, "Limits.hpp not adjusted MAX_JSON_CONFIG_FILE_SIZE"); if (HomieInternals::MAX_JSON_CONFIG_FILE_SIZE < MAX_JSON_CONFIG_FILE_SIZE_CUSTOM) { - //increase the config settings + // increase the config settings Serial << "Limits.hpp is not adjusted, please search for this string and increase" << endl; return; } - /************************* Start Homie Framework ***************/ + /************************* Start Homie Framework ***************/ Homie_setFirmware("PlantControl", FIRMWARE_VERSION); Homie.disableLedFeedback(); Homie_setBrand("PlantControl"); // Set default values - //in seconds + // in seconds deepSleepTime.setDefaultValue(600).setValidator([](long candidate) { return (candidate > 0) && (candidate < (60 * 60 * 2) /** 2h max sleep */); }); deepSleepNightTime.setDefaultValue(600); @@ -778,13 +788,15 @@ void safeSetup() { return (candidate > 0) && (candidate < (20)); }); #if defined(TIMED_LIGHT_PIN) + timedLightPowerLevel.setDefaultValue(25).setValidator([](long candidate) + { return (candidate > 0) && (candidate <= (255)); }); timedLightStart.setDefaultValue(18).setValidator([](long candidate) { return (candidate > 0) && (candidate < (25)); }); timedLightEnd.setDefaultValue(23).setValidator([](long candidate) { return (candidate > 0) && (candidate < (24)); }); timedLightOnlyWhenDark.setDefaultValue(true); timedLightVoltageCutoff.setDefaultValue(3.8).setValidator([](double candidate) - { return (candidate > 3.3) && (candidate < (4.2)); }); + { return ((candidate > 3.3 || candidate == -1) && (candidate < (50))); }); #endif // TIMED_LIGHT_PIN Homie.setLoopFunction(homieLoop); @@ -797,12 +809,13 @@ void safeSetup() } readPowerSwitchedSensors(); - Homie.setup(); + Wire = TwoWire(0); + Wire.setPins(SENSOR_TANK_SDA, SENSOR_TANK_SCL); + Wire.begin(); - - /************************* Start One-Wire bus ***************/ + /************************* Start One-Wire bus ***************/ int tempInitStartTime = millis(); uint8_t sensorCount = 0U; @@ -820,7 +833,7 @@ void safeSetup() /* Measure temperature TODO idea: move this into setup */ if (sensorCount > 0) { - //sensors.setResolution(DS18B20_RESOLUTION); + // sensors.setResolution(DS18B20_RESOLUTION); sensors.requestTemperatures(); } @@ -830,10 +843,6 @@ void safeSetup() for (int i = 0; i < MAX_PLANTS; i++) { mPlants[i].advertise(); - //write to temperature node instead - if(!equalish(mPlants[i].getCurrentTemperature(),PLANT_WITHOUT_TEMPSENSOR)){ - mqttWrite(&sensorTemp, "Plant" + String(i), String(mPlants[i].getCurrentTemperature())); - } } mPlants[0].setSwitchHandler(switch1); mPlants[1].setSwitchHandler(switch2); @@ -894,7 +903,7 @@ void safeSetup() } readOneWireSensors(); - //prevent BOD to be paranoid + // prevent BOD to be paranoid WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); digitalWrite(OUTPUT_ENABLE_PUMP, HIGH); delay(100); @@ -970,7 +979,14 @@ void loop() digitalWrite(OUTPUT_ENABLE_SENSOR, !digitalRead(OUTPUT_ENABLE_SENSOR)); if (mConfigured) { - nextBlink = millis() + 500; + if (otaRunning) + { + nextBlink = millis() + 100; + } + else + { + nextBlink = millis() + 501; + } } else { @@ -1056,11 +1072,24 @@ void plantcontrol() Serial.flush(); } - bool isLowLight = (mSolarVoltage < SOLAR_CHARGE_MIN_VOLTAGE); - bool hasWater = true; //FIXME remaining > waterLevelMin.get(); - //FIXME no water warning message +#if defined(TIMED_LIGHT_PIN) + bool isLowLight = mSolarVoltage <= 9; + bool shouldLight = determineTimedLightState(isLowLight); + if (shouldLight) + { + ulp_pwm_set_level(timedLightPowerLevel.get()); + } + else + { + ulp_pwm_set_level(0); + } + +#endif // TIMED_LIGHT_PIN + + bool hasWater = true; // FIXME remaining > waterLevelMin.get(); + // FIXME no water warning message pumpToRun = determineNextPump(isLowLight); - //early aborts + // early aborts if (pumpToRun != -1) { if (hasWater) @@ -1083,12 +1112,6 @@ void plantcontrol() { espDeepSleep(); } - -#if defined(TIMED_LIGHT_PIN) - bool shouldLight = determineTimedLightState(isLowLight); - timedLightOn = shouldLight; - digitalWrite(TIMED_LIGHT_PIN, shouldLight); -#endif // TIMED_LIGHT_PIN } /** @}*/ @@ -1100,7 +1123,7 @@ bool determineTimedLightState(bool lowLight) long hoursStart = timedLightStart.get(); long hoursEnd = timedLightEnd.get(); - //ntp missing + // ntp missing if (getCurrentTime() < 10000) { timedLightNode.setProperty("state").send(String("Off, missing ntp")); @@ -1113,13 +1136,19 @@ bool determineTimedLightState(bool lowLight) return false; } - if (((hoursStart > hoursEnd) && - (getCurrentHour() >= hoursStart || getCurrentHour() <= hoursEnd)) || - /* Handle e.g. start = 8, end = 21 */ + int curHour = getCurrentHour(); + + bool condition1 = ((hoursStart > hoursEnd) && + (curHour >= hoursStart || curHour <= hoursEnd)); + bool condition2 = /* Handle e.g. start = 8, end = 21 */ + ((hoursStart < hoursEnd) && - (getCurrentHour() >= hoursStart && getCurrentHour() <= hoursEnd))) + (curHour >= hoursStart && curHour <= hoursEnd)); + timedLightNode.setProperty("debug").send(String(curHour) + " " + String(hoursStart) + " " + String(hoursEnd) + " " + String(condition1) + " " + String(condition2)); + if (condition1 || condition2) { - if (!timedLightLowVoltageTriggered && battery.getVoltage(BATTSENSOR_INDEX_BATTERY) >= timedLightVoltageCutoff.get()) + bool voltageOk = !timedLightLowVoltageTriggered && battery.getVoltage(BATTSENSOR_INDEX_BATTERY) >= timedLightVoltageCutoff.get(); + if (voltageOk || equalish(timedLightVoltageCutoff.get(), -1)) { timedLightNode.setProperty("state").send(String("On")); return true; diff --git a/esp32test/Esp32DeepSleepTest/include/DS2438.h b/esp32test/Esp32DeepSleepTest/include/DS2438.h deleted file mode 100644 index 0809015..0000000 --- a/esp32test/Esp32DeepSleepTest/include/DS2438.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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/esp32test/Esp32DeepSleepTest/include/ulp-pwm.h b/esp32test/Esp32DeepSleepTest/include/ulp-pwm.h new file mode 100644 index 0000000..15f5d35 --- /dev/null +++ b/esp32test/Esp32DeepSleepTest/include/ulp-pwm.h @@ -0,0 +1,187 @@ +#ifndef ULP_PWM_h +#define ILP_PWM_h + +#include +#include "driver/rtc_io.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/rtc.h" +#include "esp32/ulp.h" + +#define LBL_START 1 +#define LBL_DELAY_ON 2 +#define LBL_DELAY_OFF 3 +#define LBL_SKIP_ON 4 +#define LBL_SKIP_OFF 5 +#define REGISTER_DELAY_LOOP_COUNTER R0 +#define REGISTER_TICKS_ON R1 +#define REGISTER_TICKS_OFF R2 +#define TOTAL_TICKS_DELAY 255 +#define PIN GPIO_NUM_12 + +//support 20 vars +const size_t ulp_var_offset = CONFIG_ULP_COPROC_RESERVE_MEM - 20; +//use the first for dimming +const size_t ulp_dimm_offset = ulp_var_offset + 1; +const size_t ulp_alive_offset = ulp_var_offset + 2; + +//see https://github.com/perseus086/ESP32-notes +const uint32_t rtc_bit[40] = { + 25, //gpio0 + 0, //gpio1 + 26, //gpio2 + 0, //gpio3 + 24, //gpio4 + 0, //gpio5 + 0, //gpio6 + 0, //gpio7 + 0, //gpio8 + 0, //gpio9 + 0, //gpio10 + 0, //gpio11 + 29, //gpio12 + 28, //gpio13 + 30, //gpio14 + 27, //gpio15 + 0, //gpio16 + 31, //gpio17 + 0, //gpio18 + 0, //gpio19 + 0, //gpio20 + 0, //gpio21 + 0, //gpio22 + 0, //gpio23 + 0, //gpio24 + 20, //gpio25 + 21, //gpio26 + 0, //gpio27 + 0, //gpio28 + 0, //gpio29 + 0, //gpio30 + 0, //gpio31 + 23, //gpio32 + 22, //gpio33 + 18, //gpio34 + 19, //gpio35 + 14, //gpio36 + 15, //gpio37 + 16, //gpio38 + 17 //gpio39 +}; + +static inline void ulp_data_write(size_t offset, uint16_t value) +{ + if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset) + { + Serial.print("Invalid ULP offset detected, refusing write!"); + Serial.print(offset); + Serial.print("-"); + Serial.print(ulp_var_offset); + Serial.print("-"); + Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM); + return; + } + else + { + RTC_SLOW_MEM[offset] = value; + } +} + +static inline uint16_t ulp_data_read(size_t offset) +{ + if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset) + { + Serial.print("Invalid ULP offset detected"); + Serial.print(offset); + Serial.print("-"); + Serial.print(ulp_var_offset); + Serial.print("-"); + Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM); + } + return RTC_SLOW_MEM[offset] & 0xffff; +} + +static inline uint32_t rtc_io_number_get(gpio_num_t gpio_num) +{ + assert(rtc_gpio_is_valid_gpio(gpio_num) && "Invalid GPIO for RTC"); + uint32_t bit = rtc_bit[gpio_num]; + Serial.print("Resolved GPIO "); + Serial.print(gpio_num); + Serial.print(" to rtc bit "); + Serial.println(bit); + return bit; +} + +void ulp_pwm_start(void) +{ + rtc_gpio_init(PIN); + rtc_gpio_set_direction(PIN, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(PIN, 0); + const uint32_t rtc_gpio = rtc_io_number_get(PIN); + + // Define ULP program + const ulp_insn_t ulp_prog[] = { + M_LABEL(LBL_START), + + I_MOVI(REGISTER_DELAY_LOOP_COUNTER, 1), + I_MOVI(REGISTER_TICKS_ON, 0), + I_ST(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON, ulp_alive_offset), //store 1 with 0 offset into alive + + I_LD(REGISTER_TICKS_ON, REGISTER_TICKS_ON, ulp_dimm_offset), //REGISTER_TICKS_ON = RTC_DATA[0+ulp_dimm_offset] + //in total there is always 255 delay loop iterations, but in different duty cycle + I_MOVI(REGISTER_TICKS_OFF, TOTAL_TICKS_DELAY), + I_SUBR(REGISTER_TICKS_OFF, REGISTER_TICKS_OFF, REGISTER_TICKS_ON), + + //on phase + I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON), + M_BL(LBL_SKIP_ON, 1), //if never on, skip on phase + I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, HIGH), // on + M_LABEL(LBL_DELAY_ON), + I_DELAY(1), //wait 1 clock + I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER-- + M_BGE(LBL_DELAY_ON, 1), //if time left, goto start of on loop + M_LABEL(LBL_SKIP_ON), + + //off phase + I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_OFF), + + M_BL(LBL_SKIP_OFF, 1), //if never off, skip on phase + I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, LOW), // on + M_LABEL(3), + I_DELAY(1), //wait 1 clock + I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER-- + M_BGE(3, 1), //if time left, goto start of on loop + M_LABEL(LBL_SKIP_OFF), + + M_BX(LBL_START), + }; + // Run ULP program + size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t); + assert(size < ulp_var_offset && "ULP_DATA_OFFSET needs to be greater or equal to the program size"); + esp_err_t error = ulp_process_macros_and_load(0, ulp_prog, &size); + Serial.print("ULP bootstrap status "); + Serial.println(error); + + //allow glitchless start + ulp_data_write(ulp_alive_offset, 0); + + error = ulp_run(0); + Serial.print("ULP start status "); + Serial.println(error); +} + +static inline void ulp_pwm_set_level(uint8_t level) +{ + ulp_data_write(ulp_dimm_offset, level); +} + +static inline void ulp_pwm_init() +{ + ulp_data_write(ulp_alive_offset, 0); + delay(10); + if (ulp_data_read(ulp_alive_offset) == 0) + { + ulp_pwm_start(); + } +} + +#endif \ No newline at end of file diff --git a/esp32test/Esp32DeepSleepTest/src/DS2438.cpp b/esp32test/Esp32DeepSleepTest/src/DS2438.cpp deleted file mode 100644 index 791b76d..0000000 --- a/esp32test/Esp32DeepSleepTest/src/DS2438.cpp +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 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/esp32test/Esp32DeepSleepTest/src/main.cpp b/esp32test/Esp32DeepSleepTest/src/main.cpp index 9d7d074..76edff7 100644 --- a/esp32test/Esp32DeepSleepTest/src/main.cpp +++ b/esp32test/Esp32DeepSleepTest/src/main.cpp @@ -13,7 +13,10 @@ int16_t pulses2 = 0; int plantId = 0; void setup() { - +RTC_SLOW_ATTR uint8_t tick = 0; +RTC_SLOW_ATTR bool dir = true; +void setup() +{ Serial.begin(115200); pinMode(OUTPUT_SENSOR, OUTPUT); @@ -55,4 +58,4 @@ void loop() { pcnt_counter_clear(PCNT_UNIT_0); Serial.println(pulses*2); -} \ No newline at end of file +}