GD32F427移植FreeRTOS详细教程

 一、下载Freertos源码

(1)浏览器输入以下网址进入Freertos官网

https://www.freertos.org/

(2)点击dowmload下载FreeRTOS源码。

(3)下载成功后进入FreeRTOS-LTS目录,这便是我们需要的Freertos源码。

二、准备一个GD32标准库工程

为了方便测试,本次利用GD32标准库的串口通信实验代码为准,复制整个文件夹到并重命名为01_Template

三、移植FreeRTOS

(1)在工程文件根目录下创建 Middlewares 文件夹,在 Middlewares 里面再创建一个叫 FreeRTOS 的文件夹。

(2)进入FreeRTOS源码根目录,点击进入 FreeRTOS 文件夹

(3)其中有很多文件,本次移植只需要 FreeRTOS-Kernel 里的内容,点击进入FreeRTOS-Kernel文件夹。

(4)按下 ctrl+A 复制所有文件,到我们创建的 Middlewares\FreeRTOS 文件下。

(5)其中 portable 文件夹下有好多文件夹,但是实际上我们只需要 MemMang 和 RVDS 文件夹,剩下的可以删除。

(6)打开keil,加入我们需要的c文件,首先创建两个组分别为 FreeRTOS_CORE 和 FreeRTOS_PORT。

(7)将c文件导入工程组文件夹

1、将 Middlewares\FreeRTOS 文件夹下的所有文件导入 FreeRTOS_CORE组,这些是FreeRTOS的核心代码。

2、将 Middlewares\FreeRTOS\portable 下的heap4导入 FreeRTOS_PORT组中,本文件夹主要定义了五个内存管理方案,可自行了解,一般情况下使用heap4就行了。

3、将 Middlewares\FreeRTOS\portable\RVDS\ARM_CM4F 下的Port导入FreeRTOS_PORT组, port.c 文件里面的内容是由 FreeRTOS 官方的技术人员为 Cortex-M4 内核的处理 器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成。

(8)移植完成后的工程目录如下,接着我们需要添加INCLUDE路径。

1、依次点击看,进入配置路径页面。

2、分别添加 Middlewares\FreeRTOS\include 路径和 Middlewares\FreeRTOS\portable\RVDS\ARM_CM4F 路径,添加完毕后可以看到。

(9)进行编译,不出意外是会出错的,因为没有 FreeRTOSConfig.h 文件。

四、完善工程

(1)首先,进入 FreeRTOS 源码根目录,可以看到 examples 文件夹。

(2)进入 Middlewares\FreeRTOS\examples\coverity 文件夹下,可以看到一个 FreeRTOSConfig.h 文件,将其复制下载放置在 user 目录下

(3)此时编译,会出现以下错误,只需要将 FreeRTOSConfig.h 文件中的 TICK_TYPE_WIDTH_64_BITS 改成 TICK_TYPE_WIDTH_32_BITS 或者 TICK_TYPE_WIDTH_16_BITS就可以了

修改前

修改后

(4)修改后错误消失,出现新错误,以下错误只需要我们将 configMAX_SYSCALL_INTERRUPT_PRIORITY 修改就行,这里我们修改成32。

修改前

修改后

(5)此时出现新错误,该错误只需要将 configAPPLICATION_ALLOCATED_HEAP 屏蔽就行

修改前

修改后

(6)此时编译通过,但是依然无法正常运行,原因是为未进行中断相关配置。复制以下代码,加入到 FreeRTOSConfig.h 底部。

/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS 4
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15                  /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5                   /* FreeRTOS可管理的最高中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY                 ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY            ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY           configMAX_SYSCALL_INTERRUPT_PRIORITY

/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler                              PendSV_Handler
#define vPortSVCHandler                                 SVC_Handler

/* 断言 */
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )

(7)出现重复定义错误,去 gd32f4xx_it.c 中屏蔽即可

(8)FreeRTOS的运行是需要心跳的,一般是1ms为周期,打开delay.c文件.修改delay代码,利用systick为FreeRTOS提供心跳。将delay.c修改为以下代码。

