HC32F460开发之rtthread+finsh组件的移植

HC32F460开发之rtthread+finsh组件的移植文章目录前言一、RT-Thread简介二、rtthread的移植1.裸机例程2.RT-Thread下载2.RT-Thread移植总结前言对于从事单片机的开发人员,操作系统可以说是绕不过的一个必修课程。在稍复杂的应用开发中,一个好的操作系统可以帮助我们将单片机的资源最大化的利用起来,而系统提供的各种API接口也可以可靠地帮我们实现各种应用逻辑功能。日常生活里,在各种各样的电子设备中,操作系统被广泛地应用,常见的有Linux,ucos,以及现在在各种物联网设备中被广泛应用的freertos,RT-Thre

大家好,又见面了,我是你们的朋友全栈君。


前言

对于从事单片机的开发人员,操作系统可以说是绕不过的一个必修课程。在稍复杂的应用开发中,一个好的操作系统可以帮助我们将单片机的资源最大化的利用起来,而系统提供的各种API接口也可以可靠地帮我们实现各种应用逻辑功能。日常生活里,在各种各样的电子设备中,操作系统被广泛地应用,常见的有Linux,ucos,以及现在在各种物联网设备中被广泛应用的freertos,RT-Thread等。本文主要从裸机工程开始,一步步介绍RT-Thread系统的移植。


一、RT-Thread简介

RT-Thread是一款开源的嵌入式实时操作系统,作为一款国产的操作系统,经过10余年的发展,RT-Thread逐渐得到了市场的认可,开始被广泛应用在智能家电,智能穿戴设备中。相对于其他的RTOS,RT-Thread最大的特点就是有着丰富的中间层组件的支持,我们可以根据产品功能的需要,移植各种组件,打造出自己需要的系统。
在这里插入图片描述
具体的介绍就不多说了,有兴趣了解的朋友可以自己去看官方介绍。
官方介绍

二、rtthread的移植

1.裸机例程

在移植RT-Thread前,我们需要一个能正常运行的裸机工程,同时为了方便后续的调试,我们还需要一个USART串口作为一个调试口。从开发板的原理图上我们可以知道,板子上有两路LED可供驱动,并且选择PB0和PB2作为调试串口。
在这里插入图片描述
在这里插入图片描述
参考开发板提供的例程,(例程可以到华大官网下载官方例程),完成对应的功能,代码如下:
LED驱动部分

/* LED0 Port/Pin definition */
#define LED0_PORT (PortE)
#define LED0_PIN (Pin06)

#define LED0_ON() (PORT_SetBits(LED0_PORT, LED0_PIN))
#define LED0_OFF() (PORT_ResetBits(LED0_PORT, LED0_PIN))
#define LED0_TOGGLE() (PORT_Toggle(LED0_PORT, LED0_PIN))

/* LED1 Port/Pin definition */
#define LED1_PORT (PortA)
#define LED1_PIN (Pin07)

#define LED1_ON() (PORT_SetBits(LED1_PORT, LED1_PIN))
#define LED1_OFF() (PORT_ResetBits(LED1_PORT, LED1_PIN))
#define LED1_TOGGLE() (PORT_Toggle(LED1_PORT, LED1_PIN))

void LedInit(void)
{ 
   
	stc_port_init_t stcPortInit;

    /* configuration structure initialization */
    MEM_ZERO_STRUCT(stcPortInit);

	stcPortInit.enPinMode = Pin_Mode_Out;
	/* LED0 Port/Pin initialization */
    LED0_OFF();
    PORT_Init(LED0_PORT, LED0_PIN, &stcPortInit);

	/* LED1 Port/Pin initialization */
    LED1_OFF();
    PORT_Init(LED1_PORT, LED1_PIN, &stcPortInit);
}

USART驱动部分

/* USART channel definition */
#define DEBUG_USART_CH (M4_USART1)
	
/* USART baudrate definition */
#define DEBUG_USART_BAUDRATE (115200ul)

