FreeRTOS基础学习(五)软件定时器

目录

一. 基础概念

1.概念

2.FreeRTOS软件定时器实现的底层原理

3.软件定时器的作用

4. FreeRTOS软件定时器优点与缺点

二.相关函数

1.软件定时器相关配置项

2.xTimerCreate() 创建定时器

(1)函数原型

(2)参数解释

(3)示例

3. xTimerStart()启动一个已经通过 xTimerCreate() 创建好的定时器

(1)函数原型

(2)参数解释

(3)返回值

(4)示例

(5)注意

4. xTimerStop()停止一个软件定时器

(1)函数原型

(2)参数解释

(3)返回值

(4)使用场景

(5)示例

5.xTimerStartFromISR()从中断中启动软件定时器

(1) 函数原型:

(2)示例

6.xTimerStopFromISR()从中断中停止一个已经启动的定时器

(1)函数原型

(2)参数解释

(3)返回值

(4)示例

7.xTimerReset()任务中重启定时器(从头计时)

(1)函数原型

(2)参数解释

(3)返回值

(4)示例

(5)xTimerReset()与xTimerStop() + xTimerStart()的区别

8.xTimerResetFromISR() —— 在中断中重置软件定时器

(1)函数原型

(2)参数解释

(3)返回值

(4)示例

(5)注意

9.xTimerChangePeriod()修改软件定时器的周期

(1)函数原型

(2)参数解释

(3)返回值

(4)示例

(5)注意

一. 基础概念
1.概念

FreeRTOS 软件定时器是通过系统 tick 中断实现的定时回调机制,可以在将来某个时刻或周期性地调用一个用户提供的函数,用于定时处理任务,而不需要你自己维护计数器。这个机制不需要你自己写计数器,也不使用芯片的硬件定时器外设。

2.FreeRTOS软件定时器实现的底层原理

FreeRTOS 中的软件定时器是一种由系统调度器控制的延时/周期性触发机制,它允许你在不创建额外任务的情况下,在某个时间点自动执行指定的“回调函数”。

1. 软件定时器依赖一个专用的 Timer 服务任务:FreeRTOS 内部有一个专门的系统任务(叫 Timer Service Task),它负责:

  • 维护一个定时器链表(按超时时间排序);

  • 根据系统 Tick 递增,判断哪个定时器到期;

  • 到期后自动执行你传入的回调函数(callback);

【注意】这个回调函数不是在中断中执行的,是在 Timer Service Task 里执行的,所以不能太耗时,也不能阻塞。

2. 软件定时器的 Tick 源:使用的是系统节拍(SysTick)。每当系统节拍(比如 1ms)增加,FreeRTOS 会更新各个定时器的剩余时间;达到你设置的定时时间后,进入“超时队列”,由 Timer 服务任务调度。

软件定时器 = 一个后台任务维护的定时器列表,到时间了就帮你自动执行你提供的函数。

3.软件定时器的作用

在嵌入式系统中,你经常会遇到下面这些需求:

  • 每 500ms 闪一次 LED

  • 延迟 2 秒后关闭电机

  • 每 10 秒采集一次传感器数据

  • 启动后等待 3 秒再做某事

如果你用任务 + vTaskDelay() 来实现,会浪费任务资源、栈空间,而且效率低。

软件定时器解决这个问题:

  • 不需要为这些“等时间”的小逻辑专门建一个任务;

  • FreeRTOS 会帮你管理 tick 并在时间到了调用你写的回调函数;

  • 比写 while(1){ delay; 做事; } 更轻巧、结构更清晰;

4. FreeRTOS软件定时器优点与缺点

优点:

  • 无需创建额外任务;

  • 代码简单,适合定时触发类应用;

  • 定时精度一般在 1 Tick 级别(通常是 1ms);

  • 任务栈压力低(Timer callback 由系统统一调度)。

缺点:

  • 不适合高精度定时(无法做到微秒级);

  • 不能阻塞、不能耗时长操作;

  • 所有回调都由同一个 Timer Service Task 处理,回调时间太久会阻塞其他定时器。

二.相关函数
1.软件定时器相关配置项

