ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
software_serial_rx_decoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <cstdint>
5
7
8namespace esphome::uart {
9
14 public:
15 static constexpr uint8_t RX_IDLE = 0xFF;
16
18 void setup(uint32_t bit_ticks, uint8_t data_bits, bool parity, uint8_t stop_bits, uint8_t *buffer,
19 size_t buffer_size) {
20 this->bit_ticks_ = bit_ticks;
21 this->data_bits_ = data_bits;
22 this->stop_bit_ = data_bits + (parity ? 1 : 0);
23 this->max_run_ticks_ = bit_ticks * (this->stop_bit_ + stop_bits + 2);
24 this->buffer_ = buffer;
26 this->in_pos_ = 0;
27 this->out_pos_ = 0;
28 this->reset(0, true);
29 }
30
32 void reset(uint32_t now, bool level) {
33 this->bit_ = RX_IDLE;
34 this->cur_byte_ = 0;
35 this->last_level_ = level;
36 this->last_edge_ = now;
37 }
38
41 bool ESPHOME_ALWAYS_INLINE on_edge(uint32_t now, bool level) {
42 const bool last_level = this->last_level_;
43 // Collapsed edge
44 if (level == last_level)
45 return false;
46 // LX106 has no divider, so count
47 uint32_t delta = now - this->last_edge_;
48 if (delta > this->max_run_ticks_)
49 delta = this->max_run_ticks_;
50 delta += this->bit_ticks_ / 2;
51 uint32_t bits = 0;
52 while (delta >= this->bit_ticks_) {
53 delta -= this->bit_ticks_;
54 bits++;
55 }
56 const bool pushed = this->consume_run_(bits, last_level);
57 this->last_edge_ = now;
58 this->last_level_ = level;
59 return pushed || (level && this->bit_ != RX_IDLE);
60 }
61
62 bool pending() const { return this->bit_ != RX_IDLE && this->last_level_; }
63
64 bool finalize_due(uint32_t now) const {
65 const uint8_t bit = this->bit_;
66 if (bit == RX_IDLE)
67 return false;
68 return now - this->last_edge_ >= this->tail_ticks_(bit);
69 }
70
72 void finalize(uint32_t now) {
73 if (!this->last_level_ || !this->finalize_due(now))
74 return;
75 // Remaining bits are all ones
76 const uint8_t bit = this->bit_;
77 uint8_t cur = this->cur_byte_;
78 if (bit < this->data_bits_)
79 cur |= ((1U << this->data_bits_) - 1) & ~((1U << bit) - 1);
80 this->push_byte(cur);
81 this->bit_ = RX_IDLE;
82 }
83
85 bool ESPHOME_ALWAYS_INLINE push_byte(uint8_t data) {
86 size_t in = this->in_pos_;
87 size_t next = in + 1;
88 if (next == this->buffer_size_)
89 next = 0;
90 if (next == this->out_pos_)
91 return false;
92 this->buffer_[in] = data;
93 this->in_pos_ = next;
94 return true;
95 }
96
97 uint8_t *buffer() const { return this->buffer_; }
98 size_t buffer_size() const { return this->buffer_size_; }
99
100 size_t available() const {
101 // Read volatile in_pos_ once to avoid TOCTOU race with ISR.
102 size_t in = this->in_pos_;
103 if (in >= this->out_pos_)
104 return in - this->out_pos_;
105 return this->buffer_size_ - this->out_pos_ + in;
106 }
107 uint8_t peek_byte() const {
108 if (this->in_pos_ == this->out_pos_)
109 return 0;
110 return this->buffer_[this->out_pos_];
111 }
112 uint8_t read_byte() {
113 if (this->in_pos_ == this->out_pos_)
114 return 0;
115 uint8_t data = this->buffer_[this->out_pos_];
116 size_t next = this->out_pos_ + 1;
117 this->out_pos_ = next == this->buffer_size_ ? 0 : next;
118 return data;
119 }
120
121 protected:
122 uint32_t tail_ticks_(uint8_t bit) const {
123 return (this->stop_bit_ + 1 - bit) * this->bit_ticks_ + this->bit_ticks_ / 2;
124 }
125
126 bool ESPHOME_ALWAYS_INLINE consume_run_(uint32_t bits, bool level) {
127 uint8_t bit = this->bit_;
128 uint8_t cur = this->cur_byte_;
129 bool pushed = false;
130 while (bits > 0) {
131 if (bit == RX_IDLE) {
132 if (level)
133 break;
134 // Start bit
135 bit = 0;
136 cur = 0;
137 bits--;
138 } else if (bit < this->data_bits_) {
139 uint8_t n = this->data_bits_ - bit;
140 if (n > bits)
141 n = bits;
142 if (level)
143 cur |= ((1U << n) - 1) << bit;
144 bit += n;
145 bits -= n;
146 } else if (bit < this->stop_bit_) {
147 // Parity, not checked
148 bit++;
149 bits--;
150 } else {
151 // Stop bit; low is a framing error
152 if (level)
153 pushed = this->push_byte(cur);
154 bit = RX_IDLE;
155 break;
156 }
157 }
158 this->bit_ = bit;
159 this->cur_byte_ = cur;
160 return pushed;
161 }
162
163 // Members ordered largest to smallest to minimize padding
166 volatile uint32_t last_edge_{0};
167 uint8_t *buffer_{nullptr};
168 size_t buffer_size_{0};
169 volatile size_t in_pos_{0};
170 size_t out_pos_{0}; // reader only
172 volatile uint8_t bit_{RX_IDLE};
173 volatile uint8_t cur_byte_{0};
174 volatile bool last_level_{true};
175 uint8_t data_bits_{8};
176 uint8_t stop_bit_{8};
177};
178
179} // namespace esphome::uart
Software serial RX decoder: on_edge() counts bits from the time between edges; finalize() completes a...
bool ESPHOME_ALWAYS_INLINE consume_run_(uint32_t bits, bool level)
volatile uint8_t bit_
Next frame bit (data, parity, then stop at stop_bit_) or RX_IDLE.
void finalize(uint32_t now)
Call with the ISR masked.
void setup(uint32_t bit_ticks, uint8_t data_bits, bool parity, uint8_t stop_bits, uint8_t *buffer, size_t buffer_size)
Configure framing and buffer; drops all state. Follow with reset().
bool ESPHOME_ALWAYS_INLINE on_edge(uint32_t now, bool level)
ISR.
void reset(uint32_t now, bool level)
Forget any partial frame; level is the current line level.
bool ESPHOME_ALWAYS_INLINE push_byte(uint8_t data)
Drops the byte when full; holds buffer_size - 1 bytes.
uint16_t reset
Definition ina226.h:5
static void uint32_t