前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >超轻量级网红软件定时器multi_timer(51+stm32双平台实战)

超轻量级网红软件定时器multi_timer(51+stm32双平台实战)

作者头像
杨源鑫
发布于 2020-05-21 07:51:57
发布于 2020-05-21 07:51:57
2.4K00
代码可运行
举报
文章被收录于专栏:嵌入式开发圈嵌入式开发圈
运行总次数:0
代码可运行

一、multi_timer简介

网红multi_timer是一个极其轻量的软件定时器,只要你的MCU容量够的情况下,就可以无限拓展成为N个定时器,这在一定程度上方便了定期器资源较少的MCU,但有经验的老工程师会说:"我可以只用一个定时器,用计数器+标志位的方式也可以COPY出N个定时器呀,资源少也阻挡不了我对它的充分利用"。是的没错,但multi_timer对比老工程师方法的优势在哪里呢?它可以取代传统的标志位+计数器的判断方式,让程序看起来更加优雅更加好维护。

特点:简单、优雅、便捷、易维护

二、multi_timer的使用方法

1、定义一个multi_timer结构体变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Timer timer1 ;

2、注册并初始化multi_timer定时器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timer_init(&timer1, timer1_callback, TIMER_TIMEOUT_500MS, TIMER_TIMEOUT_500MS);

3、启动multi_timer定时器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timer_start(&timer1);

4、设置1ms硬件定时器循环调用计数器以提供时基

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void xxx_callback(void)
{
  timer_ticks();
}

5、在while循环中循环调用multi_timer的后台处理函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
while(1)
{
   //....
   timer_loop();
}

三、multi_timer实战

实战演练1:在STC15F104W-35I-SOP8上实践

买这个小模块的原因是后面需要做一些开源项目,想做一些传感器,最后用stm32或者其它的MCU与它建立通信用,还有一个用途就是以后移植一些开源项目,我希望现在低端一点的平台上验证(如果低端跑不了就直接上能跑的),后面再在高端点的平台上实践,有时间有条件我也会多在别的平台上跑跑,这样相当于积累了多个平台的开发经验,这款CPU完全兼容51单片机的指令集,所以把它当成51单片机来用就行了。

这个小板子对应的原理图如下:

限于文章篇幅,如果想多了解这个小板子的信息,可以去我的CSDN博客上看看,之前写了介绍:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://blog.csdn.net/morixinguan/article/details/105130462

下面直接看实战需求功能描述:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1、用multi_timer创建软件定时器1,用来以500ms的频率让LED灯交替闪烁。
2、用multi_timer创建软件定时器2,当定时10s到达以后,常亮LED,并且删除multi_timer创建的软件定时器1和软件定时器2

创建51的Keil4工程,然后开始编写代码:

1、打开Keil4,然后创建一个AT89C51的工程

2、将multi_timer添加到keil4工程

3、创建一个Package目录,将multi_timer的程序文件添加进来

4、编写代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <reg51.h>
#include "multi_timer.h"

Timer timer1 ;
Timer timer2 ;
/*用于定时10s的计数器*/
int Counter = 0 ;
/*根据板子原理图,灯位于P3^3*/
sbit LED = P3 ^ 3 ;

/*晶振频率为12M*/
#define FOSC 12000000L
/*指令速度为12T*/
#define command_speed 12
/*用multi_timer创建的定时器1定时时间  单位:ms*/
#define TIMER_TIMEOUT_500MS 500
/*用multi_timer创建的定时器2定时时间  单位:ms*/
#define TIMER_TIMEOUT_1S 1000

void timer0_init(void);
void timer1_callback(void);
void timer2_callback(void);

void main(void)
{
    LED = 0;
    timer0_init();
    timer_init(&timer1, timer1_callback, TIMER_TIMEOUT_500MS, TIMER_TIMEOUT_500MS);
    timer_init(&timer2, timer2_callback, TIMER_TIMEOUT_1S, TIMER_TIMEOUT_1S);
    timer_start(&timer1);
    timer_start(&timer2);

    while(1)
    {
        timer_loop();
    }
}