FreeRTOS 内部会自动创建一个“定时器服务任务”(Timer Service Task),用于运行所有软件定时器的回调函数。

所以不需要你再手动创建定时器的任务,你只要确保:

  • FreeRTOSConfig.h 中定义了宏 configUSE_TIMERS == 1

  • 并在FreeRTOSConfig.h 配置了下面这些宏(一般系统默认就设置好了):

#define configUSE_TIMERS            1       // 启用软件定时器
#define configTIMER_TASK_PRIORITY   2       // 定时器服务任务的优先级
#define configTIMER_QUEUE_LENGTH    10      // 定时器命令队列的长度
#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE // 栈大小

这些选项告诉 FreeRTOS:

  • 是否启用软件定时器

  • 创建一个什么优先级的后台任务来处理软件定时器

  • 定时器命令队列长度(用于支持多个启动/停止等请求)

2.xTimerCreate() 创建定时器
(1)函数原型
TimerHandle_t xTimerCreate(
    const char *pcTimerName,     // 定时器名称(可用于调试)
    TickType_t xTimerPeriod,     // 定时周期,单位是 Tick,这个参数定义定时器多久超时一次,一旦超时,就会调用你指定的回调函数;
    UBaseType_t uxAutoReload,    // 是否自动重装载(pdTRUE=周期性,pdFALSE=一次性,只执行一次)
    void *pvTimerID,             // 用户自定义参数(通过 xTimerGetTimerID 获取)
    TimerCallbackFunction_t pxCallbackFunction // 到期时调用的函数
);
(2)参数解释

const char *pcTimerName:定时器命名(调试用)

  • 这个是定时器的“名字”,主要用于调试或追踪,不会影响功能本身。

  • 可以在 FreeRTOS 提供的调试工具、日志、命令行调试接口中查看这个名字。

TickType_t xTimerPeriodInTicks:定时器的超时时间,以 Tick 为单位

  • 这个参数定义定时器多久“超时”一次。

  • 一旦超时,就会调用你指定的回调函数。

  • 若你希望用毫秒指定时间,可以用 pdMS_TO_TICKS(ms) 宏转换。

  • 举例说明:

  • 假设 configTICK_RATE_HZ = 1000(每秒 1000 tick,即 1 tick = 1ms)那么:

    • pdMS_TO_TICKS(1000) = 1000 tick = 1 秒

    • pdMS_TO_TICKS(500) = 500 tick = 0.5 秒

UBaseType_t uxAutoReload:是否自动重复执行

  • 该参数决定定时器是:

    • pdTRUE:周期性定时器(超时后会自动重装载并重新计时)

    • pdFALSE:一次性定时器(只执行一次,之后就停了)

void *pvTimerID:用户定义的 ID,可在回调函数中识别定时器来源

  • 你可以传入任意指针(结构体指针、字符串、整数的指针等)。

  • 然后在回调函数中用 xTimerGetTimerID() 取回,进行判断或处理。

  • 如果你用一个回调函数处理多个定时器,这个字段尤其有用。

int timer_id = 1;
my_timer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdFALSE, (void*)&timer_id, my_callback);
//然后在回调函数中:
void my_callback(TimerHandle_t xTimer) {
    int* pID = (int*)xTimerGetTimerID(xTimer);
    printf("Timer ID = %d\n", *pID);
    //...处理程序
}

TimerCallbackFunction_t pxCallbackFunction定时器超时时要执行的回调函数

  • 类型为 void (*TimerCallbackFunction_t)( TimerHandle_t xTimer )

  • 每次定时器“超时”,这个函数就会被“定时器服务任务”自动调用。

  • 函数参数是该定时器的句柄,你可以用它获取定时器 ID 或名字等。

  • 【注意】回调函数应当快速返回,不能阻塞、不能等待、不能死循环。

(3)示例
TimerHandle_t my_timer;

void my_timer_callback(TimerHandle_t xTimer) {
    printf("Timer triggered!\n");
}

