硬汉嵌入式论坛

 找回密码
 立即注册
查看: 5645|回复: 5
收起左侧

[FreeRTOS教程] 第19章 FreeRTOS定时器组

[复制链接]

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
发表于 2016-8-30 13:49:21 | 显示全部楼层 |阅读模式



第19章      FreeRTOS定时器组


    本章节为大家讲解FreeRTOS支持的定时器组,或者叫软件定时器,又或者叫用户定时器均可。软件定时器的功能比较简单,也容易掌握。被称为定时器组是因为用户可以创建多个定时器,创建的个数是可配置的。
    本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407以及F429。
19.1 定时器组介绍
19.2 定时器任务(Daemon任务)
19.3 使用软件定时器组注意事项
19.4 定时器组API函数
19.5 实验例程说明
19.6      总结


19.1  定时器组介绍

    FreeRTOS软件定时器组的时基是基于系统时钟节拍实现的,之所以叫软件定时器是因为它的实现不需要使用任何硬件定时器,而且可以创建很多个,综合这些因素,这个功能就被称之为软件定时器组。
    既然是定时器,那么它实现的功能与硬件定时器也是类似的。在硬件定时器中,我们是在定时器中断中实现需要的功能,而使用软件定时器时,我们是在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。

19.1.1 单次模式和周期模式

    FreeRTOS提供的软件定时器支持单次模式和周期性模式,单次模式就是用户创建了定时器并启动了定时器后,定时时间到将不再重新执行,这就是单次模式软件定时器的含义。周期模式就是此定时器会按照设置的时间周期重复去执行,这就是周期模式软件定时器的含义。另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。

19.2 定时器任务(Daemon任务)

    为了更好的管理FreeRTOS的定时器组件,专门创建了一个定时器任务,或者称之为Daemon任务。关于这个任务,我们上章节在讲解事件标志组的时候有用到。
    FreeRTOS定时器组的大部分API函数都是通过消息队列给定时器任务发消息,在定时器任务里面执行实际的操作。为了更好的说明这个问题,我们将官方在线版手册中的这个截图贴出来进行说明:
19.1.jpg


左侧图是用户应用程序,右侧是定时器任务。在用户应用程序里面调用了定时器组API函数xTimerReset,这个函数会通过消息队列给定时器任务发消息,在定时器任务里面执行实际操作。消息队列在此处的作用有一个专门的名字:Timer command queue,即专门发送定时器组命令的队列。

19.3 使用软件定时器组注意事项

    定时器回调函数是在定时器任务中执行的,实际应用中切不可在定时器回调函数中调用任何将定时器任务挂起的函数,比如vTaskDelay(), vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数。将定时器任务挂起,会导致定时器任务负责的相关功能都不能正确执行了。
努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2016-8-30 13:54:52 | 显示全部楼层
19.4 定时器组API函数


    使用如下20个函数可以实现FreeRTOS的定时器组:
(1)    xTimerCreate()
(2)    xTimerCreateStatic()
(3)    xTimerIsTimerActive()
(4)    xTimerStart()
(5)    xTimerStop()
(6)    xTimerChangePeriod()   
(7)    xTimerDelete()
(8)    xTimerReset()
(9)    xTimerStartFromISR()
(10)    xTimerStopFromISR()
(11)    xTimerChangePeriodFromISR()
(12)    xTimerResetFromISR()
(13)    pvTimerGetTimerID()
(14)    vTimerSetTimerID()
(15)    xTimerGetTimerDaemonTaskHandle()
(16)    xTimerPendFunctionCall()
(17)    xTimerPendFunctionCallFromISR()
(18)    pcTimerGetName()
(19)    xTimerGetPeriod()
(20)    xTimerGetExpiryTime()

    关于这20个函数的讲解及其使用方法可以看FreeRTOS在线版手册:
19.2.jpg

这里我们重点的说以下3个函数:
    (1)    xTimerCreate()
    (2)    xTimerStart ()
    (3)    pvTimerGetTimerID ()
因为本章节配套的例子使用的是这3个函数。

19.4.1 函数xTimerCreate


函数原型:
  1. TimerHandle_t xTimerCreate
  2.                  ( const char * const pcTimerName,               /* 定时器名字 */
  3.                    const TickType_t xTimerPeriod,                /* 定时器周期,单位系统时钟节拍 */
  4.                    const UBaseType_t uxAutoReload,               /* 选择单次模式或者周期模式 */
  5.                    void * const pvTimerID,                       /* 定时器ID */
  6.                    TimerCallbackFunction_t pxCallbackFunction ); /* 定时器回调函数 */
复制代码
函数描述:
函数xTimerCreate用于创建软件定时器。
    (1)第1个参数是定时器名字,用于调试目的,方便识别不同的定时器。
    (2)第2个参数是定时器周期,单位系统时钟节拍。
    (3)第3个参数是选择周期模式还是单次模式,若参数为pdTRUE,则表示选择周期模式,若参数为pdFALSE,则表示选择单次模式。
    (4)第4个参数是定时器ID,当创建不同的定时器,但使用相同的回调函数时,在回调函数中通过不同的ID号来区分不同的定时器。
    (5)第5个参数是定时器回调函数。
    (6)返回值,创建成功返回定时器的句柄,由于FreeRTOSCongfig.h文件中heap空间不足,或者定时器周期设置为0,会返回NULL
使用这个函数要注意以下问题:
1.     在FreeRTOSConfig.h文件中使能宏定义:
        #defineconfigUSE_TIMERS                            1
使用举例:
  1. static TimerHandle_t xTimers = NULL;
  2. /*
  3. *********************************************************************************************************
  4. *    函 数 名: AppObjCreate
  5. *    功能说明: 创建任务通信机制
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. static void AppObjCreate (void)
  11. {
  12.      const TickType_t  xTimerPer = 100;
  13.    
  14.      /*
  15.         创建定时器,如果在RTOS调度开始前初始化定时器,那么系统启动后才会执行。
  16.      */
  17.      xTimers = xTimerCreate("Timer",            /* 定时器名字 */
  18.                                   xTimerPer,       /* 定时器周期,单位时钟节拍 */
  19.                                   pdTRUE,          /* 周期性 */
  20.                                   (void *) i,      /* 定时器ID */
  21.                                   vTimerCallback); /* 定时器回调函数 */
  22.      if(xTimers == NULL)
  23.      {
  24.          /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  25.      }
  26.      else
  27.      {
  28.          /* 启动定时器,系统启动后才开始工作 */
  29.          if(xTimerStart(xTimers, 100) != pdPASS)
  30.          {
  31.               /* 定时器还没有进入激活状态 */
  32.          }
  33.      }
  34. }
复制代码

19.4.2 函数xTimerStart


函数原型:
  1. BaseType_t xTimerStart( TimerHandle_t xTimer,    /* 定时器句柄 */
  2.                         TickType_t xBlockTime ); /* 成功启动定时器前的最大等待时间设置,单位系统时钟节拍 */
