赵工的个人空间


专业技术部分转网页计算转业余爱好部分


 单片机与嵌入式

首页 > 专业技术 > 单片机与嵌入式 > FreeRTOS系统介绍
FreeRTOS系统介绍
  1. FreeRTOS体系结构
  2. 移植到MCU的方法
  3. 操作示例

FreeRTOS是一个轻量级的完全免费的实时操作系统内核,源码公开、可裁减,包括了任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可移植到各种单片机上运行,可基本满足较小系统的需要。应用程序开发者可以直接从FreeRTOS官方网站http://www. freertos.org下载源码并参考其中的Demo进行开发。

1. FreeRTOS体系结构:

FreeRTOS体系结构包括任务调度机制、系统时间管理机制、内存分配机制、任务通信与同步机制等。FreeRTOS还提供IO库、系统跟踪Trace、TCP/IP协议栈等相关组件。

1) 任务调度机制:

FreeRTOS可根据用户需要,设置为可剥夺型内核或不可剥夺型内核。设置为可剥夺型内核时,处于就绪态的高优先级任务能剥夺低优先级任务的CPU使用权,以保证系统满足实时性的要求;设置为不可剥夺型内核时,处于就绪态的高优先级任务只有等当前运行任务主动释放CPU的使用权后才能获得运行,这样可以提高CPU的运行效率。
FreeRTOS系统没有对优先级设置数量上的限制,开发者可以通过修改宏参数configMAX _PRIORITIES来指定系统的优先级数量。开发者可以根据系统需要对不同的任务指定不同的优先级,优先级为0的任务优先级最低;也可以对不同的任务设置相同的优先级,同一优先级的任务,轮流在系统的每一个时间片内执行。若此优先级下只有一个就绪任务,则此就绪任务进入运行态;若此优先级下有多个就绪任务,则采用轮换调度算法实现多任务轮流执行。
假设系统的最大任务优先级为portMAX_PRIORITIES,在某一时刻进行任务调度时,得到链表数组pxReadyTasksLists[i],usNumberOfItems=i为优先级标号以及同时得到优先级为1的任务链表pxReadyTasksLists[1]且此时链表中任务个数usNumberOfItems=3。由此内核可知当前最高就绪优先级为1,且此优先级下已有3个任务已进入就绪态。此时最高就绪优先级下有多个就绪任务,系统需执行轮换调度算法实现任务切换。通过指针pxIndex可知任务1为当前任务,而任务1的pxNext结点指向任务2,因此系统把pxIndex指向任务2并执行2来实现任务调度。当下一个时钟节拍到来时,若最高就绪优先级仍为1,系统会把pxIndex指向任务3并执行任务3。
为了加快任务调度的速度,FreeRTOS通过变量ucTopReadyPriotity跟踪当前就绪的最高优先级。当把一个任务加入就绪链表时,如果此任务的优先级高于ucTopReadyPriority,则把这个任务的优先级赋予ucTopReadyPriority。这样,当进行优先级调度时,调度算法不是从portMAX_PRIORITIES而是从ucTopReadyPriotity开始搜索,这就加快了搜索速度,同时缩短了内核关断时间。
FreeRTOS的任务调度算法可由用户自己制定,用户可修改FreeRTOS_V8.x.x的源码中的FreeRTOSConfig.h中文件的参数configUSE_PREEMPTION和configUSE_TIME_SLICING来指定所使用的调度算法,不过大多都使用优先级抢占式调度。
任务调度基于如下假设:
  ·  每个任务都赋予一个优先级
  ·  每个任务都可以存在于一个或多个状态
  ·  任何时候都只有一个任务可以处于运行状态
  ·  调度器总是在所有处于就绪态的任务中选择具有最高优先级的任务来执行