void app_main(void) {
    my_timer = xTimerCreate(
        "MyTimer",
        pdMS_TO_TICKS(1000),     // 1000 ms
        pdTRUE,  // pdTRUE:表示每隔1秒调用一次回调函数(周期性) pdFALSE:只在1秒(你设定的第2个参数)后调用一次回调函数(一次性)
        NULL,
        my_timer_callback
    );

    if (my_timer == NULL) {
        printf("Timer create failed!\n");
    }
}
3. xTimerStart()启动一个已经通过 xTimerCreate() 创建好的定时器
(1)函数原型
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
(2)参数解释
  • 参数1: xTimer :xTimerCreate()返回的定时器句柄;

  • 参数2:xTicksToWait:如果定时器命令队列满了,等待多久(tick 为单位);

(3)返回值
  • pdPASS:命令成功放入队列,稍后将被执行

  • pdFAIL:队列已满,或者 Timer 服务任务没有启动

(4)示例
// 在某个任务中
if (xTimerStart(myTimerHandle, portMAX_DELAY) == pdPASS) {
    printf("定时器启动成功!\n");
}
(5)注意
  • xTimerCreate() 创建定时器时并不会自动启动,必须手动 xTimerStart()。

  • 如果是一次性定时器(uxAutoReload = pdFALSE),调用一次就用一次。

  • 如果是周期性定时器(uxAutoReload = pdTRUE),会自动按设定周期反复执行。

  • xTimerStart()不可用于中断!

4. xTimerStop()停止一个软件定时器

请求停止一个已经运行的定时器。调用次函数后,定时器就不会再调用它的回调函数了;用于停止一个已启动的定时器,防止它触发回调函数;当你的逻辑上认为“这个动作不该再执行了”,就用它;

(1)函数原型
BaseType_t xTimerStop(TimerHandle_t xTimer, TickType_t xTicksToWait);
(2)参数解释

参数1:xTimer:要停止的定时器句柄

参数2: xTicksToWait:如果定时器服务队列满了,等待的最大 Tick 数,通常传 0 表示不等待;

(3)返回值

pdPASS :停止定时器的命令已成功发给定时器服务任务;

pdFAIL:定时器服务队列满,或定时器还没创建,或者未初始化定时器任务;

(4)使用场景
  • 用户中断某个等待动作:如“按键长按3秒关机”,但中途松手了 ;

  • 任务提前完成:如你设了一个超时定时器,但任务提前完成了,不想触发超时回调了

  • 设备状态改变:比如定时LED自动熄灭,但用户手动熄灭了

(5)示例
//目标:实现用户如果 5 秒内没操作,就关闭 Wi-Fi 模块

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include <stdio.h>

TimerHandle_t timeoutTimer;  // 超时定时器句柄

// 模拟:用户无操作超时的处理函数
void timeoutCallback(TimerHandle_t xTimer)
{
    printf("超时未操作,关闭 Wi-Fi 模块!\n");
    // 关闭 WiFi 的实际函数写这里
    // esp_wifi_stop();
}

// 模拟:用户有操作(比如按键、串口输入)
void userActivityDetected()
{
    BaseType_t xResult;
    xResult = xTimerStop(timeoutTimer, 0);    // 如果定时器正在运行,先停止
    if (xResult == pdPASS)
    {
        printf("已停止原定时器\r\n");
    }
    xResult = xTimerStart(timeoutTimer, 0);    // 重启定时器等待新的 5 秒
    if (xResult == pdPASS)
    {
        printf("重启定时器,等待用户输入...\r\n");
    }
}

void app_main()
{
    // 创建一个一次性定时器,5 秒超时
    timeoutTimer = xTimerCreate("TimeoutTimer",
                                pdMS_TO_TICKS(5000),
                                pdFALSE,      // 一次性定时器
                                NULL,
                                timeoutCallback);

    if (timeoutTimer == NULL)
    {
        printf("创建定时器失败\n");
        return;
    }
    
 xTimerStart(timeoutTimer, 0);    // 启动定时器,等待用户首次输入
 
    // 模拟系统运行:检测用户操作
    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(2000));  // 每 2 秒检查一次输入
        printf("检测到用户有操作\n");
        userActivityDetected();  // 每次用户输入完后都重启定时器
    }
}
5.xTimerStartFromISR()从中断中启动软件定时器

