1 Star 2 Fork 1

gaobinbin/perf_counter

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

perf_counter (v2.3.1)

A dedicated performance counter for Cortex-M Systick. It shares the SysTick with users' original SysTick function(s) without interfering with it. This library will bring new functionalities, such as performance counter, delay_us and clock() service defined in time.h.

Features:

  • Measure CPU cycles for specified code segment

  • Add Coremark 1.0

  • Provide Timer Service for EventRecorder automatically.

  • Enhanced measurement services for RTOS

    • Measures RAW / True cycles used for specified code segment inside a thread, i.e. scheduling cost are removed.
    • Measure RAW/True cycles used for a data-process-path across multiple threads.
  • Easy to use

    • Helper macros: __cycleof__() , __super_loop_monitor__() , __cpu_usage__(), __cpu_perf__() etc.
    • Helper functions: start_cycle_counter(), stop_cycle_counter() etc.
  • Enable a broader processor architecture support

    • Support ALL Cortex-M processors

      • SysTick
      • **[new]**Performance Monitor Unit (PMU)
    • **[new]**Easy to port to a different architecture with a porting template

  • Provide Free Services

    • Do NOT interfer with existing SysTick based applications
  • Support ALL arm compilers

    • Arm Compiler 5 (armcc), Arm Compiler 6 (armclang)
    • arm gcc
    • LLVM
    • IAR
  • Simplified Deployment

    • Drag-and-Drop deployment for Arm Compiler 5 and Arm Compiler 6.
    • CMSIS-Pack is available
    • RT-Thread package is avaialble
  • Time based services

    • [new]delay_us() and delay_ms() with 64bit return value.
    • Provides Timestamp services via get_system_ticks(), get_system_us and get_system_ms().
  • Support both RTOS and bare-metal environments

    • Support SysTick Reconfiguration
    • Support changing System Frequency
    • Support stack-overflow detection in RTOS environment via perfc_check_task_stack_canary_safe()
  • Utilities for C language enhancement

    • Macros to detect compilers, e.g. __IS_COMPILER_ARM_COMPILER_6__, __IS_COMPILER_LLVM__ etc.
    • Macro to create atomicity for specified code block, i.e. __IRQ_SAFE{...}
    • Helper macros for C language extension:
      • VB like with()
      • foreach(), dimof(), CONNECT()
      • C# like using()
      • simple overload feature of OOPC made out of ANSI-C99, __PLOOC_VA_NUM_ARGS()
      • ...

1. How To Use

1.1 Measure CPU cycles for specified code segment

You can measure specified code segment with a macro helper __cycleof__(), it is a wrapper of get_system_ticks().

Syntax:

__cycleof__(<Description String for the target>, [User Code, see ref 1]) {
    //! target code segment of measurement
    ...
}

Here, [ref 1] is a small user code to read the measurement result via a local variable __cycle_count__ . This User Code is optional. If you don't put anything here, the measured result will be shown with a __perf_counter_printf__.

Example 1: Simple measurement with printf

    __cycleof__() {
        foreach(example_lv0_t, s_tItem, ptItem) {
            printf("Processing item with ID = %d\r\n", _->chID);
        }
    }

You will see the measured result in console:

image-20220509004258020

Example 2: Read measured result via __cycle_counter__

    int32_t iCycleResult = 0;

    /* measure cycles and store it in a dedicated variable without printf */
    __cycleof__("delay_us(1000ul)", 
        /* insert code to __cycleof__ body, "{}" can be omitted  */
        {
            iCycleResult = __cycle_count__;   /*< "__cycle_count__" stores the result */
        }) {
        delay_us(1000ul);
    }

    printf("\r\n delay_us(1000ul) takes %d cycles\r\n", (int)iCycleResult);

The result is read out from __cycle_count__and used in other place:

image-20220509004714845

1.2 Performance Analysis

1.2.1 CPU Usage

For both bare-metal and OS environment, you can measure the CPU Usage with macro __cpu_usage__() for a given code segment as long as it is executed repeatedly.

Syntax

__cycleof__(<Iteration Count before getting an average result>, [User Code, see ref 1]) {
    //! target code segment of measurement
    ...
}

Here, [ref 1] is a small user code to read the measurement result via a local variable __usage__. This User Code is optional. If you don't put anything here, the measured result will be shown with a __perf_counter_printf__.