复制代码
函数描述:
函数xTimerStart用于启动软件定时器。
    (1)第1个参数是定时器句柄。
    (2)第2个参数是成功启动定时器前的最大等待时间设置,单位系统时钟节拍,定时器组的大部分API函数不是直接运行的(见19.2小节说明),而是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。
    (3)返回值,返回pdFAIL表示此函数向消息队列发送消息失败,返回pdPASS表示此函数向消息队列发送消息成功。定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放CPU权才可以得到执行。
使用这个函数要注意以下问题:
1.     使用前一定要保证定时器组已经通过函数xTimerCreate创建了。
2.     在FreeRTOSConfig.h文件中使能宏定义:
        #defineconfigUSE_TIMERS                            1
3.     对于已经被激活的定时器,即调用过函数xTimerStart进行启动,再次调用此函数相当于调用了函数xTimerReset对定时器时间进行了复位。
4.     如果在启动FreeRTOS调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate中设置的单次或者周期性延迟时间才会执行相应的回调函数。
使用举例:
  1. static TimerHandle_t xTimers = NULL;
  2. /*
  3. *********************************************************************************************************
  4. *    函 数 名: AppObjCreate
  5. *    功能说明: 创建任务通信机制
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. static void AppObjCreate (void)
  11. {
  12.      const TickType_t  xTimerPer = 100;
  13.    
  14.      /*
  15.         创建定时器,如果在RTOS调度开始前初始化定时器,那么系统启动后才会执行。
  16.      */
  17.      xTimers = xTimerCreate("Timer",            /* 定时器名字 */
  18.                                   xTimerPer,       /* 定时器周期,单位时钟节拍 */
  19.                                   pdTRUE,          /* 周期性 */
  20.                                   (void *) i,      /* 定时器ID */
  21.                                   vTimerCallback); /* 定时器回调函数 */
  22.      if(xTimers == NULL)
  23.      {
  24.          /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  25.      }
  26.      else
  27.      {
  28.          /* 启动定时器,系统启动后才开始工作 */
  29.          if(xTimerStart(xTimers, 100) != pdPASS)
  30.          {
  31.               /* 定时器还没有进入激活状态 */
  32.          }
  33.      }
  34. }
复制代码

19.4.3 函数pvTimerGetTimerID


函数原型:
  1. void *pvTimerGetTimerID( TimerHandle_t xTimer ); /* 定时器句柄 */
复制代码
函数描述:
函数pvTimerGetTimerID用于返回使用函数xTimerCreate创建的软件定时器ID。
    (1)第1个参数是定时器句柄。
    (2)返回值,返回定时器ID
使用这个函数要注意以下问题:
1.     使用前一定要保证定时器组已经通过函数xTimerCreate创建了。
2.     在FreeRTOSConfig.h文件中使能宏定义:
      #defineconfigUSE_TIMERS                            1
3.     创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。
使用举例:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTimerCallback
  4. *    功能说明: 定时器回调函数
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void vTimerCallback(xTimerHandle pxTimer)
  10. {
  11.      uint32_t ulTimerID;
  12.      configASSERT(pxTimer);
  13.      /* 获取那个定时器时间到 */
  14.      ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
  15.    
  16.      /* 处理定时器0任务 */
  17.      if(ulTimerID == 0)
  18.      {
  19.          bsp_LedToggle(1);
  20.      }
  21.    
  22.      /* 处理定时器1任务 */
  23.      if(ulTimerID == 1)
  24.      {
  25.          bsp_LedToggle(2);
  26.      }
  27. }
复制代码
努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2016-8-30 14:03:32 | 显示全部楼层
19.5 实验例程说明



19.5.1 STM32F103开发板实验


配套例子:
    V4-314_FreeRTOS实验_定时器组
实验目的:
    1.     学习FreeRTOS的定时器组。
实验内容:
    1.     K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。
    2.     创建两个软件定时器。
    3.     各个任务实现的功能如下:
              vTaskUserIF任务   :按键消息处理。
              vTaskLED任务     :LED闪烁。
              vTaskMsgPro任务 :消息处理,这里用作LED闪烁。
              vTaskStart任务    :启动任务,也是最高优先级任务,这里实现按键扫描。
FreeRTOS的配置:
    FreeRTOSConfig.h文件中的配置如下:
  1. /* Ensure stdint is only used by the compiler, and not the assembler. */
  2. #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  3. #include <stdint.h>
  4. extern volatile uint32_t ulHighFrequencyTimerTicks;
  5. #endif
  6. #define configUSE_PREEMPTION         1
  7. #define configUSE_IDLE_HOOK          0
  8. #define configUSE_TICK_HOOK          0
  9. #define configCPU_CLOCK_HZ           ( ( unsigned long ) 72000000 )  
  10. #define configTICK_RATE_HZ           ( ( TickType_t ) 1000 )
  11. #define configMAX_PRIORITIES         ( 5 )
  12. #define configMINIMAL_STACK_SIZE     ( ( unsigned short ) 128 )
  13. #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )
  14. #define configMAX_TASK_NAME_LEN      ( 16 )
  15. #define configUSE_TRACE_FACILITY      1
  16. #define configUSE_16_BIT_TICKS       0
  17. #define configIDLE_SHOULD_YIELD      1
  18. /* Run time and task stats gathering related definitions. */
  19. #define configGENERATE_RUN_TIME_STATS                1
  20. #define configUSE_STATS_FORMATTING_FUNCTIONS         1
  21. #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     (ulHighFrequencyTimerTicks = 0ul)
  22. #define portGET_RUN_TIME_COUNTER_VALUE()             ulHighFrequencyTimerTicks
  23. //#define portALT_GET_RUN_TIME_COUNTER_VALUE           1
  24. /* Co-routine definitions. */
  25. #define configUSE_CO_ROUTINES            0
  26. #define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
  27. /* Set the following definitions to 1 to include the API function, or zero
  28. to exclude the API function. */
  29. #define INCLUDE_vTaskPrioritySet          1
  30. #define INCLUDE_uxTaskPriorityGet         1
  31. #define INCLUDE_vTaskDelete               1
  32. #define INCLUDE_vTaskCleanUpResources      0
  33. #define INCLUDE_vTaskSuspend              1
  34. #define INCLUDE_vTaskDelayUntil           1
  35. #define INCLUDE_vTaskDelay                1
  36. /* Software timer definitions. */
  37. #define configUSE_TIMERS                    1
  38. #define configTIMER_TASK_PRIORITY         ( 2 )
  39. #define configTIMER_QUEUE_LENGTH          10
  40. #define configTIMER_TASK_STACK_DEPTH      ( configMINIMAL_STACK_SIZE * 2 )
  41. /* Cortex-M specific definitions. */
  42. #ifdef __NVIC_PRIO_BITS
  43.      /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
  44.      #define configPRIO_BITS              __NVIC_PRIO_BITS
  45. #else
  46.      #define configPRIO_BITS              4        /* 15 priority levels */
  47. #endif
  48. /* The lowest interrupt priority that can be used in a call to a "set priority"
  49. function. */
  50. #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY              0x0f
  51. /* The highest interrupt priority that can be used by any interrupt service
  52. routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
  53. INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
  54. PRIORITY THAN THIS! (higher priorities are lower numeric values. */
  55. #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY         0x01
复制代码

几个重要选项说明:
1、#define configUSE_PREEMPTION        1
        使能抢占式调度器