中断函数内不能做复杂操作,比如不能调用 xTimerStart(),但你可以通过 xTimerStartFromISR() 安全地告诉系统“我想启动这个定时器”。FreeRTOS 会将请求放入定时器命令队列中,由后台的定时器服务任务异步处理。

(1) 函数原型:
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken );
//函数返回类型和第一个参数和xTimerStart()是一样的;
//参数2:pxHigherPriorityTaskWoken:指向一个变量,如果调用这个函数导致上下文切换,这个变量会被置为 pdTRUE;
(2)示例
TimerHandle_t myTimer;

void IRAM_ATTR gpio_isr_handler(void* arg)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xTimerStartFromISR(myTimer, &xHigherPriorityTaskWoken);

    // 如果有更高优先级的任务被唤醒,则请求上下文切换
    if (xHigherPriorityTaskWoken)
    {
        portYIELD_FROM_ISR();
    }
}
6.xTimerStopFromISR()从中断中停止一个已经启动的定时器

在中断上下文里,如果要让某个软件定时器立即停止,就用这个函数,而不能用 xTimerStop()(因为它是线程上下文函数)。

(1)函数原型
BaseType_t xTimerStopFromISR(TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken);
(2)参数解释

参数1:xTimer:你要停止的定时器句柄

参数2:*pxHigherPriorityTaskWoken:指向一个布尔值的指针,用来告知中断退出时是否需要触发上下文切换(标准 ISR 机制);

(3)返回值

返回 pdPASS 表示操作成功,否则失败。

(4)示例
//你设置了一个定时器,如果用户 5 秒内没按按钮 就执行关机。
//但如果用户按了按钮(触发外部中断)——我们就在 ISR 里把这个定时器停掉:


//中断函数中:
void IRAM_ATTR gpio_isr_handler(void* arg)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xTimerStopFromISR(myShutdownTimer, &xHigherPriorityTaskWoken);    // 检测到按键,就取消定时关机;
    // 如果需要切换任务:
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

注意:ISR 中的操作尽量简洁,建议不要频繁调用定时器创建/删除,而是只做控制启停即可。

7.xTimerReset()任务中重启定时器(从头计时)

实现在任务上下文中将定时器“重置”(Restart),如果定时器尚未启动,这个函数的效果相当于 xTimerStart()

如果定时器已经在运行,它会被停止并重新开始计时,周期从当前时刻重新计算。

(1)函数原型
BaseType_t xTimerReset(TimerHandle_t xTimer, TickType_t xTicksToWait);
(2)参数解释
  • 参数1:xTimer:要重置的定时器的句柄

  • 参数2:xTicksToWait如果内部命令队列满了,允许等待的最大 tick 数。通常设为 0 或 portMAX_DELAY

(3)返回值
  • pdPASS:操作成功;

  • pdFAIL:操作失败(例如队列满了或定时器无效)

(4)示例
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_system.h"

//软件定时器句柄
TimerHandle_t shutdown_timer = NULL;

//模拟用户输入检测任务(串口收数据)
void user_input_task(void *param)
{
    char ch;
    while (1)
    {
        // 模拟串口接收字符:从 stdin 获取字符
        if (scanf(" %c", &ch) == 1)
        {
            printf("[用户操作] 检测到输入字符 '%c'\n", ch);

            // 既然已经检测到用户有操作,就重置定时器
            if (xTimerReset(shutdown_timer, 0) == pdPASS)
            {
                printf("[系统] 关机倒计时被重置\n");
            }
            else
            {
                printf("[错误] 重置定时器失败!\n");
            }
        }

        vTaskDelay(pdMS_TO_TICKS(100)); // 稍作延时,防止过快
    }
}

//软件定时器的回调函数:关机操作
void shutdown_callback(TimerHandle_t xTimer)
{
    printf("用户已达到5秒无操作,自动执行关机逻辑!\n");
    // 这里可以加上关机处理,如关闭设备、切断供电等
}