/* USART RX Port/Pin definition */
#define DEBUG_USART_RX_PORT (PortB)
#define DEBUG_USART_RX_PIN (Pin02)
#define DEBUG_USART_RX_FUNC (Func_Usart1_Rx)
	
/* USART TX Port/Pin definition */
#define DEBUG_USART_TX_PORT (PortB)
#define DEBUG_USART_TX_PIN (Pin00)
#define DEBUG_USART_TX_FUNC (Func_Usart1_Tx)

void DebugUsartInit(void)
{ 
   
    en_result_t enRet = Ok;
    uint32_t u32Fcg1Periph = PWC_FCG1_PERIPH_USART1 | PWC_FCG1_PERIPH_USART2 | \
                             PWC_FCG1_PERIPH_USART3 | PWC_FCG1_PERIPH_USART4;
    const stc_usart_uart_init_t stcInitCfg = { 
   
        UsartIntClkCkNoOutput,
        UsartClkDiv_1,
        UsartDataBits8,
        UsartDataLsbFirst,
        UsartOneStopBit,
        UsartParityNone,
        UsartSampleBit8,
        UsartStartBitFallEdge,
        UsartRtsEnable,
    };

    /* Enable peripheral clock */
    PWC_Fcg1PeriphClockCmd(u32Fcg1Periph, Enable);

    /* Initialize USART IO */
    PORT_SetFunc(DEBUG_USART_RX_PORT, DEBUG_USART_RX_PIN, DEBUG_USART_RX_FUNC, Disable);
    PORT_SetFunc(DEBUG_USART_TX_PORT, DEBUG_USART_TX_PIN, DEBUG_USART_TX_FUNC, Disable);

    /* Initialize UART */
    enRet = USART_UART_Init(DEBUG_USART_CH, &stcInitCfg);
    if (enRet != Ok)
    { 
   
        while (1)
        { 
   
        }
    }

    /* Set baudrate */
    enRet = USART_SetBaudrate(DEBUG_USART_CH, DEBUG_USART_BAUDRATE);
    if (enRet != Ok)
    { 
   
        while (1)
        { 
   
        }
    }

    /*Enable RX && TX function*/
    USART_FuncCmd(DEBUG_USART_CH, UsartRx, Enable);
    USART_FuncCmd(DEBUG_USART_CH, UsartTx, Enable);
}

在PC上开发我们习惯使用C库中的printf做输出和调试,而将printf重定向为串口输出的方法,就是需要重新实现fputc()这个函数。在华大的驱动库中,我们可以看到官方已经帮我们实现了相关的功能(感兴趣的可以自行查看官方代码hc32f460_utility.c,这里不做讲解),而我们所需要做的,就是调用官方的API接口,注册对应的串口驱动,就可以在我们的工程中使用printf函数做输出和调试。最后代码如下:
main.c