这种类型的调度方案称为固定优先级抢占式调度。所谓固定优先级是指每个任务都被赋予了一个优先级,这个优先级不能被内核本身改变(只能被任务修改)。抢占式是指任务进入就绪态或是优先级被改变时,如果处于运行态的任务优先级更低,则该任务总是抢占当前运行的任务。而在阻塞态的任务可以等待一个事件,当事件发生时将任务自动回到就绪态,发生在某个特定的时刻的事件,比如阻塞超时,通常称为时间事件,用于周期性或超时行为。任务或中断服务例程往队列发送消息或发送任务一种信号量的事件,都将触发同步事件。同步事件通常用于触发同步行为,比如某个外围设备的数据到达。
空闲任务具有最低优先级,所以每当有更高优先级任务处于就绪态,空闲任务就会被抢占。如果一个事件驱动任务工作在相对较低优先级,但优先级高于空闲任务,其大部分时间都在阻塞态等待其关心的事件;每当其关心的事件发生时,就从阻塞态转移到就绪态。FreeRTOS中所有的任务间通信机制(队列、信号量等)都可以通过这种方式发送事件以及让任务解除阻塞。
周期性任务优先级高于任务3但低于任务1,根据周期间隔,周期任务可在任务3运行时抢占并立刻得到执行,处理完成后返回阻塞态;这时被阻塞的任务3得以重新进入运行态,继续完成处理。具有最高优先级的任务1可以抢占系统中的任何其他任务,被抢占的任务进入阻塞态,只有当任务1再次进入阻塞态后才得以有机会继续完成处理。

2) 任务管理:

FreeRTOS中提供多种管理任务的函数供开发者使用,可以对任务进行创建、删除。FreeRTOS还将任务分为几种状态。
⑴任务创建:
通过调用xTaskCreate()函数创建一个新的任务,FreeRTOS首先为新任务分配所需的内存。若内存分配成功,则初始化任务控制块TCB的任务名称、堆栈深度和任务优先级,然后根据堆栈的增长方向初始化任务控制块的堆栈。然后把当前创建的任务加入到就绪任务队列链表中,若当前此任务的优先级最高,则把此优先级赋值给变量usTopReadyPriority,若任务调度程序已经运行且当前创建的任务优先级最高,则进行任务切换。
⑵任务删除:
当用户使用vTaskDelete()函数后,分两步进行任务删除。首先系统把删除的任务从就绪任务链表和事件等待链表中删除,然后把此任务加入到任务删除链表;然后释放该任务占用的内存空间,并把该任务从任务删除链表中删除,这样才彻底删除了这个任务。
从V9.0开始,如果一个任务删除另外一个任务,被删除的任务的堆栈和TCB立即释放;如果一个任务删除自己,则任务的堆栈和TCB将按上述的分两步进行,通过空闲任务删除。所以,空闲任务开始就会检查是否有任务删除了自己,如果有就负责删除这个任务的TCB和堆栈空间。
⑶系统中的任务状态:
应用程序可以包含多个任务,每个任务的状态可分为运行态和非运行态。在某一时刻,总是只有一个任务处于运行状态,当某个任务处于运行态时,处理器就执行它的代码;当一个任务处于非运行态时,该任务休眠,它的所有状态都被妥善保存,以便在下一次调度器决定让它进入运行态时可以恢复执行。
FreeRTOS系统中,任务包含运行态、就绪态、阻塞态和挂起态。当前被执行的任务处于运行态,此任务占用处理器资源;就绪的任务是指不处在挂起或阻塞状态,已经可以运行,但是因为其他优先级更高或相等的任务正在运行,而没有运行的任务;阻塞态又称为等待状态,若任务正在等待某些事件,如定时事件、同步事件或资源,则此任务处于阻塞态;挂起态又称睡眠状态,处于挂起态的任务对于调度器来说是不可见的,任务只是以代码的形式驻留在程序空间,但没有参与调度,任务挂起的唯一办法就是调用vTaskSuspend()函数,任务唤醒的唯一途径就是调用vTaskResume()或vTaskResumeFromSR()函数。

3) 任务通信机制:

在FreeRTOS系统中,任务间的通信是基于队列实现的。通过系统提供的服务,任务或中断服务子程序可以将一则消息放入队列中,实现任务间以及任务与中断之间的消息传递。
队列可以保存有限个具有确定长度的数据单元,通常情况下队列作为先入先出FIFO使用,即数据由队列尾写入,从队列首读出。队列是具有独立权限的内核对象,并不属于任何任务。所有任务的可以向同一个队列发送消息或读取消息,当队列在使用时,可通过消息链表查询当前队列是否为空或满。

4) 任务同步机制:

