news 2026/8/19 21:10:14

nRF54L15 GPIOTE初始化详解:从基础配置到硬件自动化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
nRF54L15 GPIOTE初始化详解:从基础配置到硬件自动化实战

1. 从零开始:为什么nRF54L15的GPIOTE值得单独聊

如果你刚拿到一块nRF54L15的开发板,或者正在从nRF52系列迁移过来,第一个让你感觉“既熟悉又陌生”的模块,很可能就是GPIOTE。在nRF52时代,操作一个GPIO引脚的高低电平,最直接的方式可能就是调用nrf_gpio_pin_write这类函数,简单粗暴。但到了nRF54系列,尤其是主打高性能和低功耗的nRF54L15上,Nordic Semiconductor强烈推荐,甚至可以说是“设计导向”你使用GPIOTE(GPIO Tasks and Events)来完成绝大多数GPIO操作。

这背后有一个核心的驱动力:将CPU从繁琐的轮询和位操作中解放出来。在nRF54L15的架构里,GPIOTE不再是一个简单的“GPIO增强版”,而是一个能够与PPI(Programmable Peripheral Interconnect)、DPPI(Distributed Programmable Peripheral Interconnect)以及其它硬件外设(如定时器、射频模块)直接协作的独立硬件单元。这意味着,你可以配置一个GPIO引脚在特定事件(比如定时器匹配)发生时自动翻转,而整个过程完全不需要CPU干预。对于需要精确时序控制(如驱动WS2812B灯带)或极致低功耗(由事件触发,CPU深度睡眠)的应用场景,这是不可或缺的能力。

所以,初始化GPIOTE,在nRF54L15上不再是“可选项”,而是构建高效、可靠嵌入式系统的“起手式”。它决定了你后续能否灵活地运用事件驱动架构,能否实现复杂的硬件自动联动。本篇随笔,我就结合在NCS(nRF Connect SDK)v2.6.x环境下的实际项目经验,拆解nRF54L15上GPIOTE初始化的完整流程、关键配置,以及那些容易踩坑的细节。我会假设你已经有基本的Zephyr RTOS和NCS开发环境搭建经验,我们的焦点将完全放在GPIOTE本身。

2. 理解nRF54L15 GPIOTE的架构与核心概念

在动手写代码之前,我们必须先厘清几个关键概念,否则配置文件时很容易一头雾水。nRF54L15的GPIOTE模块与nRF52系列有继承性,但也有了显著增强,尤其是在通道和端口事件的处理上。

2.1 GPIOTE通道(Channel):有限的硬件资源

这是GPIOTE模块最核心的稀缺资源。nRF54L15提供了多个GPIOTE通道(具体数量需查阅芯片数据手册,例如可能是8个)。每个通道在同一时间只能关联一个GPIO引脚,并为其配置一种任务或事件模式。你可以把通道想象成一条专用的“硬件连接线”,一头连着某个GPIO引脚,另一头连着GPIOTE模块内部的一个特定功能单元。

通道支持两种主要工作模式:

  1. 任务模式(Task Mode):配置后,你可以通过触发一个“任务”(Task),让GPIOTE硬件自动去设置或清除该通道所关联引脚的电平。这是“输出”场景的基石。
  2. 事件模式(Event Mode):配置后,当该通道所关联的引脚发生指定的电平变化(如上升沿、下降沿)时,GPIOTE硬件会自动产生一个“事件”(Event)。这个事件可以通过PPI/DPPI直接触发其他外设的任务,完全绕过CPU。这是“输入”和事件驱动的核心。

关键理解:一个引脚如果想同时用于输出任务(如PWM控制)和输入事件(如按键中断),理论上需要占用两个独立的GPIOTE通道。因此,在系统设计初期,就需要合理规划这些通道的分配,避免资源耗尽。

2.2 端口事件(Port Event):引脚变化的集合检测

除了每个引脚独占的通道事件,GPIOTE还提供了一个“端口事件”。它可以监视多达32个GPIO引脚(通常是一个端口组)的电平变化。当这组引脚中任何一个引脚的电平状态发生改变时,都会触发同一个端口事件。

