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