FreeRTOS多任务开发:打造高效稳定的嵌入式系统

FreeRTOS多任务开发打造嵌入式系统

🌟 关注「嵌入式软件客栈」公众号 🌟,解锁实战技巧!💻🚀

1. FreeRTOS多任务基础架构

1.1 任务模型概述

FreeRTOS作为一款轻量级实时操作系统,其核心是多任务处理能力。在FreeRTOS中,应用程序被组织为一组自主任务,每个任务在自己的上下文(如独立的栈空间)中执行,彼此间没有依赖关系。

每个任务可以被视为一个独立的程序,拥有自己的入口函数、栈空间和局部变量。调度器负责任务的启动、停止、切入和切出。

1.2 任务状态与转换

FreeRTOS中的任务可以处于以下几种状态:

  • 运行态(Running):当前正在CPU上执行
  • 就绪态(Ready):可以运行,但当前没有获得CPU
  • 阻塞态(Blocked):等待某个事件或延时到期
  • 挂起态(Suspended):通过API手动挂起,不参与调度
  • 删除态(Deleted):任务被删除,资源释放

任务状态转换图:

    创建            调度器选择           
   -----> [就绪态] ----------> [运行态]
           ^  ^                 |  |
           |  |     时间片到期   |  |
           |  +<----------------+  |
           |                       |
           |      等待事件/延时     |
           +<----------------------+
           ^                       |
           |                       v
    解除挂起|                  |手动挂起
           |                       |
          [挂起态] <---------------+

2. 任务创建与生命周期管理

2.1 创建任务

在FreeRTOS中,任务通常使用xTaskCreate()函数创建:

/* 创建数据采集任务 */
void vSensorTask(void *pvParameters)
{
    // 任务初始化代码
    float sensorData = 0.0f;
    
    // 任务主循环
    while(1)
    {
        // 读取传感器数据
        sensorData = readSensor();
        
        // 发送数据到处理队列
        xQueueSend(dataQueue, &sensorData, portMAX_DELAY);
        
        // 延时100ms
        vTaskDelay(pdMS_TO_TICKS(100));
    }
    
    // 任务永远不应该到达这里,如果到达,应该删除自己
    vTaskDelete(NULL);
}

// 主函数中创建任务
BaseType_t xReturned;
TaskHandle_t xSensorTaskHandle = NULL;

xReturned = xTaskCreate(
    vSensorTask,          /* 任务函数 */
    "Sensor",             /* 任务名称 */
    configMINIMAL_STACK_SIZE*2,  /* 栈大小(字)*/
    NULL,                 /* 任务参数 */
    tskIDLE_PRIORITY + 1, /* 优先级 */
    &xSensorTaskHandle    /* 任务句柄 */
);

if(xReturned != pdPASS)
{
    /* 任务创建失败,通常是内存不足 */
    configASSERT(0);
}

2.2 任务控制

2.2.1 挂起和恢复任务
// 在特定条件下挂起传感器任务
if(systemStatus == SYSTEM_ERROR)
{
    vTaskSuspend(xSensorTaskHandle);
    printf("传感器任务已挂起\r\n");
}

// 系统恢复后重新启动任务
if(systemStatus == SYSTEM_NORMAL)
{
    vTaskResume(xSensorTaskHandle);
    printf("传感器任务已恢复\r\n");
}
2.2.2 删除任务
// 删除不再需要的任务
vTaskDelete(xSensorTaskHandle);
xSensorTaskHandle = NULL;
2.2.3 延时任务
// 简单延时(相对延时)
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒

// 精确周期延时(绝对延时)
TickType_t xLastWakeTime = xTaskGetTickCount();
for(;;)
{
    // 执行周期性工作
    doPeriodicWork();
    
    // 固定周期500ms
    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(500));
}

3. 高级任务调度策略

3.1 优先级调度

FreeRTOS使用优先级调度算法,高优先级任务会抢占低优先级任务:

// 定义不同优先级
#define PRIORITY_HIGH    (configMAX_PRIORITIES - 1)
#define PRIORITY_MEDIUM  (configMAX_PRIORITIES / 2)
#define PRIORITY_LOW     (tskIDLE_PRIORITY + 1)

// 创建不同优先级任务
xTaskCreate(vEmergencyTask, "Emergency", 1024, NULL, PRIORITY_HIGH, NULL);
xTaskCreate(vNormalTask,    "Normal",    1024, NULL, PRIORITY_MEDIUM, NULL);
xTaskCreate(vBackgroundTask,"Background",1024, NULL, PRIORITY_LOW, NULL);

3.2 时间片轮转

对于相同优先级任务,FreeRTOS采用时间片轮转调度:

