ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
gpio.cpp
Go to the documentation of this file.
2// Always built: InternalGPIOPin is this class, so components that merely hold a
3// pointer to it reference these methods even in configs without a pin.
4#ifdef USE_ESP32
5
6#include "gpio.h"
7#include "esphome/core/hal.h"
8#include "esphome/core/log.h"
9#include "driver/gpio.h"
10#include "driver/rtc_io.h"
11#include "hal/gpio_hal.h"
12#include "soc/soc_caps.h"
13#include "soc/gpio_periph.h"
14#include <cinttypes>
15
16#if (SOC_RTCIO_PIN_COUNT > 0)
17#include "hal/rtc_io_hal.h"
18#endif
19
20#ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT
21#define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0 // NOLINT
22#endif
23
24namespace esphome {
25namespace esp32 {
26
27ESPHOME_LOG_TAG(TAG, "esp32");
28
29static const gpio_hal_context_t GPIO_HAL = {.dev = GPIO_HAL_GET_HW(GPIO_PORT_0)};
30
31bool ESP32InternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
32
33static gpio_mode_t flags_to_mode(gpio::Flags flags) {
36 return GPIO_MODE_INPUT;
38 return GPIO_MODE_OUTPUT;
40 return GPIO_MODE_OUTPUT_OD;
42 return GPIO_MODE_INPUT_OUTPUT_OD;
44 return GPIO_MODE_INPUT_OUTPUT;
45 // unsupported or gpio::FLAG_NONE
46 return GPIO_MODE_DISABLE;
47}
48
49struct ISRPinArg {
50 gpio_num_t pin;
52 bool inverted;
53#ifdef USE_GPIO_HOLD
54 bool hold;
55#endif
56#if defined(USE_ESP32_VARIANT_ESP32)
57 bool use_rtc;
58 int rtc_pin;
59#endif
60};
61
62#ifdef USE_GPIO_HOLD
63// Re-latch a held pad onto the values just written to its registers.
64static inline void refresh_hold(const ESP32InternalGPIOPin &pin) {
65 pin.set_hold(false);
66 pin.set_hold(true);
67}
68
69static inline void IRAM_ATTR isr_refresh_hold(const ISRPinArg *arg) {
70 if (!arg->hold)
71 return;
72#if defined(USE_ESP32_VARIANT_ESP32)
73 if (arg->use_rtc) {
74 rtcio_hal_hold_disable(arg->rtc_pin);
75 rtcio_hal_hold_enable(arg->rtc_pin);
76 return;
77 }
78#elif defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
79 // GPIO0-21 are held from the RTC domain; the digital hold mask is zero for them
80 if (arg->pin < SOC_RTCIO_PIN_COUNT) {
81 rtcio_hal_hold_disable(arg->pin);
82 rtcio_hal_hold_enable(arg->pin);
83 return;
84 }
85#endif
86 gpio_hal_hold_dis(&GPIO_HAL, arg->pin);
87 gpio_hal_hold_en(&GPIO_HAL, arg->pin);
88}
89#endif
90
92 auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
93 arg->pin = this->get_pin_num();
94 arg->flags = gpio::FLAG_NONE;
95 arg->inverted = this->pin_flags_.inverted;
96#ifdef USE_GPIO_HOLD
97 arg->hold = this->get_hold();
98#endif
99#if defined(USE_ESP32_VARIANT_ESP32)
100 arg->use_rtc = rtc_gpio_is_valid_gpio(this->get_pin_num());
101 if (arg->use_rtc)
102 arg->rtc_pin = rtc_io_number_get(this->get_pin_num());
103#endif
104 return ISRInternalGPIOPin((void *) arg);
105}
106
107void ESP32InternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const {
108 gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
109 switch (type) {
111 idf_type = this->pin_flags_.inverted ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
112 break;
114 idf_type = this->pin_flags_.inverted ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
115 break;
117 idf_type = GPIO_INTR_ANYEDGE;
118 break;
120 idf_type = this->pin_flags_.inverted ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
121 break;
123 idf_type = this->pin_flags_.inverted ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
124 break;
125 }
126 gpio_set_intr_type(this->get_pin_num(), idf_type);
127 gpio_intr_enable(this->get_pin_num());
129 auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
130 if (res != ESP_OK) {
131 ESP_LOGE(TAG, "attach_interrupt(): call to gpio_install_isr_service() failed, error code: %d", res);
132 return;
133 }
135 }
136 gpio_isr_handler_add(this->get_pin_num(), func, arg);
137}
138
139size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {
140 return snprintf(buffer, len, "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
141}
142
143#ifdef USE_GPIO_HOLD
144void ESP32InternalGPIOPin::set_hold(bool hold) const {
145 if (!this->get_hold())
146 return;
147 if (hold) {
148 gpio_hold_en(this->get_pin_num());
149 } else {
150 gpio_hold_dis(this->get_pin_num());
151 }
152}
153#endif
154
156#ifdef USE_GPIO_HOLD
157 // Hold before gpio_config so the pad keeps its state while the registers change
158 this->set_hold(true);
159#endif
160 gpio_config_t conf{};
161 conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(this->pin_);
162 conf.mode = flags_to_mode(this->flags_);
163 conf.pull_up_en = this->flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
164 conf.pull_down_en = this->flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
165 conf.intr_type = GPIO_INTR_DISABLE;
166 gpio_config(&conf);
167#ifdef CONFIG_FREERTOS_USE_TICKLESS_IDLE
168 // If auto light sleep is used gpio sleep mode needs to be disabled for the pin
169 gpio_sleep_sel_dis(this->get_pin_num());
170#endif
171 if (this->flags_ & gpio::FLAG_OUTPUT) {
172 gpio_set_drive_capability(this->get_pin_num(), this->get_drive_strength());
173 }
174#ifdef USE_GPIO_HOLD
175 // gpio_hold_dis logs an error on input-only pads
176 if (GPIO_IS_VALID_OUTPUT_GPIO(this->get_pin_num())) {
177 if (!this->get_hold()) {
178 // Release a hold left behind by an earlier boot
179 gpio_hold_dis(this->get_pin_num());
180 } else if (this->flags_ & gpio::FLAG_INPUT) {
181 // Inputs apply the config now so reads work; outputs wait for the first write
182 refresh_hold(*this);
183 }
184 }
185#endif
186}
187
189 // can't call gpio_config here because that logs in esp-idf which may cause issues
190 gpio_set_direction(this->get_pin_num(), flags_to_mode(flags));
191 gpio_pull_mode_t pull_mode = GPIO_FLOATING;
193 pull_mode = GPIO_PULLUP_PULLDOWN;
194 } else if (flags & gpio::FLAG_PULLUP) {
195 pull_mode = GPIO_PULLUP_ONLY;
196 } else if (flags & gpio::FLAG_PULLDOWN) {
197 pull_mode = GPIO_PULLDOWN_ONLY;
198 }
199 gpio_set_pull_mode(this->get_pin_num(), pull_mode);
200#ifdef USE_GPIO_HOLD
201 refresh_hold(*this);
202#endif
203}
204
206 return bool(gpio_get_level(this->get_pin_num())) != this->pin_flags_.inverted;
207}
209 gpio_set_level(this->get_pin_num(), value != this->pin_flags_.inverted ? 1 : 0);
210#ifdef USE_GPIO_HOLD
211 refresh_hold(*this);
212#endif
213}
214void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->get_pin_num()); }
215
216} // namespace esp32
217
218using namespace esp32;
219
220// NOLINTBEGIN(clang-analyzer-core.FixedAddressDereference) -- some gpio_hal functions use MMIO at fixed addresses
221// internally
223 auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
224 return bool(gpio_hal_get_level(&GPIO_HAL, arg->pin)) != arg->inverted;
225}
226
227void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
228 auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
229 gpio_hal_set_level(&GPIO_HAL, arg->pin, value != arg->inverted);
230#ifdef USE_GPIO_HOLD
231 isr_refresh_hold(arg);
232#endif
233}
234
236 // not supported
237}
238
240 auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
241 gpio::Flags diff = (gpio::Flags)(flags ^ arg->flags);
242 if (diff & gpio::FLAG_OUTPUT) {
243 if (flags & gpio::FLAG_OUTPUT) {
244 gpio_hal_output_enable(&GPIO_HAL, arg->pin);
246 gpio_hal_od_enable(&GPIO_HAL, arg->pin);
247 } else {
248 gpio_hal_output_disable(&GPIO_HAL, arg->pin);
249 }
250 }
251 if (diff & gpio::FLAG_INPUT) {
252 if (flags & gpio::FLAG_INPUT) {
253 gpio_hal_input_enable(&GPIO_HAL, arg->pin);
254#if defined(USE_ESP32_VARIANT_ESP32)
255 if (arg->use_rtc) {
256 if (flags & gpio::FLAG_PULLUP) {
257 rtcio_hal_pullup_enable(arg->rtc_pin);
258 } else {
259 rtcio_hal_pullup_disable(arg->rtc_pin);
260 }
262 rtcio_hal_pulldown_enable(arg->rtc_pin);
263 } else {
264 rtcio_hal_pulldown_disable(arg->rtc_pin);
265 }
266 } else
267#endif
268 {
269 if (flags & gpio::FLAG_PULLUP) {
270 gpio_hal_pullup_en(&GPIO_HAL, arg->pin);
271 } else {
272 gpio_hal_pullup_dis(&GPIO_HAL, arg->pin);
273 }
275 gpio_hal_pulldown_en(&GPIO_HAL, arg->pin);
276 } else {
277 gpio_hal_pulldown_dis(&GPIO_HAL, arg->pin);
278 }
279 }
280 } else {
281 gpio_hal_input_disable(&GPIO_HAL, arg->pin);
282 }
283 }
284#ifdef USE_GPIO_HOLD
285 isr_refresh_hold(arg);
286#endif
287 arg->flags = flags;
288}
289// NOLINTEND(clang-analyzer-core.FixedAddressDereference)
290
291} // namespace esphome
292
293#endif // USE_ESP32
bool get_hold() const
Definition gpio_pin.h:89
Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
Definition gpio_pin.h:98
void digital_write(bool value)
Definition gpio.cpp:227
void pin_mode(gpio::Flags flags)
Definition gpio.cpp:239
gpio_num_t get_pin_num() const
Definition gpio.h:37
struct esphome::esp32::ESP32InternalGPIOPin::PinFlags pin_flags_
gpio_drive_cap_t get_drive_strength() const
Definition gpio.h:38
void set_hold(bool hold) const
Apply or release the pad hold. Does nothing unless the pin is configured with hold_state.
Definition gpio.cpp:144
void pin_mode(gpio::Flags flags) override
Definition gpio.cpp:188
void attach_interrupt_(void(*func)(void *), void *arg, gpio::InterruptType type) const
Definition gpio.cpp:107
void digital_write(bool value) override
Definition gpio.cpp:208
ISRInternalGPIOPin to_isr() const
Definition gpio.cpp:91
size_t dump_summary(char *buffer, size_t len) const override
Definition gpio.cpp:139
uint16_t flags
uint8_t type
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, CRASH_LOG_TAG)
@ INTERRUPT_FALLING_EDGE
Definition gpio_pin.h:53
@ INTERRUPT_RISING_EDGE
Definition gpio_pin.h:52
@ INTERRUPT_HIGH_LEVEL
Definition gpio_pin.h:56
@ INTERRUPT_LOW_LEVEL
Definition gpio_pin.h:55
@ INTERRUPT_ANY_EDGE
Definition gpio_pin.h:54
const void size_t len
Definition hal.h:68
static void uint32_t