FreeRTOS中可采用二值信号量和计数信号量进行任务之间的同步。
二值信号量常常在某个特殊的中断发生时让某项任务解除阻塞,相当于让任务与中断同步,这使得大部分的中断事件可以放入其同步事件,而在中断处理程序中只是快速处理少部分工作。如果某个中断处理请求特别紧急,可以将其处理的任务的优先级设为最高,以保证处理任务随时都能抢占系统中其他的任务,使其成为对应的ISR退出后第一个执行的任务,相当于所有的处理都在ISR中完成一样。
在中断发生的频率相对较慢的情况下,处理任务完成之前如果还有中断事件发生,后续的中断事件将会丢失,此时如果使用计数信号量替代二值信号量,这种丢中断情况就可以避免。计数信号量可以看作是多个数据单元的队列,当中断发生后,队列中的一个空间将被使用,其有效数据单元个数就是信号量的计数值。

2. 移植到MCU的方法:

嵌入式操作系统的编写者无法一次性完成整个操作系统的所有代码,而必须把一部分硬件平台相关的代码作为接口保留出来,让用户自行修改并将其移植到目标平台上。FreeRTOS的绝大多数代码使用C语言编写,只有一小部分与具体编译器和CPU相关的代码需要开发人员用汇编语言完成,因此移植比较方便。FreeRTOS的移植主要集中在portmacro.h和port.c两个文件中。
portmacro.h主要包含编译器相关的数据类型的定义、堆栈类型的定义以及几个宏定义和函数说明。port.c中包含与移植有关的C函数,包括堆栈的初始化函数、任务调度器启动函数、临界区的进入与退出、时钟中断服务程序等。
此外,FreeRTOS只是一个操作系统内核,需外扩第三方的GUI、TCP/IP协议栈、文件系统等才能实现一个较复杂的系统。
目前,FreeRTOS内核已经是亚马逊计算服务平台AWS(Amazon Web Service)的开源项目,并更名为Amazon FreeRTOS,通过软件库扩展使得小型低功耗设备安全连接到平台,主要解决MCU连接到云服务器的功能,目前已有Microchip、NXP、ST、TI等芯片厂商提供的MCU提供支持。

3. 操作示例:

1) 任务创建及删除:

⑴函数xTaskCreate():
  BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, const char* const pcName, const uint16_t usStackDepth, void* const pvParameters, UBaseType_t uxPriority, TaskHandle_t* const pxCreatedTask)
创建一个任务的实例,每个任务需要RAM资源来存储任务状态(任务控制块),内存区域将从FreeRTOS的堆中自动分配,刚创建的任务被初始化为就绪状态,并在没有更高优先级任务时转化为运行状态,任务可创建在调度器开始之前或之后。参数说明:
  ·  pxTaskCode:任务是永不退出的C函数,通常是一个死循环,此参数为指向任务的实现函数的指针
  ·  pcName:具有描述性的任务名,此参数不会被系统使用,单纯用于辅助调试,易于识别
  ·  usStackDepth:创建任务时系统为其分配空间的容量,单位是字(4字节)。注意栈深度乘以栈宽度不能超过一个size_t所能表达的最大值,应根据实际需求预测并结合测试来设置
  ·  pvParameters:任务函数接收一个指向void的指针,其值就是将要传递到任务中的值
  ·  uxPriority:任务执行的优先级,取值范围可从最低优先级0到最高优先级configMAX_ PRIORITIES-1
  ·  pxCreatedTask:用于传出任务的句柄,将在API调用中对该创建出来的任务进行引用,如果应用程序不会用到这个句柄,可以设为NULL
返回值:
  ·  pdTRUE:表明任务创建成功
  ·  ErrCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:由于内存空间不足,系统无法分配足够的空间来保存任务结构数据和任务栈,因此无法创建任务
⑵xTaskDelete():
  void xTaskDelete(TaskHandle_t* xTaskToDelete)
删除一个任务的实例,该实例由xTaskCreate()或xTaskCreateStatic()创建。参数xTaskToDelete为指向要被删除任务的句柄,如果该参数为NULL,该函数调用将会删除自身。
⑶xTASKStartScheduler():
  void xTASKStartScheduler(void)