Example 1: the following code will show 30% of CPU Usage:
void main(void)
{
    ...
    while (1) {
        __cpu_usage__(10) {
            delay_us(30000);
        }
        delay_us(70000);
    }
    ...
}
Example 2: Read measurement result via __usage__
void main(void)
{
    ...
    while (1) {
        
        float fUsage = 0.0f;
        __cpu_usage__(10, {
            fUsage = __usage__; /*< "__usage__" stores the result */
        }) {
            delay_us(30000);
        }
        printf("task 1 cpu usage %3.2f %%\r\n", (double)fUsage);

        delay_us(70000);
    }
    ...
}

NOTE: The __usage__ stores the percentage information.

1.2.2 Cycle per Instruction and L1 DCache Miss Rate

For Armv8.1-m processors that implement the PMU, it is easy to measure the CPI (Cycle per Instruction) and L1 DCache miss rate with the macro __cpu_perf__().

Syntax:

__cpu_perf__(<Description String for the target>, [User Code, see ref 1]) {
    //! target code segment of measurement
    ...
}

Here, [ref 1] is a small user code to read the measurement result via a local struct variable __PERF_INFO__ . This User Code is optional. If you don't put anything here, the measured result will be shown with a __perf_counter_printf__. The prototype of the __PERF_INFO__ is shown below:

struct {                                                                
    uint64_t dwNoInstr;                 /* number of instruction executed */        
    uint64_t dwNoMemAccess;             /* number of memory access */
    uint64_t dwNoL1DCacheRefill;        /* number of L1 DCache Refill */
    int64_t lCycles;                    /* number of CPU cycles */
    uint32_t wInstrCalib;                                               
    uint32_t wMemAccessCalib;                                           
    float fCPI;                         /* Cycle per Instruction */
    float fDCacheMissRate;              /* L1 DCache miss rate in percentage */
} __PERF_INFO__;

For example, when insert user code, you can read CPI from __PERF_INFO__.fCPI.

Example 1: measure the Coremark

void main(void)
{
    init_cycle_counter(false);

    printf("Run coremark\r\n");

#ifdef __PERF_COUNTER_COREMARK__
    __cpu_perf__("Coremark") {
        coremark_main();
    }
#endif

    while(1) {
        __NOP();
    }
}

The result might look like the following:

1.3 Timestamp

You can get the system timestamp (since the initialization of perf_counter service) via function get_system_ticks() and get_system_ms().

NOTE: The get_system_ms() is NOT a wrapper of the function get_system_ticks().

There are various way to take advantage of those functions.

Example 3: Use get_system_ms() as random seed

#include <stdio.h>
#include <stdlib.h>
#include "perf_counter.h"

int main (void) 
{
   int i, n;
   
   n = 5;
   
   /* Intializes random number generator */
   srand((unsigned) get_system_ticks());

   /* Print 5 random numbers from 0 to 1024 */
   for( i = 0 ; i < n ; i++ ) {
      printf("%d\n", rand() & 0x3FF);
   }
   
   return(0);
}

Example 4: Measure CPU cycles

    do {
        int64_t tStart = get_system_ticks();
        __IRQ_SAFE {
            printf("no interrupt \r\n");
        }
        printf("used clock cycle: %d", (int32_t)(get_system_ticks() - tStart));
    } while(0);

This example shows how to use the delta value of get_system_ticks() to measure the CPU cycles used by specified code segment. In fact, the __cycleof__() is implemented in the same way:

#define __cycleof__(__STR, ...)                                                 \
            using(int64_t _ = get_system_ticks(), __cycle_count__ = _,          \
                _=_, {                                                          \
                _ = get_system_ticks() - _;                                     \
                __cycle_count__ = _;                                            \
                if (__PLOOC_VA_NUM_ARGS(__VA_ARGS__) == 0) {                    \
                    printf("\r\n");                                             \
                    printf("-[Cycle Report]");                                  \
                    printf("--------------------------------------------\r\n"); \
                    printf(__STR " total cycle count: %d [%08x]\r\n",           \
                            (int)_, (int)_);                                    \
                } else {                                                        \
                    __VA_ARGS__                                                 \
                };                                                              \
            })

1.4 Timer Services

perf_counter provides the basic timer services for delaying a given period of time and polling-for-timeout. For example:

delay_ms(1000);   /* block the program for 1000ms */
delay_us(50);	  /* block the program for 50us */