2、#define configCPU_CLOCK_HZ      ( ( unsigned long ) 72000000 )   
        系统主频72MHz。
3、#define configTICK_RATE_HZ              ( ( TickType_t ) 1000 )
        系统时钟节拍1KHz,即1ms。
4、#define configMAX_PRIORITIES          ( 5 )
        定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4,不包含5,对于这一点,初学者要特别的注意。
5、#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )
        定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。
6、#define configUSE_TIMERS                         1
        使能定时器组。
7、#define configTIMER_TASK_PRIORITY       ( 2 )
        配置定时器任务的优先级。
8、#define configTIMER_QUEUE_LENGTH       10
        配置定时器任务用到的消息队列大小,即能够存储的消息个数。
9、#define configTIMER_TASK_STACK_DEPTH      ( configMINIMAL_STACK_SIZE * 2 )
        配置定时器任务的任务栈大小。
10、configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY          0x01
        定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOS的API的最高优先级。为了进一步说明这个宏定义的的作用,解释如下:
    (1)使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。
    (2)对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为015,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。
    (3)这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。
    更多关于这个参数说明请参看第12章。
FreeRTOS任务调试信息(按K1按键,串口打印):
19.3.jpg

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
    #definetskBLOCKED_CHAR          ( 'B' )  任务阻塞
    #definetskREADY_CHAR           ( 'R' ) 任务就绪
    #definetskDELETED_CHAR           ( 'D' )  任务删除
    #definetskSUSPENDED_CHAR   ( 'S' ) 任务挂起
程序设计:
任务栈大小分配:
    vTaskUserIF任务   :2048字节
    vTaskLED任务     :2048字节
    vTaskMsgPro任务 :2048字节
    vTaskStart任务    :2048字节
    任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的
    #defineconfigTOTAL_HEAP_SIZE        ( ( size_t )( 17 * 1024 ) )
系统栈大小分配:
19.4.jpg

FreeROTS初始化:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: 标准c程序入口。
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.      /*
  12.        在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。
  13.        这样做的好处是:
  14.        1. 防止执行的中断服务程序中有FreeRTOS的API函数。
  15.        2. 保证系统正常启动,不受别的中断影响。
  16.        3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。
  17.        在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1)
  18.        和cpsie i是等效的。
  19.      */
  20.      __set_PRIMASK(1);
  21.    
  22.      /* 硬件初始化 */
  23.      bsp_Init();
  24.    
  25.      /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项
  26.            目中不要使用,因为这个功能比较影响系统实时性。
  27.         2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
  28.      */
  29.      vSetupSysInfoTest();
  30.    
  31.      /* 创建任务 */
  32.      AppTaskCreate();
  33.      /* 创建任务通信机制 */
  34.      AppObjCreate();
  35.    
  36.     /* 启动调度,开始执行任务 */
  37.     vTaskStartScheduler();
  38.      /*
  39.        如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
  40.        heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小:
  41.        #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )
  42.      */
  43.      while(1);
  44. }
复制代码

硬件外设初始化
    硬件外设的初始化是在bsp.c文件实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化硬件设备。只需要调用一次。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。
  5. *             全局变量。
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_Init(void)
  11. {
  12.      /*
  13.          由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。
  14.          启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。
  15.          系统时钟缺省配置为72MHz,如果需要更改,可以修改 system_stm32f10x.c 文件
  16.      */
  17.    
  18.      /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  19.      bsp_InitUart();    /* 初始化串口 */
  20.      bsp_InitLed();     /* 初始LED指示灯端口 */
  21.      bsp_InitKey();     /* 初始化按键 */
  22. }
复制代码

FreeRTOS任务创建:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: AppTaskCreate
  4. *    功能说明: 创建应用任务
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void AppTaskCreate (void)
  10. {
  11.     xTaskCreate( vTaskTaskUserIF,   /* 任务函数  */
  12.                  "vTaskUserIF",     /* 任务名    */
  13.                  512,               /* 任务栈大小,单位word,也就是4字节 */
  14.                  NULL,              /* 任务参数  */
  15.                  1,                 /* 任务优先级*/
  16.                  &xHandleTaskUserIF );  /* 任务句柄  */
  17.    
  18.    
  19.      xTaskCreate( vTaskLED,           /* 任务函数  */
  20.                  "vTaskLED",         /* 任务名    */
  21.                  512,                /* 任务栈大小,单位word,也就是4字节 */
  22.                  NULL,               /* 任务参数  */
  23.                  2,                  /* 任务优先级*/
  24.                  &xHandleTaskLED ); /* 任务句柄  */
  25.    
  26.      xTaskCreate( vTaskMsgPro,            /* 任务函数  */
  27.                  "vTaskMsgPro",           /* 任务名    */
  28.                  512,                     /* 任务栈大小,单位word,也就是4字节 */
  29.                  NULL,                    /* 任务参数  */
  30.                  3,                       /* 任务优先级*/
  31.                  &xHandleTaskMsgPro );  /* 任务句柄  */
  32.    
  33.    
  34.      xTaskCreate( vTaskStart,             /* 任务函数  */
  35.                  "vTaskStart",            /* 任务名    */
  36.                  512,                     /* 任务栈大小,单位word,也就是4字节 */
  37.                  NULL,                    /* 任务参数  */
  38.                  4,                       /* 任务优先级*/
  39.                  &xHandleTaskStart );   /* 任务句柄  */
  40. }
复制代码

FreeRTOS定时器组创建:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: AppObjCreate
  4. *    功能说明: 创建任务通信机制
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void AppObjCreate (void)
  10. {
  11.      uint8_t i;
  12.      const TickType_t  xTimerPer[2] = {100, 100};
  13.    
  14.      /*
  15.        1. 创建定时器,如果在RTOS调度开始前初始化定时器,那么系统启动后才会执行。
  16.        2. 统一初始化两个定时器,他们使用共同的回调函数,在回调函数中通过定时器ID来区分
  17.           是那个定时器的时间到。当然,使用不同的回调函数也是没问题的。
  18.      */
  19.      for(i = 0; i < 2; i++)
  20.      {
  21.          xTimers[i] = xTimerCreate("Timer",              /* 定时器名字 */
  22.                                         xTimerPer[i],    /* 定时器周期,单位时钟节拍 */
  23.                                         pdTRUE,          /* 周期性 */
  24.                                         (void *) i,      /* 定时器ID */
  25.                                         vTimerCallback); /* 定时器回调函数 */
  26.          if(xTimers[i] == NULL)
  27.          {
  28.               /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  29.          }
  30.          else
  31.          {
  32.                /* 启动定时器,系统启动后才开始工作 */
  33.                if(xTimerStart(xTimers[i], 100) != pdPASS)
  34.                {
  35.                     /* 定时器还没有进入激活状态 */
  36.                }
  37.          }
  38.      }
  39. }
复制代码