// 创建两个相同优先级的任务
xTaskCreate(vProcessTask1, "Process1", 1024, NULL, PRIORITY_MEDIUM, NULL);
xTaskCreate(vProcessTask2, "Process2", 1024, NULL, PRIORITY_MEDIUM, NULL);

// 这两个任务会轮流执行,每个任务每次执行configTICK_RATE_HZ个时钟周期

3.3 动态优先级调整

// 根据任务重要性临时提高优先级
void vHandleImportantEvent(void)
{
    // 保存原始优先级
    UBaseType_t uxOriginalPriority;
    uxOriginalPriority = uxTaskPriorityGet(NULL);
    
    // 临时提高当前任务优先级
    vTaskPrioritySet(NULL, PRIORITY_HIGH);
    
    // 处理重要事件
    processImportantEvent();
    
    // 恢复原始优先级
    vTaskPrioritySet(NULL, uxOriginalPriority);
}

4. 任务间通信机制

4.1 队列通信

// 创建队列
QueueHandle_t xDataQueue;
xDataQueue = xQueueCreate(10, sizeof(SensorData_t));

// 发送方任务
void vSensorTask(void *pvParameters)
{
    SensorData_t xData;
    while(1)
    {
        // 采集数据
        xData.value = readSensor();
        xData.timestamp = xTaskGetTickCount();
        
        // 发送到队列
        if(xQueueSend(xDataQueue, &xData, pdMS_TO_TICKS(100)) != pdPASS)
        {
            // 发送失败处理
            errorHandler(ERROR_QUEUE_FULL);
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// 接收方任务
void vProcessTask(void *pvParameters)
{
    SensorData_t xReceivedData;
    while(1)
    {
        // 从队列接收数据
        if(xQueueReceive(xDataQueue, &xReceivedData, portMAX_DELAY) == pdPASS)
        {
            // 处理数据
            processData(&xReceivedData);
        }
    }
}

4.2 信号量

// 创建二值信号量(互斥锁)
SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();

// 使用互斥锁保护共享资源
void vTask(void *pvParameters)
{
    while(1)
    {
        // 尝试获取互斥锁
        if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(500)) == pdTRUE)
        {
            // 访问共享资源
            accessSharedResource();
            
            // 释放互斥锁
            xSemaphoreGive(xMutex);
        }
        else
        {
            // 无法获取互斥锁的处理
            handleMutexTimeout();
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

4.3 事件组

// 创建事件组
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();

// 定义事件位
#define EVENT_TEMPERATURE_BIT   (1 << 0)
#define EVENT_HUMIDITY_BIT      (1 << 1)
#define EVENT_PRESSURE_BIT      (1 << 2)
#define ALL_SENSOR_BITS         (EVENT_TEMPERATURE_BIT | EVENT_HUMIDITY_BIT | EVENT_PRESSURE_BIT)

// 生产者任务
void vTemperatureTask(void *pvParameters)
{
    while(1)
    {
        // 读取温度
        readTemperature();
        
        // 设置对应事件位
        xEventGroupSetBits(xEventGroup, EVENT_TEMPERATURE_BIT);
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 消费者任务
void vProcessingTask(void *pvParameters)
{
    EventBits_t uxBits;
    while(1)
    {
        // 等待所有传感器数据就绪
        uxBits = xEventGroupWaitBits(
                    xEventGroup,        // 事件组
                    ALL_SENSOR_BITS,    // 等待的位
                    pdTRUE,             // 清除位
                    pdTRUE,             // 等待所有位
                    portMAX_DELAY);     // 无限等待
        
        // 所有数据就绪,进行处理
        processSensorData();
    }
}

5. 多任务异常处理与排查

5.1 常见异常类型

5.1.1 栈溢出
// 配置栈溢出钩子函数
void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName)
{
    // 记录栈溢出信息
    printf("栈溢出: 任务[%s]发生栈溢出\r\n", pcTaskName);
    
    // 系统复位或其他处理
    NVIC_SystemReset();
}
5.1.2 优先级反转
// 使用优先级继承互斥锁防止优先级反转
SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();

// 高优先级任务
void vHighPriorityTask(void *pvParameters)
{
    while(1)
    {
        // 尝试获取互斥锁
        if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
        {
            // 访问共享资源
            accessSharedResource();
            
            // 释放互斥锁
            xSemaphoreGive(xMutex);
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}
5.1.3 死锁检测
// 使用超时机制防止死锁
void vTask(void *pvParameters)
{
    while(1)
    {
        // 尝试获取互斥锁,设置超时时间
        if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(1000)) == pdTRUE)
        {
            // 访问共享资源
            accessSharedResource();
            
            // 释放互斥锁
            xSemaphoreGive(xMutex);
        }
        else
        {
            // 超时,可能存在死锁
            printf("警告:可能存在死锁\r\n");
            // 记录系统状态或重启
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

5.2 任务监控与调试

5.2.1 看门狗任务
// 定义任务状态检查结构
typedef struct {
    TaskHandle_t handle;
    uint32_t lastCheckpoint;
    bool healthy;
} TaskMonitor_t;

TaskMonitor_t taskMonitors[MAX_MONITORED_TASKS];

// 监控任务实现
void vWatchdogTask(void *pvParameters)
{
    while(1)
    {
        // 检查各个任务的状态
        for(int i = 0; i < MAX_MONITORED_TASKS; i++)
        {
            if(taskMonitors[i].handle != NULL)
            {
                // 获取任务状态
                eTaskState state = eTaskGetState(taskMonitors[i].handle);
                
                // 检查任务是否更新了检查点
                if(taskMonitors[i].lastCheckpoint == 0 ||
                   (state == eBlocked && taskMonitors[i].healthy == false))
                {
                    // 任务可能已经卡住
                    handleTaskFailure(i);
                }
            }
        }
        
        // 喂硬件看门狗
        HAL_IWDG_Refresh(&hiwdg);
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 任务检查点更新
#define UPDATE_TASK_CHECKPOINT() do { \
    for(int i = 0; i < MAX_MONITORED_TASKS; i++) { \
        if(taskMonitors[i].handle == xTaskGetCurrentTaskHandle()) { \
            taskMonitors[i].lastCheckpoint = xTaskGetTickCount(); \
            taskMonitors[i].healthy = true; \
            break; \
        } \
    } \
} while(0)
5.2.2 任务统计信息
// 定义任务统计信息结构
typedef struct {
    char taskName[configMAX_TASK_NAME_LEN];
    UBaseType_t priority;
    uint32_t stackRemaining;
    eTaskState state;
    uint32_t runTime; // 百分比 * 100
} TaskStats_t;

// 统计任务实现
void vStatisticsTask(void *pvParameters)
{
    TaskStats_t stats[MAX_TASKS];
    UBaseType_t uxArraySize, x;
    
    while(1)
    {
        // 获取任务数量
        uxArraySize = uxTaskGetNumberOfTasks();
        
        // 遍历所有任务
        for(x = 0; x < uxArraySize; x++)
        {
            TaskHandle_t xHandle;
            TaskStatus_t xTaskDetails;
            
            // 获取下一个任务句柄
            xHandle = xTaskGetIdleTaskHandle(); // 简化版,实际需要遍历所有任务
            
            // 获取任务详细信息
            vTaskGetInfo(xHandle, &xTaskDetails, pdTRUE, eInvalid);
            
            // 填充统计结构
            strncpy(stats[x].taskName, xTaskDetails.pcTaskName, configMAX_TASK_NAME_LEN);
            stats[x].priority = xTaskDetails.uxCurrentPriority;
            stats[x].stackRemaining = xTaskDetails.usStackHighWaterMark;
            stats[x].state = xTaskDetails.eCurrentState;
        }
        
        // 打印统计信息
        printTaskStats(stats, uxArraySize);
        
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

6. 工业级应用案例分析

6.1 智能传感器网络系统

以下是一个智能传感器网络系统的简化实现,包含数据采集、处理、存储和通信多个任务:

// 系统架构图
/*
+---------------+    +---------------+    +---------------+
|  传感器采集任务  | -> |  数据处理任务  | -> |  数据存储任务  |
+---------------+    +---------------+    +---------------+
        |                    |                    |
        v                    v                    v
+-----------------------------------------------+
|                  监控任务                      |
+-----------------------------------------------+
        ^                    ^                    ^
        |                    |                    |
+---------------+    +---------------+    +---------------+
|  网络通信任务  | <- |  命令处理任务  | <- |  告警处理任务  |
+---------------+    +---------------+    +---------------+
*/

// 主要队列和信号量定义
QueueHandle_t xRawDataQueue;    // 原始数据队列
QueueHandle_t xProcessedQueue;  // 处理后数据队列
QueueHandle_t xStorageQueue;    // 存储任务队列
QueueHandle_t xAlarmQueue;      // 告警队列
QueueHandle_t xCommandQueue;    // 命令队列

SemaphoreHandle_t xSpiMutex;    // SPI总线互斥锁
SemaphoreHandle_t xI2CMutex;    // I2C总线互斥锁

// 传感器采集任务
void vSensorTask(void *pvParameters)
{
    SensorData_t xData;
    
    // 任务初始化
    sensorInit();
    
    while(1)
    {
        // 获取SPI总线访问权
        if(xSemaphoreTake(xSpiMutex, pdMS_TO_TICKS(100)) == pdPASS)
        {
            // 读取传感器数据
            xData.temperature = readTemperature();
            xData.humidity = readHumidity();
            xData.pressure = readPressure();
            xData.timestamp = xTaskGetTickCount();
            
            // 释放SPI总线
            xSemaphoreGive(xSpiMutex);
            
            // 将数据发送到处理队列
            xQueueSend(xRawDataQueue, &xData, pdMS_TO_TICKS(10));
            
            // 超出阈值检查
            if(xData.temperature > TEMP_HIGH_THRESHOLD)
            {
                AlarmData_t xAlarm = {
                    .type = ALARM_HIGH_TEMPERATURE,
                    .value = xData.temperature,
                    .timestamp = xData.timestamp
                };
                
                xQueueSend(xAlarmQueue, &xAlarm, pdMS_TO_TICKS(10));
            }
        }
        
        // 周期性延时
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 数据处理任务
void vProcessTask(void *pvParameters)
{
    SensorData_t xRawData;
    ProcessedData_t xProcessedData;
    
    while(1)
    {
        // 等待原始数据
        if(xQueueReceive(xRawDataQueue, &xRawData, portMAX_DELAY) == pdPASS)
        {
            // 数据过滤和处理
            xProcessedData.temperature = applyFilter(xRawData.temperature);
            xProcessedData.humidity = applyFilter(xRawData.humidity);
            xProcessedData.pressure = applyFilter(xRawData.pressure);
            xProcessedData.timestamp = xRawData.timestamp;
            
            // 计算统计数据
            calculateStatistics(&xProcessedData);
            
            // 发送到存储队列
            xQueueSend(xProcessedQueue, &xProcessedData, pdMS_TO_TICKS(100));
        }
        
        // 检查点更新
        UPDATE_TASK_CHECKPOINT();
    }
}

// 网络通信任务
void vNetworkTask(void *pvParameters)
{
    ProcessedData_t xData;
    CommandData_t xCommand;
    
    // 初始化网络
    networkInit();
    
    while(1)
    {
        // 检查是否有数据需要发送
        if(xQueueReceive(xProcessedQueue, &xData, pdMS_TO_TICKS(10)) == pdPASS)
        {
            // 发送数据到云平台
            sendDataToCloud(&xData);
        }
        
        // 检查是否有新命令
        if(checkForRemoteCommands(&xCommand))
        {
            // 发送到命令队列
            xQueueSend(xCommandQueue, &xCommand, pdMS_TO_TICKS(10));
        }
        
        // 检查点更新
        UPDATE_TASK_CHECKPOINT();
        
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

6.2 实时控制系统案例

// 定义控制系统参数
typedef struct {
    float setPoint;         // 设定值
    float currentValue;     // 当前值
    float kp, ki, kd;       // PID参数
    float integral;         // 积分项
    float lastError;        // 上一次误差
    float outputLimit;      // 输出限制
} PIDController_t;

// 控制任务
void vControlTask(void *pvParameters)
{
    PIDController_t xPID = {
        .setPoint = 25.0f,
        .kp = 1.2f,
        .ki = 0.1f,
        .kd = 0.05f,
        .integral = 0.0f,
        .lastError = 0.0f,
        .outputLimit = 100.0f
    };
    
    float sensorValue = 0.0f;
    float controlOutput = 0.0f;
    
    // 初始化硬件
    initActuators();
    
    // 获取采样时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const float dt = 0.01f; // 10ms控制周期
    
    while(1)
    {
        // 读取传感器值
        if(xQueueReceive(xSensorQueue, &sensorValue, 0) != pdPASS)
        {
            // 如果队列为空,直接读取
            xSemaphoreTake(xSensorMutex, portMAX_DELAY);
            sensorValue = readSensorDirect();
            xSemaphoreGive(xSensorMutex);
        }
        
        xPID.currentValue = sensorValue;
        
        // 计算误差
        float error = xPID.setPoint - xPID.currentValue;
        
        // 计算PID各项
        float proportional = xPID.kp * error;
        xPID.integral += xPID.ki * error * dt;
        
        // 积分限幅
        if(xPID.integral > xPID.outputLimit) xPID.integral = xPID.outputLimit;
        if(xPID.integral < -xPID.outputLimit) xPID.integral = -xPID.outputLimit;
        
        float derivative = xPID.kd * (error - xPID.lastError) / dt;
        xPID.lastError = error;
        
        // 计算输出
        controlOutput = proportional + xPID.integral + derivative;
        
        // 输出限幅
        if(controlOutput > xPID.outputLimit) controlOutput = xPID.outputLimit;
        if(controlOutput < -xPID.outputLimit) controlOutput = -xPID.outputLimit;
        
        // 设置执行器输出
        setActuator(controlOutput);
        
        // 精确周期控制
        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10));
    }
}

7. 系统优化与性能调优

7.1 内存优化

// 使用静态分配替代动态内存
// 定义任务所需的栈和控制块
static StaticTask_t xTaskBuffer;
static StackType_t xStack[configMINIMAL_STACK_SIZE];

// 使用静态方式创建任务
TaskHandle_t xHandle = xTaskCreateStatic(
    vTaskCode,                  // 任务函数
    "NAME",                     // 任务名称
    configMINIMAL_STACK_SIZE,   // 栈深度
    NULL,                       // 参数
    tskIDLE_PRIORITY + 1,       // 优先级
    xStack,                     // 栈缓冲区
    &xTaskBuffer                // 任务控制块
);

7.2 栈大小优化

// 检查栈使用情况
void vStackUsageTask(void *pvParameters)
{
    UBaseType_t uxHighWaterMark;
    
    while(1)
    {
        // 获取各任务栈的高水位标记
        uxHighWaterMark = uxTaskGetStackHighWaterMark(xTask1Handle);
        printf("Task1 stack remaining: %lu\r\n", uxHighWaterMark);
        
        uxHighWaterMark = uxTaskGetStackHighWaterMark(xTask2Handle);
        printf("Task2 stack remaining: %lu\r\n", uxHighWaterMark);
        
        // 根据高水位标记调整栈大小
        
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

7.3 CPU利用率监控

// 定义运行时间统计变量
volatile uint32_t ulIdleCycleCount = 0;
volatile uint32_t ulTotalCycleCount = 0;

// 空闲任务钩子函数
void vApplicationIdleHook(void)
{
    ulIdleCycleCount++;
}

// CPU利用率监控任务
void vCPUMonitorTask(void *pvParameters)
{
    uint32_t ulLastTotalCycles, ulLastIdleCycles;
    uint32_t ulCurrentTotalCycles, ulCurrentIdleCycles;
    
    while(1)
    {
        // 保存当前计数
        ulLastTotalCycles = ulTotalCycleCount;
        ulLastIdleCycles = ulIdleCycleCount;
        
        // 等待采样周期
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // 读取新计数
        ulCurrentTotalCycles = ulTotalCycleCount;
        ulCurrentIdleCycles = ulIdleCycleCount;
        
        // 计算CPU利用率
        uint32_t ulTotalCyclesDiff = ulCurrentTotalCycles - ulLastTotalCycles;
        uint32_t ulIdleCyclesDiff = ulCurrentIdleCycles - ulLastIdleCycles;
        
        float cpuUsage = 100.0f - ((float)ulIdleCyclesDiff / (float)ulTotalCyclesDiff * 100.0f);
        
        printf("CPU使用率: %.2f%%\r\n", cpuUsage);
    }
}

7.4 实时性能优化技巧

  1. 合理设置任务优先级

    • 实时任务 > 通信任务 > 后台任务
    • 避免过多相同优先级任务
  2. 最小化关中断时间

    • 关中断代码应尽可能短
    • 使用临界区保护资源
  3. 避免任务过度阻塞

    • 任务阻塞应有超时机制
    • 避免长时间持有互斥量
  4. 优化任务切换开销

    • 减少不必要的任务挂起/恢复操作
    • 合并频繁切换的小任务
  5. 使用任务通知替代轻量级同步

    • 任务通知比信号量更轻量级
    • 速度快,内存占用小
// 使用任务通知代替信号量
void vSenderTask(void *pvParameters)
{
    while(1)
    {
        // 处理数据
        processData();
        
        // 通知接收任务
        xTaskNotifyGive(xReceiverTaskHandle);
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vReceiverTask(void *pvParameters)
{
    while(1)
    {
        // 等待通知
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        // 处理数据
        handleNotification();
    }
}

总结

FreeRTOS多任务开发是一项需要综合考虑系统架构、资源分配、通信协调和异常处理的复杂工作。通过合理设计任务结构,使用适当的通信机制,实施有效的异常处理策略,可以构建出高效、可靠的嵌入式系统。

在实际应用中,开发者需要根据特定的硬件资源和应用需求,灵活运用这些技术,找到效率、资源占用和实时性之间的最佳平衡点。

关注 嵌入式软件客栈 公众号,获取更多内容
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Psyduck_ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值