启动任务调度器,开始执行任务。当调度器开始时,空闲任务才会被自动创建,只有当没有足够的堆内存空间可供空闲任务使用时才会有返回值。
⑷xTaskDelay():
  void xTaskDelay(const TickType_t xTicksToDelay)
用于将任务进入阻塞态,并保持固定数量的节拍中断。调用的任务需要保持节拍中断的数量,之后才会返回就绪状态。
⑸示例代码:
  #include "FreeRTOS.h"
  #include "task.h"
  void vTask1(void *pvParam)
  void vTask2(void *pvParam)
  TaskHandle_t xTask2Handle;
  int main(void){
     xTaskCreate(xTask1,"Task1",1000,NULL,1,NULL);
     vTaskStartScheduler();
     //正常情况下程序不会运行到这里,除非内存堆空间不足
     for(;;);
     return 0;
  }
  void vTask1(void *pvParam){
     const TickType_t xDelay100ms=pdMS_TO_TICKS(100UL);
     for(;;){
       vPrintString("Task1 is running\r\n");
       xTaskCreate(vTask2,"Task2",1000,NULL,2,&xTask2Handle);
       vTaskDelay(xDelay100ms);
     }
  }
  void vTask2(void *pvParam){
     vPrintString("Task2 is running\r\n");
     xTaskDelete(xTask2Handle);
  }

代码中先创建任务1,优先级1,并启动任务调度器;任务1则创建任务2,优先级2,因此会立即执行;任务2先执行打印,然后提供传递NULL值以删除自己;任务2删除后,任务1为最高优先级,所以继续执行,调用延迟阻塞一段时间。在任务1进入阻塞态后,空闲任务执行,会释放任务2占用的内存。任务1离开阻塞态后,优先级高于空闲任务,再次创建任务2,如此往复。

2) 任务通信:

⑴xQueueCreate():
  QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize)
创建一个新的队列并且返回QueueHandle句柄以便于对其创建的队列进行引用。创建队列时,系统从堆空间中分配内存空间,用于存储队列数据结构及包含的数据单元。如果没有足够的空间创建队列将返回NULL。其中参数:
  ·  uxQueueLength:队列能够存储的最大单元数目,即队列深度
  ·  uxItemSize:队列中存储的数据单元长度,以字节为单位
⑵xQueueSendToBack():
  BaseType_t xQueueSendToBack(QueueHandle_t xQueue, const void* pvItemToQueue, TickType_t xTicksToWait)
往一个队列的后部发送一个数据。参数说明:
  ·  xQueue:需要发送数据的队列句柄,一般使用xQueueCreate()创建
  ·  pvItemToQueue:指向要把数据复制到队列的指针
  ·  xTicksToWait:阻塞超时时间。如果这个队列已经满了,这个时间就是任务处于阻塞态等待队列空间有效的最长等待时间,指节拍周期。可以使用宏pdMS_TO_TICKS()将一个ms为单位的时间转化为节拍周期
返回值:
  ·  pdPASS:说明数据已经成功发送到队列中。如果设定了超时时间,在函数返回之前任务将被转移到阻塞态以等待队列空间有效。在超时时间到来前能够将数据成功写入队列则会返回此值
  ·  errQUEUE_FULL:如果队列已满无法写入数据,则函数返回此值。如果设定了超时时间,在函数返回之前,任务将被转移到阻塞态以等待队列空间有效。但直到超时也没有其他任务或是中断服务例程读取队列而腾出空间,函数则会返回此值
⑶xQueueReceive():
  BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
从队列中接收一个数据单元。参数说明:
  ·  xQueue:被读队列的句柄,即调用xQueueCreate()创建队列时的返回值
  ·  pvBuffer:接收缓存指针,指向一段内存区域,用于接收从队列中复制来的数据。数据单元长度在创建队列时已经设定,因此该指针指向的内存区域大小应足够保存一个数据单元
  ·  xTicksToWait:阻塞超时时间。如果接收队列为空,这个时间是任务处于阻塞态以等待队列数据有效的最长等待时间。如果此值设为0,并且队列为空,此函数将与xQueuePeek()都会立刻返回,时间也以节拍为单位,可以使用pdMS_TO_TICKS()宏函数来转换