四个FreeRTOS任务的实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTaskTaskUserIF
  4. *    功能说明: 接口消息处理。
  5. *    形    参: pvParameters 是在创建该任务时传递的形参
  6. *    返 回 值: 无
  7. *   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)
  8. *********************************************************************************************************
  9. */
  10. static void vTaskTaskUserIF(void *pvParameters)
  11. {
  12.      uint8_t ucKeyCode;
  13.      uint8_t pcWriteBuffer[500];
  14.      EventBits_t uxBits;
  15.     while(1)
  16.     {
  17.          ucKeyCode = bsp_GetKey();
  18.         
  19.          if (ucKeyCode != KEY_NONE)
  20.          {
  21.               switch (ucKeyCode)
  22.               {
  23.                    /* K1键按下 打印任务执行情况 */
  24.                    case KEY_DOWN_K1:         
  25.                        printf("=================================================\\r\\n");
  26.                         printf("任务名      任务状态 优先级   剩余栈 任务序号\\r\\n");
  27.                        vTaskList((char *)&pcWriteBuffer);
  28.                        printf("%s\\r\\n", pcWriteBuffer);
  29.                   
  30.                        printf("\\r\\n任务名       运行计数         使用率\\r\\n");
  31.                        vTaskGetRunTimeStats((char *)&pcWriteBuffer);
  32.                        printf("%s\\r\\n", pcWriteBuffer);
  33.                        break;
  34.                   
  35.                    /* 其他的键值不处理 */
  36.                    default:                    
  37.                        break;
  38.               }
  39.          }
  40.         
  41.          vTaskDelay(20);
  42.      }
  43. }
  44. /*
  45. *********************************************************************************************************
  46. *    函 数 名: vTaskLED
  47. *    功能说明: LED闪烁
  48. *    形    参: pvParameters 是在创建该任务时传递的形参
  49. *    返 回 值: 无
  50. *   优 先 级: 2
  51. *********************************************************************************************************
  52. */
  53. static void vTaskLED(void *pvParameters)
  54. {
  55.      TickType_t xLastWakeTime;
  56.      const TickType_t xFrequency = 200;
  57.      /* 获取当前的系统时间 */
  58.     xLastWakeTime = xTaskGetTickCount();
  59.    
  60.     while(1)
  61.     {
  62.          bsp_LedToggle(2);
  63.         
  64.          /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
  65.         vTaskDelayUntil(&xLastWakeTime, xFrequency);
  66.     }
  67. }
  68. /*
  69. *********************************************************************************************************
  70. *    函 数 名: vTaskMsgPro
  71. *    功能说明: 消息处理,这里用作LED闪烁
  72. *    形    参: pvParameters 是在创建该任务时传递的形参
  73. *    返 回 值: 无
  74. *   优 先 级: 3
  75. *********************************************************************************************************
  76. */
  77. static void vTaskMsgPro(void *pvParameters)
  78. {
  79.      TickType_t xLastWakeTime;
  80.      const TickType_t xFrequency = 200;
  81.      /* 获取当前的系统时间 */
  82.     xLastWakeTime = xTaskGetTickCount();
  83.    
  84.     while(1)
  85.     {
  86.          bsp_LedToggle(4);
  87.         
  88.          /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
  89.         vTaskDelayUntil(&xLastWakeTime, xFrequency);
  90.     }
  91. }
  92. /*
  93. *********************************************************************************************************
  94. *    函 数 名: vTaskStart
  95. *    功能说明: 启动任务,也就是最高优先级任务,这里用作按键扫描。
  96. *    形    参: pvParameters 是在创建该任务时传递的形参
  97. *    返 回 值: 无
  98. *   优 先 级: 4
  99. *********************************************************************************************************
  100. */
  101. static void vTaskStart(void *pvParameters)
  102. {
  103.     while(1)
  104.     {
  105.          /* 按键扫描 */
  106.          bsp_KeyScan();
  107.         vTaskDelay(10);
  108.     }
  109. }
复制代码

定时器组回调函数的实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTimerCallback
  4. *    功能说明: 定时器回调函数
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void vTimerCallback(xTimerHandle pxTimer)
  10. {
  11.      uint32_t ulTimerID;
  12.      configASSERT(pxTimer);
  13.      /* 获取那个定时器时间到 */
  14.      ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
  15.    
  16.      /* 处理定时器0任务 */
  17.      if(ulTimerID == 0)
  18.      {
  19.          bsp_LedToggle(1);
  20.      }
  21.    
  22.      /* 处理定时器1任务 */
  23.      if(ulTimerID == 1)
  24.      {
  25.          bsp_LedToggle(2);
  26.      }
  27. }
复制代码
努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2016-8-30 14:27:48 | 显示全部楼层
19.5.2   STM32F407开发板实验


配套例子:
     V5-314_FreeRTOS实验_定时器组
实验目的:
     1.     学习FreeRTOS的定时器组。
实验内容:
     1.     K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。
     2.     创建两个软件定时器。
     3.     各个任务实现的功能如下:
              vTaskUserIF任务   :按键消息处理。
              vTaskLED任务     :LED闪烁。
              vTaskMsgPro任务 :消息处理,这里用作LED闪烁。
              vTaskStart任务    :启动任务,也是最高优先级任务,这里实现按键扫描。
FreeRTOS的配置:
    FreeRTOSConfig.h文件中的配置如下:
  1. /* Ensure stdint is only used by the compiler, and not the assembler. */
  2. #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  3. #include <stdint.h>
  4. extern volatile uint32_t ulHighFrequencyTimerTicks;
  5. /* Ensure stdint is only used by the compiler, and not the assembler. */
  6. #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  7. #include <stdint.h>
  8. extern volatile uint32_t ulHighFrequencyTimerTicks;
  9. #endif
  10. #define configUSE_PREEMPTION         1
  11. #define configUSE_IDLE_HOOK          0
  12. #define configUSE_TICK_HOOK          0
  13. #define configCPU_CLOCK_HZ           ( ( unsigned long ) 168000000 )
  14. #define configTICK_RATE_HZ           ( ( TickType_t ) 1000 )
  15. #define configMAX_PRIORITIES         ( 5 )
  16. #define configMINIMAL_STACK_SIZE     ( ( unsigned short ) 128 )
  17. #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 30 * 1024 ) )
  18. #define configMAX_TASK_NAME_LEN      ( 16 )
  19. #define configUSE_TRACE_FACILITY      1
  20. #define configUSE_16_BIT_TICKS       0
  21. #define configIDLE_SHOULD_YIELD      1
  22. /* Run time and task stats gathering related definitions. */
  23. #define configGENERATE_RUN_TIME_STATS                1
  24. #define configUSE_STATS_FORMATTING_FUNCTIONS         1
  25. #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     (ulHighFrequencyTimerTicks = 0ul)
  26. #define portGET_RUN_TIME_COUNTER_VALUE()             ulHighFrequencyTimerTicks
  27. //#define portALT_GET_RUN_TIME_COUNTER_VALUE           1
  28. /* Co-routine definitions. */
  29. #define configUSE_CO_ROUTINES             0
  30. #define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
  31. /* Set the following definitions to 1 to include the API function, or zero
  32. to exclude the API function. */
  33. #define INCLUDE_vTaskPrioritySet     1
  34. #define INCLUDE_uxTaskPriorityGet         1
  35. #define INCLUDE_vTaskDelete               1
  36. #define INCLUDE_vTaskCleanUpResources 0
  37. #define INCLUDE_vTaskSuspend              1
  38. #define INCLUDE_vTaskDelayUntil           1
  39. #define INCLUDE_vTaskDelay                1
  40. /* Software timer definitions. */
  41. #define configUSE_TIMERS                    1
  42. #define configTIMER_TASK_PRIORITY         ( 2 )
  43. #define configTIMER_QUEUE_LENGTH          10
  44. #define configTIMER_TASK_STACK_DEPTH      ( configMINIMAL_STACK_SIZE * 2 )
  45. /* Cortex-M specific definitions. */
  46. #ifdef __NVIC_PRIO_BITS
  47.      /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
  48.      #define configPRIO_BITS              __NVIC_PRIO_BITS
  49. #else
  50.      #define configPRIO_BITS              4        /* 15 priority levels */
  51. #endif
  52. /* The lowest interrupt priority that can be used in a call to a "set priority"
  53. function. */
  54. #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY              0x0f
  55. /* The highest interrupt priority that can be used by any interrupt service
  56. routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
  57. INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
  58. PRIORITY THAN THIS! (higher priorities are lower numeric values. */
  59. #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    0x01
