ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
hdmi_cec.cpp
Go to the documentation of this file.
1#include "hdmi_cec.h"
2
3#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5#ifdef USE_ESP8266
6#include <Arduino.h>
7#endif
8#ifdef USE_RP2040
9#include <pico/time.h>
10#endif
11
13
14ESPHOME_LOG_TAG(TAG, "hdmi_cec");
15
16#if defined(USE_ESP8266) || defined(USE_RP2040)
17// The timer interrupt of these platforms carries no argument
18static HDMICEC *ack_timer_owner = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
19#endif
20
21// Bit timing from the HDMI 1.4 CEC specification, in microseconds
22static constexpr uint32_t START_BIT_MIN_US = 3500; // a low period at least this long is a start bit
23static constexpr uint32_t START_BIT_NOM_US = 3700;
24static constexpr uint32_t START_BIT_HIGH_US = 800;
25static constexpr uint32_t HIGH_BIT_MIN_US = 400; // shortest low period of a logical 1
26static constexpr uint32_t HIGH_BIT_US = 600; // nominal low period of a logical 1
27static constexpr uint32_t LOW_BIT_US = 1500; // nominal low period of a logical 0
28static constexpr uint32_t SAFE_SAMPLE_US = 1050; // the line is sampled here; a longer low period is a logical 0
29static constexpr uint32_t TOTAL_BIT_US = 2400;
30static constexpr uint32_t SIGNAL_FREE_TIME_AFTER_RECEIVE = TOTAL_BIT_US * 5; // also for a new initiator
31static constexpr uint32_t SIGNAL_FREE_TIME_AFTER_XMIT_FAIL = TOTAL_BIT_US * 3;
32static constexpr uint32_t SIGNAL_FREE_TIME_AFTER_XMIT_SUCCESS = TOTAL_BIT_US * 7;
33static constexpr uint32_t ABORTED_FRAME_US = TOTAL_BIT_US * 15; // no edge for this long: our frame was cut short
34static constexpr uint8_t MAX_ATTEMPTS = 8; // CEC allows 5, but a busy bus counts as an attempt here too
35
36Frame::Frame(uint8_t initiator_addr, uint8_t destination_addr, std::span<const uint8_t> payload) {
37 this->data_[0] = ((initiator_addr & 0xf) << 4) | (destination_addr & 0xf);
38 this->size_ = 1 + payload.size();
39 std::copy(payload.begin(), payload.end(), this->data_.begin() + 1);
40}
41
43 // The line is open drain with a weak pull-up: driven low to send, released to read
44 this->pin_->setup();
45 this->isr_pin_ = this->pin_->to_isr();
46 this->set_pin_input_high_();
47 this->required_idle_period_ = SIGNAL_FREE_TIME_AFTER_RECEIVE;
48 if (!this->setup_ack_timer_()) {
49 ESP_LOGE(TAG, "Failed to set up the acknowledge timer");
50 this->mark_failed();
51 return;
52 }
54 // loop() only has work once the interrupt queues a frame or send() queues one
55 this->disable_loop();
56}
57
59 ESP_LOGCONFIG(TAG,
60 "HDMI-CEC:\n"
61 " Address: 0x%X\n"
62 " Monitor mode: %s\n"
63 " Promiscuous mode: %s",
64 this->address_, YESNO(this->monitor_mode_), YESNO(this->promiscuous_mode_));
65 LOG_PIN(" Pin: ", this->pin_);
66}
67
69 const uint8_t dropped = atomic_get(this->dropped_frames_);
70 if (dropped != this->reported_dropped_frames_) {
71 const uint8_t count = dropped - this->reported_dropped_frames_;
72 ESP_LOGW(TAG, "%u received frame%s dropped (queue full or oversized)", count,
73 count == 1 ? LOG_STR_LITERAL("") : LOG_STR_LITERAL("s"));
74 this->reported_dropped_frames_ = dropped;
75 }
76 // One inbound frame per pass keeps the time spent in the automations bounded
77 if (const Frame *frame = this->frames_queue_.front()) {
78#ifdef ESPHOME_LOG_HAS_DEBUG
80 ESP_LOGD(TAG, "Received: %s", format_hex_pretty_to(line, frame->bytes().data(), frame->size()));
81#endif
82#ifdef HDMI_CEC_MESSAGE_CALLBACK_COUNT
83 this->message_callbacks_.call(frame->initiator_addr(), frame->destination_addr(), frame->payload());
84#endif
86 }
87 // A frame being sent stays at the front of the queue until its attempts are over
88 if (!this->send_queue_.is_empty()) {
89 this->transmit_message_();
90 }
91 if (this->frames_queue_.is_empty() && this->send_queue_.is_empty()) {
92 this->disable_loop();
93 }
94}
95
96bool HDMICEC::send(uint8_t source, uint8_t destination, std::span<const uint8_t> payload) {
97 if (this->monitor_mode_) {
98 ESP_LOGW(TAG, "Monitor mode, not sending 0x%X->0x%X", source, destination);
99 return false;
100 }
101 if (source > 0xf || destination > 0xf) {
102 ESP_LOGE(TAG, "Send abort: logical address 0x%X->0x%X out of range", source, destination);
103 return false;
104 }
105 if (payload.size() > Frame::MAX_PAYLOAD_LENGTH) {
106 ESP_LOGE(TAG, "Send abort 0x%X->0x%X: payload of %zu bytes exceeds %zu", source, destination, payload.size(),
108 return false;
109 }
110 Frame *frame = this->send_queue_.back();
111 if (frame == nullptr) {
112 ESP_LOGW(TAG, "Send queue full, frame 0x%X->0x%X dropped", source, destination);
113 return false;
114 }
115 *frame = Frame(source, destination, payload);
116 this->send_queue_.push_back();
117 this->enable_loop();
118 return true;
119}
120
122 const uint32_t now = micros();
123 const uint32_t since_last_edge = now - atomic_get(this->last_edge_us_);
124 if (this->transmitting_) {
125 // The interrupt sees our own frame; wait for its end of message bit, or give up when the
126 // bus went quiet without one
127 if (!atomic_get(this->eom_received_) && since_last_edge < ABORTED_FRAME_US) {
128 return;
129 }
130 this->transmitting_ = false;
131 atomic_set(this->eom_received_, false); // the end of our own frame is not a foreign one
132 const Frame *frame = this->send_queue_.front();
133 // A broadcast is acknowledged by nobody; a directed frame on every byte
134 const bool bytes_ok = this->n_bytes_received_ == frame->size();
135 const bool acks_ok = this->n_acks_received_ == (frame->is_broadcast() ? 0 : frame->size());
136 if (bytes_ok && acks_ok) {
137 ESP_LOGD(TAG, "Send frame success in %u attempt%s", this->transmit_attempts_,
138 this->transmit_attempts_ > 1 ? LOG_STR_LITERAL("s") : LOG_STR_LITERAL(""));
139 } else if (!bytes_ok) {
140 ESP_LOGD(TAG, "Send frame incorrect on attempt %u, saw %u bytes but expected %zu", this->transmit_attempts_,
141 this->n_bytes_received_, frame->size());
142 } else {
143 ESP_LOGD(TAG, "Send frame not acknowledged on attempt %u", this->transmit_attempts_);
144 }
145 this->finish_attempt_(bytes_ok && acks_ok);
146 } else if (atomic_get(this->eom_received_)) {
147 // Another device finished a frame; the standard asks a new initiator to wait after it
148 this->required_idle_period_ = SIGNAL_FREE_TIME_AFTER_RECEIVE;
149 atomic_set(this->eom_received_, false);
150 }
151
152 const Frame *frame = this->send_queue_.front();
153 // An edge within the signal free time means the bus is in use or not yet free for us; a start
154 // bit refused by a held line drives no edge, so that attempt is timed on its own
155 if (frame == nullptr || since_last_edge < this->required_idle_period_ ||
157 return;
158 }
159
160 this->last_attempt_us_ = now;
161 this->transmit_attempts_++;
162#ifdef ESPHOME_LOG_HAS_DEBUG
163 if (this->transmit_attempts_ == 1) {
165 ESP_LOGD(TAG, "Sending: %s", format_hex_pretty_to(line, frame->bytes().data(), frame->size()));
166 }
167#endif
168 // The interrupt counts the bytes and acknowledge bits of our own frame
169 this->n_bytes_received_ = 0;
170 this->n_acks_received_ = 0;
171 atomic_set(this->eom_received_, false);
172 this->transmitting_ = true;
173 if (!this->transmit_frame_(frame)) {
174 // The line was held, or another initiator sent at the same time
175 this->transmitting_ = false;
176 this->finish_attempt_(false);
177 }
178 // Otherwise the acknowledge result arrives through the interrupt
179}
180
181void HDMICEC::finish_attempt_(bool sent_ok) {
182 this->required_idle_period_ = sent_ok ? SIGNAL_FREE_TIME_AFTER_XMIT_SUCCESS : SIGNAL_FREE_TIME_AFTER_XMIT_FAIL;
183 if (!sent_ok) {
184 if (this->transmit_attempts_ < MAX_ATTEMPTS) {
185 return; // the signal free time has to pass before the retry
186 }
187 ESP_LOGW(TAG, "Frame not sent after %u attempts, dropped", this->transmit_attempts_);
188 }
189 this->transmit_attempts_ = 0;
190 this->send_queue_.pop_front();
191}
192
193bool HDMICEC::transmit_frame_(const Frame *frame) {
194 if (!this->send_start_bit_()) {
195 return false;
196 }
197 const auto bytes = frame->bytes();
198 for (size_t byte_inx = 0; byte_inx < bytes.size(); byte_inx++) {
199 const uint8_t current_byte = bytes[byte_inx];
200 // Most significant bit first. The four initiator address bits watch for a collision with
201 // another initiator that started at the same moment; a lower address wins by holding the line
202 for (int8_t i = 7; i >= 0; i--) {
203 const bool bit_value = (current_byte >> i) & 0x1;
204 if (!this->send_bit_(bit_value) && byte_inx == 0 && i >= 4) {
205 ESP_LOGD(TAG, "Bus collision on sending initiator address 0x%X on attempt %u", frame->initiator_addr(),
206 this->transmit_attempts_);
207 return false;
208 }
209 }
210 // End of message bit, then the acknowledge bit which the receiver may pull low
211 this->send_bit_(byte_inx == bytes.size() - 1);
212 this->send_bit_(true);
213 }
214 return true;
215}
216
218 if (!this->isr_pin_.digital_read()) {
219 ESP_LOGD(TAG, "Bus occupied before start bit on attempt %u", this->transmit_attempts_);
220 return false;
221 }
222 this->set_pin_output_low_();
223 delay_microseconds_safe(START_BIT_NOM_US);
224 // Release the line and check that nobody else keeps it low
225 this->set_pin_input_high_();
226 delay_microseconds_safe(START_BIT_HIGH_US / 2);
227 bool value = this->isr_pin_.digital_read();
228 delay_microseconds_safe(START_BIT_HIGH_US / 2);
229 value &= this->isr_pin_.digital_read();
230 if (!value) {
231 ESP_LOGD(TAG, "Bus collision on sending start bit on attempt %u", this->transmit_attempts_);
232 }
233 return value;
234}
235
236bool HDMICEC::send_bit_(bool bit_value) {
237 const uint32_t start_us = micros();
238 this->set_pin_output_low_();
239 delay_microseconds_safe(bit_value ? HIGH_BIT_US : LOW_BIT_US);
240 this->set_pin_input_high_();
241 bool clear = true;
242 if (bit_value) {
243 // Sample in the middle of the safe sample period: a line still low means another initiator
244 // is sending a logical 0 at the same time
245 uint32_t elapsed = micros() - start_us;
246 if (elapsed < SAFE_SAMPLE_US) {
247 delay_microseconds_safe(SAFE_SAMPLE_US - elapsed);
248 }
249 clear = this->isr_pin_.digital_read();
250 }
251 const uint32_t elapsed = micros() - start_us;
252 if (elapsed < TOTAL_BIT_US) {
253 delay_microseconds_safe(TOTAL_BIT_US - elapsed);
254 }
255 return clear;
256}
257
258void IRAM_ATTR HDMICEC::send_ack_() {
259 // The acknowledge has to start right at this falling edge; the timer releases the line
260 this->set_pin_output_low_();
261 this->start_ack_timer_();
262}
263
264#if defined(USE_ESP32)
266 gptimer_config_t config = {
267 .clk_src = GPTIMER_CLK_SRC_DEFAULT,
268 .direction = GPTIMER_COUNT_UP,
269 .resolution_hz = 1000000, // one tick per microsecond
270 };
271 if (gptimer_new_timer(&config, &this->ack_timer_) != ESP_OK) {
272 return false;
273 }
274 gptimer_event_callbacks_t callbacks = {.on_alarm = HDMICEC::ack_timer_isr_s};
275 // The alarm reloads the count to zero itself, so the interrupt only has to start the timer
276 gptimer_alarm_config_t alarm = {
277 .alarm_count = LOW_BIT_US, .reload_count = 0, .flags = {.auto_reload_on_alarm = true}};
278 return gptimer_register_event_callbacks(this->ack_timer_, &callbacks, this) == ESP_OK &&
279 gptimer_set_alarm_action(this->ack_timer_, &alarm) == ESP_OK && gptimer_enable(this->ack_timer_) == ESP_OK;
280}
281
282void IRAM_ATTR HDMICEC::start_ack_timer_() { gptimer_start(this->ack_timer_); }
283
284bool IRAM_ATTR HDMICEC::ack_timer_isr_s(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *arg) {
285 auto *self = static_cast<HDMICEC *>(arg);
286 self->set_pin_input_high_();
287 gptimer_stop(timer);
288 return false; // no task to wake
289}
290
291#elif defined(USE_ESP8266)
292static constexpr uint32_t TIMER1_TICKS_PER_US = 5; // TIM_DIV16 of the 80 MHz clock
293
295 ack_timer_owner = this;
296 timer1_isr_init();
297 timer1_attachInterrupt(HDMICEC::ack_timer_isr_s);
298 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); // a write arms one shot
299 return true;
300}
301
302void IRAM_ATTR HDMICEC::start_ack_timer_() { timer1_write(LOW_BIT_US * TIMER1_TICKS_PER_US); }
303
304void IRAM_ATTR HDMICEC::ack_timer_isr_s() { ack_timer_owner->set_pin_input_high_(); }
305
306#elif defined(USE_RP2040)
308 this->ack_alarm_ = hardware_alarm_claim_unused(false);
309 if (this->ack_alarm_ < 0) {
310 return false; // every hardware alarm is taken
311 }
312 ack_timer_owner = this;
313 hardware_alarm_set_callback(this->ack_alarm_, HDMICEC::ack_timer_isr_s);
314 return true;
315}
316
317void IRAM_ATTR HDMICEC::start_ack_timer_() {
318 hardware_alarm_set_target(this->ack_alarm_, make_timeout_time_us(LOW_BIT_US));
319}
320
321void IRAM_ATTR HDMICEC::ack_timer_isr_s(uint alarm_num) { ack_timer_owner->set_pin_input_high_(); }
322
323#else
324bool HDMICEC::setup_ack_timer_() { return true; }
325
326// Without a one-shot timer the interrupt holds the line itself
327void IRAM_ATTR HDMICEC::start_ack_timer_() {
328 delay_microseconds_safe(LOW_BIT_US);
329 this->set_pin_input_high_();
330}
331#endif
332
333void IRAM_ATTR HDMICEC::gpio_isr_s(HDMICEC *self) { self->gpio_isr_(); }
334
335void IRAM_ATTR HDMICEC::gpio_isr_() {
336 const uint32_t now = micros();
337 const bool level = this->isr_pin_.digital_read();
338 if (level == this->prev_pin_level_) {
339 return; // a mode change of the pin, not a bus edge
340 }
341 this->prev_pin_level_ = level;
342 atomic_set(this->last_edge_us_, now);
343
344 if (!level) {
345 // Falling edge: the start of a low period
346 if (now - this->last_falling_edge_us_ > START_BIT_NOM_US + TOTAL_BIT_US) {
347 // The bus was quiet; whatever was gathered belongs to an aborted frame
348 this->reset_receiver_();
349 }
350 this->last_falling_edge_us_ = now;
351 if (this->recv_ack_queued_) {
352 // Acknowledge by stretching this low period
353 this->send_ack_();
354 this->recv_ack_queued_ = false;
355 }
356 return;
357 }
358
359 // Rising edge: the low period tells the bit value
360 const uint32_t pulse_duration = now - this->last_falling_edge_us_;
361 if (pulse_duration > START_BIT_MIN_US) {
362 this->reset_receiver_();
364 // Without a free slot the frame is neither acknowledged nor delivered, so its initiator retries
365 this->recv_frame_ = this->frames_queue_.back();
366 if (this->recv_frame_ != nullptr) {
367 this->recv_frame_->clear();
368 }
369 return;
370 }
371 if (pulse_duration < HIGH_BIT_MIN_US / 4) {
372 return; // glitch
373 }
374 const bool value = pulse_duration <= SAFE_SAMPLE_US;
375 // Our own frame is on the bus until its end of message; send() may give it any initiator address
376 const bool own_frame = this->transmitting_ && !atomic_get(this->eom_received_);
377
378 switch (this->receiver_state_) {
380 break;
381
383 this->recv_byte_ = (this->recv_byte_ << 1) | value;
384 if (++this->recv_bit_counter_ == 8) {
385 if (this->recv_length_++ == 0) {
386 this->recv_header_ = this->recv_byte_;
387 }
388 if (this->recv_frame_ != nullptr && !this->recv_frame_->push_back(this->recv_byte_)) {
389 this->recv_frame_ = nullptr; // longer than the bus allows: corrupt, do not deliver it
390 }
391 this->recv_bit_counter_ = 0;
393 }
394 break;
395
397 this->recv_eom_ = value;
398 // A frame for us is acknowledged on the next bit, unless there is no room for it (so its
399 // initiator retries) or it is our own, which includes a polling message from a device
400 // claiming our logical address. A polling message for us (header only) needs no room, and
401 // goes unacknowledged only when nobody holds the address
402 if (!this->monitor_mode_ && !own_frame && (this->recv_frame_ != nullptr || (value && this->recv_length_ == 1)) &&
403 (this->recv_header_ & 0xf) == this->address_ && (this->recv_header_ >> 4) != this->address_) {
404 this->recv_ack_queued_ = true;
405 }
407 break;
408
410 if (own_frame) {
411 // A low acknowledge bit means the addressed device took the byte
412 // (for a broadcast it means some device refused it)
413 this->n_bytes_received_++;
414 if (!value) {
415 this->n_acks_received_++;
416 }
417 }
418 if (!this->recv_eom_) {
420 break;
421 }
422 atomic_set(this->eom_received_, true); // publishes the tally above to loop()
423 // The application only gets frames that are not our own and not a polling message (header
424 // only), which the acknowledge answered already, and that are addressed to us unless
425 // promiscuous
426 const uint8_t destination = this->recv_header_ & 0xf;
427 const bool wanted = this->recv_length_ > 1 && !own_frame && (this->recv_header_ >> 4) != this->address_ &&
428 (this->promiscuous_mode_ || destination == 0xf || destination == this->address_);
429 if (!wanted) {
430 // nothing to deliver
431 } else if (this->recv_frame_ == nullptr) {
433 } else {
434 this->frames_queue_.push_back();
436 }
437 this->reset_receiver_();
438 break;
439 }
440}
441
442void IRAM_ATTR HDMICEC::reset_receiver_() {
444 this->recv_bit_counter_ = 0;
445 this->recv_length_ = 0;
446 this->recv_ack_queued_ = false;
447 this->recv_frame_ = nullptr;
448}
449
450} // namespace esphome::hdmi_cec
void mark_failed()
Mark this component as failed.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:29
ISRInternalGPIOPin to_isr() const
Definition gpio.cpp:91
One CEC bus message: a header byte with the initiator and destination addresses, then up to 15 payloa...
Definition hdmi_cec.h:35
ESPHOME_ALWAYS_INLINE bool push_back(uint8_t data)
False when the frame is full already; the bus allows no more.
Definition hdmi_cec.h:53
std::span< const uint8_t > bytes() const
Definition hdmi_cec.h:48
static constexpr size_t MAX_LENGTH
Definition hdmi_cec.h:37
static constexpr size_t MAX_PAYLOAD_LENGTH
Definition hdmi_cec.h:38
std::span< const uint8_t > payload() const
The bytes after the header, starting with the opcode.
Definition hdmi_cec.h:50
ESPHOME_ALWAYS_INLINE uint8_t initiator_addr() const
Definition hdmi_cec.h:44
ESPHOME_ALWAYS_INLINE size_t size() const
Definition hdmi_cec.h:47
ESPHOME_ALWAYS_INLINE bool is_broadcast() const
Definition hdmi_cec.h:46
ESPHOME_ALWAYS_INLINE uint8_t destination_addr() const
Definition hdmi_cec.h:45
std::array< uint8_t, MAX_LENGTH > data_
Definition hdmi_cec.h:62
ESPHOME_ALWAYS_INLINE void clear()
Definition hdmi_cec.h:51
ESPHOME_ALWAYS_INLINE void pop_front()
Definition hdmi_cec.h:75
ESPHOME_ALWAYS_INLINE Frame * back()
Definition hdmi_cec.h:76
ESPHOME_ALWAYS_INLINE void push_back()
Definition hdmi_cec.h:77
ESPHOME_ALWAYS_INLINE Frame * front()
Definition hdmi_cec.h:74
ESPHOME_ALWAYS_INLINE bool is_empty() const
Definition hdmi_cec.h:78
The HDMI-CEC bus on one GPIO pin.
Definition hdmi_cec.h:97
std::atomic< uint32_t > last_edge_us_
Definition hdmi_cec.h:190
void dump_config() override
Definition hdmi_cec.cpp:58
FrameRingBuffer< QUEUE_SIZE > send_queue_
Definition hdmi_cec.h:173
void setup() override
Definition hdmi_cec.cpp:42
static void gpio_isr_s(HDMICEC *self)
Definition hdmi_cec.cpp:333
std::atomic< uint8_t > dropped_frames_
Definition hdmi_cec.h:200
bool send(uint8_t source, uint8_t destination, std::span< const uint8_t > payload)
Queue a frame for transmission; false when the queue is full or in monitor mode.
Definition hdmi_cec.cpp:96
gptimer_handle_t ack_timer_
Definition hdmi_cec.h:183
static void ack_timer_isr_s()
Definition hdmi_cec.cpp:304
StaticCallbackManager< HDMI_CEC_MESSAGE_CALLBACK_COUNT, void(uint8_t, uint8_t, std::span< const uint8_t >)> message_callbacks_
Definition hdmi_cec.h:178
ISRInternalGPIOPin isr_pin_
Definition hdmi_cec.h:181
void send_ack_()
Pull the line low for the acknowledge bit of a frame addressed to us.
Definition hdmi_cec.cpp:258
ESPHOME_ALWAYS_INLINE void set_pin_output_low_()
Definition hdmi_cec.h:163
void finish_attempt_(bool sent_ok)
Keep the frame for another attempt, or drop it once the attempts are used up.
Definition hdmi_cec.cpp:181
ReceiverState receiver_state_
Definition hdmi_cec.h:208
std::atomic< bool > eom_received_
Definition hdmi_cec.h:199
bool send_bit_(bool bit_value)
Send one bit; false when a logical 1 found the line held low by another initiator.
Definition hdmi_cec.cpp:236
ESPHOME_ALWAYS_INLINE void set_pin_input_high_()
Definition hdmi_cec.h:162
FrameRingBuffer< QUEUE_SIZE > frames_queue_
Definition hdmi_cec.h:170
InternalGPIOPin * pin_
Definition hdmi_cec.h:180
bool transmit_frame_(const Frame *frame)
Drive the frame onto the line; false when another initiator sent at the same time.
Definition hdmi_cec.cpp:193
unsigned count
Definition gif.cpp:51
@ INTERRUPT_ANY_EDGE
Definition gpio_pin.h:54
ESPHOME_ALWAYS_INLINE void atomic_set(std::atomic< T > &atom, T value)
Definition hdmi_cec.h:25
ESPHOME_LOG_TAG(TAG, "hdmi_cec")
ESPHOME_ALWAYS_INLINE T atomic_get(const std::atomic< T > &atom)
Definition hdmi_cec.h:28
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:498
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
void delay_microseconds_safe(uint32_t us)
Delay for the given amount of microseconds, possibly yielding to other processes during the wait.
Definition helpers.cpp:889
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1533
static void uint32_t