端口事件 vs 通道事件

  • 通道事件:精度高,可以识别特定的边沿(上升沿、下降沿),并且能知道是哪个具体引脚触发的。消耗GPIOTE通道资源。
  • 端口事件:只能知道这个端口组里有人变了,但无法直接区分是哪个引脚变的,也无法区分是上升沿还是下降沿。不占用GPIOTE通道资源。

端口事件通常用于一些不需要精确知道是谁、怎么变的场景,比如唤醒处于深度睡眠的CPU。CPU被唤醒后,再去读取具体的引脚状态进行判断。在nRF54L15的低功耗设计中,端口事件至关重要。

2.3 任务(Task)与事件(Event):硬件自动化的语言

在nRF的生态中,“任务”和“事件”是硬件外设之间通信的通用语言。

  • 任务(Task):是一个“动作”或“命令”。例如,GPIOTE_TASKS_OUT[0]任务被触发,就会让通道0关联的引脚执行输出操作(置高或置低,取决于配置)。任务通常由软件写寄存器、定时器通过PPI、或其他事件来触发。
  • 事件(Event):是一个“通知”或“状态”。例如,GPIOTE_EVENTS_IN[0]事件产生,表示通道0关联的引脚发生了配置的边沿变化。事件可以触发中断,也可以通过PPI去触发其他任务。

GPIOTE初始化的大部分工作,就是在Zephyr的配置框架下,正确地建立“引脚”与“通道/端口”的关联,并定义好它们产生“事件”或响应“任务”的规则。

3. 基于NCS与Zephyr的GPIOTE初始化实战

NCS使用Zephyr RTOS,因此GPIOTE的初始化强烈依赖于设备树(Device Tree)和Kconfig配置,代码层面则主要使用Zephyr提供的GPIO和GPIOTE API。下面我们分步骤进行。

3.1 设备树(DTS)配置:硬件资源的声明

首先,我们需要在项目的设备树文件(通常是boards/arm/<your_board>/<your_board>.dts或项目目录下的app.overlay)中,声明对GPIOTE外设的使用。对于nRF54L15,GPIOTE节点通常已经由SoC的dtsi文件定义好了,我们主要是在应用层覆盖文件中进行引脚分配。

假设我们要使用P0.03引脚作为LED输出(使用GPIOTE任务),使用P0.12引脚作为按键输入(使用GPIOTE事件)。

在你的app.overlay文件中添加:

/ { /* 定义LED设备,关联到GPIO0的03引脚 */ led0: led_0 { compatible = "gpio-leds"; led0: led_0 { gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; label = "User LED 0"; }; }; /* 定义按键设备,关联到GPIO0的12引脚 */ buttons { compatible = "gpio-keys"; button0: button_0 { gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; label = "User Button 0"; zephyr,code = <INPUT_KEY_0>; }; }; }; /* 这是一个关键配置:启用GPIOTE驱动 */ &gpiote { status = "okay"; };

配置解读

  1. compatible = "gpio-leds"compatible = "gpio-keys"告诉Zephyr这是标准的LED和按键设备,Zephyr会为它们创建对应的设备实例,并允许使用标准的GPIO API。
  2. gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>指定了具体的GPIO控制器(gpio0)、引脚编号(3)和有效电平(高电平点亮)。对于按键,GPIO_ACTIVE_LOW表示按下时引脚为低电平,GPIO_PULL_UP启用了内部上拉电阻。
  3. &gpiote { status = "okay"; }确保GPIOTE外设驱动被启用。这是后续使用GPIOTE高级功能的前提。

注意:设备树配置主要解决了引脚的“归属”和“基本电气特性”(如上拉下拉)问题。但它并没有直接指定这个引脚要使用GPIOTE的哪个通道。通道的绑定是在运行时通过API调用完成的。

3.2 Kconfig配置:功能与驱动的选择

接下来,需要在项目的Kconfig文件(通常是prj.conf)中,启用必要的Zephyr驱动和功能。