复制代码
几个重要选项说明:
1、#define configUSE_PREEMPTION        1
        使能抢占式调度器
2、#define configCPU_CLOCK_HZ      ( ( unsigned long ) 168000000 )
       系统主频168MHz。
3、#define configTICK_RATE_HZ              ( ( TickType_t ) 1000 )
        系统时钟节拍1KHz,即1ms。
4、#define configMAX_PRIORITIES          ( 5 )
        定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4,不包含5,对于这一点,初学者要特别的注意。
5、#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 30 * 1024 ) )
        定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。
6、#define configUSE_TIMERS                         1
        使能定时器组。
7、#define configTIMER_TASK_PRIORITY       ( 2 )
        配置定时器任务的优先级。
8、#define configTIMER_QUEUE_LENGTH       10
        配置定时器任务用到的消息队列大小,即能够存储的消息个数。
9、#define configTIMER_TASK_STACK_DEPTH      ( configMINIMAL_STACK_SIZE * 2 )
        配置定时器任务的任务栈大小。
10、configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY          0x01
        定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOS的API的最高优先级。为了进一步说明这个宏定义的的作用,解释如下:
    (1)使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。
    (2)对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为015,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。
    (3)这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。
    更多关于这个参数说明请参看第12章。
FreeRTOS任务调试信息(按K1按键,串口打印):
19.5.jpg

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
    #definetskBLOCKED_CHAR          ( 'B' )  任务阻塞
    #definetskREADY_CHAR           ( 'R' ) 任务就绪
    #definetskDELETED_CHAR           ( 'D' )  任务删除
    #definetskSUSPENDED_CHAR   ( 'S' ) 任务挂起
程序设计:
任务栈大小分配:
    vTaskUserIF任务   :2048字节
    vTaskLED任务     :2048字节
    vTaskMsgPro任务 :2048字节
    vTaskStart任务    :2048字节
    任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的
    #defineconfigTOTAL_HEAP_SIZE        ( ( size_t )( 30 * 1024 ) )
系统栈大小分配:
19.6.jpg

FreeROTS初始化:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: 标准c程序入口。
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.      /*
  12.        在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。
  13.        这样做的好处是:
  14.        1. 防止执行的中断服务程序中有FreeRTOS的API函数。
  15.        2. 保证系统正常启动,不受别的中断影响。
  16.        3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。
  17.        在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1)
  18.        和cpsie i是等效的。
  19.      */
  20.      __set_PRIMASK(1);
  21.    
  22.      /* 硬件初始化 */
  23.      bsp_Init();
  24.    
  25.      /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项
  26.            目中不要使用,因为这个功能比较影响系统实时性。
  27.         2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
  28.      */
  29.      vSetupSysInfoTest();
  30.    
  31.      /* 创建任务 */
  32.      AppTaskCreate();
  33.    
  34.      /* 创建任务通信机制 */
  35.      AppObjCreate();
  36.     /* 启动调度,开始执行任务 */
  37.     vTaskStartScheduler();
  38.      /*
  39.        如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
  40.        heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小:
  41.        #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )
  42.      */
  43.      while(1);
  44. }
复制代码
硬件外设初始化
    硬件外设的初始化是在bsp.c文件实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.      /*
  12.          由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。
  13.          启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。
  14.          系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件
  15.      */
  16.      /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/
  17.      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  18.      bsp_InitUart();    /* 初始化串口 */
  19.      bsp_InitKey();     /* 初始化按键变量 */
  20.      bsp_InitLed();     /* 初始LED指示灯端口 */
  21. }
复制代码
FreeRTOS任务创建:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: AppTaskCreate
  4. *    功能说明: 创建应用任务
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void AppTaskCreate (void)
  10. {
  11.     xTaskCreate( vTaskTaskUserIF,   /* 任务函数  */
  12.                  "vTaskUserIF",     /* 任务名    */
  13.                  512,               /* 任务栈大小,单位word,也就是4字节 */
  14.                  NULL,              /* 任务参数  */
  15.                  1,                 /* 任务优先级*/
  16.                  &xHandleTaskUserIF );  /* 任务句柄  */
  17.    
  18.    
  19.      xTaskCreate( vTaskLED,           /* 任务函数  */
  20.                  "vTaskLED",         /* 任务名    */
  21.                  512,                /* 任务栈大小,单位word,也就是4字节 */
  22.                  NULL,               /* 任务参数  */
  23.                  2,                  /* 任务优先级*/
  24.                  &xHandleTaskLED ); /* 任务句柄  */
  25.    
  26.      xTaskCreate( vTaskMsgPro,            /* 任务函数  */
  27.                  "vTaskMsgPro",           /* 任务名    */
  28.                  512,                     /* 任务栈大小,单位word,也就是4字节 */
  29.                  NULL,                    /* 任务参数  */
  30.                  3,                       /* 任务优先级*/
  31.                  &xHandleTaskMsgPro );  /* 任务句柄  */
  32.    
  33.    
  34.      xTaskCreate( vTaskStart,             /* 任务函数  */
  35.                  "vTaskStart",            /* 任务名    */
  36.                  512,                     /* 任务栈大小,单位word,也就是4字节 */
  37.                  NULL,                    /* 任务参数  */
  38.                  4,                       /* 任务优先级*/
  39.                  &xHandleTaskStart );   /* 任务句柄  */
  40. }
复制代码
FreeRTOS定时器组创建:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: AppObjCreate
  4. *    功能说明: 创建任务通信机制
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void AppObjCreate (void)
  10. {
  11.      uint8_t i;
  12.      const TickType_t  xTimerPer[2] = {100, 100};
  13.    
  14.      /*
  15.        1. 创建定时器,如果在RTOS调度开始前初始化定时器,那么系统启动后才会执行。
  16.        2. 统一初始化两个定时器,他们使用共同的回调函数,在回调函数中通过定时器ID来区分
  17.           是那个定时器的时间到。当然,使用不同的回调函数也是没问题的。
  18.      */
  19.      for(i = 0; i < 2; i++)
  20.      {
  21.          xTimers[i] = xTimerCreate("Timer",              /* 定时器名字 */
  22.                                         xTimerPer[i],    /* 定时器周期,单位时钟节拍 */
  23.                                         pdTRUE,          /* 周期性 */
  24.                                         (void *) i,      /* 定时器ID */
  25.                                         vTimerCallback); /* 定时器回调函数 */
  26.          if(xTimers[i] == NULL)
  27.          {
  28.               /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  29.          }
  30.          else
  31.          {
  32.                /* 启动定时器,系统启动后才开始工作 */
  33.                if(xTimerStart(xTimers[i], 100) != pdPASS)
  34.                {
  35.                     /* 定时器还没有进入激活状态 */
  36.                }
  37.          }
  38.      }
  39. }