/************************************************ 
* 使用SysTick的普通计数模式对延迟进行管理
* 包括delay_us,delay_ms	   						  
************************************************/	

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"

static uint16_t  g_fac_us = 0;      /* us延时倍乘数 */

#if SYS_SUPPORT_OS
#include "FreeRTOS.h"
#include "task.h"  

extern void xPortSysTickHandler(void);

/**
 * @brief       systick中断服务函数,使用OS时用到
 * @param       ticks: 延时的节拍数
 * @retval      无
 */
void SysTick_Handler(void)
{
		if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
		{
			xPortSysTickHandler();
		}
}

#endif

/**
 * @brief       初始化延迟函数
 * @param       sysclk: 系统时钟频率, 即CPU频率(HCLK)
 * @retval      无
 */
void delay_init(uint16_t sysclk)
{
#if SYS_SUPPORT_OS                                              /* 如果需要支持OS. */
    uint32_t reload;
#endif
    SysTick->CTRL = 0;                                          /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
    SysTick->CTRL&=~(1<<2);                                     /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
    g_fac_us = sysclk / 8;                                      /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;                   /* 开启SYSTICK */
    SysTick->LOAD = 0XFFFFFF;                                   /* 注意systick计数器24位,所以这里设置最大重装载值 */
#if SYS_SUPPORT_OS                                              /* 如果需要支持OS. */
    reload = sysclk / 8;                                        /* 每秒钟的计数次数 单位为M */
    reload *= 1000000 / configTICK_RATE_HZ;                     /* 根据delay_ostickspersec设定溢出时间 */
                                                                                                                     
    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;                  /* 开启SYSTICK中断 */
    SysTick->LOAD = reload;                                     /* 每1/delay_ostickspersec秒中断一次 */
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}


/**
 * @brief       延时nus
 * @note        无论是否使用OS, 都是用时钟摘取法来做us延时
 * @param       nus: 要延时的us数.
 * @note        nus取值范围: 0 ~ (2^32 / g_fac_us) (g_fac_us一般等于系统主频/8, 自行套入计算)
 * @retval      无
 */
void delay_us(uint32_t nus)
{
    uint32_t ticks;
    uint32_t told, tnow, tcnt = 0;
    uint32_t reload;
    reload = SysTick->LOAD;     /* LOAD的值 */
    ticks = nus * g_fac_us;     /* 需要的节拍数 */
  
    told = SysTick->VAL;        /* 刚进入时的计数器值 */
    while (1)
    {
        tnow = SysTick->VAL;

        if (tnow != told)
        {
            if (tnow < told)
            {
                tcnt += told - tnow;    /* 这里注意一下SYSTICK是一个递减的计数器就可以了. */
            }
            else
            {
                tcnt += reload - tnow + told;
            }
            told = tnow;
            if (tcnt >= ticks) 
            {
                break;                  /* 时间超过/等于要延迟的时间,则退出. */
            }
        }
    }
}

/**
 * @brief       延时nms
 * @param       nms: 要延时的ms数 (0< nms <= (2^32 / g_fac_us / 1000))(g_fac_us一般等于系统主频/8, 自行套入计算)
 * @retval      无
 */
void delay_ms(uint16_t nms)
{
	uint32_t i;
 
 for (i=0; i<nms; i++)
 {
 delay_us(1000);
 }
}

(9)记得将 SYS_SUPPORT_OS 置1支持OS

(10)编译出现重复定义将以下三行注释即可。

(11)出现 printf未定义,只需要将 usart 相关加入在前面即可,此时程序已经没有任何问题。

五、测试运行

(1)由于程序量不大,我们直接在main函数里书写,实际上这种书写方式是不推荐的。如下,不出意外程序运行,板载led会闪烁和并且串口会循环hello。

/************************************************
 WKS GD32F427ZGT6核心板
 串口通信 实验
************************************************/

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "FreeRTOS.h"
#include "task.h"

