mirror of
https://github.com/esphome/esphome.git
synced 2025-06-15 23:06:58 +02:00
Merge 4bfee0133eb46981e711e21e9a4c2aed1bccf8d9 into a1e4143600a86ff6fd60633b0e8c43beae7e5d21
This commit is contained in:
commit
8750172a12
@ -348,6 +348,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_counter_ulp/* @brisk0
|
||||
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/pylontech/* @functionpointer
|
||||
|
1
esphome/components/pulse_counter_ulp/.gitignore
vendored
Normal file
1
esphome/components/pulse_counter_ulp/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
!CMakeLists.txt
|
27
esphome/components/pulse_counter_ulp/CMakeLists.txt
Normal file
27
esphome/components/pulse_counter_ulp/CMakeLists.txt
Normal file
@ -0,0 +1,27 @@
|
||||
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
|
||||
|
||||
idf_component_register(SRCS ${app_sources}
|
||||
INCLUDE_DIRS ""
|
||||
REQUIRES driver soc nvs_flash ulp)
|
||||
|
||||
##
|
||||
## ULP support additions to component CMakeLists.txt.
|
||||
##
|
||||
## 1. The ULP app name must be unique (if multiple components use ULP).
|
||||
set(ulp_app_name ulp_main)
|
||||
##
|
||||
## 2. Specify all assembly source files.
|
||||
## Files should be placed into a separate directory (in this case, ulp/),
|
||||
## which should not be added to COMPONENT_SRCS.
|
||||
##TODO
|
||||
set(ulp_s_sources "../ulp/pulse_cnt.S" "../ulp/wake_up.S")
|
||||
##
|
||||
## 3. List all the component source files which include automatically
|
||||
## generated ULP export file, ${ulp_app_name}.h:
|
||||
set(ulp_exp_dep_srcs "components/pulse_counter/pulse_counter_sensor.cpp")
|
||||
##
|
||||
## 4. Call function to build ULP binary and embed in project using the argument
|
||||
## values above.
|
||||
ulp_embed_binary(${ulp_app_name} "${ulp_s_sources}" "${ulp_exp_dep_srcs}")
|
||||
#
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
2
esphome/components/pulse_counter_ulp/__init__.py
Normal file
2
esphome/components/pulse_counter_ulp/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
CODEOWNERS = ["@brisk0"]
|
||||
DEPENDENCIES = ["esp32"]
|
24
esphome/components/pulse_counter_ulp/automation.h
Normal file
24
esphome/components/pulse_counter_ulp/automation.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/pulse_counter_ulp/pulse_counter_ulp_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace pulse_counter_ulp {
|
||||
|
||||
template<typename... Ts> class SetTotalPulsesAction : public Action<Ts...> {
|
||||
public:
|
||||
SetTotalPulsesAction(PulseCounterUlpSensor *pulse_counter) : pulse_counter_(pulse_counter) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint32_t, total_pulses)
|
||||
|
||||
void play(Ts... x) override { this->pulse_counter_->set_total_pulses(this->total_pulses_.value(x...)); }
|
||||
|
||||
protected:
|
||||
PulseCounterUlpSensor *pulse_counter_;
|
||||
};
|
||||
|
||||
} // namespace pulse_counter_ulp
|
||||
} // namespace esphome
|
@ -0,0 +1,205 @@
|
||||
#include "pulse_counter_ulp_sensor.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "esp32/ulp.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "soc/rtc_periph.h"
|
||||
#include "ulp_main.h"
|
||||
#include <esp_sleep.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace pulse_counter_ulp {
|
||||
|
||||
static const char *const TAG = "pulse_counter_ulp";
|
||||
|
||||
namespace {
|
||||
const char *to_string(CountMode count_mode) {
|
||||
switch (count_mode) {
|
||||
case CountMode::DISABLE:
|
||||
return "disable";
|
||||
case CountMode::INCREMENT:
|
||||
return "increment";
|
||||
case CountMode::DECREMENT:
|
||||
return "decrement";
|
||||
}
|
||||
return "UNKNOWN MODE";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// These identifiers are defined by ESP-IDF and we don't have control over them
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
|
||||
extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
std::unique_ptr<UlpProgram> UlpProgram::start(const Config &config) {
|
||||
esp_err_t error = ulp_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Loading ULP binary failed: %s", esp_err_to_name(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* GPIO used for pulse counting. */
|
||||
gpio_num_t gpio_num = static_cast<gpio_num_t>(config.pin_->get_pin());
|
||||
int rtcio_num = rtc_io_number_get(gpio_num);
|
||||
if (!rtc_gpio_is_valid_gpio(gpio_num)) {
|
||||
ESP_LOGE(TAG, "GPIO used for pulse counting must be an RTC IO");
|
||||
}
|
||||
|
||||
/* Initialize variables in ULP program.
|
||||
* Note that the ULP reads only the lower 16 bits of these variables. */
|
||||
ulp_rising_edge_en = config.rising_edge_mode_ != CountMode::DISABLE;
|
||||
ulp_rising_edge_count = 0;
|
||||
ulp_falling_edge_en = config.falling_edge_mode_ != CountMode::DISABLE;
|
||||
ulp_falling_edge_count = 0;
|
||||
ulp_run_count = 0;
|
||||
ulp_debounce_counter = 3;
|
||||
ulp_edges_wakeup = config.edges_wakeup_ > 0 ? config.edges_wakeup_ : std::numeric_limits<uint16_t>::max();
|
||||
ulp_debounce_max_count = config.debounce_;
|
||||
ulp_next_edge = static_cast<uint16_t>(!config.pin_->digital_read());
|
||||
ulp_io_number = rtcio_num; /* map from GPIO# to RTC_IO# */
|
||||
|
||||
/* If pin is inverted, we need to swap activating detection of rising / falling edges */
|
||||
if (config.pin_->is_inverted()) {
|
||||
std::swap(ulp_rising_edge_en, ulp_falling_edge_en);
|
||||
}
|
||||
|
||||
/* Initialize selected GPIO as RTC IO, enable input */
|
||||
rtc_gpio_init(gpio_num);
|
||||
rtc_gpio_set_direction(gpio_num, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_hold_en(gpio_num);
|
||||
|
||||
/* Minimum pulse width is sleep_duration_ * (ulp_debounce_counter + 1). */
|
||||
ulp_set_wakeup_period(0, config.sleep_duration_ / std::chrono::microseconds{1});
|
||||
|
||||
/* Start the program */
|
||||
error = ulp_run(&ulp_entry - RTC_SLOW_MEM);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Starting ULP program failed: %s", esp_err_to_name(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return make_unique<UlpProgram>();
|
||||
}
|
||||
|
||||
UlpProgram::State UlpProgram::pop_state() {
|
||||
State state = UlpProgram::peek_state();
|
||||
ulp_rising_edge_count = 0;
|
||||
ulp_falling_edge_count = 0;
|
||||
ulp_run_count = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
UlpProgram::State UlpProgram::peek_state() const {
|
||||
auto rising_edge_count = static_cast<uint16_t>(ulp_rising_edge_count);
|
||||
auto falling_edge_count = static_cast<uint16_t>(ulp_falling_edge_count);
|
||||
auto run_count = static_cast<uint16_t>(ulp_run_count);
|
||||
return {.rising_edge_count_ = rising_edge_count, .falling_edge_count_ = falling_edge_count, .run_count_ = run_count};
|
||||
}
|
||||
|
||||
RTC_DATA_ATTR int PulseCounterUlpSensor::pulse_count_persist = 0;
|
||||
RTC_DATA_ATTR int PulseCounterUlpSensor::mean_exec_time = 0;
|
||||
|
||||
float PulseCounterUlpSensor::get_setup_priority() const {
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
return setup_priority::DATA;
|
||||
} else {
|
||||
/* In case of deep sleep wakup, we need to delay sending new values until we have a connection */
|
||||
return setup_priority::AFTER_CONNECTION;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseCounterUlpSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());
|
||||
|
||||
this->config_.pin_->setup();
|
||||
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
ESP_LOGD(TAG, "Did not wake up from sleep, assuming restart or first boot and setting up ULP program");
|
||||
this->storage_ = UlpProgram::start(this->config_);
|
||||
mean_exec_time = static_cast<int>(this->config_.sleep_duration_ / microseconds{1});
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Woke up from sleep, skipping set-up of ULP program");
|
||||
this->storage_ = make_unique<UlpProgram>();
|
||||
UlpProgram::State state = this->storage_->peek_state();
|
||||
this->last_time_ = clock::now() - state.run_count_ * (mean_exec_time * microseconds{1});
|
||||
}
|
||||
|
||||
/* Enable wakeup from ulp */
|
||||
if (this->config_.edges_wakeup_ > 0) {
|
||||
ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
|
||||
}
|
||||
|
||||
if (!this->storage_) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseCounterUlpSensor::set_total_pulses(uint32_t pulses) {
|
||||
if (this->total_sensor_ != nullptr) {
|
||||
ESP_LOGD(TAG, "'%s': Reset pulses of total count to pulses: %d", this->get_name().c_str(), pulses);
|
||||
|
||||
pulse_count_persist = std::max(static_cast<int>(pulses), 0);
|
||||
this->total_sensor_->publish_state(pulses);
|
||||
}
|
||||
}
|
||||
|
||||
void PulseCounterUlpSensor::dump_config() {
|
||||
LOG_SENSOR("", "Pulse Counter", this);
|
||||
LOG_PIN(" Pin: ", this->config_.pin_);
|
||||
ESP_LOGCONFIG(TAG, " Rising Edge: %s", to_string(this->config_.rising_edge_mode_));
|
||||
ESP_LOGCONFIG(TAG, " Falling Edge: %s", to_string(this->config_.falling_edge_mode_));
|
||||
ESP_LOGCONFIG(TAG, " Sleep Duration: %" PRIu32 " µs", this->config_.sleep_duration_ / microseconds{1});
|
||||
ESP_LOGCONFIG(TAG, " Debounce: %" PRIu16, this->config_.debounce_);
|
||||
ESP_LOGCONFIG(TAG, " Edges Wakeup: %" PRIu16, this->config_.edges_wakeup_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void PulseCounterUlpSensor::update() {
|
||||
// Can't update if ulp program hasn't been initialised
|
||||
if (!this->storage_) {
|
||||
return;
|
||||
}
|
||||
UlpProgram::State raw = this->storage_->pop_state();
|
||||
// Since ULP can't use the GPIOPin abstraction, pin inversion needs to be
|
||||
// manually implemented.
|
||||
int32_t pulse_count_ulp;
|
||||
if (this->config_.pin_->is_inverted()) {
|
||||
pulse_count_ulp = static_cast<int32_t>(config_.rising_edge_mode_) * raw.falling_edge_count_ +
|
||||
static_cast<int32_t>(config_.falling_edge_mode_) * raw.rising_edge_count_;
|
||||
} else {
|
||||
pulse_count_ulp = static_cast<int32_t>(config_.rising_edge_mode_) * raw.rising_edge_count_ +
|
||||
static_cast<int32_t>(config_.falling_edge_mode_) * raw.falling_edge_count_;
|
||||
}
|
||||
|
||||
clock::time_point now = clock::now();
|
||||
clock::duration interval = now - this->last_time_;
|
||||
if (interval != clock::duration::zero()) {
|
||||
mean_exec_time =
|
||||
static_cast<int>((std::chrono::duration_cast<microseconds>(interval / raw.run_count_)) / microseconds{1});
|
||||
float value = std::chrono::minutes{1} * static_cast<float>(pulse_count_ulp) / interval; // pulses per minute
|
||||
ESP_LOGD(TAG, "'%s': Retrieved counter: %d pulses at %0.2f pulses/min", this->get_name().c_str(), pulse_count_ulp,
|
||||
value);
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
if (this->total_sensor_ != nullptr) {
|
||||
// Get new overall value
|
||||
const int32_t pulse_count = pulse_count_persist + pulse_count_ulp;
|
||||
|
||||
// Update persistent counter
|
||||
pulse_count_persist = (int) pulse_count;
|
||||
|
||||
// publish new state
|
||||
ESP_LOGD(TAG, "'%s': Pulses from ULP: %d; Overall pulses: %d", this->get_name().c_str(), pulse_count_ulp,
|
||||
pulse_count);
|
||||
|
||||
this->total_sensor_->publish_state(pulse_count);
|
||||
}
|
||||
|
||||
this->last_time_ = now;
|
||||
}
|
||||
|
||||
} // namespace pulse_counter_ulp
|
||||
} // namespace esphome
|
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome {
|
||||
namespace pulse_counter_ulp {
|
||||
|
||||
enum class CountMode { DISABLE = 0, INCREMENT = 1, DECREMENT = -1 };
|
||||
|
||||
using clock = std::chrono::steady_clock;
|
||||
using microseconds = std::chrono::duration<uint32_t, std::micro>;
|
||||
|
||||
class UlpProgram {
|
||||
public:
|
||||
struct Config {
|
||||
InternalGPIOPin *pin_;
|
||||
CountMode rising_edge_mode_;
|
||||
CountMode falling_edge_mode_;
|
||||
microseconds sleep_duration_;
|
||||
uint16_t debounce_;
|
||||
uint16_t edges_wakeup_;
|
||||
};
|
||||
struct State {
|
||||
uint16_t rising_edge_count_;
|
||||
uint16_t falling_edge_count_;
|
||||
uint16_t run_count_;
|
||||
};
|
||||
State pop_state();
|
||||
State peek_state() const;
|
||||
|
||||
static std::unique_ptr<UlpProgram> start(const Config &config);
|
||||
};
|
||||
|
||||
class PulseCounterUlpSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
explicit PulseCounterUlpSensor() {}
|
||||
|
||||
void set_pin(InternalGPIOPin *pin) { this->config_.pin_ = pin; }
|
||||
void set_rising_edge_mode(CountMode mode) { this->config_.rising_edge_mode_ = mode; }
|
||||
void set_falling_edge_mode(CountMode mode) { this->config_.falling_edge_mode_ = mode; }
|
||||
void set_sleep_duration(uint32_t duration_us) { this->config_.sleep_duration_ = duration_us * microseconds{1}; }
|
||||
void set_debounce(uint16_t debounce) { this->config_.debounce_ = debounce; }
|
||||
void set_edges_wakeup(uint16_t edges_wakeup) { this->config_.edges_wakeup_ = edges_wakeup; }
|
||||
void set_total_sensor(sensor::Sensor *total_sensor) { total_sensor_ = total_sensor; }
|
||||
|
||||
void set_total_pulses(uint32_t pulses);
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
// Messages sent before WiFi is established are lost - including the vital
|
||||
// on-wake-up message
|
||||
float get_setup_priority() const override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *total_sensor_{nullptr};
|
||||
UlpProgram::Config config_{};
|
||||
std::unique_ptr<UlpProgram> storage_{};
|
||||
clock::time_point last_time_{};
|
||||
|
||||
static int pulse_count_persist;
|
||||
static int mean_exec_time;
|
||||
};
|
||||
|
||||
} // namespace pulse_counter_ulp
|
||||
} // namespace esphome
|
151
esphome/components/pulse_counter_ulp/sensor.py
Normal file
151
esphome/components/pulse_counter_ulp/sensor.py
Normal file
@ -0,0 +1,151 @@
|
||||
import os
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_COUNT_MODE,
|
||||
CONF_DEBOUNCE,
|
||||
CONF_EDGES_WAKEUP,
|
||||
CONF_FALLING_EDGE,
|
||||
CONF_ID,
|
||||
CONF_PIN,
|
||||
CONF_RISING_EDGE,
|
||||
CONF_SLEEP_DURATION,
|
||||
CONF_TOTAL,
|
||||
CONF_VALUE,
|
||||
ICON_PULSE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_PULSES_PER_MINUTE,
|
||||
UNIT_PULSES,
|
||||
)
|
||||
|
||||
pulse_counter_ulp_ns = cg.esphome_ns.namespace("pulse_counter_ulp")
|
||||
CountMode = pulse_counter_ulp_ns.enum("CountMode", is_class=True)
|
||||
COUNT_MODES = {
|
||||
"DISABLE": CountMode.DISABLE,
|
||||
"INCREMENT": CountMode.INCREMENT,
|
||||
"DECREMENT": CountMode.DECREMENT,
|
||||
}
|
||||
|
||||
COUNT_MODE_SCHEMA = cv.enum(COUNT_MODES, upper=True)
|
||||
|
||||
PulseCounterUlpSensor = pulse_counter_ulp_ns.class_(
|
||||
"PulseCounterUlpSensor", sensor.Sensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
SetTotalPulsesAction = pulse_counter_ulp_ns.class_(
|
||||
"SetTotalPulsesAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
def validate_pulse_counter_pin(value):
|
||||
value = pins.internal_gpio_input_pin_schema(value)
|
||||
return value
|
||||
|
||||
|
||||
def validate_count_mode(value):
|
||||
rising_edge = value[CONF_RISING_EDGE]
|
||||
falling_edge = value[CONF_FALLING_EDGE]
|
||||
if rising_edge == "DISABLE" and falling_edge == "DISABLE":
|
||||
raise cv.Invalid(
|
||||
"Can't set both count modes to DISABLE! This means no counting occurs at "
|
||||
"all!"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
PulseCounterUlpSensor,
|
||||
unit_of_measurement=UNIT_PULSES_PER_MINUTE,
|
||||
icon=ICON_PULSE,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_PIN): validate_pulse_counter_pin,
|
||||
cv.Optional(
|
||||
CONF_COUNT_MODE,
|
||||
default={
|
||||
CONF_RISING_EDGE: "INCREMENT",
|
||||
CONF_FALLING_EDGE: "DISABLE",
|
||||
},
|
||||
): cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_RISING_EDGE, "INCREMENT"): COUNT_MODE_SCHEMA,
|
||||
cv.Optional(CONF_FALLING_EDGE, "DISABLE"): COUNT_MODE_SCHEMA,
|
||||
}
|
||||
),
|
||||
validate_count_mode,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_SLEEP_DURATION, default="20000us"
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Optional(CONF_DEBOUNCE, default=3): cv.positive_int,
|
||||
cv.Optional(CONF_EDGES_WAKEUP, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_TOTAL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PULSES,
|
||||
icon=ICON_PULSE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
cv.only_on_esp32,
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
esp32.add_extra_build_file(
|
||||
"src/CMakeLists.txt",
|
||||
os.path.join(os.path.dirname(__file__), "CMakeLists.txt"),
|
||||
)
|
||||
esp32.add_extra_build_file(
|
||||
"ulp/pulse_cnt.S",
|
||||
os.path.join(os.path.dirname(__file__), "ulp/pulse_cnt.S"),
|
||||
)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ULP_COPROC_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ULP_COPROC_TYPE_FSM", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ULP_COPROC_RESERVE_MEM", 1024)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP32S3_ULP_COPROC_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM", 1024)
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
count = config[CONF_COUNT_MODE]
|
||||
cg.add(var.set_rising_edge_mode(count[CONF_RISING_EDGE]))
|
||||
cg.add(var.set_falling_edge_mode(count[CONF_FALLING_EDGE]))
|
||||
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
|
||||
cg.add(var.set_edges_wakeup(config[CONF_EDGES_WAKEUP]))
|
||||
cg.add(var.set_debounce(config[CONF_DEBOUNCE]))
|
||||
|
||||
if CONF_TOTAL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL])
|
||||
cg.add(var.set_total_sensor(sens))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"pulse_counter_ulp.set_total_pulses",
|
||||
SetTotalPulsesAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(PulseCounterUlpSensor),
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.uint32_t),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def set_total_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, int)
|
||||
cg.add(var.set_total_pulses(template_))
|
||||
return var
|
254
esphome/components/pulse_counter_ulp/ulp/pulse_cnt.S
Normal file
254
esphome/components/pulse_counter_ulp/ulp/pulse_cnt.S
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/* ULP Example: pulse counting
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
This file contains assembly code which runs on the ULP.
|
||||
|
||||
ULP wakes up to run this code at a certain period, determined by the values
|
||||
in SENS_ULP_CP_SLEEP_CYCx_REG registers. On each wake up, the program checks
|
||||
the input on GPIO0. If the value is different from the previous one, the
|
||||
program "debounces" the input: on the next debounce_max_count wake ups,
|
||||
it expects to see the same value of input.
|
||||
If this condition holds true, the program increments edge_count and starts
|
||||
waiting for input signal polarity to change again.
|
||||
*/
|
||||
|
||||
/* ULP assembly files are passed through C preprocessor first, so include directives
|
||||
and C macros may be used in these files
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "soc/soc_ulp.h"
|
||||
#include "soc/sens_reg.h"
|
||||
|
||||
/* Define variables, which go into .bss section (zero-initialized data) */
|
||||
.bss
|
||||
/* Next input signal edge expected: 0 (negative) or 1 (positive) */
|
||||
.global next_edge
|
||||
next_edge:
|
||||
.long 0
|
||||
|
||||
/* Counter started when signal value changes.
|
||||
Edge is "debounced" when the counter reaches zero. */
|
||||
.global debounce_counter
|
||||
debounce_counter:
|
||||
.long 0
|
||||
|
||||
/* Value to which debounce_counter gets reset.
|
||||
Set by the main program. */
|
||||
.global debounce_max_count
|
||||
debounce_max_count:
|
||||
.long 0
|
||||
|
||||
/* Flag to be one, for detecting rising edges */
|
||||
.global rising_edge_en
|
||||
rising_edge_en:
|
||||
.long 0
|
||||
|
||||
/* Number of rising signal edges acquired since last read */
|
||||
.global rising_edge_count
|
||||
rising_edge_count:
|
||||
.long 0
|
||||
|
||||
/* Flag to be one, for detecting falling edges */
|
||||
.global falling_edge_en
|
||||
falling_edge_en:
|
||||
.long 0
|
||||
|
||||
/* Number of falling signal edges acquired since last read */
|
||||
.global falling_edge_count
|
||||
falling_edge_count:
|
||||
.long 0
|
||||
|
||||
/* Number of times program run since last read */
|
||||
.global run_count
|
||||
run_count:
|
||||
.long 0
|
||||
|
||||
/* Total number of signal edges acquired */
|
||||
.global edge_count_total
|
||||
edge_count_total:
|
||||
.long 0
|
||||
|
||||
/* Number of edges after which main SoC shall be woken up */
|
||||
.global edges_wakeup
|
||||
edges_wakeup:
|
||||
.long 0
|
||||
|
||||
/* RTC IO number used to sample the input signal.
|
||||
Set by main program. */
|
||||
.global io_number
|
||||
io_number:
|
||||
.long 0
|
||||
|
||||
/* Code goes into .text section */
|
||||
.text
|
||||
.global entry
|
||||
entry:
|
||||
/* Increment run_count */
|
||||
move r3, run_count
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 1
|
||||
st r2, r3, 0
|
||||
|
||||
/* Load io_number */
|
||||
move r3, io_number
|
||||
ld r3, r3, 0
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
/* ESP32S2 powers down RTC periph when entering deep sleep and thus by association SENS_SAR_IO_MUX_CONF_REG */
|
||||
WRITE_RTC_FIELD(SENS_SAR_IO_MUX_CONF_REG, SENS_IOMUX_CLK_GATE_EN, 1)
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
/* ESP32S3 powers down RTC periph when entering deep sleep and thus by association SENS_SAR_PERI_CLK_GATE_CONF_REG */
|
||||
WRITE_RTC_FIELD(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_IOMUX_CLK_EN, 1);
|
||||
#endif
|
||||
|
||||
/* Lower 16 IOs and higher need to be handled separately,
|
||||
* because r0-r3 registers are 16 bit wide.
|
||||
* Check which IO this is.
|
||||
*/
|
||||
move r0, r3
|
||||
jumpr read_io_high, 16, ge
|
||||
|
||||
/* Read the value of lower 16 RTC IOs into R0 */
|
||||
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S, 16)
|
||||
rsh r0, r0, r3
|
||||
jump read_done
|
||||
|
||||
/* Read the value of RTC IOs 16-17, into R0 */
|
||||
read_io_high:
|
||||
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 16, 2)
|
||||
sub r3, r3, 16
|
||||
rsh r0, r0, r3
|
||||
|
||||
read_done:
|
||||
and r0, r0, 1
|
||||
/* State of input changed? */
|
||||
move r3, next_edge
|
||||
ld r3, r3, 0
|
||||
add r3, r0, r3
|
||||
and r3, r3, 1
|
||||
jump changed, eq
|
||||
/* Not changed */
|
||||
/* Reset debounce_counter to debounce_max_count */
|
||||
move r3, debounce_max_count
|
||||
move r2, debounce_counter
|
||||
ld r3, r3, 0
|
||||
st r3, r2, 0
|
||||
/* End program */
|
||||
halt
|
||||
|
||||
.global changed
|
||||
changed:
|
||||
/* Input state changed */
|
||||
/* Has debounce_counter reached zero? */
|
||||
move r3, debounce_counter
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 0 /* dummy ADD to use "jump if ALU result is zero" */
|
||||
jump edge_detected, eq
|
||||
/* Not yet. Decrement debounce_counter */
|
||||
sub r2, r2, 1
|
||||
st r2, r3, 0
|
||||
/* End program */
|
||||
halt
|
||||
|
||||
.global edge_detected
|
||||
edge_detected:
|
||||
/* Reset debounce_counter to debounce_max_count */
|
||||
move r3, debounce_max_count
|
||||
move r2, debounce_counter
|
||||
ld r3, r3, 0
|
||||
st r3, r2, 0
|
||||
/* Flip next_edge */
|
||||
move r3, next_edge
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 1
|
||||
and r2, r2, 1
|
||||
st r2, r3, 0
|
||||
/* Jump to increment of current edge counter */
|
||||
add r2, r2, 0 /* dummy ADD to use "jump if ALU result is zero" */
|
||||
jump rising, eq
|
||||
|
||||
.global falling
|
||||
falling:
|
||||
/* Check if detecting falling edges is enabled */
|
||||
move r3, falling_edge_en
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 0 /* dummy ADD to use "jump if ALU result is zero" */
|
||||
jump wake_up_skip, eq
|
||||
|
||||
/* Increment falling_edge_count */
|
||||
move r3, falling_edge_count
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 1
|
||||
st r2, r3, 0
|
||||
jump wake_up
|
||||
|
||||
.global rising
|
||||
rising:
|
||||
/* Check if detecting rising edges is enabled */
|
||||
move r3, rising_edge_en
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 0 /* dummy ADD to use "jump if ALU result is zero" */
|
||||
jump wake_up_skip, eq
|
||||
|
||||
/* Increment rising_edge_count */
|
||||
move r3, rising_edge_count
|
||||
ld r2, r3, 0
|
||||
add r2, r2, 1
|
||||
st r2, r3, 0
|
||||
jump wake_up
|
||||
|
||||
.global wake_up
|
||||
wake_up:
|
||||
/* Check if the system can be woken up. Checking here to not increment
|
||||
edge wait counter while being awake. We want to wake the system after
|
||||
counting the user specified number while being asleep. */
|
||||
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
|
||||
and r0, r0, 1
|
||||
jump am_awake, eq
|
||||
|
||||
/* Increment edge_count_total */
|
||||
move r2, edge_count_total
|
||||
ld r1, r2, 0
|
||||
add r1, r1, 1
|
||||
st r1, r2, 0
|
||||
|
||||
/* compare with edges_wakeup; wake up if edge_count_total >= edges_wakeup */
|
||||
move r0, edges_wakeup
|
||||
ld r1, r0, 0
|
||||
move r0, edge_count_total
|
||||
ld r2, r0, 0
|
||||
sub r0, r1, r2
|
||||
jumpr wake_up_skip, 0, gt /* jump if edges_wakeup - edge_count_total > 0 */
|
||||
|
||||
.global am_awake
|
||||
am_awake:
|
||||
|
||||
/* Reset total edge counter */
|
||||
move r2, 0
|
||||
move r1, edge_count_total
|
||||
st r2, r1, 0
|
||||
|
||||
/* Check if the system can be woken up. Checking again to prevent wake
|
||||
whilen not being asleep */
|
||||
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
|
||||
and r0, r0, 1
|
||||
jump wake_up_skip, eq
|
||||
|
||||
/* Wake up the SoC, end program */
|
||||
wake
|
||||
|
||||
.global wake_up_skip
|
||||
wake_up_skip:
|
||||
halt
|
33
esphome/components/pulse_counter_ulp/ulp_main.h
Normal file
33
esphome/components/pulse_counter_ulp/ulp_main.h
Normal file
@ -0,0 +1,33 @@
|
||||
// This file has been copied into place to satisfy clang-tidy, but it should be
|
||||
// created at compile-time by IDF. Please synchronise this file if changes are
|
||||
// made to pulse__cnt.S
|
||||
// NOLINTBEGIN
|
||||
|
||||
// Variable definitions for ESP32ULP
|
||||
// This file is generated automatically by esp32ulp_mapgen.py utility
|
||||
|
||||
#pragma once
|
||||
|
||||
// Required to pass linting
|
||||
namespace esphome {
|
||||
namespace pulse_counter_ulp {} // namespace pulse_counter_ulp
|
||||
} // namespace esphome
|
||||
|
||||
extern uint32_t ulp_changed;
|
||||
extern uint32_t ulp_debounce_counter;
|
||||
extern uint32_t ulp_debounce_max_count;
|
||||
extern uint32_t ulp_edge_count_total;
|
||||
extern uint32_t ulp_edge_detected;
|
||||
extern uint32_t ulp_edges_wakeup;
|
||||
extern uint32_t ulp_entry;
|
||||
extern uint32_t ulp_falling;
|
||||
extern uint32_t ulp_falling_edge_en;
|
||||
extern uint32_t ulp_falling_edge_count;
|
||||
extern uint32_t ulp_io_number;
|
||||
extern uint32_t ulp_next_edge;
|
||||
extern uint32_t ulp_rising;
|
||||
extern uint32_t ulp_rising_edge_en;
|
||||
extern uint32_t ulp_rising_edge_count;
|
||||
extern uint32_t ulp_run_count;
|
||||
|
||||
// NOLINTEND
|
@ -254,6 +254,7 @@ CONF_EAP = "eap"
|
||||
CONF_EC = "ec"
|
||||
CONF_ECHO_PIN = "echo_pin"
|
||||
CONF_ECO2 = "eco2"
|
||||
CONF_EDGES_WAKEUP = "edges_wakeup"
|
||||
CONF_EFFECT = "effect"
|
||||
CONF_EFFECTS = "effects"
|
||||
CONF_ELSE = "else"
|
||||
|
11
tests/components/pulse_counter_ulp/common.yaml
Normal file
11
tests/components/pulse_counter_ulp/common.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
sensor:
|
||||
- platform: pulse_counter_ulp
|
||||
name: Pulse Counter ULP
|
||||
pin: 4
|
||||
count_mode:
|
||||
rising_edge: INCREMENT
|
||||
falling_edge: DECREMENT
|
||||
sleep_duration: 13ms
|
||||
debounce: 4
|
||||
update_interval: 15s
|
||||
edges_wakeup: 4
|
1
tests/components/pulse_counter_ulp/test.esp32-idf.yaml
Normal file
1
tests/components/pulse_counter_ulp/test.esp32-idf.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
Loading…
x
Reference in New Issue
Block a user