复制代码
四个FreeRTOS任务的实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTaskTaskUserIF
  4. *    功能说明: 接口消息处理。
  5. *    形    参: pvParameters 是在创建该任务时传递的形参
  6. *    返 回 值: 无
  7. *   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)
  8. *********************************************************************************************************
  9. */
  10. static void vTaskTaskUserIF(void *pvParameters)
  11. {
  12.      uint8_t ucKeyCode;
  13.      uint8_t pcWriteBuffer[500];
  14.      EventBits_t uxBits;
  15.     while(1)
  16.     {
  17.          ucKeyCode = bsp_GetKey();
  18.         
  19.          if (ucKeyCode != KEY_NONE)
  20.          {
  21.               switch (ucKeyCode)
  22.               {
  23.                    /* K1键按下 打印任务执行情况 */
  24.                    case KEY_DOWN_K1:         
  25.                        printf("=================================================\\r\\n");
  26.                         printf("任务名      任务状态 优先级   剩余栈 任务序号\\r\\n");
  27.                        vTaskList((char *)&pcWriteBuffer);
  28.                        printf("%s\\r\\n", pcWriteBuffer);
  29.                   
  30.                        printf("\\r\\n任务名       运行计数         使用率\\r\\n");
  31.                        vTaskGetRunTimeStats((char *)&pcWriteBuffer);
  32.                        printf("%s\\r\\n", pcWriteBuffer);
  33.                        break;
  34.                   
  35.                    /* 其他的键值不处理 */
  36.                    default:                    
  37.                        break;
  38.               }
  39.          }
  40.         
  41.          vTaskDelay(20);
  42.      }
  43. }
  44. /*
  45. *********************************************************************************************************
  46. *    函 数 名: vTaskLED
  47. *    功能说明: LED闪烁
  48. *    形    参: pvParameters 是在创建该任务时传递的形参
  49. *    返 回 值: 无
  50. *   优 先 级: 2
  51. *********************************************************************************************************
  52. */
  53. static void vTaskLED(void *pvParameters)
  54. {
  55.      TickType_t xLastWakeTime;
  56.      const TickType_t xFrequency = 200;
  57.      /* 获取当前的系统时间 */
  58.     xLastWakeTime = xTaskGetTickCount();
  59.    
  60.     while(1)
  61.     {
  62.          bsp_LedToggle(2);
  63.         
  64.          /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
  65.         vTaskDelayUntil(&xLastWakeTime, xFrequency);
  66.     }
  67. }
  68. /*
  69. *********************************************************************************************************
  70. *    函 数 名: vTaskMsgPro
  71. *    功能说明: 消息处理,这里用作LED闪烁
  72. *    形    参: pvParameters 是在创建该任务时传递的形参
  73. *    返 回 值: 无
  74. *   优 先 级: 3
  75. *********************************************************************************************************
  76. */
  77. static void vTaskMsgPro(void *pvParameters)
  78. {
  79.      TickType_t xLastWakeTime;
  80.      const TickType_t xFrequency = 200;
  81.      /* 获取当前的系统时间 */
  82.     xLastWakeTime = xTaskGetTickCount();
  83.    
  84.     while(1)
  85.     {
  86.          bsp_LedToggle(4);
  87.         
  88.          /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
  89.         vTaskDelayUntil(&xLastWakeTime, xFrequency);
  90.     }
  91. }
  92. /*
  93. *********************************************************************************************************
  94. *    函 数 名: vTaskStart
  95. *    功能说明: 启动任务,也就是最高优先级任务,这里用作按键扫描。
  96. *    形    参: pvParameters 是在创建该任务时传递的形参
  97. *    返 回 值: 无
  98. *   优 先 级: 4
  99. *********************************************************************************************************
  100. */
  101. static void vTaskStart(void *pvParameters)
  102. {
  103.     while(1)
  104.     {
  105.          /* 按键扫描 */
  106.          bsp_KeyScan();
  107.         vTaskDelay(10);
  108.     }
  109. }
复制代码
定时器组回调函数的实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTimerCallback
  4. *    功能说明: 定时器回调函数
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void vTimerCallback(xTimerHandle pxTimer)
  10. {
  11.      uint32_t ulTimerID;
  12.      configASSERT(pxTimer);
  13.      /* 获取那个定时器时间到 */
  14.      ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
  15.    
  16.      /* 处理定时器0任务 */
  17.      if(ulTimerID == 0)
  18.      {
  19.          bsp_LedToggle(1);
  20.      }
  21.    
  22.      /* 处理定时器1任务 */
  23.      if(ulTimerID == 1)
  24.      {
  25.          bsp_LedToggle(2);
  26.      }
  27. }
复制代码
努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2016-8-30 14:34:32 | 显示全部楼层
19.5.3   STM32F429开发板实验


配套例子:
      V6-314_FreeRTOS实验_定时器组
实验目的:
    1.     学习FreeRTOS的定时器组。
实验内容:
    1.     K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。
    2.     创建两个软件定时器。
    3.     各个任务实现的功能如下:
              vTaskUserIF任务   :按键消息处理。
              vTaskLED任务     :LED闪烁。
              vTaskMsgPro任务 :消息处理,这里用作LED闪烁。
              vTaskStart任务    :启动任务,也是最高优先级任务,这里实现按键扫描。