# 启用GPIO驱动(必须) CONFIG_GPIO=y # 启用GPIOTE驱动(必须,用于底层硬件访问) CONFIG_NRFX_GPIOTE=y # 如果使用GPIOTE事件的中断回调功能,需要启用GPIO回调 CONFIG_GPIO_CALLBACK=y # 如果使用定义的LED和按键设备,启用对应的驱动 CONFIG_LED=y CONFIG_INPUT=y CONFIG_INPUT_GPIO=y

配置解读

  • CONFIG_NRFX_GPIOTE=y:这是nRF系列芯片GPIOTE底层驱动(nrfx库)的开关。必须启用,否则所有GPIOTE相关API都无法工作。
  • CONFIG_GPIO_CALLBACK=y:当你想为GPIOTE输入事件设置一个中断回调函数时,这个配置必须打开。它提供了gpio_add_callbackgpio_remove_callback等API。

3.3 代码初始化:获取设备、配置引脚与回调

现在进入C代码部分。我们创建一个main.c文件。

#include <zephyr/kernel.h> #include <zephyr/drivers/gpio.h> #include <zephyr/input/input.h> /* 定义线程栈和优先级 */ #define LED_THREAD_STACK_SIZE 512 #define LED_THREAD_PRIORITY 5 /* 通过设备树标签获取设备实例 */ static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios); static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios); // 假设按键别名是sw0 /* GPIO回调结构体 */ static struct gpio_callback button_cb_data; /* 按键回调函数 */ void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { /* 注意:这个回调是在中断上下文(ISR)中执行的! 必须快速处理,不能调用可能导致阻塞的API(如k_sleep)。 通常只做标记,通过信号量、消息队列等通知工作线程。 */ printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32()); } void main(void) { int ret; printk("nRF54L15 GPIOTE Initialization Demo\n"); /* 1. 检查设备是否就绪 */ if (!gpio_is_ready_dt(&led)) { printk("LED device is not ready\n"); return; } if (!gpio_is_ready_dt(&button)) { printk("Button device is not ready\n"); return; } /* 2. 配置LED引脚为输出,并初始化为低电平(熄灭)*/ ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE); if (ret < 0) { printk("Failed to configure LED pin: %d\n", ret); return; } /* 3. 配置按键引脚为输入,并启用中断 */ /* 注意:GPIO_INT_EDGE_TO_ACTIVE 对应下降沿(因为按键是ACTIVE_LOW) */ ret = gpio_pin_configure_dt(&button, GPIO_INPUT | GPIO_INT_EDGE_TO_ACTIVE); if (ret < 0) { printk("Failed to configure button pin: %d\n", ret); return; } /* 4. 初始化GPIO回调结构体,并添加回调函数 */ gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin)); ret = gpio_add_callback_dt(&button, &button_cb_data); if (ret < 0) { printk("Failed to add button callback: %d\n", ret); return; } /* 5. 启用按键引脚的中断 */ ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE); if (ret < 0) { printk("Failed to enable button interrupt: %d\n", ret); return; } printk("Initialization complete. Press the button.\n"); /* 主循环:演示用GPIOTE任务控制LED闪烁 */ while (1) { /* 使用gpio_pin_toggle_dt,其底层在nRF54上会尝试使用GPIOTE任务 */ gpio_pin_toggle_dt(&led); k_msleep(1000); // 睡眠1秒 } }

代码关键点解析

  1. 设备获取GPIO_DT_SPEC_GET是一个宏,它通过设备树中的节点别名(如led0,sw0)来获取一个gpio_dt_spec结构体,里面包含了设备指针、引脚号和标志位。这是Zephyr推荐的方式。
  2. 引脚配置gpio_pin_configure_dt是核心配置函数。对于LED,我们配置为输出(GPIO_OUTPUT_INACTIVE)。对于按键,配置为输入(GPIO_INPUT)并带中断边沿类型(GPIO_INT_EDGE_TO_ACTIVE)。这里配置的中断边沿类型,就决定了GPIOTE通道事件在哪种电平变化下触发。
  3. 回调机制gpio_init_callback将回调函数button_pressed与一个具体的引脚位图绑定。gpio_add_callback_dt将这个回调注册到对应的GPIO设备。当GPIOTE硬件检测到事件并产生中断后,Zephyr的GPIO驱动层会调用这里注册的回调函数。
  4. 中断使能gpio_pin_interrupt_configure_dt是最终使能引脚中断的函数。在这之前,即使有边沿变化,也不会触发回调。这个调用最终会配置GPIOTE通道的IN事件。
  5. 任务使用:在主循环的gpio_pin_toggle_dt(&led)中,Zephyr的GPIO驱动会判断:如果该引脚配置了输出,并且系统支持,它会优先使用GPIOTE的OUT任务来翻转电平,而不是传统的CPU写寄存器方式。这在nRF54L15上是默认且高效的行为。