/*******************************************FreeRTOS配置***********************************************/

//START_TASK 任务 配置
//包括: 任务句柄 任务优先级 堆栈大小 创建任务
#define START_TASK_PRIO 1                   /* 任务优先级 */
#define START_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            StartTask_Handler;  /* 任务句柄 */
void start_task(void *pvParameters);        /* 任务函数 */

//TASK1 任务 配置
//包括: 任务句柄 任务优先级 堆栈大小 创建任务
#define TASK1_PRIO      2                   /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);             /* 任务函数 */

//TASK2 任务 配置
//包括: 任务句柄 任务优先级 堆栈大小 创建任务
#define TASK2_PRIO      3                   /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);             /* 任务函数 */

/*
函数名:freertos_demo
功能:FreeRTOS例程入口函数
参数:无
返回值:无
备注:无
*/
void freertos_demo(void)
{
    xTaskCreate((TaskFunction_t )start_task,            /* 任务函数 */
                (const char*    )"start_task",          /* 任务名称 */
                (uint16_t       )START_STK_SIZE,        /* 任务堆栈大小 */
                (void*          )NULL,                  /* 传入给任务函数的参数 */
                (UBaseType_t    )START_TASK_PRIO,       /* 任务优先级 */
                (TaskHandle_t*  )&StartTask_Handler);   /* 任务句柄 */
    vTaskStartScheduler();
}

/*
函数名:start_task
功能:启动任务
参数:pvParameters  传入参数(未用到)
返回值:无
备注:无
*/
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           /* 进入临界区 */
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
    taskEXIT_CRITICAL();            /* 退出临界区 */
}

/*
函数名:task1
功能:任务1
参数:pvParameters  传入参数(未用到)
返回值:无
备注:无
*/
void task1(void *pvParameters)
{
    while(1)
    {
        LED0_TOGGLE();                                                  /* LED0闪烁 */
        vTaskDelay(1000);                                               /* 延时1000ticks */
    }
}

/*
函数名:task2
功能:任务2
参数:pvParameters  传入参数(未用到)
返回值:无
备注:无
*/
void task2(void *pvParameters)
{  
    while(1)
    {
        printf("hello\r\n");  /* 打印数值 */
		vTaskDelay(1000);
    }
}

int main(void)
{
    delay_init(200);                        /* 初始化延时函数 */
    usart_init(115200);                     /* 初始化串口 */
    led_init();							                /* 初始化LED */
	freertos_demo();
}

(2)将程序烧入,可以看到 led 灯以1s为周期进行闪烁,同时串口循环打印"hello"。

六、附录

本次移植,只进行了相对简单的配置,详细配置解析并了解配置请参考 慧勤智远 的官方移植案例,如下:

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/* 头文件 */
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include <stdint.h>

extern uint32_t SystemCoreClock;