返回值:
  ·  pdPASS:说明数据单元成功从队列中读取到。如果设定了超时时间,在函数返回之前任务将被转移到阻塞态以等待队列数据有效。在超时时间到来前能够从队列中成功读取数据则会返回此值
  ·  errQUEUE_EMPTY:如果因为队列已空,无法从队列读出数据,则函数返回此值。如果设定了超时时间,任务将被转移到阻塞态以等待队列有数据。但直到超时也没有其他任务或是中断服务例程发送数据到队列,函数则会返回此值
⑷uxQueueMessageWaiting():
  UBaseType_t uxQueueMessageWaiting(const QueueHandle_t xQueue)
查询队列里已有的数据单元数量。参数xQueue为被查队列的句柄,即调用xQueueCreate()创建队列时的返回值。返回值为数据单元的数量,返回0表示队列为空。
⑸示例代码:
  static void vSenderTask(void *pvParam){
     int32_t lValueToSend;
     BaseType_t xStatus;
     lValueToSend=(int32_t)pvParam;
     for(;;){
       xStatus=xQueueSendToBack(xQueue,&lValueToSend,0);
       if(xStatus!=pdPASS){
         vPrintString("Could not send to the queue.\r\n");
       }
     }
  }
  static void vReceiverTask(void *pvParam){
     int32_t lReceivedValue;
     BaseType_t xStatus;
     const TickType_t xTicksToWait=pdMS_TO_TICKS(100UL);
     for(;;){
       if(xQueueMessagesWaiting(xQueue)!=0){
         vPrintString("Queue should have been empty!\r\n");
       }
       xStatus=xQueueReceive(xQueue,&lReceivedValue,xTicksToWait);
       if(xStatus==pdPASS){
         vPrintString("Received=: ",lReceivedValue);
       }else{
         vPrintString("Could not receive from the queue.\r\n");
       }
     }
  }
  #include "FreeRTOS.h""
  #include "task.h"
  #include "queue.h"
  static void vSenderTask(void *pvParam)
  static void vReceiverTask(void *pvParam)
  QueueHandle_t xQueue;
  int main(void){
     xQueue=xQueueCreate(5,sizeof(int32_t));
     if(xQueue!=NULL){
       xTaskCreate(vSenderTask,"Sender1",1000,(void *)100,1,NULL);
       xTaskCreate(vSenderTask,"Sender2",1000,(void *)200,1,NULL);
       xTaskCreate(vReceiverTask,"Receiver",1000,NULL,2,NULL);
       xTaskStartScheduler();
     }else{
       vPrintString("failuer to create queue.\r\n");
     }
     for(;;);
     return 0;
  }

代码中创建一个队列,由两个任务往队列写数据,一个任务从队列中读出数据。往队列写数据的任务的优先级低于读队列任务的优先级,意味着队列中永远不会保持超过一个的数据单元,因为一旦数据被写入队列,读队列任务立即解除阻塞,抢占写队列任务,并从队列中接收数据,队列再次变为空队列。

3) 任务同步:

⑴xSemaphoreCreateBinary():
  SemaphoreHandle_t xSemaphoreCreateBinary(void)
创建一个二进制信号量,并返回一个指向这个信号量的指针。每个信号量需要小部分内存,用来存储信号量状态。
如果系统没有足够的堆空间去创建信号量的数据结构将返回NULL,而返回非NULL表示信号量创建成功,返回值应保存下来,以作为操作此信号量的句柄。
⑵xSemaphoreTake():
  BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait)
获得一个之前已由xSemaphoreCreateBinary()创建的二进制信号量。参数说明:
  ·  xSemaphore:获取得到的信号量,在使用前必须创建
  ·  xTicksToWait:阻塞超时时间,任务进入阻塞态以等待信号量有效的最长时间,为节拍周期。如果值为0,信号量无效会立刻返回。可以使用pdMS_TO_TICKS()宏函数来把ms转换为节拍周期
返回值:
  ·  pdPASS:调用函数成功获得二进制信号量
  ·  pdFAIL:调用无法获取信号量。如果设定了阻塞超时时间,在函数返回之前将被转移到阻塞态以等待信号量有效。但直到超时,信号量也没有变为有效将返回此值
注意,此函数不能在中断服务例程中调用,必须在正在执行的任务中调用,也不能用于获取互斥信号量Recursive Semaphore。
⑶xSemaphoreGiveFromISR():
  BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken)