4. 进阶:直接使用nrfx GPIOTE API进行精细控制

Zephyr的标准GPIO API已经为我们做了很好的封装,适用于大部分场景。但如果你需要更底层的控制,比如明确指定使用哪个GPIOTE通道,或者配置端口事件,就需要直接调用Nordic提供的nrfx库API。这需要包含不同的头文件并直接操作硬件。

4.1 配置一个特定的GPIOTE通道任务

假设我们想用GPIOTE通道2来精确控制P0.05引脚,实现一个硬件PWM(通过PPI连接定时器)。

#include <nrfx_gpiote.h> #include <hal/nrf_gpio.h> void configure_gpiote_channel_for_task(void) { nrfx_gpiote_output_config_t output_config = { .drive = NRF_GPIO_PIN_S0S1, // 标准0/1驱动强度 .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT, // 输出引脚,输入断开 .pull = NRF_GPIO_PIN_NOPULL, // 无上拉下拉 }; nrfx_gpiote_task_config_t task_config = { .task_ch = 2, // 明确使用通道2 .polarity = NRF_GPIOTE_POLARITY_TOGGLE, // 任务触发时翻转电平 .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW, // 初始值为低 .p_port = NULL, // 不使用端口事件 }; // 初始化GPIOTE驱动(整个应用应只调用一次) nrfx_gpiote_init(6); // 中断优先级设为6 // 配置引脚P0.05使用通道2作为输出任务 nrfx_gpiote_output_configure(NRF_GPIO_PIN_MAP(0, 5), &output_config, &task_config); // 使能这个通道的GPIOTE功能 nrfx_gpiote_out_task_enable(NRF_GPIO_PIN_MAP(0, 5)); // 现在,可以通过触发 NRF_GPIOTE_TASKS_OUT[2] 任务来翻转P0.05的电平 // 例如:nrf_gpiote_task_trigger(NRF_GPIOTE_TASKS_OUT(2)); }

关键点

  • nrfx_gpiote_init必须在使用任何nrfx GPIOTE函数前调用一次,通常放在main函数开头。
  • NRF_GPIO_PIN_MAP(port, pin)宏用于将端口和引脚号编码成一个整数。
  • polarity = NRF_GPIOTE_POLARITY_TOGGLE意味着每次触发OUT任务,引脚电平就翻转一次。你也可以设置为NRF_GPIOTE_POLARITY_HIGHLOW,让任务将引脚设为固定电平。
  • 配置完成后,引脚的电平控制权就交给了GPIOTE硬件。你不能再使用普通的gpio_pin_set去操作它,否则会产生冲突。

4.2 配置GPIOTE端口事件用于唤醒

端口事件常用于系统休眠(System OFF 或 深度睡眠)时的唤醒。

#include <nrfx_gpiote.h> #include <zephyr/pm/pm.h> #include <zephyr/pm/policy.h> static void gpiote_port_event_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { // 注意:这个处理函数在端口事件中断中调用。 // 它只能知道是哪个PORT有变化,不知道具体引脚。 printk("Port event detected. System woke up.\n"); // 通常在这里清除唤醒标志,或者通知应用。 } void configure_port_event_for_wakeup(void) { nrfx_gpiote_port_config_t port_config = { .handler = gpiote_port_event_handler, // 事件处理函数 .pins_mask = 0x0000F000, // 监视GPIO0的引脚12~15 (位掩码) .sense = NRF_GPIOTE_POLARITY_TOGGLE, // 任何变化都触发 .pull = NRF_GPIO_PIN_NOPULL, // 根据外设决定,按键可能需要上拉 }; // 添加端口事件配置 if (nrfx_gpiote_port_configure(&port_config) != NRFX_SUCCESS) { printk("Failed to configure port event\n"); return; } // 使能端口事件 nrfx_gpiote_port_enable(); printk("Port event configured. Entering sleep, change on P0.12-P0.15 to wake.\n"); // 设置系统进入低功耗状态,等待端口事件唤醒 // 这里需要配合Zephyr的电源管理API,例如: // k_sleep(K_FOREVER); // 对于空闲睡眠 // 对于深度睡眠,需要更复杂的PM配置,此处仅为示意。 }

