1
0
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:
Brandon Blake 2025-06-15 08:35:46 +05:30 committed by GitHub
commit 8750172a12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 784 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1 @@
!CMakeLists.txt

View 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")

View File

@ -0,0 +1,2 @@
CODEOWNERS = ["@brisk0"]
DEPENDENCIES = ["esp32"]

View 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

View File

@ -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

View File

@ -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

View 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

View 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

View 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

View File

@ -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"

View 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

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -0,0 +1 @@
<<: !include common.yaml