1#include "linux_gpio.hpp"
15#if !defined(XR_LINUX_GPIO_DISABLE_V2) && defined(GPIO_V2_LINE_FLAG_INPUT) && \
16 defined(GPIO_V2_GET_LINEINFO_IOCTL) && defined(GPIO_V2_GET_LINE_IOCTL) && \
17 defined(GPIO_V2_LINE_SET_CONFIG_IOCTL) && defined(GPIO_V2_LINE_GET_VALUES_IOCTL) && \
18 defined(GPIO_V2_LINE_SET_VALUES_IOCTL) && defined(GPIO_V2_LINE_EVENT_RISING_EDGE) && \
19 defined(GPIO_V2_LINE_EVENT_FALLING_EDGE)
20#define XR_LINUX_GPIO_HAS_V2 1
22#define XR_LINUX_GPIO_HAS_V2 0
25#if defined(GPIOHANDLE_REQUEST_BIAS_DISABLE) && \
26 defined(GPIOHANDLE_REQUEST_BIAS_PULL_UP) && \
27 defined(GPIOHANDLE_REQUEST_BIAS_PULL_DOWN)
28#define XR_LINUX_GPIO_V1_HAS_BIAS_FLAGS 1
30#define XR_LINUX_GPIO_V1_HAS_BIAS_FLAGS 0
33#if defined(GPIOHANDLE_SET_CONFIG_IOCTL)
34#define XR_LINUX_GPIO_V1_HAS_SET_CONFIG 1
36#define XR_LINUX_GPIO_V1_HAS_SET_CONFIG 0
41constexpr const char* kLinuxGPIOConsumer =
"LinuxGPIO";
44 bool interrupt_enabled)
46 if (!interrupt_enabled &&
58void CopyConsumer(
char (&dst)[N],
const char* src)
60 std::strncpy(dst, src, N - 1U);
64#if XR_LINUX_GPIO_HAS_V2
72 flags |= GPIO_V2_LINE_FLAG_INPUT;
75 flags |= GPIO_V2_LINE_FLAG_OUTPUT;
78 flags |= GPIO_V2_LINE_FLAG_OUTPUT | GPIO_V2_LINE_FLAG_OPEN_DRAIN;
81 flags |= GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_RISING;
84 flags |= GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_FALLING;
87 flags |= GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_RISING |
88 GPIO_V2_LINE_FLAG_EDGE_FALLING;
95 flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
98 flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
101 flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
119 flags |= GPIOHANDLE_REQUEST_INPUT;
122 flags |= GPIOHANDLE_REQUEST_OUTPUT;
125 flags |= GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_OPEN_DRAIN;
132#if XR_LINUX_GPIO_V1_HAS_BIAS_FLAGS
133 flags |= GPIOHANDLE_REQUEST_BIAS_DISABLE;
137#if XR_LINUX_GPIO_V1_HAS_BIAS_FLAGS
138 flags |= GPIOHANDLE_REQUEST_BIAS_PULL_UP;
142#if XR_LINUX_GPIO_V1_HAS_BIAS_FLAGS
143 flags |= GPIOHANDLE_REQUEST_BIAS_PULL_DOWN;
156 return GPIOEVENT_REQUEST_RISING_EDGE;
158 return GPIOEVENT_REQUEST_FALLING_EDGE;
160 return GPIOEVENT_REQUEST_BOTH_EDGES;
166int SetNonBlocking(
int fd)
168 const int flags = fcntl(fd, F_GETFL, 0);
174 return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
177void DrainInterruptWakeFd(
int fd)
179 std::array<uint8_t, 32> buffer = {};
182 const ssize_t bytes = read(fd, buffer.data(), buffer.size());
188 if ((bytes < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR)))
197enum class PollReadableResult : int8_t
205PollReadableResult PollReadable(
int fd,
int wake_fd,
int timeout_ms)
207 std::array<pollfd, 2> poll_fds = {};
211 poll_fds[nfds].fd = fd;
212 poll_fds[nfds].events = POLLIN;
218 poll_fds[nfds].fd = wake_fd;
219 poll_fds[nfds].events = POLLIN;
225 const int ret = poll(poll_fds.data(), nfds, timeout_ms);
226 if ((ret < 0) && (errno == EINTR))
232 return PollReadableResult::ERROR;
237 return PollReadableResult::TIMEOUT;
240 if ((wake_fd >= 0) && ((poll_fds[1].revents & POLLIN) != 0))
242 DrainInterruptWakeFd(wake_fd);
243 return PollReadableResult::WAKE;
246 if ((poll_fds[0].revents & (POLLNVAL | POLLERR | POLLHUP)) != 0)
249 return PollReadableResult::ERROR;
252 if ((poll_fds[0].revents & POLLIN) != 0)
254 return PollReadableResult::DATA;
258 return PollReadableResult::ERROR;
262bool IsKnownGPIOEvent(uint32_t event_id)
264 if (event_id == GPIOEVENT_EVENT_RISING_EDGE)
269 if (event_id == GPIOEVENT_EVENT_FALLING_EDGE)
274#if XR_LINUX_GPIO_HAS_V2
275 if (event_id == GPIO_V2_LINE_EVENT_RISING_EDGE)
280 if (event_id == GPIO_V2_LINE_EVENT_FALLING_EDGE)
286 XR_LOG_WARN(
"Unknown GPIO event id: %u", event_id);
295LinuxGPIO::LinuxGPIO(
const std::string& chip_path,
unsigned int line_offset)
296 : chip_path_(chip_path), line_offset_(line_offset)
298 UNUSED(InitInterruptWakePipe());
302LinuxGPIO::~LinuxGPIO()
304 interrupt_thread_exit_.store(
true);
305 request_kind_.store(RequestKind::NONE);
306 interrupt_enabled_.store(
false);
307 NotifyInterruptThread();
308 WaitForInterruptThreadStopped();
311 CloseInterruptWakePipe();
314bool LinuxGPIO::Read()
316 if (EnsureConfigured() != ErrorCode::OK)
321 const int request_fd = request_fd_.load();
322 if (abi_version_.load() == AbiVersion::V2)
324#if XR_LINUX_GPIO_HAS_V2
325 gpio_v2_line_values values = {};
327 if (ioctl(request_fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &values) < 0)
329 XR_LOG_ERROR(
"Failed to read GPIO value: %s", std::strerror(errno));
332 return (values.bits & 1ULL) != 0U;
339 gpiohandle_data values = {};
340 if (ioctl(request_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &values) < 0)
342 XR_LOG_ERROR(
"Failed to read GPIO value: %s", std::strerror(errno));
346 return values.values[0] != 0U;
349void LinuxGPIO::Write(
bool value)
351 if (EnsureConfigured() != ErrorCode::OK)
356 const int request_fd = request_fd_.load();
357 if (abi_version_.load() == AbiVersion::V2)
359#if XR_LINUX_GPIO_HAS_V2
360 gpio_v2_line_values values = {};
362 values.bits = value ? 1ULL : 0ULL;
363 if (ioctl(request_fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &values) < 0)
365 XR_LOG_WARN(
"Failed to write GPIO value: %s", std::strerror(errno));
374 gpiohandle_data values = {};
375 values.values[0] = value ? 1U : 0U;
376 if (ioctl(request_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &values) < 0)
378 XR_LOG_WARN(
"Failed to write GPIO value: %s", std::strerror(errno));
384 if (EnsureConfigured() != ErrorCode::OK)
386 return ErrorCode::STATE_ERR;
389 if (!IsInterruptDirection(current_config_.direction))
391 return ErrorCode::ARG_ERR;
394 if (interrupt_enabled_.load())
396 return ErrorCode::OK;
400 if (NeedsRequestReopen(current_config_))
402 ec = ReopenRequest(current_config_);
404 else if (abi_version_.load() == AbiVersion::V2)
406 ec = ReconfigureRequestV2(current_config_);
410 ec = ReconfigureRequestV1(current_config_);
412 if (ec != ErrorCode::OK)
418 interrupt_enabled_.store(
true);
419 StartInterruptThread();
420 return ErrorCode::OK;
425 if (EnsureConfigured() != ErrorCode::OK)
427 return ErrorCode::STATE_ERR;
430 if (!IsInterruptDirection(current_config_.direction))
432 return ErrorCode::ARG_ERR;
435 if (!interrupt_enabled_.load())
437 return ErrorCode::OK;
440 const Configuration inactive_config = ResolveRequestConfig(current_config_,
false);
442 if (NeedsRequestReopen(inactive_config))
444 ec = ReopenRequest(inactive_config);
446 else if (abi_version_.load() == AbiVersion::V2)
448 ec = ReconfigureRequestV2(inactive_config);
452 ec = ReconfigureRequestV1(inactive_config);
454 if (ec != ErrorCode::OK)
460 interrupt_enabled_.store(
false);
461 return ErrorCode::OK;
467 if (chip_ready != ErrorCode::OK)
472 const bool keep_interrupt_enabled =
473 interrupt_enabled_.load() && IsInterruptDirection(config.
direction);
475 ResolveRequestConfig(config, keep_interrupt_enabled);
478 if (request_fd_.load() < 0)
480 ec = ReopenRequest(request_config);
482 else if (NeedsRequestReopen(request_config))
484 ec = ReopenRequest(request_config);
486 else if (abi_version_.load() == AbiVersion::V2)
488 ec = ReconfigureRequestV2(request_config);
492 ec = ReconfigureRequestV1(request_config);
495 if (ec != ErrorCode::OK)
500 current_config_ = config;
502 interrupt_enabled_.store(keep_interrupt_enabled);
503 if (keep_interrupt_enabled)
505 StartInterruptThread();
507 return ErrorCode::OK;
510void LinuxGPIO::StartInterruptThread()
512 if (interrupt_thread_started_.exchange(
true))
517 interrupt_thread_.Create<LinuxGPIO*>(
518 this, [](LinuxGPIO* self) { self->InterruptLoop(); },
"irq_gpio",
519 INTERRUPT_THREAD_STACK_SIZE, Thread::Priority::MEDIUM);
522void LinuxGPIO::InterruptLoop()
524 while (!interrupt_thread_exit_.load())
526 if (!interrupt_enabled_.load())
532 if (request_kind_.load() != RequestKind::EVENT)
538 const int fd = request_fd_.load();
545 size_t event_count = 0;
546 interrupt_poll_active_.store(
true);
547 const ErrorCode pump = PumpEventQueue(fd, abi_version_.load(), event_count, 100);
548 interrupt_poll_active_.store(
false);
549 if (pump == ErrorCode::OK)
551 for (
size_t i = 0; i < event_count; ++i)
553 callback_.Run(
false);
558 if (pump != ErrorCode::EMPTY)
564 interrupt_poll_active_.store(
false);
565 interrupt_thread_started_.store(
false);
572 return ErrorCode::OK;
575 chip_fd_ = open(chip_path_.c_str(), O_RDONLY | O_CLOEXEC);
578 XR_LOG_ERROR(
"Failed to open GPIO chip: %s (%s)", chip_path_.c_str(),
579 std::strerror(errno));
581 return ErrorCode::INIT_ERR;
584 return DetectAbiVersion();
587void LinuxGPIO::CloseChip()
594 abi_version_.store(AbiVersion::UNKNOWN);
597void LinuxGPIO::CloseRequest()
599 request_kind_.store(RequestKind::NONE);
600 interrupt_enabled_.store(
false);
601 NotifyInterruptThread();
602 WaitForInterruptLoopIdle();
604 const int request_fd = request_fd_.exchange(-1);
610 request_kind_.store(RequestKind::NONE);
611 interrupt_enabled_.store(
false);
615ErrorCode LinuxGPIO::InitInterruptWakePipe()
617 if (interrupt_wake_pipe_[0] >= 0)
619 return ErrorCode::OK;
622 if (pipe(interrupt_wake_pipe_) < 0)
624 XR_LOG_ERROR(
"Failed to create GPIO interrupt wake pipe: %s", std::strerror(errno));
625 return ErrorCode::INIT_ERR;
628 if ((SetNonBlocking(interrupt_wake_pipe_[0]) < 0) ||
629 (SetNonBlocking(interrupt_wake_pipe_[1]) < 0))
631 XR_LOG_ERROR(
"Failed to configure GPIO interrupt wake pipe: %s",
632 std::strerror(errno));
633 CloseInterruptWakePipe();
634 return ErrorCode::INIT_ERR;
637 return ErrorCode::OK;
640void LinuxGPIO::CloseInterruptWakePipe()
642 for (
int& fd : interrupt_wake_pipe_)
652void LinuxGPIO::NotifyInterruptThread()
654 if (interrupt_wake_pipe_[1] < 0)
659 const uint8_t signal = 1U;
662 const ssize_t ret = write(interrupt_wake_pipe_[1], &signal,
sizeof(signal));
668 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
670 DrainInterruptWakeFd(interrupt_wake_pipe_[0]);
683void LinuxGPIO::WaitForInterruptLoopIdle()
685 if (!interrupt_thread_started_.load())
690 for (uint32_t i = 0; i < 200; ++i)
692 if (!interrupt_poll_active_.load())
700 XR_LOG_WARN(
"Timed out waiting for GPIO interrupt loop to go idle");
703void LinuxGPIO::WaitForInterruptThreadStopped()
705 if (!interrupt_thread_started_.load())
710 for (uint32_t i = 0; i < 500; ++i)
712 if (!interrupt_thread_started_.load())
717 NotifyInterruptThread();
721 XR_LOG_WARN(
"Timed out waiting for GPIO interrupt thread to stop");
726 if (abi_version_.load() != AbiVersion::UNKNOWN)
728 return ErrorCode::OK;
731 gpiochip_info chip_info = {};
732 if (ioctl(chip_fd_, GPIO_GET_CHIPINFO_IOCTL, &chip_info) < 0)
734 XR_LOG_ERROR(
"Failed to read GPIO chip info: %s", std::strerror(errno));
735 return ErrorCode::FAILED;
738 if (chip_info.lines == 0U)
740 XR_LOG_ERROR(
"GPIO chip exposes zero lines");
741 return ErrorCode::FAILED;
744#if XR_LINUX_GPIO_HAS_V2
745 gpio_v2_line_info info = {};
746 info.offset = (line_offset_ < chip_info.lines) ? line_offset_ : 0U;
747 if (ioctl(chip_fd_, GPIO_V2_GET_LINEINFO_IOCTL, &info) == 0)
749 abi_version_.store(AbiVersion::V2);
750 return ErrorCode::OK;
753 if ((errno == ENOTTY) || (errno == EINVAL)
755 || (errno == EOPNOTSUPP)
759 abi_version_.store(AbiVersion::V1);
760 return ErrorCode::OK;
763 XR_LOG_ERROR(
"Failed to probe GPIO chardev ABI: %s", std::strerror(errno));
764 return ErrorCode::FAILED;
766 abi_version_.store(AbiVersion::V1);
767 return ErrorCode::OK;
771ErrorCode LinuxGPIO::ReopenRequest(Configuration config)
775 if (abi_version_.load() == AbiVersion::V2)
777 return OpenRequestV2(config);
780 return OpenRequestV1(config);
783ErrorCode LinuxGPIO::OpenRequestV2(Configuration config)
785#if XR_LINUX_GPIO_HAS_V2
786 gpio_v2_line_request request = {};
787 request.offsets[0] = line_offset_;
788 request.num_lines = 1U;
789 request.event_buffer_size =
790 IsInterruptDirection(config.direction) ? EVENT_BUFFER_CAPACITY : 0U;
791 CopyConsumer(request.consumer, kLinuxGPIOConsumer);
792 request.config.flags = BuildLineFlagsV2(config);
794 if (ioctl(chip_fd_, GPIO_V2_GET_LINE_IOCTL, &request) < 0)
796 XR_LOG_ERROR(
"Failed to request GPIO line (v2): %s", std::strerror(errno));
797 return ErrorCode::FAILED;
800 request_fd_.store(request.fd);
801 request_kind_.store(IsInterruptDirection(config.direction) ? RequestKind::EVENT
802 : RequestKind::HANDLE);
803 if (SetNonBlocking(request.fd) < 0)
805 XR_LOG_ERROR(
"Failed to enable non-blocking GPIO request fd: %s",
806 std::strerror(errno));
808 return ErrorCode::FAILED;
811 return ErrorCode::OK;
815 return ErrorCode::FAILED;
819ErrorCode LinuxGPIO::ReconfigureRequestV2(Configuration config)
821#if XR_LINUX_GPIO_HAS_V2
822 const bool was_interrupt_request =
823 (request_kind_.load() == RequestKind::EVENT) && interrupt_enabled_.load();
824 if (was_interrupt_request)
826 request_kind_.store(RequestKind::NONE);
827 interrupt_enabled_.store(
false);
828 NotifyInterruptThread();
829 WaitForInterruptLoopIdle();
832 gpio_v2_line_config line_config = {};
833 line_config.flags = BuildLineFlagsV2(config);
835 if (ioctl(request_fd_.load(), GPIO_V2_LINE_SET_CONFIG_IOCTL, &line_config) < 0)
837 XR_LOG_ERROR(
"Failed to reconfigure GPIO line (v2): %s", std::strerror(errno));
838 return ErrorCode::FAILED;
841 request_kind_.store(IsInterruptDirection(config.direction) ? RequestKind::EVENT
842 : RequestKind::HANDLE);
843 if (was_interrupt_request && IsInterruptDirection(config.direction))
845 interrupt_enabled_.store(
true);
847 return ErrorCode::OK;
851 return ErrorCode::FAILED;
855ErrorCode LinuxGPIO::OpenRequestV1(Configuration config)
857 if (IsInterruptDirection(config.direction))
859 gpioevent_request request = {};
860 request.lineoffset = line_offset_;
861 request.handleflags = BuildHandleFlagsV1(config);
862 request.eventflags = BuildEventFlagsV1(config.direction);
863 CopyConsumer(request.consumer_label, kLinuxGPIOConsumer);
865 if (ioctl(chip_fd_, GPIO_GET_LINEEVENT_IOCTL, &request) < 0)
867 XR_LOG_ERROR(
"Failed to request GPIO event line (v1): %s", std::strerror(errno));
868 return ErrorCode::FAILED;
871 request_fd_.store(request.fd);
872 request_kind_.store(RequestKind::EVENT);
876 gpiohandle_request request = {};
877 request.lineoffsets[0] = line_offset_;
879 request.flags = BuildHandleFlagsV1(config);
880 CopyConsumer(request.consumer_label, kLinuxGPIOConsumer);
882 if (ioctl(chip_fd_, GPIO_GET_LINEHANDLE_IOCTL, &request) < 0)
884 XR_LOG_ERROR(
"Failed to request GPIO handle line (v1): %s", std::strerror(errno));
885 return ErrorCode::FAILED;
888 request_fd_.store(request.fd);
889 request_kind_.store(RequestKind::HANDLE);
892 if (SetNonBlocking(request_fd_.load()) < 0)
894 XR_LOG_ERROR(
"Failed to enable non-blocking GPIO request fd: %s",
895 std::strerror(errno));
897 return ErrorCode::FAILED;
900 return ErrorCode::OK;
903ErrorCode LinuxGPIO::ReconfigureRequestV1(Configuration config)
905 if (request_kind_.load() != RequestKind::HANDLE)
907 return ReopenRequest(config);
910#if XR_LINUX_GPIO_V1_HAS_SET_CONFIG
911 gpiohandle_config handle_config = {};
912 handle_config.flags = BuildHandleFlagsV1(config);
913 if (ioctl(request_fd_.load(), GPIOHANDLE_SET_CONFIG_IOCTL, &handle_config) < 0)
915 XR_LOG_ERROR(
"Failed to reconfigure GPIO line (v1): %s", std::strerror(errno));
916 return ErrorCode::FAILED;
919 return ErrorCode::OK;
921 return ReopenRequest(config);
925ErrorCode LinuxGPIO::PumpEventQueue(
int fd, AbiVersion abi_version,
size_t& event_count,
926 int timeout_ms)
const
930 return ErrorCode::STATE_ERR;
933 const PollReadableResult ready = PollReadable(fd, interrupt_wake_pipe_[0], timeout_ms);
934 if (ready == PollReadableResult::ERROR)
938 return ErrorCode::EMPTY;
940 XR_LOG_ERROR(
"Failed to poll GPIO fd: %s", std::strerror(errno));
941 return ErrorCode::FAILED;
944 if ((ready == PollReadableResult::TIMEOUT) || (ready == PollReadableResult::WAKE))
946 return ErrorCode::EMPTY;
949 if (abi_version == AbiVersion::V2)
951 return ReadEventsV2(fd, event_count);
954 return ReadEventsV1(fd, event_count);
957ErrorCode LinuxGPIO::ReadEventsV2(
int fd,
size_t& event_count)
const
959#if XR_LINUX_GPIO_HAS_V2
960 bool received =
false;
963 std::array<gpio_v2_line_event, EVENT_BUFFER_CAPACITY> events = {};
964 const ssize_t bytes = read(fd, events.data(),
sizeof(events));
967 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
973 return received ? ErrorCode::OK : ErrorCode::EMPTY;
976 XR_LOG_ERROR(
"Failed to read GPIO v2 events: %s", std::strerror(errno));
977 return ErrorCode::FAILED;
985 if ((bytes %
static_cast<ssize_t
>(
sizeof(gpio_v2_line_event))) != 0)
987 XR_LOG_ERROR(
"Corrupted GPIO v2 event payload length");
988 return ErrorCode::FAILED;
991 const size_t count =
static_cast<size_t>(bytes /
sizeof(gpio_v2_line_event));
992 for (
size_t i = 0; i < count; ++i)
994 if (!IsKnownGPIOEvent(events[i].
id))
1004 return received ? ErrorCode::OK : ErrorCode::EMPTY;
1009 return ErrorCode::FAILED;
1013ErrorCode LinuxGPIO::ReadEventsV1(
int fd,
size_t& event_count)
const
1015 bool received =
false;
1018 gpioevent_data
event = {};
1019 const ssize_t bytes = read(fd, &event,
sizeof(event));
1022 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
1028 return received ? ErrorCode::OK : ErrorCode::EMPTY;
1031 XR_LOG_ERROR(
"Failed to read GPIO v1 events: %s", std::strerror(errno));
1032 return ErrorCode::FAILED;
1040 if (bytes !=
static_cast<ssize_t
>(
sizeof(event)))
1042 XR_LOG_ERROR(
"Corrupted GPIO v1 event payload length");
1043 return ErrorCode::FAILED;
1046 if (!IsKnownGPIOEvent(event.id))
1055 return received ? ErrorCode::OK : ErrorCode::EMPTY;
1058ErrorCode LinuxGPIO::EnsureConfigured()
const
1060 if (!has_config_ || (request_fd_.load() < 0))
1062 XR_LOG_ERROR(
"GPIO is not configured");
1064 return ErrorCode::STATE_ERR;
1067 return ErrorCode::OK;
1069bool LinuxGPIO::IsInterruptDirection(Direction direction)
1071 return direction == Direction::RISING_INTERRUPT ||
1072 direction == Direction::FALL_INTERRUPT ||
1073 direction == Direction::FALL_RISING_INTERRUPT;
1076bool LinuxGPIO::NeedsRequestReopen(Configuration config)
const
1078 if (request_fd_.load() < 0)
1083 const bool old_interrupt =
1084 interrupt_enabled_.load() && IsInterruptDirection(current_config_.direction);
1085 const bool new_interrupt = IsInterruptDirection(config.direction);
1087 if (abi_version_.load() == AbiVersion::V2)
1089 return old_interrupt != new_interrupt;
1102 return request_kind_.load() != RequestKind::HANDLE;
Direction
定义 GPIO 引脚的方向类型。Defines the direction types for GPIO pins.
@ OUTPUT_PUSH_PULL
推挽输出模式。Push-pull output mode.
@ RISING_INTERRUPT
上升沿中断模式。Rising edge interrupt mode.
@ FALL_RISING_INTERRUPT
双沿触发中断模式。Both edge interrupt mode.
@ OUTPUT_OPEN_DRAIN
开漏输出模式。Open-drain output mode.
@ FALL_INTERRUPT
下降沿中断模式。Falling edge interrupt mode.
@ NONE
无上拉或下拉。No pull-up or pull-down.
@ DOWN
下拉模式。Pull-down mode.
存储 GPIO 配置参数的结构体。Structure storing GPIO configuration parameters.
Pull pull
GPIO 上拉/下拉配置。GPIO pull-up/pull-down configuration.
Direction direction
GPIO 引脚方向。GPIO pin direction.