mirror of
https://github.com/esphome/esphome.git
synced 2026-01-10 04:00:51 -07:00
[ultrasonic] Use interrupt-based measurement for reliability (#12617)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance,
|
||||
cv.Optional(
|
||||
|
||||
@@ -1,64 +1,96 @@
|
||||
#include "ultrasonic_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ultrasonic {
|
||||
namespace esphome::ultrasonic {
|
||||
|
||||
static const char *const TAG = "ultrasonic.sensor";
|
||||
|
||||
static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering)
|
||||
static constexpr uint32_t TIMEOUT_MARGIN_US = 1000; // Extra margin for sensor processing time
|
||||
|
||||
void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) {
|
||||
uint32_t now = micros();
|
||||
if (!arg->echo_start || (now - arg->echo_start_us) <= DEBOUNCE_US) {
|
||||
arg->echo_start_us = now;
|
||||
arg->echo_start = true;
|
||||
} else {
|
||||
arg->echo_end_us = now;
|
||||
arg->echo_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR UltrasonicSensorComponent::send_trigger_pulse_() {
|
||||
InterruptLock lock;
|
||||
this->store_.echo_start_us = 0;
|
||||
this->store_.echo_end_us = 0;
|
||||
this->store_.echo_start = false;
|
||||
this->store_.echo_end = false;
|
||||
this->trigger_pin_isr_.digital_write(true);
|
||||
delayMicroseconds(this->pulse_time_us_);
|
||||
this->trigger_pin_isr_.digital_write(false);
|
||||
this->measurement_pending_ = true;
|
||||
this->measurement_start_us_ = micros();
|
||||
}
|
||||
|
||||
void UltrasonicSensorComponent::setup() {
|
||||
this->trigger_pin_->setup();
|
||||
this->trigger_pin_->digital_write(false);
|
||||
this->trigger_pin_isr_ = this->trigger_pin_->to_isr();
|
||||
this->echo_pin_->setup();
|
||||
// isr is faster to access
|
||||
echo_isr_ = echo_pin_->to_isr();
|
||||
this->echo_pin_->attach_interrupt(UltrasonicSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
}
|
||||
|
||||
void UltrasonicSensorComponent::update() {
|
||||
this->trigger_pin_->digital_write(true);
|
||||
delayMicroseconds(this->pulse_time_us_);
|
||||
this->trigger_pin_->digital_write(false);
|
||||
if (this->measurement_pending_) {
|
||||
return;
|
||||
}
|
||||
this->send_trigger_pulse_();
|
||||
}
|
||||
|
||||
const uint32_t start = micros();
|
||||
while (micros() - start < timeout_us_ && echo_isr_.digital_read())
|
||||
;
|
||||
while (micros() - start < timeout_us_ && !echo_isr_.digital_read())
|
||||
;
|
||||
const uint32_t pulse_start = micros();
|
||||
while (micros() - start < timeout_us_ && echo_isr_.digital_read())
|
||||
;
|
||||
const uint32_t pulse_end = micros();
|
||||
void UltrasonicSensorComponent::loop() {
|
||||
if (!this->measurement_pending_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Echo took %" PRIu32 "µs", pulse_end - pulse_start);
|
||||
|
||||
if (pulse_end - start >= timeout_us_) {
|
||||
ESP_LOGD(TAG, "'%s' - Distance measurement timed out!", this->name_.c_str());
|
||||
this->publish_state(NAN);
|
||||
} else {
|
||||
float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start);
|
||||
if (this->store_.echo_end) {
|
||||
uint32_t pulse_duration = this->store_.echo_end_us - this->store_.echo_start_us;
|
||||
ESP_LOGV(TAG, "Echo took %" PRIu32 "us", pulse_duration);
|
||||
float result = UltrasonicSensorComponent::us_to_m(pulse_duration);
|
||||
ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result);
|
||||
this->publish_state(result);
|
||||
this->measurement_pending_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t elapsed = micros() - this->measurement_start_us_;
|
||||
if (elapsed >= this->timeout_us_ + TIMEOUT_MARGIN_US) {
|
||||
ESP_LOGD(TAG,
|
||||
"'%s' - Timeout after %" PRIu32 "us (measurement_start=%" PRIu32 ", echo_start=%" PRIu32
|
||||
", echo_end=%" PRIu32 ")",
|
||||
this->name_.c_str(), elapsed, this->measurement_start_us_, this->store_.echo_start_us,
|
||||
this->store_.echo_end_us);
|
||||
this->publish_state(NAN);
|
||||
this->measurement_pending_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UltrasonicSensorComponent::dump_config() {
|
||||
LOG_SENSOR("", "Ultrasonic Sensor", this);
|
||||
LOG_PIN(" Echo Pin: ", this->echo_pin_);
|
||||
LOG_PIN(" Trigger Pin: ", this->trigger_pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Pulse time: %" PRIu32 " µs\n"
|
||||
" Timeout: %" PRIu32 " µs",
|
||||
" Pulse time: %" PRIu32 " us\n"
|
||||
" Timeout: %" PRIu32 " us",
|
||||
this->pulse_time_us_, this->timeout_us_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
float UltrasonicSensorComponent::us_to_m(uint32_t us) {
|
||||
const float speed_sound_m_per_s = 343.0f;
|
||||
const float time_s = us / 1e6f;
|
||||
const float total_dist = time_s * speed_sound_m_per_s;
|
||||
return total_dist / 2.0f;
|
||||
}
|
||||
float UltrasonicSensorComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void UltrasonicSensorComponent::set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; }
|
||||
void UltrasonicSensorComponent::set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; }
|
||||
|
||||
} // namespace ultrasonic
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ultrasonic
|
||||
|
||||
@@ -6,41 +6,49 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace ultrasonic {
|
||||
namespace esphome::ultrasonic {
|
||||
|
||||
struct UltrasonicSensorStore {
|
||||
static void gpio_intr(UltrasonicSensorStore *arg);
|
||||
|
||||
volatile uint32_t echo_start_us{0};
|
||||
volatile uint32_t echo_end_us{0};
|
||||
volatile bool echo_start{false};
|
||||
volatile bool echo_end{false};
|
||||
};
|
||||
|
||||
class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void set_trigger_pin(GPIOPin *trigger_pin) { trigger_pin_ = trigger_pin; }
|
||||
void set_echo_pin(InternalGPIOPin *echo_pin) { echo_pin_ = echo_pin; }
|
||||
void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; }
|
||||
void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; }
|
||||
|
||||
/// Set the timeout for waiting for the echo in µs.
|
||||
void set_timeout_us(uint32_t timeout_us);
|
||||
void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Set up pins and register interval.
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
/// Set the time in µs the trigger pin should be enabled for in µs, defaults to 10µs (for HC-SR04)
|
||||
void set_pulse_time_us(uint32_t pulse_time_us);
|
||||
void set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; }
|
||||
|
||||
protected:
|
||||
/// Helper function to convert the specified echo duration in µs to meters.
|
||||
static float us_to_m(uint32_t us);
|
||||
/// Helper function to convert the specified distance in meters to the echo duration in µs.
|
||||
void send_trigger_pulse_();
|
||||
|
||||
GPIOPin *trigger_pin_;
|
||||
InternalGPIOPin *trigger_pin_;
|
||||
ISRInternalGPIOPin trigger_pin_isr_;
|
||||
InternalGPIOPin *echo_pin_;
|
||||
ISRInternalGPIOPin echo_isr_;
|
||||
uint32_t timeout_us_{}; /// 2 meters.
|
||||
UltrasonicSensorStore store_;
|
||||
uint32_t timeout_us_{};
|
||||
uint32_t pulse_time_us_{};
|
||||
|
||||
uint32_t measurement_start_us_{0};
|
||||
bool measurement_pending_{false};
|
||||
};
|
||||
|
||||
} // namespace ultrasonic
|
||||
} // namespace esphome
|
||||
} // namespace esphome::ultrasonic
|
||||
|
||||
Reference in New Issue
Block a user