//主函数入口
void app_main(void)
{
    printf("系统启动,初始化...\n");

    // 创建定时器:5秒后执行一次
    shutdown_timer = xTimerCreate("ShutdownTimer", pdMS_TO_TICKS(5000), pdFALSE, NULL, shutdown_callback);
    if (shutdown_timer == NULL)
    {
        printf("创建定时器失败!\n");
        return;
    }

    xTimerStart(shutdown_timer, 0);    // 启动定时器(第一次倒计时)

    xTaskCreate(user_input_task, "UserInputTask", 2048, NULL, 1, NULL);    // 启动任务模拟用户输入
}
(5)xTimerReset()xTimerStop() + xTimerStart()的区别
方面
xTimerReset()
xTimerStop() + xTimerStart()
作用
重启定时器(相当于“重新倒计时”)
停止定时器,再重新启动(彻底重置状态)
前提条件
定时器必须已经“创建”并“启动过”
无需判断定时器状态,直接全流程处理
效率
更高,只执行一次动作
慢一些,涉及两个调用
使用限制
定时器未启动时调用,会失败返回 pdFAIL
总是能成功(只要定时器句柄正确)
推荐场景
明确知道定时器当前“已启动”
状态不确定、想保证可靠性

假设你设置了一个“5秒后自动关灯”的定时器:

  • xTimerReset() 相当于:“我知道定时器已经在倒计时了,现在我再按一下按钮,重新从5秒开始数。”

  • xTimerStop() + xTimerStart() 相当于:“我不管你在不在倒计时了,我就先把你停掉,然后再重新开始5秒倒计时。”

关键区别:

  • xTimerReset() 只能重启已在运行的定时器。

  • xTimerStop()+xTimerStart() 不管定时器是否运行,永远能重启倒计时。

实际开发中选哪一个?

  • 你确认定时器已经启动过?→ xTimerReset()

  • 你不确定定时器状态、或逻辑复杂?→ xTimerStop() + xTimerStart()

  • 能用 xTimerReset() 的地方优先用它;但在定时器状态不确定或希望“彻底重启”时,xTimerStop()+xTimerStart() 是更安全的选择。

8.xTimerResetFromISR() —— 在中断中重置软件定时器

用于从中断服务例程(ISR)中调用,来重置一个一次性或周期性的软件定时器。比如我们使用一个 GPIO中断 检测按键:每次按下按键,就要重置“5秒倒计时定时器”。

(1)函数原型
BaseType_t xTimerResetFromISR(
    TimerHandle_t xTimer,
    BaseType_t *pxHigherPriorityTaskWoken
);
(2)参数解释
  • 参数1:xTimer要重置的定时器句柄

  • 参数2:pxHigherPriorityTaskWoken若定时器操作唤醒了一个更高优先级的任务,需设置:

*pxHigherPriorityTaskWoken = pdTRUE,之后调用 portYIELD_FROM_ISR()

(3)返回值
  • pdPASS:成功

  • pdFAIL:失败(例如定时器未创建、定时器命令队列满了)

(4)示例
// ISR 中使用的变量
extern TimerHandle_t timeout_timer;

void IRAM_ATTR gpio_isr_handler(void *arg) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    printf("检测到按键中断,重置定时器\n");
    xTimerResetFromISR(timeout_timer, &xHigherPriorityTaskWoken);

    if (xHigherPriorityTaskWoken) {
        portYIELD_FROM_ISR(); // 触发任务切换(如果有更高优先级的任务被唤醒)
    }
}
(5)注意
  • xTimerResetFromISR() 只能在 中断上下文 使用;

  • 中断函数必须标记为 IRAM_ATTR;

  • 必须使用 portYIELD_FROM_ISR() 来处理优先级切换;

9.xTimerChangePeriod()修改软件定时器的周期

修改定时器的周期,并自动重启定时器(即从新周期开始重新计时)。 所以 你不需要先 stop、再 set 周期、再 start,一步完成!

使用场景:

  • 低功耗设备 → 根据信号强弱、连接状态切换定时周期;

  • 传感器采样 → 白天/夜晚用不同采样率;

  • 任务节拍 → 有人操作时频繁刷新,没人操作时变慢;