/** * @brief BSP clock initialize. * Set board system clock to MPLL@200MHz * @param None * @retval None */
void BSP_CLK_Init(void)
{ 
   
    stc_clk_sysclk_cfg_t    stcSysClkCfg;
    stc_clk_xtal_cfg_t      stcXtalCfg;
    stc_clk_mpll_cfg_t      stcMpllCfg;
    stc_sram_config_t       stcSramConfig;

    MEM_ZERO_STRUCT(stcSysClkCfg);
    MEM_ZERO_STRUCT(stcXtalCfg);
    MEM_ZERO_STRUCT(stcMpllCfg);
    MEM_ZERO_STRUCT(stcSramConfig);

    /* Set bus clk div. */
    stcSysClkCfg.enHclkDiv  = ClkSysclkDiv1;
    stcSysClkCfg.enExclkDiv = ClkSysclkDiv2;
    stcSysClkCfg.enPclk0Div = ClkSysclkDiv1;
    stcSysClkCfg.enPclk1Div = ClkSysclkDiv2;
    stcSysClkCfg.enPclk2Div = ClkSysclkDiv4;
    stcSysClkCfg.enPclk3Div = ClkSysclkDiv4;
    stcSysClkCfg.enPclk4Div = ClkSysclkDiv2;
    CLK_SysClkConfig(&stcSysClkCfg);

    /* Config Xtal and Enable Xtal */
    stcXtalCfg.enMode = ClkXtalModeOsc;
    stcXtalCfg.enDrv = ClkXtalLowDrv;
    stcXtalCfg.enFastStartup = Enable;
    CLK_XtalConfig(&stcXtalCfg);
    CLK_XtalCmd(Enable);

    /* sram init include read/write wait cycle setting */
    stcSramConfig.u8SramIdx = Sram12Idx | Sram3Idx | SramHsIdx | SramRetIdx;
    stcSramConfig.enSramRC = SramCycle2;
    stcSramConfig.enSramWC = SramCycle2;
    SRAM_Init(&stcSramConfig);

    /* flash read wait cycle setting */
    EFM_Unlock();
    EFM_SetLatency(EFM_LATENCY_5);
    EFM_Lock();

    /* MPLL config (XTAL / pllmDiv * plln / PllpDiv = 200M). */
    stcMpllCfg.pllmDiv = 1ul;
    stcMpllCfg.plln    = 50ul;
    stcMpllCfg.PllpDiv = 2ul;
    stcMpllCfg.PllqDiv = 2ul;
    stcMpllCfg.PllrDiv = 2ul;
    CLK_SetPllSource(ClkPllSrcXTAL);
    CLK_MpllConfig(&stcMpllCfg);

    /* Enable MPLL. */
    CLK_MpllCmd(Enable);
    /* Wait MPLL ready. */
    while(Set != CLK_GetFlagStatus(ClkFlagMPLLRdy))
    { 
   
        ;
    }
    /* Switch driver ability */
    PWC_HS2HP();
    /* Switch system clock source to MPLL. */
    CLK_SetSysClkSource(CLKSysSrcMPLL);
}

/** ******************************************************************************* ** \brief Main function of template project ** ** \param None ** ** \retval int32_t return value, if needed ** ******************************************************************************/
int32_t main(void)
{ 
   
    BSP_CLK_Init();	//系统时钟初始化
    DDL_PrintfInit(DEBUG_USART_CH, DEBUG_USART_BAUDRATE, DebugUsartInit);
    LedInit();
    DDL_Printf("hc32 example. \r\n");
    /* add your code here */
    while (1)
    { 
   
        LED0_TOGGLE();
        LED1_TOGGLE();
        Ddl_Delay1ms(500);
    }
}

运行,通过pc端可以看到串口有输出,同时LED灯也有闪烁。
在这里插入图片描述

2.RT-Thread下载

RT-Thread针对不同的应用场景,提供了三个主版本:RT-Thread 标准版RT-Thread NanoRT-Thread Smart
下载地址
这里根据项目需求,我们选择RT-Thread Nano(目前版本更新到3.1.5)。
下载后解压缩,目录结构如下:

在这里插入图片描述

3.RT-Thread移植

准备工作都已经做好了,接下来可以开始RT-Thread的移植了。
将rt-thread整个文件夹拷贝到我们的工程目录下,为了工程的精简,我们可以删掉一些我们不需要的文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将RT-Thread添加到我们的工程中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
添加头文件路径
在这里插入图片描述
移植RT-Thread,我们主要需要修改的是board.crtconfig.h这两个文件,board.c主要是配置系统时钟和OS_TICK(为操作系统提供心跳),而rtconfig.h则是可以根据需要配置系统的各项功能的。这里我们的配置如下(仅供参考):
rtconfig.h

/* RT-Thread config file */

#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__

#include <rtthread.h>

#if defined(__CC_ARM) || defined(__CLANG_ARM)
//#include "RTE_Components.h"

#if defined(RTE_USING_FINSH)
#define RT_USING_FINSH
#endif //RTE_USING_FINSH

#endif //(__CC_ARM) || (__CLANG_ARM)