/*multi_timer回调函数1调用*/
void timer1_callback(void)
{
    /*LED灯电平翻转*/
    LED = !LED ;
}
/*multi_timer回调函数2调用*/
void timer2_callback(void)
{
    /*当计数器到达10次以后删除所有创建的软件定时器
      计数器清0,将LED电平置为1,常亮
    */
    ++Counter ;

    if(Counter == 10)
    {
        Counter = 0 ;
        LED = 1 ;
        timer_stop(&timer1);
        timer_stop(&timer2);
    }
}

/*硬件定时器初始化*/
void timer0_init(void)
{
    TMOD = 0x00;
    TH0 = (65536 - FOSC / command_speed / 1000) >> 8;
    TL0 = (65536 - FOSC / command_speed / 1000);
    EA = 1;
    ET0 = 1;
    TR0 = 1;
}

/*利用系统定时器产生1ms的定时中断*/
void timer0() interrupt 1
{
    TH0 = (65536 - FOSC / command_speed / 1000) >> 8;
    TL0 = (65536 - FOSC / command_speed / 1000);
    /*multi_timer计数器自增*/
    timer_ticks();
}

4、程序编译与固件生成

我们看到编译过后,整个程序的大小仅占用1.3K多,确实够轻量!接下来将生成的.hex文件下载到开发板上。

最终程序按照我的设计思路完美运行!这里相当于带大家重新复习了下51单片机平台的基本使用。

实战演练2:在小熊派开发板上实战

关于小熊派,这是一个基于stm32的网红物联网开发平台,昨天收到王总给我赠送的板子以后就写了相关的评测,大家可以点击下面链接看:

网红物联网开发板小熊派使用评测

接下来我们在这个平台上把实战演练1的需求实现一下,首先先看小熊派开发板的原理图,找到LED的位置:

使用stm32cubmx配置基础工程:

1、芯片选型,这里选择stm32l431rctx

2、配置rcc时钟以及串行调试接口

这里我选择的是高速,时钟的话,直接用系统默认的内部时钟也可以,时钟默认配置最高80MHz。

因为以前被坑过,导致程序没法下载了,所以习惯性配置这个选项,后续有时间我写篇文章解释下。

3、配置LED

4、配置串口调试

方便根据调试信息查看程序执行流程。

5、生成Keil5基础工程

实际开发建议硬件外设分模块,这样看起来不要把所有的生成全部都挤到main.c里面去了,这点让我非常讨厌,所以生成工程时候习惯点击设置以下这一项:

接下来点击生成代码:

1、将multi_timer添加到keil5工程

2、创建一个Package目录,将multi_timer的程序文件添加进来

3、编写代码

由于篇幅限制,只看我自己代码添加的位置:

main.h

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/*添加必要的头文件*/
#include <stdio.h>
#include "multi_timer.h"
/* USER CODE END Includes */

main.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/*用multi_timer创建的定时器1定时时间  单位:ms*/
#define TIMER_TIMEOUT_500MS 500
/*用multi_timer创建的定时器2定时时间  单位:ms*/
#define TIMER_TIMEOUT_1S 1000
/* USER CODE END PD */

/* USER CODE BEGIN PV */
Timer timer1 ;
Timer timer2 ;
/*用于定时10s的计数器*/
int Counter = 0 ;
/* USER CODE END PV */

/* USER CODE BEGIN PFP */
/*定义重定向,这样才能使用printf函数*/
int fputc(int ch, FILE *file)
{
    return HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
}

/*multi_timer回调函数1调用*/
void timer1_callback(void)
{
    /*LED灯电平翻转*/
    //LED = !LED ;
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/*multi_timer回调函数2调用*/
void timer2_callback(void)
{
    /*当计数器到达10次以后删除所有创建的软件定时器
      计数器清0,将LED电平置为1,常亮
    */
    ++Counter ;

    if(Counter == 10)
    {
        Counter = 0 ;
        //LED = 1 ;
        HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
        printf("LED灯常亮\n");
        timer_stop(&timer1);
        printf("关闭定时器1\n");
        timer_stop(&timer2);
        printf("关闭定时器2\n");
    }
}

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    /* USER CODE BEGIN 2 */
    timer_init(&timer1, timer1_callback, TIMER_TIMEOUT_500MS, TIMER_TIMEOUT_500MS);
    timer_init(&timer2, timer2_callback, TIMER_TIMEOUT_1S, TIMER_TIMEOUT_1S);
    timer_start(&timer1);
    timer_start(&timer2);
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
        timer_loop();
    }

    /* USER CODE END 3 */
}

