Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
J-LINK
J-Trace
J-Trace入门系列:2)ART-PI 也能玩转Stream Trace
发布于 2022-08-16 20:02:54 浏览:776
订阅该版
[tocm] # 前言 往期回顾:[J-Trace入门系列:1)感动人心的功能与更感动人心的售价] 距离上一篇更新已经过了很长时间,很多小伙伴都在催更,属实惭愧。拖更太久就印证了一句话,有些事情现在不去做,以后便再也不会去做。言归正传,在我使用J-Trace这段时间里,大多数场景是拿它作为正版J-Link使用,只有很少的情况下,才会用到它的核心功能。 朋友间打趣交流的时候,都会说,普通调试器能解决95%以上的问题,高端调试器用来解决剩下的5%问题。 事实的确如此,很多时候我们并没有太在乎代码在MCU内部实时运行的情况,直到撞到了瓶颈(性能不达标、死机异常),成为一堵绕不开的墙之后,才会想到使用这样的工具。 由于Trace在ARM Cortex-M上,相比SWD/JTAG需要单独拉起Trace信号,本篇将着重篇幅介绍**软硬件搭建**环节。 # 1. 连接ART-PI 事实上J-Trace包装盒内附赠了一块STM32F407的板子,用于下载官网Trace例程,学习Ozone调试流程之用:  这张板子太丐了,还卖$99,除了点灯就没别的了,所以在验证过J-Trace之后,我们直接上手去对接ART-PI。 ## 1.1 硬件连接 首先确定ART-PI是否有Trace Pin的扩展能力,通过查看ART-PI硬件原理图,确定了如下管脚: | IO管脚 | Trace Pin复用 | | ------ | ------------- | | PE2 | TCLK | | PG13 | TD0 | | PG14 | TD1 | | PE5 | TD2 | | PE6 | TD3 | 确定IO这一步的过程是比较让人激动的,毕竟说明书里没有注明这个扩展,可以认为,ART-PI设计初期的IO选定是花了心思的。 当然,知道IO之后我们就要去连接它,根据[上篇]的介绍我们知道,实现Stream Trace需要SWD+Trace共同实现,Segger也提供了1.27mm间距的2x10Pin接口。所以结合ART-PI背面预留的SWD焊盘,使用不同高度的【PogoPin】,这种测试接针,在接触面上有弹性,可作为烧录架使用,完美解决了调试部分,同时不会损失原有的扩展性。据此,可以简单画个孔位对齐的板子出来: 
焊接好器件,使用M3孔径的螺柱就能锁紧开发板,可以看见PogoPin的连接非常稳健:  在硬件连通阶段建议结合逻辑分析仪抓一抓波形,能够很容易确定当前内核Trace的工作状态。
整体装配图:  以上,就完成了ART-PI的外部调试器的引入,细心的同学可能会发现,在SWD部分,信号和ST-Link直连的,出现了两个调试器接入一个MCU的情况,这个的确不合理,但实测ST-Link只当串口情况下,**几乎不会干扰SWD总线**,两个调试器分时复用就好了。此外,这样也能用ST-Link下载,Jlink调试,属实NTR了。。 ## 1.2 信号通路 在开展这一步之前,需要明确 Core -> ETM -> TPIU 这条信号通路(ATB Bus)上的初始条件。 ### 1.2.1 STM32H7 Trace 信号通路 查阅STM32H7手册(rm0433),可以看见APB-D信号流需要从Core到ETM,处理后的trace信号送至CSTF合路器,最后送到TPIU并由Trace Pin输出到片外调试器。  **名词解释: >| 缩写 | 全称 | 释义 | >| ---- | ------------------------- | ---------------- | >| ETM | Embedded Trace Macrocell | 嵌入式追踪宏单元 | >| CSTF | Trace bus funnel | 追踪总线合路器 | >| TPIU | Trace port unit interface | 追踪端口界面单元 | 可以看出,Cortex-M7的CoreSight界面相比Cortex-M4有更多扩充,总线通路上要更灵活,在TPIU这一侧就可以支持: - ETM信号经合路器旁路到TPIU直出。 - ETM信号经合路器送入ETF进行FIFO缓冲,再由TPIU输出。 - ITM经Replicator分配到合路器,经由TPIU输出。 - ETF/TPIU 均可由外部触发源刷新行为。 我们不需要一一实现,只需要实现最基础的Trace流输出。
更深入的,可以参考ARM官方CoreSight文档: >1. IHI 0031C (ID080813) - Arm® Debug Interface Architecture Specification ADIv5.0 to ADIv5.2, Issue C >2. DDI 0480F (ID100313) - Arm® CoreSight™ SoC-400 r3p2 Technical Reference Manual, Issue G >3. DDI 0461B (ID010111) - Arm® CoreSight™ Trace Memory Controller r0p1 Technical Reference Manual, Issue B >4. DDI 0314H - Arm® CoreSight™ Components Technical Reference Manual, Issue H >5. DDI 0403D (ID100710) - Arm®v7-M Architecture Reference Manual, Issue E.b >6. DDI 0494-2a (ID062813) - Arm® CoreSight™ ETM™-M7 r0p1 Technical Reference Manual, Issue D ### 1.2.2 时钟域与电源域 上述Trace电路原理上与SWD/JTAG的最大区别是,**需要手动初始化它才可以使用**。我们使能它之前需要再了解它的Power Domain和Clock Domain。  可以看见,D1 电源域覆盖了Trace全程,所以可以忽略这部分的初始化,除非有在shutdown模式时还要Trace的需求。
 时钟域也是如此,但我们需要关注:rcc_c_ck时钟、TRACECLK。这里给个我浅显的理解:**CPU作为指令流的生产者,设计原理上它不会停止,作为指令流的接收者,受限于【带宽】,同时需要结合Trace粒度需求,给出合适的时钟搭配。**