给出信号量,该函数可以使用于中断处理程序中。参数说明:
  ·  xSemaphore:给出的信号量,引用之前必须创建
  ·  pxHigherPriorityTaskWoken:对某个信号量来说,可能有不止一个任务处于阻塞态在等待其有效。调用此函数会让信号量变有效,所以会让其中一个等待任务解除阻塞态。如果调用此函数使得一个任务解除阻塞,并且这个任务的优先级高于当前被中断的任务,那么此函数会在内部将此参数设为pdTRUE,并在中断退出前进行一次上下文切换,这样才能保证中断直接返回到就绪态任务中优先级最高的任务中。
返回值:
  ·  pdPASS:函数调用成功
  ·  pdFAIL:如果信号量已经有效,无法给出,则返回此值
⑷示例代码:
  static void vPeriodicTask(void *pvParam){
     const TickType_t xDelay500ms=pdMS_TO_TICKS(500UL);
     for(;;){
       vTaskDelay(xDelay500ms);
       vPrintString("Periodic task - Abort to generate an interrupt.\r\n");
       vPortGenerateSimulatedInterrupt(mainINTERRUPT_NUMBER);
       vPrintString("Periodic task - Interrupt generated.\r\n\r\n\r\n");
     }
  }
  static void vHandlerTask(void *pvParam){
     for(;;){
       xSemaphoreTake(xBinarySemaphore,portMAX_DELAY);
       vPrintString("Handler task - Processing event.\r\n");
     }
  }
  static uint32_t ulExampleInterruptHandler(void){
     BaseType_t xHigherPriorityTaskWoken;
     xHigherPriorityTaskWoken=pdFALSE;
     xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
     portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  }
  #include "FreeRTOS.h""
  #include "task.h"
  #include "semphr.h"
  #define mainINTERRUPT_NUMBER 3
  static void vHandlerTask(void *pvParam);
  static void vPeriodicTask(void *pvParam);
  static uint32_t ulExampleInterruptHandler(void):
  SemaphoreHandle_t xBinarySemaphore;
  int main(void){
     xBinarySemaphore=xBinarySemaphoreCreateBinary();
     if(xBinarySemaphore!=NULL){
       xTaskCreate(vHandlerTask,"Handler",1000,NULL,3,NULL);
       xTaskCreate(vPeriodicTask,"Periodic",1000,NULL,1,NULL);
       vPortSetInteruptHandler(mainINTERRUPT_NUMBER,ulExampleInterruptHandler);
       xTaskStartScheduler();
     }
     //正常情况下不会运行到这里
     for(;;);
     return 0;
  }

代码中一个周期任务用于每隔500ms产生一个软件中断,延迟处理任务通过使用二进制信号量与软件中断同步,也在每次循环中打印一个信息。而在中断服务例程中给出一个信号量,以让延迟处理任务解除阻塞。

4) 闪灯:

⑴xTimerCreate():
  TimerHandle_t xTimerCreate(const char * const pcTimerName, const TickType_t xTimerPeriod, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction)
创建一个软件定时器实例并返回定时器的句柄。参数说明:
  ·  pcTimerName:定时器名,有助于debug时使用
  ·  xTimerPeriod:以系统节拍为单位指明定时器所需定时的时间
  ·  uxAutoReload:若为pdTRUE,指明定时器耗尽后将会自动重新计时;若为pdFALSE,定时器只运行一次
  ·  pvTimerID:定时器标记,可以使用系统函数vTimerSetTimerID()和pvTimerGetTimerID()设置和获取此参数
  ·  pxCallbackFunction:回调函数句柄,即定时器定时耗尽后将自动调用回调函数
返回值:
  ·  NULL或0:定时器创建失败
  ·  非NULL值:定时器创建成功
⑵xTimerStart():
  BaseType_t xTimerStart(TimerHandle_t xTimer, TickType_t xBlockTimer)
开启一个软件定时器。参数说明:
  ·  xTimer:需要开始或重新开始的定时器句柄
  ·  xBlockTimer:需要将定时器阻塞的系统节拍周期
返回值:
  ·  pdPASS:定时器开启成功
  ·  pdFAIL:定时器开启失败