(1)函数原型
BaseType_t xTimerChangePeriod(
    TimerHandle_t xTimer,
    TickType_t xNewPeriod,
    TickType_t xTicksToWait
);
(2)参数解释
  • 参数1:xTimer要修改的定时器句柄

  • 参数2: xNewPeriod 新的定时器周期(单位:tick)

  • 参数3: xTicksToWait等待时间(通常填 portMAX_DELAY 就行)

(3)返回值
  • pdPASS:修改成功

  • pdFAIL:失败(比如定时器句柄无效)

(4)示例
//你在设备联网后,向服务器发心跳包:
//联网成功 → 心跳每 5 秒一次;
//联网失败 → 改为每 20 秒尝试一次连接;

// 定时器回调函数
void vHeartbeatTimerCallback(TimerHandle_t xTimer) {
    if (is_wifi_connected()) {
        printf("发送心跳包\n");
    } else {
        printf("WiFi断开,尝试重连...\n");
    }
}

// 任务中动态调整周期
if (is_wifi_connected()) {
    xTimerChangePeriod(heartbeat_timer, pdMS_TO_TICKS(5000), portMAX_DELAY);
} else {
    xTimerChangePeriod(heartbeat_timer, pdMS_TO_TICKS(20000), portMAX_DELAY);
}
//设备默认每 2 秒打印一次“心跳”;
//如果用户按下按键,则将定时器周期改为 500 毫秒,更频繁地“刷新”;
//再次按键,又改回原周期(实现周期在两种值之间切换)。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/gpio.h"

#define BUTTON_GPIO     GPIO_NUM_0    // 按键接在 GPIO0
#define SHORT_PERIOD    pdMS_TO_TICKS(500)
#define LONG_PERIOD     pdMS_TO_TICKS(2000)

// 全局变量
TimerHandle_t heartbeat_timer;
volatile bool is_fast = false;

// 定时器回调函数
void heartbeat_timer_callback(TimerHandle_t xTimer) {
    printf("心跳一次!当前周期:%s\n", is_fast ? "500ms" : "2000ms");
}

// 任务:监测按键并改变定时器周期
void button_task(void *pvParameters) {
    gpio_set_direction(BUTTON_GPIO, GPIO_MODE_INPUT);
    gpio_set_pull_mode(BUTTON_GPIO, GPIO_PULLUP_ONLY);

    while (1) {
        if (gpio_get_level(BUTTON_GPIO) == 0) {  // 按键按下
            vTaskDelay(pdMS_TO_TICKS(50));  // 简单防抖
            if (gpio_get_level(BUTTON_GPIO) == 0) {
                // 切换状态
                is_fast = !is_fast;
                // 修改定时器周期
                xTimerChangePeriod(heartbeat_timer,
                                   is_fast ? SHORT_PERIOD : LONG_PERIOD,
                                   portMAX_DELAY);

                printf("切换定时器周期为:%s\n", is_fast ? "500ms" : "2000ms");
                // 等待按键释放
                while (gpio_get_level(BUTTON_GPIO) == 0) {
                    vTaskDelay(pdMS_TO_TICKS(10));
                }
            }
        }
        vTaskDelay(pdMS_TO_TICKS(100));  // 检测间隔
    }
}

void app_main() {
    // 创建定时器,初始周期为 2000ms
    heartbeat_timer = xTimerCreate("heartbeat", LONG_PERIOD,pdTRUE,NULL, heartbeat_timer_callback);
    if (heartbeat_timer == NULL) {
        printf("定时器创建失败!\n");
    } else {
        xTimerStart(heartbeat_timer, 0);  // 启动定时器
    }
    // 创建监控按键的任务
    xTaskCreate(button_task, "button_task", 2048, NULL, 10, NULL);
}
(5)注意
  • xTimerChangePeriod()必须在任务上下文中调用,不能在中断中用,如果你在中断中想修改周期,请用:xTimerChangePeriodFromISR()

  • 修改周期后,定时器自动重启;

  • 如果你想保留当前运行状态(比如暂不启动),不要用它;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值