// <<< Use Configuration Wizard in Context Menu >>>
// <h>Basic Configuration
// <o>Maximal level of thread priority <8-256>
// <i>Default: 32
#define RT_THREAD_PRIORITY_MAX 8
// <o>OS tick per second
// <i>Default: 1000 (1ms)
#define RT_TICK_PER_SECOND 1000
// <o>Alignment size for CPU architecture data access
// <i>Default: 4
#define RT_ALIGN_SIZE 4
// <o>the max length of object name<2-16>
// <i>Default: 8
#define RT_NAME_MAX 8
// <c1>Using RT-Thread components initialization
// <i>Using RT-Thread components initialization
#define RT_USING_COMPONENTS_INIT
// </c>

#define RT_USING_USER_MAIN

// <o>the stack size of main thread<1-4086>
// <i>Default: 512
#define RT_MAIN_THREAD_STACK_SIZE 1024

// </h>

// <h>Debug Configuration
// <c1>enable kernel debug configuration
// <i>Default: enable kernel debug configuration
//#define RT_DEBUG
// </c>
// <o>enable components initialization debug configuration<0-1>
// <i>Default: 0
#define RT_DEBUG_INIT 0
// <c1>thread stack over flow detect
// <i> Diable Thread stack over flow detect
//#define RT_USING_OVERFLOW_CHECK
// </c>
// </h>

// <h>Hook Configuration
// <c1>using hook
// <i>using hook
//#define RT_USING_HOOK
// </c>
// <c1>using idle hook
// <i>using idle hook
//#define RT_USING_IDLE_HOOK
// </c>
// </h>

// <e>Software timers Configuration
// <i> Enables user timers
#define RT_USING_TIMER_SOFT 0
#if RT_USING_TIMER_SOFT == 0
    #undef RT_USING_TIMER_SOFT
#endif
// <o>The priority level of timer thread <0-31>
// <i>Default: 4
#define RT_TIMER_THREAD_PRIO 4
// <o>The stack size of timer thread <0-8192>
// <i>Default: 512
#define RT_TIMER_THREAD_STACK_SIZE 512
// </e>

// <h>IPC(Inter-process communication) Configuration
// <c1>Using Semaphore
// <i>Using Semaphore
#define RT_USING_SEMAPHORE
// </c>
// <c1>Using Mutex
// <i>Using Mutex
//#define RT_USING_MUTEX
// </c>
// <c1>Using Event
// <i>Using Event
//#define RT_USING_EVENT
// </c>
// <c1>Using MailBox
// <i>Using MailBox
#define RT_USING_MAILBOX
// </c>
// <c1>Using Message Queue
// <i>Using Message Queue
//#define RT_USING_MESSAGEQUEUE
// </c>
// </h>

// <h>Memory Management Configuration
// <c1>Dynamic Heap Management
// <i>Dynamic Heap Management
//#define RT_USING_HEAP
// </c>
// <c1>using small memory
// <i>using small memory
#define RT_USING_SMALL_MEM
// </c>
// <c1>using tiny size of memory
// <i>using tiny size of memory
//#define RT_USING_TINY_SIZE
// </c>
// </h>

// <h>Console Configuration
// <c1>Using console
// <i>Using console
#define RT_USING_CONSOLE
// </c>
// <o>the buffer size of console <1-1024>
// <i>the buffer size of console
// <i>Default: 128 (128Byte)
#define RT_CONSOLEBUF_SIZE 128
// </h>

#if defined(RT_USING_FINSH)
    #define FINSH_USING_MSH
    #define FINSH_USING_MSH_ONLY
    // <h>Finsh Configuration
    // <o>the priority of finsh thread <1-7>
    // <i>the priority of finsh thread
    // <i>Default: 6
    #define __FINSH_THREAD_PRIORITY 5
    #define FINSH_THREAD_PRIORITY (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1)
    // <o>the stack of finsh thread <1-4096>
    // <i>the stack of finsh thread
    // <i>Default: 4096 (4096Byte)
    #define FINSH_THREAD_STACK_SIZE 512
    // <o>the history lines of finsh thread <1-32>
    // <i>the history lines of finsh thread
    // <i>Default: 5
    #define FINSH_HISTORY_LINES 1

    #define FINSH_USING_SYMTAB
    // </h>