⑶示例代码:
  #include
  #include "FreeRTOS.h""
  #include "task.h"
  #include "timer.h"
  static void prvSetupHardware(void);
  static void LED1(void *pvParam);
  static void LED2(void *pvParam);
  static void prvConfigureClocks(void);
  void CALLBACK_LED(TimerHandle_t xTimer){  //回调函数
     uint32_t ulTimerID;
     configASSERT(xTimer);
     ulTimerID=(uint32_t)pvTimerGetTimerID(xTimer);  //获取ID以判断哪个定时器进入回调函数
     switch(ulTimerID){
       case 0:  //LED1闪灯
         GPIO_toggleOutputOnPin(GPIO_PORT_P1,GPIO_PIN0);
         for(int i=0;i<10000;i++);  //足够长的空循环以显式LED亮灭
         GPIO_toggleOutputOnPin(GPIO_PORT_P1,GPIO_PIN0);
       break;
       case 1:  //LED2闪灯
         GPIO_toggleOutputOnPin(GPIO_PORT_P2,GPIO_PIN2);
         for(int i=0;i<10000;i++);  //足够长的空循环以显式LED亮灭
         GPIO_toggleOutputOnPin(GPIO_PORT_P2,GPIO_PIN2);
       break;
     }
  }
  int main(void){
     prvSetupHardware();
     prvConfigureClocks();
     TimerHandle_t LED1_Timer;
     LED1_Timer=xTimerCreate("Timer1",(pdMS_TO_TICKS(1000UL)),,pdTRUE,(void *)0,CALLBACK_LED);  //1s
     TimerHandle_t LED2_Timer;
     LED2_Timer=xTimerCreate("Timer2",(pdMS_TO_TICKS(2000UL)),,pdTRUE,(void *)0,CALLBACK_LED);  //2s
     if(LED2_Timer==NULL||LED1_Timer==NULL)
       //err
     }else{
       if((xTimerStart(LED1_Timer,0)!=pdPASS)||(xTimerStart(LED2_Timer,0)!=pdPASS)){
         //err
       }
     }
     vTaskStartScheduler();
     return 0;
  }
  static void prvSetupHardware(void){
     extern void FPU_enableModule(void);
     MAP_WDT_A_holdTimer();  //停止看门狗定时器
     FPU_enableModule();
     MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN2|GPIO_PIN3,
           GPIO_PRIMARY_MODULE_FUNCTION);
     MAP_GPIO_setOutputLowOnPin(GPIO_PORT_P1,GPIO_PIN0);
     MAP_GPIO_setAsOutputPin(GPIO_PORT_P1,GPIO_PIN0);
     MAP_GPIO_setOutputLowOnPin(GPIO_PORT_P2,GPIO_PIN2);
     MAP_GPIO_setAsOutputPin(GPIO_PORT_P2,GPIO_PIN2);
  }
  void vApplicationMallocFailedHook(void){
     for(;;);
  }
  void vApplicationIdleHook(void){
  }
  void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName){
     (void)pcTaskName;
     (void)pxTask;
     taskDISABLE_INTERRUPTS();
     for(;;);
  }
  void *malloc(size_t xSize){
     Interrupt_disableMaster();
     for(;;);
  }
  static void prvConfigureClocks(void){
     FlashCtl_setWaitState(Flash_BANK0,2);
     FlashCtl_setWaitState(Flash_BANK1,2);
     CS_setDCOCenterFrequency(CS_DCO_FREQUENCY_3);
     CS_initClockSignal(CS_HSMCLK,CS_DCOCLK_SELECT,CS_CLOCK_DIVIDER_1);
     CS_initClockSignal(CS_SMCLK,CS_DCOCLK_SELECT,CS_CLOCK_DIVIDER_1);
     CS_initClockSignal(CS_MCLK,CS_DCOCLK_SELECT,CS_CLOCK_DIVIDER_1);
     CS_initClockSignal(CS_ACLK,CS_REFOCLK_SELECT,CS_CLOCK_DIVIDER_1);
     PCM_setCoreVoltageLevel(CM_VCORE0);
  }

使用FreeRTOS的软件定时器实现闪灯功能。

Copyright@dwenzhao.cn All Rights Reserved   备案号:粤ICP备15026949号
联系邮箱:dwenzhao@163.com  QQ:1608288659