stm32l4xx_it.c

这里利用系统时钟的1ms的时基,就不用重新去创建一个硬件定时器了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
    /* USER CODE BEGIN SysTick_IRQn 0 */
    timer_ticks();
    /* USER CODE END SysTick_IRQn 0 */
    HAL_IncTick();
    /* USER CODE BEGIN SysTick_IRQn 1 */

    /* USER CODE END SysTick_IRQn 1 */
}

4、程序编译与固件生成

生成固件

选择调试和下载器,这里是st-link

选择下载程序后复位,如果不选择则需要按开发板上的硬件复位。

编译成功后直接点击下载

5、程序执行(看串口调试助手)

实验成功!

四、multi_timer设计思想

所谓有道是知其然而知所以然,这么优秀的作品,我们有必要来了解一下:

4.1 multi_timer的数据结构及参数含义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct Timer
{
    uint32_t timeout;
    uint32_t repeat;
    void (*timeout_cb)(void);
    struct Timer* next;
} Timer;

参数含义:

参数

含义

timeout

定时时间(ms)

repeat

循环定时触发时间

timeout_cb

定时器回调处理函数

next

指向下一个定时器节点

4.2 multi_timer的函数解析

程序文件里的全局变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//timer handle list head.
static struct Timer* head_handle = NULL;

//Timer ticks
static uint32_t _timer_ticks = 0;
4.2.1 定时时基触发:timer_ticks
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
  * @brief  background ticks, timer repeat invoking interval 1ms.
  * @param  None.
  * @retval None.
  */
void timer_ticks()
{
    _timer_ticks++;
}

这个函数的功能主要是产生计数,而产生计数一定要有另外一个介质去驱动它运行。

4.2.2 定时器初始化:timer_init
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//Timer ticks
static uint32_t _timer_ticks = 0;

/**
  * @brief  Initializes the timer struct handle.
  * @param  handle: the timer handle strcut.
  * @param  timeout_cb: timeout callback.
  * @param  repeat: repeat interval time.
  * @retval None
  */
void timer_init(struct Timer* handle, void(*timeout_cb)(), uint32_t timeout, uint32_t repeat)
{
    handle->timeout_cb = timeout_cb;
    handle->timeout = _timer_ticks + timeout;
    handle->repeat = repeat;
}

初始化主要是给结构体参数进行赋值操作,首先要确定是哪个结构体成员,由handle参数确定,当定时时间到了要做什么事情,由timeout_cb(定时器回调处理函数)参数确定,定时时间多长才会触发所谓的事情,由timeout(定时时间)参数确定,如果这个功能需要重复触发,我们就需要给repeat(循环定时触发时间)参数()指定。

4.2.3 定时器启动:timer_start
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
  * @brief  Start the timer work, add the handle into work list.
  * @param  btn: target handle strcut.
  * @retval 0: succeed. -1: already exist.
  */
int timer_start(struct Timer* handle)
{
    struct Timer* target = head_handle;

    while(target)
    {
        if(target == handle) return -1;	//already exist.

        target = target->next;
    }

    handle->next = head_handle;
    head_handle = handle;
    return 0;
}

这里将定时器句柄添加到链表里进行保存,循环指向链表的下一个节点去添加定时器节点,如果发现是同一个定时器句柄,则直接返回-1,表示当前添加句柄不合法。

4.2.4 定时器停止:timer_stop
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
  * @brief  Stop the timer work, remove the handle off work list.
  * @param  handle: target handle strcut.
  * @retval None
  */
void timer_stop(struct Timer* handle)
{
    struct Timer** curr;

    for(curr = &head_handle; *curr; )
    {
        struct Timer* entry = *curr;

        if (entry == handle)
        {
            *curr = entry->next;
        }
        else
            curr = &entry->next;
    }
}

这里非常巧妙的使用了一个二级指针curr,指向了对应定时器句柄的地址,通过循环遍历,找到对应的句柄后将其删除。

4.2.5 定时器循环调用后台处理:timer_loop
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
  * @brief  main loop.
  * @param  None.
  * @retval None
  */
void timer_loop()
{
    struct Timer* target;

    for(target = head_handle; target; target = target->next)
    {
        if(_timer_ticks >= target->timeout)
        {
            if(target->repeat == 0)
            {
                timer_stop(target);
            }
            else
            {
                target->timeout = _timer_ticks + target->repeat;
            }

            target->timeout_cb();
        }
    }
}