while(1) {
    /* return true every 1000 ms */
    if (perfc_is_time_out_ms(1000)) {
        /* print hello world every 1000 ms */
        printf("\r\nHello world\r\n");
    }
}

1.5 Work with EventRecorder in MDK

If you are using EventRecorder in MDK, once you deployed the perf_counter, it will provide the timer service for EventRecorder by implenting the following functions: EventRecorderTimerSetup(), EventRecorderTimerGetFreq() and EventRecorderTimerGetCount().

If you have not modify anything in EventRecorderConf.h, you don't have to, and please keep the default configuration. If you see warnings like this:

Invalid Time Stamp Source selected in EventRecorderConf.h!

Please set the macro EVENT_TIMESTAMP_SOURCE to 3 to suppress it.

IMPORTANT: Please always make sure the macro EVENT_TIMESTAMP_FREQ is 0

By using perf_counter as the reference clock, EventRecorder can have the highest clock resolution on the target system without worring about the presence of DWT or any conflicting usage of SysTick.

1.6 On System Environment Changing

1.6.1 System Frequency Changing

If you want to change the System Frequency, after the change, make sure:

  1. The SystemCoreClock has been updated with the new system frequency. Usually, the HAL will update the SystemCoreClock automatically, but in some rare cases where SystemCoreClock is updated accordingly, you should do it yourself.

  2. please call update_perf_counter() to notify perf_counter.

1.6.2 Reconfigure the SysTick

Some systems (e.g. FreeRTOS) might reconfigure the systick timer to fulfil the requirement of their feature. To support this:

  1. Before the reconfiguration, please call function before_cycle_counter_reconfiguration().

    NOTE: This function will stop the SysTick, clear the pending bit, and set the Load register and the Current Value registers to zero.

  2. After the reconfiguration, please call update_perf_counter() to notify perf_counter the new changes.

2. How To Deploy

2.1 Generic(Default) method for all compilers

2.1.1 For Bare-metal:

  1. Clone the code to your local with following command lines:
git clone https://github.com/GorgonMeducer/perf_counter.git
  1. Add including path for perf_counter folder
  2. Add perf_counter.c to your compilation.

NOTE: Please do NOT add any assembly source files of this perf_counter library to your compilation, i.e. systick_wrapper_gcc.S, systick_wrapper_gnu.s or systick_wrapper_ual.s.

  1. Include perf_counter.h in corresponding c source file:
#include "perf_counter.h"
  1. Make sure your system contains the CMSIS (with a version 5.7.0 or above) as perf_counter.h includes cmsis_compiler.h.
  2. Call the function perfc_port_insert_to_system_timer_insert_ovf_handler() in your SysTick_Handler()
void SysTick_Handler(void)
{
    ...
    perfc_port_insert_to_system_timer_insert_ovf_handler();
    ...
}
  1. Make sure the SystemCoreClock is updated with the same value as CPU frequency.
  2. IMPORTANT: Make sure the SysTick_CTRL_CLKSOURCE_Msk bit ( bit 2) of SysTick->CTRL register is 1 that means SysTick runs with the same clock source as the target Cortex-M processor.
  3. Initialize the perf_counter with boolean value that indicates whether the user applications and/or RTOS have already occupied the SysTick.
void main(void)
{
    //! setup system clock 
    
    /*! \brief Update SystemCoreClock with the latest CPU frequency
     *!        If the function doesn't exist or doesn't work correctly,
     *!        Please update SystemCoreClock directly with the correct
     *!        system frequency in Hz.
     *!       
     *!        extern volatile uint32_t SystemCoreClock;
     */
    SystemCoreClockUpdate();
    
    /*! \brief initialize perf_counter() and pass true if SysTick is 
     *!        occupied by user applications or RTOS, otherwise pass
     *!        false. 
     */
    init_cycle_counter(true);
    
    ...
    while(1) {
        ...
    }
}
  1. IMPORTANT: Please enable GNU extension in your compiler. For GCC and CLANG, it is --std=gnu99 or --std=gnu11, and for other compilers, please check the user manual first. Failed to do so, you will not only trigger the warning in perf_counter.h, but also lose the function correctness of __cycleof__() and __super_loop_monitor__(), because __PLOOC_VA_NUM_ARGS() isn't report 0 when passed with no argument.
#if __PLOOC_VA_NUM_ARGS() != 0
#warning Please enable GNC extensions, it is required by __cycleof__() and \
__super_loop_monitor__()
#endif
  1. It is nice to add macro definition __PERF_COUNTER__ to your project GLOBALLY. It helps other module to detect the existence of perf_counter. For Example, LVGL lv_conf_cmsis.h use this macro to detect perf_counter and uses get_system_ms() to implement lv_tick_get().