/* 基础配置项 */
#define configUSE_PREEMPTION                            1                       /* 1: 抢占式调度器, 0: 协程式调度器, 无默认需定义 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION         1                       /* 1: 使用硬件计算下一个要运行的任务, 0: 使用软件算法计算下一个要运行的任务, 默认: 0 */
#define configUSE_TICKLESS_IDLE                         0                       /* 1: 使能tickless低功耗模式, 默认: 0 */
#define configCPU_CLOCK_HZ                              SystemCoreClock         /* 定义CPU主频, 单位: Hz, 无默认需定义 */
#define configSYSTICK_CLOCK_HZ                          (configCPU_CLOCK_HZ / 8)/* 定义SysTick时钟频率,当SysTick时钟频率与内核时钟频率不同时才可以定义, 单位: Hz, 默认: 不定义 */
#define configTICK_RATE_HZ                              1000                    /* 定义系统时钟节拍频率, 单位: Hz, 无默认需定义 */
#define configMAX_PRIORITIES                            32                      /* 定义最大优先级数, 最大优先级=configMAX_PRIORITIES-1, 无默认需定义 */
#define configMINIMAL_STACK_SIZE                        128                     /* 定义空闲任务的栈空间大小, 单位: Word, 无默认需定义 */
#define configMAX_TASK_NAME_LEN                         16                      /* 定义任务名最大字符数, 默认: 16 */
#define configUSE_16_BIT_TICKS                          0                       /* 1: 定义系统时钟节拍计数器的数据类型为16位无符号数, 无默认需定义 */
#define configIDLE_SHOULD_YIELD                         1                       /* 1: 使能在抢占式调度下,同优先级的任务能抢占空闲任务, 默认: 1 */
#define configUSE_TASK_NOTIFICATIONS                    1                       /* 1: 使能任务间直接的消息传递,包括信号量、事件标志组和消息邮箱, 默认: 1 */
#define configTASK_NOTIFICATION_ARRAY_ENTRIES           1                       /* 定义任务通知数组的大小, 默认: 1 */
#define configUSE_MUTEXES                               1                       /* 1: 使能互斥信号量, 默认: 0 */
#define configUSE_RECURSIVE_MUTEXES                     1                       /* 1: 使能递归互斥信号量, 默认: 0 */
#define configUSE_COUNTING_SEMAPHORES                   1                       /* 1: 使能计数信号量, 默认: 0 */
#define configUSE_ALTERNATIVE_API                       0                       /* 已弃用!!! */
#define configQUEUE_REGISTRY_SIZE                       8                       /* 定义可以注册的信号量和消息队列的个数, 默认: 0 */
#define configUSE_QUEUE_SETS                            1                       /* 1: 使能队列集, 默认: 0 */
#define configUSE_TIME_SLICING                          1                       /* 1: 使能时间片调度, 默认: 1 */
#define configUSE_NEWLIB_REENTRANT                      0                       /* 1: 任务创建时分配Newlib的重入结构体, 默认: 0 */
#define configENABLE_BACKWARD_COMPATIBILITY             0                       /* 1: 使能兼容老版本, 默认: 1 */
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS         0                       /* 定义线程本地存储指针的个数, 默认: 0 */
#define configSTACK_DEPTH_TYPE                          uint16_t                /* 定义任务堆栈深度的数据类型, 默认: uint16_t */
#define configMESSAGE_BUFFER_LENGTH_TYPE                size_t                  /* 定义消息缓冲区中消息长度的数据类型, 默认: size_t */

/* 内存分配相关定义 */
#define configSUPPORT_STATIC_ALLOCATION                 0                       /* 1: 支持静态申请内存, 默认: 0 */
#define configSUPPORT_DYNAMIC_ALLOCATION                1                       /* 1: 支持动态申请内存, 默认: 1 */
#define configTOTAL_HEAP_SIZE                           ((size_t)(10 * 1024))   /* FreeRTOS堆中可用的RAM总量, 单位: Byte, 无默认需定义 */
#define configAPPLICATION_ALLOCATED_HEAP                0                       /* 1: 用户手动分配FreeRTOS内存堆(ucHeap), 默认: 0 */
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP       0                       /* 1: 用户自行实现任务创建时使用的内存申请与释放函数, 默认: 0 */

/* 钩子函数相关定义 */
#define configUSE_IDLE_HOOK                             0                       /* 1: 使能空闲任务钩子函数, 无默认需定义  */
#define configUSE_TICK_HOOK                             0                       /* 1: 使能系统时钟节拍中断钩子函数, 无默认需定义 */
#define configCHECK_FOR_STACK_OVERFLOW                  0                       /* 1: 使能栈溢出检测方法1, 2: 使能栈溢出检测方法2, 默认: 0 */
#define configUSE_MALLOC_FAILED_HOOK                    0                       /* 1: 使能动态内存申请失败钩子函数, 默认: 0 */
#define configUSE_DAEMON_TASK_STARTUP_HOOK              0                       /* 1: 使能定时器服务任务首次执行前的钩子函数, 默认: 0 */

/* 运行时间和任务状态统计相关定义 */
#define configGENERATE_RUN_TIME_STATS                   0                       /* 1: 使能任务运行时间统计功能, 默认: 0 */