这个函数实现非常简单,就是通过不断遍历链表各个节点,判断是否到达定时时间(timeout参数),如果到达了定时时间,没有指定循环定时触发时间(repeat参数)的时候,这时就会把当前定时器句柄给移除,如果指定了循环定时触发时间(repeat参数),则定时时间会被重新赋值,直到下一个定时到来,接下来会一直循环触发。

不得不说,这真是一个良好的程序设计,值得学习和应用,针对作者这样的思想,还可以衍生出更多的开源项目。

实践工程下载

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
链接:https://pan.baidu.com/s/1xwCnkMDnwjPTrKd8ulw58w
提取码:eo5y
复制这段内容后打开百度网盘手机App,操作更方便哦

注意:

大部分朋友的电脑都同时装了Keil5和Keil4,不小心操作有时打开Keil4工程会卡死,解决方法如下:

将下面这个文件删除,再重新打开就不会了。

公众号粉丝福利时刻

这里我给大家申请到了福利,本公众号读者购买小熊派开发板可享受9折优惠,有需要的朋友可联系我获取优惠码,本福利长期有效。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式云IOT技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
SpringBoot服务端表单数据校验
1、首先说明一下,这里使用的是Springboot2.2.6.RELEASE版本,由于Springboot迭代很快,所以要注意版本问题。
别先生
2020/05/18
8250
SpringBoot服务端表单数据校验
SpringBoot整合Servlet、Filter、Listener、访问静态资源、文件上传
首先说明一下,这里使用的是Springboot2.2.6.RELEASE版本,由于Springboot迭代很快,所以要注意版本问题。
别先生
2020/05/08
1.7K0
SpringBoot整合SpringMVC、持久层技术MyBatis
1、通过使用SpringBoot、SpringMVC、MyBatis整合,实现一个对数据库中的数据表的增加、修改、删除、查询操作。
别先生
2020/05/18
9630
SpringBoot整合SpringMVC、持久层技术MyBatis
springBoot系列教程07:异常捕获
发生异常是很正常的事,异常种类也是千奇百怪,发生异常并不可怕,只要正确的处理,并正确的返回错误信息并无大碍,如果不进行捕获或者处理,分分钟服务器宕机是很正常的事
肖哥哥
2018/08/02
9020
springBoot系列教程07:异常捕获
聊聊springcloud的serviceRegistryEndpoint
本文主要研究一下springcloud的serviceRegistryEndpoint
code4it
2018/09/17
1.4K0
Swagger-ui在文件上传时报错net::ERR_CONNECTION_RESET
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
yingzi_code
2019/08/30
2K0
springcloud踩坑
直到我看见了 服务提供方的报错 一下子我就明白了 我数据库服务没开!!!!!!!!!!! 这里只是想分享以下踩坑经验 希望大家不要像我一样粗心
暴躁的程序猿
2022/03/24
3660
org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method service()
前言 本文中提到的解决方案,源码地址在:springboot-thymeleaf,希望可以帮你解决问题。 至于为什么已经写了一篇文章thymeleaf模板引擎调用java类中的方法,又多此一举的单独整理了这篇文章,是因为在解决此问题时首先搜索了一下关于此问题的文章,但是网上并没有搜到关于此问题的答案,因此自己做了整理。 问题描述 在springboot与thymeleaf整合过程中,出现了如下报错: org.thymeleaf.exceptions.TemplateProcessingExcepti
程序员十三
2018/03/15
2.7K0
java.lang.AbstractMethodError: org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.cho
搞了我两天真是服了 出现这个问题是没有loadbalanc,但是nacos中ribbon会造成loadbalanc包失效 在common的pom文件中加入
全栈程序员站长
2022/09/05
1.4K0
springcloud与hystrix整合时freemarker依赖问题分析
乍一看,是 freemarker 解析的问题,但是所有的依赖都是正常情况下处理的,没有头绪。
山行AI
2020/02/11
1.2K0
java.sql.SQLException: Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp
因为“0000-00-00 00:00:00”在mysql中是作为一个特殊值存在的但 java.sql.Date 将其视为 不合法的值 格式不正确,这才是报错的原因
用户5899361
2020/12/07
9370
jxls工具导出excel,报错:Cannot load XLS transformer. Please make sure a Transformer implementation is in cl
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/163882.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/15
1.2K0
feign.FeignException$MethodNotAllowed: status 405 reading xxx#yyy(Integer)
使用feign 调用异常 feign.FeignException$MethodNotAllowed: status 405 reading ConsumerService#findById(Integer)
时间静止不是简史
2020/07/27
2.3K0
feign.FeignException$MethodNotAllowed: status 405 reading xxx#yyy(Integer)
springboot 使用 freemarker 无法正常跳转的问题?
参考:https://blog.csdn.net/Lin_xiaofeng/article/details/79122053
别先生
2019/07/30
1.5K0
springboot 使用 freemarker 无法正常跳转的问题?
初学Spring Cloud踩坑之org.springframework.web.client.HttpClientErrorException: 400 null
初学Spring Cloud踩坑之org.springframework.web.client.HttpClientErrorException: 400 null
Java架构师必看
2021/05/14
1.7K0
zuul报错java.net.UnknownHostException: 4d59d509898a: Name or service not known
是这样的,eureka、zuul和普通的微服务在本地是OK的,部署到docker后,通过zuul访问某个微服务就报错了。用的是serviceId的方式,在eureka界面也能看到各个微服务都是在线的,没有问题,但就是通过zuul的路由访问时会报错。
天涯泪小武
2019/01/17
2.5K0
BadSqlGrammarException:PageHelper use near 'LIMIT 50'
作为一名Java开发人员,你可能在某个深夜,面对着那令人头秃的错误日志,不禁发问:“为什么我的SQL语句总是出错?”今天,就让我们一起深入探讨这个在Java开发项目中常见的问题——org.springframework.jdbc.BadSqlGrammarException,特别是在处理Excel导入时,如何避免这个让人头疼的错误。
疯狂的KK
2025/01/16
1610
BadSqlGrammarException:PageHelper use near 'LIMIT 50'
org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call
前言 本文中提到的解决方案,源码地址在:springboot-thymeleaf,希望可以帮你解决问题。 至于为什么已经写了一篇文章thymeleaf模板引擎调用java类中的方法,又多此一举的单独整理了这篇文章,是因为在解决此问题时首先搜索了一下关于此问题的文章,但是网上并没有搜到关于此问题的答案,因此自己做了整理。 问题描述 在springboot与thymeleaf整合过程中,出现了如下报错: ``` org.thymeleaf.exceptions.TemplateProcessingExcepti
程序员十三
2018/03/15
2K0
SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after
SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30001ms.
二十三年蝉
2018/10/11
7.4K0
SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after
聊聊springboot tomcat的maxHttpFormPostSize
本文主要研究一下spring boot tomcat的maxHttpFormPostSize参数
code4it
2023/08/17
7140
推荐阅读
相关推荐
SpringBoot服务端表单数据校验
更多 >
LV.4
开发工程师兼产品经理
目录
  • 一、multi_timer简介
  • 二、multi_timer的使用方法
  • 三、multi_timer实战
    • 实战演练1:在STC15F104W-35I-SOP8上实践
    • 1、打开Keil4,然后创建一个AT89C51的工程
    • 2、将multi_timer添加到keil4工程
    • 3、创建一个Package目录,将multi_timer的程序文件添加进来
    • 4、编写代码
    • 4、程序编译与固件生成
  • 实战演练2:在小熊派开发板上实战
    • 1、芯片选型,这里选择stm32l431rctx
    • 2、配置rcc时钟以及串行调试接口
    • 3、配置LED
    • 4、配置串口调试
    • 5、生成Keil5基础工程
    • 1、将multi_timer添加到keil5工程
    • 2、创建一个Package目录,将multi_timer的程序文件添加进来
    • 3、编写代码
    • 4、程序编译与固件生成
    • 5、程序执行(看串口调试助手)
  • 四、multi_timer设计思想
    • 4.1 multi_timer的数据结构及参数含义
    • 4.2 multi_timer的函数解析
      • 4.2.1 定时时基触发:timer_ticks
      • 4.2.2 定时器初始化:timer_init
      • 4.2.3 定时器启动:timer_start
      • 4.2.4 定时器停止:timer_stop
      • 4.2.5 定时器循环调用后台处理:timer_loop
  • 实践工程下载
  • 公众号粉丝福利时刻
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档