Enjoy !

2.2 Use cmsis-pack in MDK

  1. Download the cmsis-pack from thecmsis-pack folder. It is a file with name GorgonMeducer.perf_counter.<version>.pack, for example GorgonMeducer.perf_counter.2.2.0.pack

  2. Double click it to install this cmsis-pack. Once finished, you can find it in your Pack-Installer:

    In the future, you can pull the latest version of perf_counter from the menu Packs->Check For Updates as shown below:

    image-20220509011327392

  3. Open the RTE management window, find the Utilities and select the Core inside perf_counter as shown below:

  1. Include perf_counter.h in corresponding c source file:
#include "perf_counter.h"
  1. Make sure your system contains the CMSIS (with a version 5.7.0 or above) as perf_counter.h includes cmsis_compiler.h. Usually, you should do this with RTE as shown below:

image-20220509012432408

  1. Make sure the SystemCoreClock is updated with the same value as CPU frequency.
  2. IMPORTANT: Make sure the SysTick_CTRL_CLKSOURCE_Msk bit ( bit 2) of SysTick->CTRL register is 1 that means SysTick runs with the same clock source as the target Cortex-M processor.
  3. Initialize the perf_counter with boolean value that indicates whether the user applications and/or RTOS have already occupied the SysTick.
void main(void)
{
    //! setup system clock 
    
    /*! \brief Update SystemCoreClock with the latest CPU frequency
     *!        If the function doesn't exist or doesn't work correctly,
     *!        Please update SystemCoreClock directly with the correct
     *!        system frequency in Hz.
     *!       
     *!        extern volatile uint32_t SystemCoreClock;
     */
    SystemCoreClockUpdate();
    
    /*! \brief initialize perf_counter() and pass true if SysTick is 
     *!        occupied by user applications or RTOS, otherwise pass
     *!        false. 
     */
    init_cycle_counter(true);
    
    ...
    while(1) {
        ...
    }
}
  1. IMPORTANT: Please enable GNU extension in your compiler.

    For Arm Compiler 5, please select both C99 mode and GNU extensions in the Option for target dialog as shown below:

image-20220509012752097

For Arm Compiler 6, please select gnu99 or gnu11 in Language C drop-list as shown below:

image-20220509012944724

Failed to do so, you will not only trigger the warning in perf_counter.h, but also lose the function correctness of __cycleof__() and __super_loop_monitor__(), because __PLOOC_VA_NUM_ARGS() isn't report 0 when passed with no argument.

#if __PLOOC_VA_NUM_ARGS() != 0
#warning Please enable GNC extensions, it is required by __cycleof__() and \
__super_loop_monitor__()
#endif

2.3 Use perf_counter in RT-Thread RTOS

perf_counter has registered as one of the RT-Thread software packages, which locats in system category. In ENV or RT-Thread Studio, you just need to simply enable cputime framework. RT-Thread will automatically enable perf_counter if you are using Cortex-M architecture.

rt-thread-settings

Enjoy !

3. FAQ

3.1 Why I see Undefined symbol $Super$$SysTick_Handler

This error usually pops up in Arm Compiler 5 and Arm Compiler 6. It is because you haven't implemented any non-weak SysTick_Handler(). Please provide an EMPTY one in any c source file to solve this problem:

void SysTick_Handler(void)
{
}

NOTE: If you deploy perf_counter using cmsis-pack and encounter this issue, please DO NOT call function user_code_insert_to_systick_handler() in this should-be-empty SysTick_Handler().

3.2 Why do I see perf_counter in red in the MDK project manager?

Since version v2.1.0 I removed the unnecessary bundle feature from the cmsis-pack. If you have used the older version, you will encounter this issue. To solve this problem:

  1. please unselect ALL the performance components in RTE, press OK and close the uVision.
  2. reopen the mdk project and select the perf_counter components in RTE

Sorry about this.

4. License

Performance Counter for Cortex-M, a.k.a. perf_counter is under Apache 2.0 license.

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

MCU延时工具 展开 收起
README
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/gaobinbin/perf_counter.git
git@gitee.com:gaobinbin/perf_counter.git
gaobinbin
perf_counter
perf_counter
CMSIS-Pack

搜索帮助