重要警告:端口事件的处理函数gpiote_port_event_handler是在中断上下文执行的,必须非常简短,不能调用可能阻塞的API(如k_sleep,printk在某些情况下也可能不安全)。最佳实践是在其中设置一个标志位,然后由工作线程(workqueue)来处理实际逻辑。

5. 实战踩坑与经验总结

在nRF54L15上折腾GPIOTE,我遇到过几个典型的坑,这里分享出来,希望能帮你节省时间。

5.1 坑一:GPIOTE通道资源冲突与耗尽

现象:程序运行一段时间后,新的GPIO中断无法触发,或者输出控制失灵。根因:这是最常见的问题。每个需要独立边沿检测或硬件任务输出的引脚,都需要独占一个GPIOTE通道。如果你在多个地方(比如不同的驱动模块、库)不经意间重复配置了同一个通道,或者通道总数用完了,后续的配置就会失败。排查与解决

  1. 规划通道使用:在项目设计文档中,明确列出每个需要使用GPIOTE高级功能的引脚及其预分配的通道号(例如,按键用CH0,LED用CH1,PWM用CH2等)。
  2. 使用Zephyr API时的隐含消耗:当你使用gpio_pin_interrupt_configure_dt时,Zephyr驱动会自动为你分配一个空闲的GPIOTE通道。你可以通过CONFIG_GPIO_NRFX_INTERRUPT_PRIORITY等配置来微调,但无法直接指定通道号。如果你需要精细控制,就必须放弃部分Zephyr API的便利,直接使用nrfx_gpiote来手动管理通道。
  3. 调试方法:在nrfx_gpiote_init后,可以调用nrfx_gpiote_channel_alloc/nrfx_gpiote_channel_free来动态管理通道(如果驱动支持)。更直接的方法是,在调试时检查nrf_gpiote_channel_status_get寄存器,查看各个通道的占用状态。

5.2 坑二:端口事件与通道事件的优先级混淆

现象:系统被意外唤醒,或者预期的边沿中断没有触发。根因:同一个引脚,如果既配置了通道事件(精确中断),又处于端口事件的监视掩码下,那么当引脚变化时,两个事件都会产生。这可能导致中断处理函数被调用两次,或者产生竞争条件。此外,端口事件的优先级通常低于通道事件,但在唤醒系统时,端口事件是唯一可用的(在深度睡眠下,大部分外设包括GPIOTE通道都断电了,只有端口事件逻辑保留)。解决策略

  • 明确分工:用于唤醒的引脚(如低功耗传感器的中断线)优先使用端口事件。用于精确控制或需要区分边沿的引脚(如编码器、通信协议)使用通道事件
  • 避免重叠:确保一个引脚不要同时被两种机制监控,除非你有明确的处理逻辑来区分它们。

5.3 坑三:在中断上下文(ISR)中执行耗时操作

现象:系统变得不稳定,其他中断响应延迟,甚至看门狗复位。根因:无论是通道事件还是端口事件触发的中断,其处理函数(如button_pressedgpiote_port_event_handler)都在中断上下文中执行。在这里调用如k_msleepprintk(可能使用串口阻塞输出)、gpio_pin_set(如果涉及复杂的GPIO复用检查)等可能阻塞或耗时的函数,会严重破坏系统的实时性。黄金法则:中断处理函数(ISR)必须快进快出。只做最必要的事:

  1. 清除硬件中断标志(通常驱动已处理)。
  2. 记录时间戳或设置一个原子标志位。
  3. 释放一个信号量(k_sem_give)或发送一个消息到队列(k_msgq_put)。
  4. 将实际的处理逻辑,移到一个工作线程(workqueue)专用的应用线程中去执行。