#endif

// <<< end of configuration section >>>

#endif

board.c

/* * Copyright (c) 2006-2019, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2017-07-24 Tanek the first version * 2018-11-12 Ernest Chen modify copyright */
 
#include <stdint.h>
#include <rtthread.h>
#include "hc32_ddl.h"


static void sysTick_Init(void);
static void SysClkConfig(void);

static void SysClkConfig(void)
{ 
   
    stc_clk_xtal_cfg_t   stcXtalCfg;
    stc_clk_mpll_cfg_t   stcMpllCfg;
    en_clk_sys_source_t  enSysClkSrc;
    stc_clk_sysclk_cfg_t stcSysClkCfg;

    MEM_ZERO_STRUCT(enSysClkSrc);
    MEM_ZERO_STRUCT(stcSysClkCfg);
    MEM_ZERO_STRUCT(stcXtalCfg);
    MEM_ZERO_STRUCT(stcMpllCfg);

    /* Set bus clk div. */
    stcSysClkCfg.enHclkDiv  = ClkSysclkDiv1;
    stcSysClkCfg.enExclkDiv = ClkSysclkDiv2;
    stcSysClkCfg.enPclk0Div = ClkSysclkDiv1;
    stcSysClkCfg.enPclk1Div = ClkSysclkDiv2;
    stcSysClkCfg.enPclk2Div = ClkSysclkDiv4;
    stcSysClkCfg.enPclk3Div = ClkSysclkDiv4;
    stcSysClkCfg.enPclk4Div = ClkSysclkDiv2;
    CLK_SysClkConfig(&stcSysClkCfg);

    /* Switch system clock source to MPLL. */
    /* Use Xtal as MPLL source. */
    stcXtalCfg.enMode = ClkXtalModeOsc;
    stcXtalCfg.enDrv = ClkXtalLowDrv;
    stcXtalCfg.enFastStartup = Enable;
    CLK_XtalConfig(&stcXtalCfg);
    CLK_XtalCmd(Enable);

    /* MPLL config. */
    stcMpllCfg.pllmDiv = 1u; /* XTAL 8M / 1 */
    stcMpllCfg.plln = 50u;   /* 8M*50 = 400M */
    stcMpllCfg.PllpDiv = 4u; /* MLLP = 100M */
    stcMpllCfg.PllqDiv = 4u; /* MLLQ = 100M */
    stcMpllCfg.PllrDiv = 4u; /* MLLR = 100M */
    CLK_SetPllSource(ClkPllSrcXTAL);
    CLK_MpllConfig(&stcMpllCfg);

    /* flash read wait cycle setting */
    EFM_Unlock();
    EFM_SetLatency(EFM_LATENCY_4);
    EFM_Lock();

    /* Enable MPLL. */
    CLK_MpllCmd(Enable);

    /* Wait MPLL ready. */
    while (Set != CLK_GetFlagStatus(ClkFlagMPLLRdy))
    { 
   
    }

    /* Switch system clock source to MPLL. */
    CLK_SetSysClkSource(CLKSysSrcMPLL);
}

#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 4096
static uint32_t rt_heap[RT_HEAP_SIZE];     // heap default size: 4K(1024 * 4)
RT_WEAK void *rt_heap_begin_get(void)
{ 
   
    return rt_heap;
}

RT_WEAK void *rt_heap_end_get(void)
{ 
   
    return rt_heap + RT_HEAP_SIZE;
}
#endif

