mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[remote_transmitter] Use hardware PWM on rtl87xx to fix watchdog crash (#18579)
This commit is contained in:
@@ -185,12 +185,14 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
|||||||
PlatformFramework.ESP32_ARDUINO,
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
PlatformFramework.ESP32_IDF,
|
PlatformFramework.ESP32_IDF,
|
||||||
},
|
},
|
||||||
|
"remote_transmitter_rtl87xx.cpp": {
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
},
|
||||||
"remote_transmitter.cpp": {
|
"remote_transmitter.cpp": {
|
||||||
PlatformFramework.ESP32_ARDUINO,
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
PlatformFramework.ESP32_IDF,
|
PlatformFramework.ESP32_IDF,
|
||||||
PlatformFramework.ESP8266_ARDUINO,
|
PlatformFramework.ESP8266_ARDUINO,
|
||||||
PlatformFramework.BK72XX_ARDUINO,
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
PlatformFramework.RTL87XX_ARDUINO,
|
|
||||||
PlatformFramework.LN882X_ARDUINO,
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
PlatformFramework.RP2_ARDUINO,
|
PlatformFramework.RP2_ARDUINO,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
|
#if (defined(USE_LIBRETINY) && !defined(USE_RTL87XX)) || defined(USE_ESP8266) || defined(USE_RP2) || \
|
||||||
|
(defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
|
||||||
|
|
||||||
namespace esphome::remote_transmitter {
|
namespace esphome::remote_transmitter {
|
||||||
|
|
||||||
|
|||||||
@@ -65,14 +65,21 @@ class RemoteTransmitterComponent final : public remote_base::RemoteTransmitterBa
|
|||||||
protected:
|
protected:
|
||||||
void send_internal(uint32_t send_times, uint32_t send_wait) override;
|
void send_internal(uint32_t send_times, uint32_t send_wait) override;
|
||||||
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
|
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
|
||||||
|
void await_target_time_();
|
||||||
|
uint32_t target_time_{0};
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ESP8266) || (defined(USE_LIBRETINY) && !defined(USE_RTL87XX)) || defined(USE_RP2) || \
|
||||||
|
(defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
|
||||||
void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period);
|
void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period);
|
||||||
|
|
||||||
void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
|
void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
|
||||||
|
|
||||||
void space_(uint32_t usec);
|
void space_(uint32_t usec);
|
||||||
|
#endif
|
||||||
void await_target_time_();
|
#ifdef USE_RTL87XX
|
||||||
uint32_t target_time_{0};
|
// Carrier frequency the PWM is currently configured for; 0 = not yet configured
|
||||||
|
uint32_t current_carrier_frequency_{0};
|
||||||
|
void *pwm_{nullptr}; // pwmout_t*, opaque here to keep the SDK header out of this shared header
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
|
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#include "remote_transmitter.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
// clang-tidy cannot parse the Realtek SDK headers pulled in via ArduinoPrivate.h
|
||||||
|
#if defined(USE_RTL87XX) && !defined(CLANG_TIDY)
|
||||||
|
|
||||||
|
// ArduinoPrivate.h = Arduino.h + the SDK's mbed HAL (pwmout etc.) with the core's fixes for
|
||||||
|
// type-name collisions between the two (e.g. PinMode)
|
||||||
|
#include <ArduinoPrivate.h>
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
#include <task.h>
|
||||||
|
|
||||||
|
namespace esphome::remote_transmitter {
|
||||||
|
|
||||||
|
static const char *const TAG = "remote_transmitter";
|
||||||
|
|
||||||
|
// The carrier is generated by the PWM peripheral instead of bit-banging the pin: software carrier
|
||||||
|
// generation requires disabling interrupts for the whole frame, but this core's micros() is derived
|
||||||
|
// from the FreeRTOS tick and freezes while interrupts are off, so the timing loop never advances and
|
||||||
|
// the watchdog resets the chip. With hardware PWM, software only times the mark/space envelope and
|
||||||
|
// interrupts can stay enabled.
|
||||||
|
//
|
||||||
|
// The PWM is driven through the SDK's pwmout HAL directly rather than the Arduino wiring layer:
|
||||||
|
// changing the carrier frequency via the wiring requires a GPIO/PWM pin mode round-trip, which
|
||||||
|
// use-after-frees the core's per-pin state (pinRemoveMode() frees without nulling) and corrupts the
|
||||||
|
// heap. pwmout_period_us() changes the frequency with no mode transitions.
|
||||||
|
|
||||||
|
void RemoteTransmitterComponent::setup() {
|
||||||
|
// Deliberately no pin_->setup(): registering the pin as GPIO claims it in the SDK's pin
|
||||||
|
// management, and the pad is then never handed over to the PWM peripheral -- pwmout_init()
|
||||||
|
// must own the pin from the start.
|
||||||
|
PinInfo *info = pinInfo(this->pin_->get_pin());
|
||||||
|
if (info == nullptr || !pinSupported(info, PIN_PWM)) {
|
||||||
|
// checked here because the AmebaZ (RTL8710B) SDK does not report PWM init failure
|
||||||
|
ESP_LOGE(TAG, "Pin %u is not PWM-capable", this->pin_->get_pin());
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *pwm = new pwmout_t();
|
||||||
|
this->pwm_ = pwm;
|
||||||
|
pwmout_init(pwm, static_cast<PinName>(info->gpio));
|
||||||
|
#if LT_RTL8720C
|
||||||
|
// only the AmebaZ2 SDK's pwmout_s reports init success
|
||||||
|
if (!pwm->is_init) {
|
||||||
|
ESP_LOGE(TAG, "PWM init failed on pin %u", this->pin_->get_pin());
|
||||||
|
delete pwm;
|
||||||
|
this->pwm_ = nullptr;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
pwmout_period_us(pwm, 26); // placeholder; the real carrier period is set per transmission
|
||||||
|
pwmout_write(pwm, this->pin_->is_inverted() ? 1.0f : 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteTransmitterComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"Remote Transmitter:\n"
|
||||||
|
" Carrier Duty: %u%%",
|
||||||
|
this->carrier_duty_percent_);
|
||||||
|
LOG_PIN(" Pin: ", this->pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteTransmitterComponent::await_target_time_() {
|
||||||
|
const uint32_t current_time = micros();
|
||||||
|
if (this->target_time_ == 0) {
|
||||||
|
this->target_time_ = current_time;
|
||||||
|
} else {
|
||||||
|
while ((int32_t) (this->target_time_ - micros()) > 0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteTransmitterComponent::digital_write(bool value) {
|
||||||
|
if (this->pwm_ == nullptr)
|
||||||
|
return;
|
||||||
|
pwmout_write(static_cast<pwmout_t *>(this->pwm_), (value != this->pin_->is_inverted()) ? 1.0f : 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||||
|
auto *pwm = static_cast<pwmout_t *>(this->pwm_);
|
||||||
|
if (pwm == nullptr) {
|
||||||
|
ESP_LOGW(TAG, "Cannot send: PWM not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Sending remote code");
|
||||||
|
const uint32_t carrier_frequency = this->temp_.get_carrier_frequency();
|
||||||
|
// unmodulated protocols (no carrier or 100% duty) drive the pin constantly during marks
|
||||||
|
float mark_duty =
|
||||||
|
(carrier_frequency > 0 && this->carrier_duty_percent_ < 100) ? this->carrier_duty_percent_ / 100.0f : 1.0f;
|
||||||
|
float space_duty = 0.0f;
|
||||||
|
if (this->pin_->is_inverted()) {
|
||||||
|
mark_duty = 1.0f - mark_duty;
|
||||||
|
space_duty = 1.0f;
|
||||||
|
}
|
||||||
|
if (carrier_frequency > 0 && carrier_frequency != this->current_carrier_frequency_) {
|
||||||
|
// round(1000000/freq), clamped like the bit-bang path so a bad lambda can't hand the SDK a zero period
|
||||||
|
const uint32_t period = std::max(uint32_t(1), (1000000UL + carrier_frequency / 2) / carrier_frequency);
|
||||||
|
pwmout_period_us(pwm, period);
|
||||||
|
this->current_carrier_frequency_ = carrier_frequency;
|
||||||
|
}
|
||||||
|
this->transmit_trigger_.trigger();
|
||||||
|
const UBaseType_t saved_priority = uxTaskPriorityGet(nullptr);
|
||||||
|
for (uint32_t i = 0; i < send_times; i++) {
|
||||||
|
// Boost task priority for the frame only, so WiFi/lwIP tasks can't preempt mid-frame and
|
||||||
|
// merge adjacent marks. Interrupts stay enabled: micros() needs the FreeRTOS tick, and
|
||||||
|
// ISR latency is within receiver tolerance.
|
||||||
|
vTaskPrioritySet(nullptr, configMAX_PRIORITIES - 1);
|
||||||
|
// Re-anchor every iteration: a late exit from the normal-priority gap wait must not
|
||||||
|
// leave the schedule behind micros(), which would compress the next frame's leading items
|
||||||
|
this->target_time_ = 0;
|
||||||
|
for (int32_t item : this->temp_.get_data()) {
|
||||||
|
const bool is_mark = item > 0;
|
||||||
|
this->await_target_time_();
|
||||||
|
pwmout_write(pwm, is_mark ? mark_duty : space_duty);
|
||||||
|
this->target_time_ += is_mark ? uint32_t(item) : uint32_t(-item);
|
||||||
|
App.feed_wdt();
|
||||||
|
}
|
||||||
|
this->await_target_time_(); // wait for duration of last pulse
|
||||||
|
pwmout_write(pwm, space_duty);
|
||||||
|
vTaskPrioritySet(nullptr, saved_priority);
|
||||||
|
if (i + 1 < send_times) {
|
||||||
|
// The repeat gap is user-configurable and unbounded, so wait it out at normal
|
||||||
|
// priority, feeding the watchdog
|
||||||
|
const uint32_t gap_end = micros() + send_wait;
|
||||||
|
while ((int32_t) (gap_end - micros()) > 0) {
|
||||||
|
App.feed_wdt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->complete_trigger_.trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::remote_transmitter
|
||||||
|
|
||||||
|
#endif // USE_RTL87XX && !CLANG_TIDY
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
remote_transmitter:
|
||||||
|
id: xmitr
|
||||||
|
pin: GPIO12
|
||||||
|
carrier_duty_percent: 50%
|
||||||
|
|
||||||
|
packages:
|
||||||
|
buttons: !include common-buttons.yaml
|
||||||
Reference in New Issue
Block a user