FreeRTOS的配置:
    FreeRTOSConfig.h文件中的配置如下:
  1. /* Ensure stdint is only used by the compiler, and not the assembler. */
  2. #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  3. #include <stdint.h>
  4. extern volatile uint32_t ulHighFrequencyTimerTicks;
  5. #endif
  6. #define configUSE_PREEMPTION         1
  7. #define configUSE_IDLE_HOOK          0
  8. #define configUSE_TICK_HOOK          0
  9. #define configCPU_CLOCK_HZ           ( ( unsigned long ) 168000000 )
  10. #define configTICK_RATE_HZ           ( ( TickType_t ) 1000 )
  11. #define configMAX_PRIORITIES         ( 5 )
  12. #define configMINIMAL_STACK_SIZE     ( ( unsigned short ) 128 )
  13. #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 30 * 1024 ) )
  14. #define configMAX_TASK_NAME_LEN      ( 16 )
  15. #define configUSE_TRACE_FACILITY      1
  16. #define configUSE_16_BIT_TICKS       0
  17. #define configIDLE_SHOULD_YIELD      1
  18. /* Run time and task stats gathering related definitions. */
  19. #define configGENERATE_RUN_TIME_STATS                1
  20. #define configUSE_STATS_FORMATTING_FUNCTIONS         1
  21. #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     (ulHighFrequencyTimerTicks = 0ul)
  22. #define portGET_RUN_TIME_COUNTER_VALUE()             ulHighFrequencyTimerTicks
  23. //#define portALT_GET_RUN_TIME_COUNTER_VALUE           1
  24. /* Co-routine definitions. */
  25. #define configUSE_CO_ROUTINES            0
  26. #define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
  27. /* Set the following definitions to 1 to include the API function, or zero
  28. to exclude the API function. */
  29. #define INCLUDE_vTaskPrioritySet          1
  30. #define INCLUDE_uxTaskPriorityGet         1
  31. #define INCLUDE_vTaskDelete               1
  32. #define INCLUDE_vTaskCleanUpResources      0
  33. #define INCLUDE_vTaskSuspend              1
  34. #define INCLUDE_vTaskDelayUntil           1
  35. #define INCLUDE_vTaskDelay                1
  36. /* Software timer definitions. */
  37. #define configUSE_TIMERS                    1
  38. #define configTIMER_TASK_PRIORITY         ( 2 )
  39. #define configTIMER_QUEUE_LENGTH          10
  40. #define configTIMER_TASK_STACK_DEPTH      ( configMINIMAL_STACK_SIZE * 2 )
  41. /* Cortex-M specific definitions. */
  42. #ifdef __NVIC_PRIO_BITS
  43.      /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
  44.      #define configPRIO_BITS              __NVIC_PRIO_BITS
  45. #else
  46.      #define configPRIO_BITS              4        /* 15 priority levels */
  47. #endif
  48. /* The lowest interrupt priority that can be used in a call to a "set priority"
  49. function. */
  50. #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY              0x0f
  51. /* The highest interrupt priority that can be used by any interrupt service
  52. routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
  53. INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
  54. PRIORITY THAN THIS! (higher priorities are lower numeric values. */
  55. #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY         0x01
复制代码
几个重要选项说明:
1、#define configUSE_PREEMPTION        1
        使能抢占式调度器
2、#define configCPU_CLOCK_HZ      ( ( unsigned long ) 168000000 )
        系统主频168MHz。
3、#define configTICK_RATE_HZ              ( ( TickType_t ) 1000 )
        系统时钟节拍1KHz,即1ms。
4、#define configMAX_PRIORITIES          ( 5 )
        定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4,不包含5,对于这一点,初学者要特别的注意。
5、#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 30 * 1024 ) )
        定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。
6、#define configUSE_TIMERS                         1
        使能定时器组。
7、#define configTIMER_TASK_PRIORITY       ( 2 )
        配置定时器任务的优先级。
8、#define configTIMER_QUEUE_LENGTH       10
        配置定时器任务用到的消息队列大小,即能够存储的消息个数。
9、#define configTIMER_TASK_STACK_DEPTH      ( configMINIMAL_STACK_SIZE * 2 )
        配置定时器任务的任务栈大小。
10、configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY          0x01
        定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOS的API的最高优先级。为了进一步说明这个宏定义的的作用,解释如下:
    (1)使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。
    (2)对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为015,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。
    (3)这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。
     更多关于这个参数说明请参看第12章。
FreeRTOS任务调试信息(按K1按键,串口打印):
19.7.jpg

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
    #definetskBLOCKED_CHAR          ( 'B' )  任务阻塞
    #definetskREADY_CHAR           ( 'R' ) 任务就绪
    #definetskDELETED_CHAR           ( 'D' )  任务删除
    #definetskSUSPENDED_CHAR   ( 'S' ) 任务挂起
使用事件标志组要包含的头文件:
使用FreeRTOS的事件标志组要包含头文件#include "event_groups.h",这个头文件在includes.h文件中进行了包含,具体如下:
  1. /*
  2. *********************************************************************************************************
  3. *                                           OS
  4. *********************************************************************************************************
  5. */
  6. #include "FreeRTOS.h"
  7. #include "task.h"
  8. #include "queue.h"
  9. #include "croutine.h"
  10. #include "semphr.h"
  11. #include "event_groups.h"
复制代码
程序设计:
任务栈大小分配:
    vTaskUserIF任务   :2048字节
    vTaskLED任务     :2048字节
    vTaskMsgPro任务 :2048字节
    vTaskStart任务    :2048字节
    任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的
    #defineconfigTOTAL_HEAP_SIZE        ( ( size_t )( 30 * 1024 ) )
系统栈大小分配:
19.8.jpg

FreeROTS初始化:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: 标准c程序入口。
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.      /*
  12.        在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。
  13.        这样做的好处是:
  14.        1. 防止执行的中断服务程序中有FreeRTOS的API函数。
  15.        2. 保证系统正常启动,不受别的中断影响。
  16.        3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。
  17.        在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1)
  18.        和cpsie i是等效的。
  19.      */
  20.      __set_PRIMASK(1);
  21.    
  22.      /* 硬件初始化 */
  23.      bsp_Init();
  24.    
  25.      /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项
  26.            目中不要使用,因为这个功能比较影响系统实时性。
  27.         2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
  28.      */
  29.      vSetupSysInfoTest();
  30.    
  31.      /* 创建任务 */
  32.      AppTaskCreate();
  33.    
  34.      /* 创建任务通信机制 */
  35.      AppObjCreate();
  36.     /* 启动调度,开始执行任务 */
  37.     vTaskStartScheduler();
  38.      /*
  39.        如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
  40.        heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小:
  41.        #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )
  42.      */
  43.      while(1);
  44. }
复制代码
硬件外设初始化
    硬件外设的初始化是在bsp.c文件实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.      /*
  12.          由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。
  13.          启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。
  14.          系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件
  15.      */
  16.      /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/
  17.      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  18.    
  19.      SystemCoreClockUpdate();    /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */
  20.      bsp_InitUart();    /* 初始化串口 */
  21.      bsp_InitKey();     /* 初始化按键变量 */
  22.      bsp_InitExtIO();   /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */
  23.      bsp_InitLed();     /* 初始LED指示灯端口 */
  24. }
复制代码
FreeRTOS任务创建:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: AppTaskCreate
  4. *    功能说明: 创建应用任务
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void AppTaskCreate (void)
  10. {
  11.     xTaskCreate( vTaskTaskUserIF,   /* 任务函数  */
  12.                  "vTaskUserIF",     /* 任务名    */
  13.                  512,               /* 任务栈大小,单位word,也就是4字节 */
  14.                  NULL,              /* 任务参数  */
  15.                  1,                 /* 任务优先级*/
  16.                  &xHandleTaskUserIF );  /* 任务句柄  */
  17.    
  18.    
  19.      xTaskCreate( vTaskLED,           /* 任务函数  */
  20.                  "vTaskLED",         /* 任务名    */
  21.                  512,                /* 任务栈大小,单位word,也就是4字节 */
  22.                  NULL,               /* 任务参数  */
  23.                  2,                  /* 任务优先级*/
  24.                  &xHandleTaskLED ); /* 任务句柄  */
  25.    
  26.      xTaskCreate( vTaskMsgPro,            /* 任务函数  */
  27.                  "vTaskMsgPro",           /* 任务名    */
  28.                  512,                     /* 任务栈大小,单位word,也就是4字节 */
  29.                  NULL,                    /* 任务参数  */
  30.                  3,                       /* 任务优先级*/
  31.                  &xHandleTaskMsgPro );  /* 任务句柄  */
  32.    
  33.    
  34.      xTaskCreate( vTaskStart,             /* 任务函数  */
  35.                  "vTaskStart",            /* 任务名    */
  36.                  512,                     /* 任务栈大小,单位word,也就是4字节 */
  37.                  NULL,                    /* 任务参数  */
  38.                  4,                       /* 任务优先级*/
  39.                  &xHandleTaskStart );   /* 任务句柄  */
  40. }