/** * This function will initial your board. */
void rt_hw_board_init()
{ 
   
    /* Call components board initial (use INIT_BOARD_EXPORT()) */
	SysClkConfig();
	sysTick_Init();

    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif


#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}

void SysTick_Handler(void)
{ 
   
    /* enter interrupt */
    rt_interrupt_enter();

    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}

static void sysTick_Init(void)
{ 
   
    stc_clk_freq_t stcClkFreq;

    /* configure structure initialization */

    MEM_ZERO_STRUCT(stcClkFreq);

    /* Config 1 sec trigger interrupt*/
    CLK_GetClockFreq(&stcClkFreq);
    SysTick_Config(stcClkFreq.sysclkFreq/RT_TICK_PER_SECOND);
}

更改完这两个文件后,我们可以尝试编译,会发现会报错(如下图),我们只需要在hc32f460_interrupts.c文件中删掉对应的函数,重新编译即可。
在这里插入图片描述
最后,编写代码简单测试下系统能否正常运行。

static struct rt_thread led_thread;
ALIGN(RT_ALIGN_SIZE)
static rt_uint8_t rt_led_thread_stack[1024];
static void led_thread_entry(void * para);
static rt_uint8_t led_thread_priority = 6;

/** ******************************************************************************* ** \brief Main function of template project ** ** \param None ** ** \retval int32_t return value, if needed ** ******************************************************************************/
int32_t main(void)
{ 
   
    DDL_PrintfInit(DEBUG_USART_CH, DEBUG_USART_BAUDRATE, DebugUsartInit);
    LedInit();
    DDL_Printf("hc32 example. \r\n");

    	/* main thread */
	rt_thread_init(&led_thread,
					"led_thread",
					led_thread_entry,
					RT_NULL,
					&rt_led_thread_stack,
					sizeof(rt_led_thread_stack),
					led_thread_priority,
					1000);
	rt_thread_startup(&led_thread);
}


static void led_thread_entry(void * para)
{ 
   
    DDL_Printf("Entry %s. \r\n", __func__);
    while(1)
    { 
   
        LED0_TOGGLE();
        LED1_TOGGLE();
        rt_thread_delay(1000);
    }
}

正常运行
在这里插入图片描述

4.finsh组件移植

最后我们来讲一下RT-Thread FinSh组件的移植。Finsh是RT-Thread提供的一套命令行组件,在命令行下通过各种指令的调用,我们可以很轻松的获取到当前系统的运行状态,在项目开发中可以方便我们的调试。
添加源码
在这里插入图片描述
配置头文件路径
在这里插入图片描述

更改配置文件
rtconfig.h

#define RTE_USING_FINSH
#if defined(RTE_USING_FINSH)
#define RT_USING_FINSH
#endif //RTE_USING_FINSH

board.c

/** * This function will initial your board. */
void rt_hw_board_init()
{ 
   
    /* Call components board initial (use INIT_BOARD_EXPORT()) */
	SysClkConfig();
	sysTick_Init();

    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif


#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif

	DDL_PrintfInit(DEBUG_USART_CH, DEBUG_USART_BAUDRATE, DebugUsartInit);
}

void rt_hw_console_output(const char *str)
{ 
   
	rt_size_t i = 0, size = 0;
	char a = '\r';

	size = rt_strlen(str);
	for (i = 0; i < size; i++)
	{ 
   
		if (*(str + i) == '\n')
		{ 
   
			DEBUG_USART_CH->DR = a;
		    while (0ul == DEBUG_USART_CH->SR_f.TC)
		    { 
   
		        ;
		    }
		}
		DEBUG_USART_CH->DR = *(str + i);
	    while (0ul == DEBUG_USART_CH->SR_f.TC)
	    { 
   
	        ;
	    }
	}
}

char rt_hw_console_getchar(void)
{ 
   
    int ch = -1;

	if (Set == USART_GetStatus(DEBUG_USART_CH, UsartRxNoEmpty))
	{ 
   
		ch = DEBUG_USART_CH->DR_f.RDR;
    }
    else
    { 
   
		if(Set == USART_GetStatus(DEBUG_USART_CH, UsartOverrunErr))
		{ 
   
            USART_ClearStatus(DEBUG_USART_CH, UsartOverrunErr);
        }
        rt_thread_mdelay(10);
    }
    return ch;
}

运行
在这里插入图片描述
最后附上源码链接:hc32f460petb_template.zip


总结

以上就是今天分享内容的全部,本文仅是简单的介绍了RT-Thread+FinSh组件的移植,若是有什么问题欢迎评论或私信提出。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143465.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(1)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号