例如,多数高速MCU平台下,如果想要每个指令都捕捉到,就需要给CPU降速运行,如果需要全速运行,Cortex-M7可以实现一定限度的Trigger-Buffered Stream,取决于厂商平台为这部分预留的SRAM大小。 Notice:【带宽】这里的制约条件: - J-Trace 的最高Stream Trace clock。 - Trace IO 信号翻转能力,STM32H7 上限是133Mhz。 ## 1.3 手动配置时钟 根据前面列举的条件,我们可以尝试计算出适合ART-PI能被Trace住的最高主频参数: ``` 预定的要求: 1. 跑满 Trace IO 翻转速率。 2. 尽可能CPU频率更高。 使用 cubeMX 辅助调整时钟树: - TraceCLK 分频到最高 133Mhz。 - CPU 主频为TraceCLK 3倍频,即400Mhz。 ``` Notice: CPU时钟实际为TraceCLK的6倍,TraceCLK由于TPIU机制(DDR),输出66.6Mhz频率。 得出以下时钟树:  图中的时钟树配置,应当用在被调试的工程中去。 到这一步之前,我们都在讲实现原理,可能之前没有接触过的同学此时已经云里雾里了,这里做下本章的总结:**使用Trace之前,需要初始化Trace通路上的一切{时钟、电源域、信号通路、IO}。** # 2. Ozone工程准备 ## 2.1 创建具备Stream Trace功能的Ozone工程 可以根据上述的Demo,基于它作为模板,简单搭建Ozone工程。 ### 2.1.1 创建Ozone工程 需要先理解Ozone是个什么类型的软件: > Ozone只需要编译产物就可以支持图形界面的调试,不依赖工具链,具备完善的调试交互体验。 选用Ozone的原因: - 只要是Jlink就能使用。 - 去IDE化,不依赖IDE环境。 - Ozone能够发挥J-Trace优势。 我们装好Ozone之后就直接打开使用,可以使用project wizard辅助创建一个工程,这步也可以跟着demo做。  添加了对应MCU的SVD文件,方便手动访问完整的外设寄存器,如果没有也可跳过。  添加Jlink连接方式,这步也可以之后做。  添加.axf、.elf等对应IDE产物文件,如果只有.bin,提供ROM偏移量,则可以基于disassemby的调试。  Notice: 如果顺利检索elf文件,则可以通过elf解析到的路径打开对应的源文件。 首次使用Ozone可以先不打开Trace,先熟悉各类调试窗口、按钮和工具。 由于Ozone支持启动前后各阶段的弱函数,可以在一些特殊启动场景,例如:debug on RAM / debug after bootloader 这些,可以实现对应阶段的弱函数,手动干预MCU的运行。 Notice: 有关Ozone的深入使用,请务必参考[《Ozone_UM08025》.pdf] ### 2.1.2 配置JlinkScript 之前谈到,Trace需要手动初始化。我们当然可以在芯片初始化阶段,将Trace相关的参数全部初始化完毕,Ozone在线调试时,只有在Trace模块初始化之后的指令流才能被抓住,这样做是没有问题的。 Segger推荐了第二种做法,即利用Jlink自动化脚本,在每次需要Trace时,例如每次Halt CPU之后,jlink会通过SWD直接访问外设寄存器,并写入对应配置。所以在ART-PI的jdebug工程中,通过实现`BeforeTargetConnect()`函数的方式,在每次连接MCU时配置好Trace功能: ```C void BeforeTargetConnect (void) { // // Trace pin init is done by J-Link script file as J-Link script files are IDE independent // Project.SetJLinkScript("$(ProjectDir)/Ozone/ST_STM32H750_400Mhz_Traceconfig.JLinkScript"); } ``` 对于ART-PI,我们要在jlinkscript脚本中实现: - GPIO初始化为Trace Pin复用,并配置好合适的IO推力。 - MCUDBG寄存器开启D1DBGCKEN/D3DBGCKEN时钟。 参考资源: - 可以参考segger官方测试小板[Tracing_on_ST_STM32F407],这个例程提供了jlinkscript脚本,如果要适配自己的平台,这部分的内容需要仔细揣摩。 - 也可以参考segger官方的[STM32H743_Trace_Demo],但是这个例程对jlinkscript脚本进行了编译,变成了pex格式,反而没有太多参考价值。 这里也给出我自己按照上述流程思路,独立写好的jlinkscript脚本,实测比较稳定: ```C /********************************************************************* File : ST_STM32H750_400Mhz_Traceconfig.JLinkScript Author : stackryan (yuanjyjyj@outlook.com) Purpose : Examplescript to modify TracePortWidth and Pin init Literature: [1] J-Link User Guide Additional information: For more information about public functions that can be implemented in order to customize J-Link actions, please refer to [1] */ /********************************************************************* * * ConfigTargetSettings * * Function description * Called before InitTarget(). Mainly used to set some global DLL variables to customize the * normal connect procedure. For ARM CoreSight devices this may be specifying the base * address of some CoreSight components (ETM, …) that cannot be auto-detected by J-Link * due to erroneous ROM tables etc. May also be used to specify the device name in case * debugger does not pass it to the DLL. * * Notes * (1) May not, under absolutely NO circumstances, call any API functions that perform target communication. * (2) Should only set some global DLL variables * * Return value * >= 0 O.K. * < 0 Error * -1 Unspecified error */ int ConfigTargetSettings(void) { // // Enable erase for all flash banks (e.g. QSPI) // JLINK_SYS_Report("-ConfigSettings.-"); JLINK_ExecCommand("CORESIGHT_SetCSTFBaseAddr = 0xE00F3000 ForceUnlock = 1 APIndex = 2"); JLINK_ExecCommand("CORESIGHT_SetTPIUBaseAddr = 0xE00F5000 ForceUnlock = 1 APIndex = 2"); JLINK_ExecCommand("CORESIGHT_SetTMCBaseAddr = 0xE00F4000 ForceUnlock = 1 APIndex = 2"); return 0; } /********************************************************************* * * OnTraceStart() * * Function description * If present, called right before trace is started. * Used to initialize MCU specific trace related things like configuring the trace pins for alternate function. * * Return value * >= 0: O.K. * < 0: Error * * Notes * (1) May use high-level API functions like JLINK_MEM_ etc. * (2) Should not call JLINK_TARGET_Halt(). Can rely on target being halted when entering this function */ int OnTraceStart(void) { U32 RCC_AHB4ENR_Addr; U32 GPIOE_MODER_Addr; U32 GPIOE_PUPDR_Addr; U32 GPIOE_OSPEEDR_Addr; U32 GPIOE_AFRL_Addr; U32 GPIOG_MODER_Addr; U32 GPIOG_PUPDR_Addr; U32 GPIOG_OSPEEDR_Addr; U32 GPIOG_AFRH_Addr; U32 DBGMCU_CR_Addr; U32 iTCLK; U32 iTD0; U32 iTD1; U32 iTD2; U32 iTD3; U32 EdgeTCLK; U32 EdgeTD; U32 v; U32 PortWidth; // // Adjust sampling point of trace pin (Optional: not needed for this cpu) // //JLINK_ExecCommand("TraceSampleAdjust TD=2000"); // // Set Trace Portwidth(Optional): Default 4 Pin Trace, other possibilities: 1, 2, 4 // //JLINK_TRACE_PortWidth = 4; // // RCC_AHB4ENR_Addr 0x580244E0 Periphal clock register // GPIOE_MODER: 0x58021000 GPIO Port mode register // GPIOE_PUPDR: 0x5802100C GPIO Pullup/Puldown register // GPIOE_OSPEEDR: 0x58021008 GPIO output speed register // GPIOE_AFRL: 0x58021020 GPIO Alternate function low register // DBGMCU_CR: 0x5C001004 Debug MCU register // // PE2 => TCLK // PG13 => TD0 // PG14 => TD1 // PE5 => TD2 // PE6 => TD3 // // // Init register addresses // JLINK_SYS_Report("Start: Initializing trace pins."); RCC_AHB4ENR_Addr = 0x580244E0; GPIOE_MODER_Addr = 0x58021000; GPIOE_PUPDR_Addr = 0x5802100C; GPIOE_OSPEEDR_Addr = 0x58021008; GPIOE_AFRL_Addr = 0x58021020; GPIOG_MODER_Addr = 0x58021800; GPIOG_PUPDR_Addr = 0x5802180C; GPIOG_OSPEEDR_Addr = 0x58021808; GPIOG_AFRH_Addr = 0x58021824; DBGMCU_CR_Addr = 0x5C001004; iTCLK = 2; iTD0 = 13; iTD1 = 14; iTD2 = 5; iTD3 = 6; PortWidth = JLINK_TRACE_PortWidth; // // Set drivestrength // 0: Low speed // 1: Medium speed // 2: High speed // 3: Very high speed // EdgeTCLK = 3; EdgeTD = 3; // // Init pins // v = JLINK_MEM_ReadU32(RCC_AHB4ENR_Addr); JLINK_MEM_WriteU32(RCC_AHB4ENR_Addr, v | 1 << 6); // Enable clock for GPIOG v = JLINK_MEM_ReadU32(RCC_AHB4ENR_Addr); JLINK_MEM_WriteU32(RCC_AHB4ENR_Addr, v | 1 << 4); // Enable clock for GPIOE // // TCLK init // v = JLINK_MEM_ReadU32(GPIOE_MODER_Addr); v &= ~(3 << (2 * iTCLK)); // Mask Mode register v |= (2 << (2 * iTCLK)); // Set alt function mode JLINK_MEM_WriteU32(GPIOE_MODER_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_PUPDR_Addr); v &= ~(3 << (2 * iTCLK)); // Mask PUP register v |= (1 << (2 * iTCLK)); // Set PUP register (Pullup) JLINK_MEM_WriteU32(GPIOE_PUPDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_OSPEEDR_Addr); v &= ~(3 << (2 * iTCLK)); // Mask OSPEED register v |= (EdgeTCLK << (2 * iTCLK)); // Set OSPEED register (very high speed) JLINK_MEM_WriteU32(GPIOE_OSPEEDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_AFRL_Addr); v &= ~(15 << (4 * iTCLK)); // Select alt func 0 JLINK_MEM_WriteU32(GPIOE_AFRL_Addr, v); // // TD0 init // v = JLINK_MEM_ReadU32(GPIOG_MODER_Addr); v &= ~(3 << (2 * iTD0)); // Mask Mode register v |= (2 << (2 * iTD0)); // Set alt function mode JLINK_MEM_WriteU32(GPIOG_MODER_Addr, v); v = JLINK_MEM_ReadU32(GPIOG_PUPDR_Addr); v &= ~(3 << (2 * iTD0)); // Mask PUP register v |= (1 << (2 * iTD0)); // Set PUP register (Pullup) JLINK_MEM_WriteU32(GPIOG_PUPDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOG_OSPEEDR_Addr); v &= ~(3 << (2 * iTD0)); // Mask OSPEED register v |= (EdgeTD << (2 * iTD0)); // Set OSPEED register (very high speed) JLINK_MEM_WriteU32(GPIOG_OSPEEDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOG_AFRH_Addr); v &= ~(15 << (4 * (iTD0 - 8))); // Select alt func 0 JLINK_MEM_WriteU32(GPIOG_AFRH_Addr, v); // // TD1 init // if (PortWidth > 1) { v = JLINK_MEM_ReadU32(GPIOG_MODER_Addr); v &= ~(3 << (2 * iTD1)); // Mask Mode register v |= (2 << (2 * iTD1)); // Set alt function mode JLINK_MEM_WriteU32(GPIOG_MODER_Addr, v); v = JLINK_MEM_ReadU32(GPIOG_PUPDR_Addr); v &= ~(3 << (2 * iTD1)); // Mask PUP register v |= (1 << (2 * iTD1)); // Set PUP register (Pullup) JLINK_MEM_WriteU32(GPIOG_PUPDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOG_OSPEEDR_Addr); v &= ~(3 << (2 * iTD1)); // Mask OSPEED register v |= (EdgeTD << (2 * iTD1)); // Set OSPEED register (very high speed) JLINK_MEM_WriteU32(GPIOG_OSPEEDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOG_AFRH_Addr); v &= ~(15 << (4 * (iTD1 - 8))); // Select alt func 0 JLINK_MEM_WriteU32(GPIOG_AFRH_Addr, v); } // // TD2 & TD3 init // if (PortWidth > 2) { // // TD2 init // v = JLINK_MEM_ReadU32(GPIOE_MODER_Addr); v &= ~(3 << (2 * iTD2)); // Mask Mode register v |= (2 << (2 * iTD2)); // Set alt function mode JLINK_MEM_WriteU32(GPIOE_MODER_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_PUPDR_Addr); v &= ~(3 << (2 * iTD2)); // Mask PUP register v |= (1 << (2 * iTD2)); // Set PUP register (Pullup) JLINK_MEM_WriteU32(GPIOE_PUPDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_OSPEEDR_Addr); v &= ~(3 << (2 * iTD2)); // Mask OSPEED register v |= (EdgeTD << (2 * iTD2)); // Set OSPEED register (very high speed) JLINK_MEM_WriteU32(GPIOE_OSPEEDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_AFRL_Addr); v &= ~(15 << (4 * iTD2)); // Select alt func 0 JLINK_MEM_WriteU32(GPIOE_AFRL_Addr, v); // // TD3 init // v = JLINK_MEM_ReadU32(GPIOE_MODER_Addr); v &= ~(3 << (2 * iTD3)); // Mask Mode register v |= (2 << (2 * iTD3)); // Set alt function mode JLINK_MEM_WriteU32(GPIOE_MODER_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_PUPDR_Addr); v &= ~(3 << (2 * iTD3)); // Mask PUP register v |= (1 << (2 * iTD3)); // Set PUP register (Pullup) JLINK_MEM_WriteU32(GPIOE_PUPDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_OSPEEDR_Addr); v &= ~(3 << (2 * iTD3)); // Mask OSPEED register v |= (EdgeTD << (2 * iTD3)); // Set OSPEED register (very high speed) JLINK_MEM_WriteU32(GPIOE_OSPEEDR_Addr, v); v = JLINK_MEM_ReadU32(GPIOE_AFRL_Addr); v &= ~(15 << (4 * iTD3)); // Select alt func 0 JLINK_MEM_WriteU32(GPIOE_AFRL_Addr, v); } // // Config DBUGMCU // v = JLINK_MEM_ReadU32(DBGMCU_CR_Addr); // Debug MCU enables traceclk (STM32H7 specific) v &= ~(1 << 20); // Mask Traceclk Register v |= (1 << 20); // Enable Traceclk v |= (1 << 21); // D1DBGCKEN v |= (1 << 22); // D3DBGCKEN JLINK_MEM_WriteU32(DBGMCU_CR_Addr, v); JLINK_SYS_Report("End: Initializing trace pins."); return 0; } ``` ### 2.1.3 实现 Stream Trace 按照上述配置并刷新好工程之后,可以尝试打开Trace功能,并进入调试状态,在点击halt或者任意step按键后,Trace信号就会被正常开启,此时逻辑分析仪上可以抓到正常的Trace波形了。  这里逻辑分析仪选用了4通道数量,采样频率400Mhz,TD3并未采集,不影响预期效果。 此时,Ozone上可以打开Instruction Trace窗口,并且能够看见海量指令流的记录了。在非侵入式调试下,能够快速回溯历史指令流,找到死机的原因。  通过code profile窗口可以看见,对于只有点灯任务的ART-PI来说,系统资源主要都在idle线程上运行着。通过较长时间的挂测,可以统计各函数体的实时代码覆盖率和指令覆盖率。这项功能在需要优化的业务中会非常实用,能够轻松看出哪些资源需要被优化。  # 3. 总结 使用Ozone配合J-Trace完整体验stream trace的流程,相比基于IDE的调试是复杂些的,但它提供了工程师以更全的视角,更有说服力的去解决剩下5%的问题,用好它,能够提供远超调试器本身的价值。 当然,相比更贵的调试系统Lauterbach,J-Trace还不够与其相提并论,这款产品更像是面向嵌入式开发者的、灵活的解决方案。如果根据自己的产品需求,能够设计配套的工具脚本,实现更简便的调试闭环,那么这把利器也会跟着越磨越快。 下篇,我将会代入更多的调试视角,将这个话题进一步完善,希望这次不会鸽太久。。 [上篇]:https://club.rt-thread.org/ask/article/bbcce3088af30a9b.html [J-Trace入门系列:1)感动人心的功能与更感动人心的售价]:https://club.rt-thread.org/ask/article/bbcce3088af30a9b.html [STM32H743_Trace_Demo]:https://wiki.segger.com/STM32H7 [Tracing_on_ST_STM32F407]:https://wiki.segger.com/Tracing_on_ST_STM32F407_(SEGGER_Cortex-M_Trace_Reference_Board) [《Ozone_UM08025》.pdf]:https://www.segger.com/downloads/jlink/UM08025_Ozone.pdf
7
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
StackYuan
这家伙很懒,什么也没写!
文章
3
回答
197
被采纳
35
关注TA
发私信
相关文章
1
使用RT_Thread studio的j-link下载程序,出现无法识别装置
2
下载程序时弹出j-link窗口,点了yes之后结果是无法检测到j-link
3
j-link问题,下载程序时,弹出窗口并且说无法检测到程序
4
STM32开发板ST-Link转J-Link下载报错:hex文件未生成
5
RT-Thread Studio目录下有2个版本J-LINK驱动怎么设置
6
关于野火RT1052 pro板 RT-Thread
7
RT-thread studio如何添加J-link的外部flash算法?
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
机器人操作系统 (ROS2) 和 RT-Thread 通信
4
五分钟玩转RT-Thread新社区
5
国产MCU移植系列教程汇总,欢迎查看!
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
LWIP
Env
SPI
AT
FinSH
Bootloader
CAN总线
ART-Pi
Hardfault
文件系统
USB
DMA
RT-Thread
线程
SCons
RT-Thread Nano
stm32
MQTT
ESP8266
ota
UART
RTC
freemodbus
rtthread
rt-smart
packages_软件包
I2C
WIZnet_W5500
flash
cubemx
FAL
定时器
BSP
AB32VG1
PWM
ADC
SDIO
msh
socket
LVGL
keil
Debug
C++_cpp
中断
编译报错
SFUD
SMP
MicroPython
本月问答贡献
出出啊
1431
个答案
317
次被采纳
小小李sunny
1342
个答案
267
次被采纳
crystal266
505
个答案
149
次被采纳
whj467467222
1212
个答案
142
次被采纳
张世争
590
个答案
135
次被采纳
本月文章贡献
出出啊
3
篇文章
5
次点赞
小小李sunny
1
篇文章
1
次点赞
crystal266
1
篇文章
3
次点赞
whj467467222
2
篇文章
4
次点赞
张世争
6
篇文章
14
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部