5.4 经验:利用PPI/DPPI实现真正的硬件自动化

GPIOTE最强大的地方在于它与PPI/DPPI的结合。初始化GPIOTE的最终目的,往往是为了将它接入这个硬件事件-任务网络。

例如,实现一个精确的硬件PWM:

  1. 初始化一个定时器(如TIMER0),设置比较寄存器CC[0]和CC[1]。
  2. 初始化GPIOTE通道,配置为任务模式,控制LED引脚。
  3. 通过PPI:
    • TIMER0->EVENTS_COMPARE[0]事件连接到GPIOTE->TASKS_OUT[channel]任务(点亮LED)。
    • TIMER0->EVENTS_COMPARE[1]事件连接到GPIOTE->TASKS_OUT[channel]任务(熄灭LED)。
  4. 启动定时器。此后,LED就会以硬件级的精度自动闪烁,零CPU占用

在NCS中,你可以使用nrfx_ppinrfx_dppiAPI(取决于芯片系列)来配置这些连接,也可以使用Zephyr的counterAPI配合设备树绑定来实现。这超出了基础初始化的范围,但它是发挥nRF54L15性能的关键路径。

初始化GPIOTE只是第一步,理解其作为“硬件自动化枢纽”的定位,并善用PPI/DPPI,才能让你的nRF54L15项目在性能和功耗上脱颖而出。从简单的按键中断到复杂的多外设联动,GPIOTE都是那片承上启下的核心拼图。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/19 21:02:18

Fairphone 6 Plus正式登陆美国市场,主打可维修与可持续

荷兰电子制造商Fairphone将其最新设备带入美国市场&#xff0c;这款智能手机以模块化设计、易于维修以及比同类产品更具可持续性著称。Fairphone 6 Plus搭载Android 16系统&#xff0c;兼容AT&T和T-Mobile网络&#xff0c;售价650美元&#xff0c;可通过Fairphone官网及亚马…

作者头像 李华
网站建设 2026/8/19 20:57:58

RT-Thread就绪列表:O(1)调度与线程状态迁移深度解析

1. 从“就绪”说起&#xff1a;为什么线程调度需要一个列表&#xff1f;在嵌入式实时操作系统&#xff08;RTOS&#xff09;的世界里&#xff0c;线程&#xff08;或称任务&#xff09;是系统运行的基本单位。想象一下&#xff0c;你正在管理一个只有单核CPU的小型工厂车间&…

作者头像 李华
网站建设 2026/8/19 20:57:43

029、VLM在机器人视觉问答中的应用:从场景理解到任务决策

029、VLM在机器人视觉问答中的应用&#xff1a;从场景理解到任务决策 昨天半夜在实验室调一个抓取demo&#xff0c;机械臂死活认不出桌面上的红色马克杯——不是识别不到&#xff0c;是它把杯子和旁边的红色胶带卷搞混了。我盯着屏幕上的CLIP相似度分数看了十分钟&#xff0c;突…

作者头像 李华
网站建设 2026/8/19 20:56:12

论文复现实验选型,别只看功能清单

论文复现实验选型&#xff0c;别只看功能清单 论文复现的结论需要带上数据集、代码版本、资源约束和评测脚本。把论文中的指标直接搬到生产决策里&#xff0c;通常缺少关键前提。 论文实验与生产服务的目标不同&#xff1a;前者常聚焦固定数据集上的方法比较&#xff0c;后者还…

作者头像 李华
网站建设 2026/8/19 20:56:11

英文POB看着就头疼?Path of Building中文版PoeCharm完整上手指南

英文POB看着就头疼&#xff1f;Path of Building中文版PoeCharm完整上手指南 【免费下载链接】PoeCharm Path of Building Chinese version 项目地址: https://gitcode.com/gh_mirrors/po/PoeCharm PoeCharm 是《流放之路》构建工具 Path of Building 的中文版&#xff…

作者头像 李华