#if configGENERATE_RUN_TIME_STATS
#include "./BSP/TIMER/btim.h"
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()        ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE()                FreeRTOSRunTimeTicks
#endif

#define configUSE_TRACE_FACILITY                        1                       /* 1: 使能可视化跟踪调试, 默认: 0 */
#define configUSE_STATS_FORMATTING_FUNCTIONS            1                       /* 1: configUSE_TRACE_FACILITY为1时,会编译vTaskList()和vTaskGetRunTimeStats()函数, 默认: 0 */

/* 协程相关定义 */
#define configUSE_CO_ROUTINES                           0                       /* 1: 启用协程, 默认: 0 */
#define configMAX_CO_ROUTINE_PRIORITIES                 2                       /* 定义协程的最大优先级, 最大优先级=configMAX_CO_ROUTINE_PRIORITIES-1, 无默认configUSE_CO_ROUTINES为1时需定义 */

/* 软件定时器相关定义 */
#define configUSE_TIMERS                                1                               /* 1: 使能软件定时器, 默认: 0 */
#define configTIMER_TASK_PRIORITY                       ( configMAX_PRIORITIES - 1 )    /* 定义软件定时器任务的优先级, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_QUEUE_LENGTH                        5                               /* 定义软件定时器命令队列的长度, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_TASK_STACK_DEPTH                    ( configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小, 无默认configUSE_TIMERS为1时需定义 */

/* 可选函数, 1: 使能 */
#define INCLUDE_vTaskPrioritySet                        1                       /* 设置任务优先级 */
#define INCLUDE_uxTaskPriorityGet                       1                       /* 获取任务优先级 */
#define INCLUDE_vTaskDelete                             1                       /* 删除任务 */
#define INCLUDE_vTaskSuspend                            1                       /* 挂起任务 */
#define INCLUDE_xResumeFromISR                          1                       /* 恢复在中断中挂起的任务 */
#define INCLUDE_vTaskDelayUntil                         1                       /* 任务绝对延时 */
#define INCLUDE_vTaskDelay                              1                       /* 任务延时 */
#define INCLUDE_xTaskGetSchedulerState                  1                       /* 获取任务调度器状态 */
#define INCLUDE_xTaskGetCurrentTaskHandle               1                       /* 获取当前任务的任务句柄 */
#define INCLUDE_uxTaskGetStackHighWaterMark             1                       /* 获取任务堆栈历史剩余最小值 */
#define INCLUDE_xTaskGetIdleTaskHandle                  1                       /* 获取空闲任务的任务句柄 */
#define INCLUDE_eTaskGetState                           1                       /* 获取任务状态 */
#define INCLUDE_xEventGroupSetBitFromISR                1                       /* 在中断中设置事件标志位 */
#define INCLUDE_xTimerPendFunctionCall                  1                       /* 将函数的执行挂到定时器服务任务 */
#define INCLUDE_xTaskAbortDelay                         1                       /* 中断任务延时 */
#define INCLUDE_xTaskGetHandle                          1                       /* 通过任务名获取任务句柄 */
#define INCLUDE_xTaskResumeFromISR                      1                       /* 恢复在中断中挂起的任务 */

/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS 4
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15                  /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5                   /* FreeRTOS可管理的最高中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY                 ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY            ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY           configMAX_SYSCALL_INTERRUPT_PRIORITY

/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler                              PendSV_Handler
#define vPortSVCHandler                                 SVC_Handler

/* 断言 */
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )

/* FreeRTOS MPU 特殊定义 */
//#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
//#define configTOTAL_MPU_REGIONS                                8
//#define configTEX_S_C_B_FLASH                                  0x07UL
//#define configTEX_S_C_B_SRAM                                   0x07UL
//#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY            1
//#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS             1

/* ARMv8-M 安全侧端口相关定义。 */
//#define secureconfigMAX_SECURE_CONTEXTS         5

#endif /* FREERTOS_CONFIG_H */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值