复制代码
FreeRTOS定时器组创建:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: AppObjCreate
  4. *    功能说明: 创建任务通信机制
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void AppObjCreate (void)
  10. {
  11.      uint8_t i;
  12.      const TickType_t  xTimerPer[2] = {100, 100};
  13.    
  14.      /*
  15.        1. 创建定时器,如果在RTOS调度开始前初始化定时器,那么系统启动后才会执行。
  16.        2. 统一初始化两个定时器,他们使用共同的回调函数,在回调函数中通过定时器ID来区分
  17.           是那个定时器的时间到。当然,使用不同的回调函数也是没问题的。
  18.      */
  19.      for(i = 0; i < 2; i++)
  20.      {
  21.          xTimers[i] = xTimerCreate("Timer",              /* 定时器名字 */
  22.                                         xTimerPer[i],    /* 定时器周期,单位时钟节拍 */
  23.                                         pdTRUE,          /* 周期性 */
  24.                                         (void *) i,      /* 定时器ID */
  25.                                         vTimerCallback); /* 定时器回调函数 */
  26.          if(xTimers[i] == NULL)
  27.          {
  28.               /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  29.          }
  30.          else
  31.          {
  32.                /* 启动定时器,系统启动后才开始工作 */
  33.                if(xTimerStart(xTimers[i], 100) != pdPASS)
  34.                {
  35.                     /* 定时器还没有进入激活状态 */
  36.                }
  37.          }
  38.      }
  39. }
复制代码
四个FreeRTOS任务的实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTaskTaskUserIF
  4. *    功能说明: 接口消息处理。
  5. *    形    参: pvParameters 是在创建该任务时传递的形参
  6. *    返 回 值: 无
  7. *   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)
  8. *********************************************************************************************************
  9. */
  10. static void vTaskTaskUserIF(void *pvParameters)
  11. {
  12.      uint8_t ucKeyCode;
  13.      uint8_t pcWriteBuffer[500];
  14.      EventBits_t uxBits;
  15.     while(1)
  16.     {
  17.          ucKeyCode = bsp_GetKey();
  18.         
  19.          if (ucKeyCode != KEY_NONE)
  20.          {
  21.               switch (ucKeyCode)
  22.               {
  23.                    /* K1键按下 打印任务执行情况 */
  24.                    case KEY_DOWN_K1:         
  25.                        printf("=================================================\\r\\n");
  26.                        printf("任务名      任务状态 优先级   剩余栈 任务序号\\r\\n");
  27.                        vTaskList((char *)&pcWriteBuffer);
  28.                        printf("%s\\r\\n", pcWriteBuffer);
  29.                   
  30.                        printf("\\r\\n任务名       运行计数         使用率\\r\\n");
  31.                        vTaskGetRunTimeStats((char *)&pcWriteBuffer);
  32.                        printf("%s\\r\\n", pcWriteBuffer);
  33.                        break;
  34.                   
  35.                    /* 其他的键值不处理 */
  36.                    default:                    
  37.                        break;
  38.               }
  39.          }
  40.         
  41.          vTaskDelay(20);
  42.      }
  43. }
  44. /*
  45. *********************************************************************************************************
  46. *    函 数 名: vTaskLED
  47. *    功能说明: LED闪烁
  48. *    形    参: pvParameters 是在创建该任务时传递的形参
  49. *    返 回 值: 无
  50. *   优 先 级: 2
  51. *********************************************************************************************************
  52. */
  53. static void vTaskLED(void *pvParameters)
  54. {
  55.      TickType_t xLastWakeTime;
  56.      const TickType_t xFrequency = 200;
  57.      /* 获取当前的系统时间 */
  58.     xLastWakeTime = xTaskGetTickCount();
  59.    
  60.     while(1)
  61.     {
  62.          bsp_LedToggle(2);
  63.         
  64.          /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
  65.         vTaskDelayUntil(&xLastWakeTime, xFrequency);
  66.     }
  67. }
  68. /*
  69. *********************************************************************************************************
  70. *    函 数 名: vTaskMsgPro
  71. *    功能说明: 消息处理,这里用作LED闪烁
  72. *    形    参: pvParameters 是在创建该任务时传递的形参
  73. *    返 回 值: 无
  74. *   优 先 级: 3
  75. *********************************************************************************************************
  76. */
  77. static void vTaskMsgPro(void *pvParameters)
  78. {
  79.      TickType_t xLastWakeTime;
  80.      const TickType_t xFrequency = 200;
  81.      /* 获取当前的系统时间 */
  82.     xLastWakeTime = xTaskGetTickCount();
  83.    
  84.     while(1)
  85.     {
  86.          bsp_LedToggle(4);
  87.         
  88.          /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
  89.         vTaskDelayUntil(&xLastWakeTime, xFrequency);
  90.     }
  91. }
  92. /*
  93. *********************************************************************************************************
  94. *    函 数 名: vTaskStart
  95. *    功能说明: 启动任务,也就是最高优先级任务,这里用作按键扫描。
  96. *    形    参: pvParameters 是在创建该任务时传递的形参
  97. *    返 回 值: 无
  98. *   优 先 级: 4
  99. *********************************************************************************************************
  100. */
  101. static void vTaskStart(void *pvParameters)
  102. {
  103.     while(1)
  104.     {
  105.          /* 按键扫描 */
  106.          bsp_KeyScan();
  107.         vTaskDelay(10);
  108.     }
  109. }
复制代码
定时器组回调函数的实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: vTimerCallback
  4. *    功能说明: 定时器回调函数
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void vTimerCallback(xTimerHandle pxTimer)
  10. {
  11.      uint32_t ulTimerID;
  12.      configASSERT(pxTimer);
  13.      /* 获取那个定时器时间到 */
  14.      ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
  15.    
  16.      /* 处理定时器0任务 */
  17.      if(ulTimerID == 0)
  18.      {
  19.          bsp_LedToggle(1);
  20.      }
  21.    
  22.      /* 处理定时器1任务 */
  23.      if(ulTimerID == 1)
  24.      {
  25.          bsp_LedToggle(2);
  26.      }
  27. }
复制代码
努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

740

主题

1326

回帖

3546

积分

管理员

春暖花开

Rank: 9Rank: 9Rank: 9

积分
3546
QQ
 楼主| 发表于 2016-8-30 14:35:32 | 显示全部楼层
19.6 总结

    本章节主要为大家讲解了软件定时器,对于初学者来说,比较容易掌握。
努力打造安富莱高质量微信公众号:点击扫描图片关注
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-30 20:38 , Processed in 0.328576 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表