怎么制作一个网站,网站开发环境选择,怎么做多语言网站,做移动端活动页面参考网站目录
1. 空闲任务详解
1.1 空闲任务简介
1.2 空闲任务的创建
1.3 空闲任务函数
2. 空闲任务钩子函数详解
2.1 钩子函数
2.2 空闲任务钩子函数
3. 空闲任务钩子函数实验
3.1 main.c 空闲任务是 FreeRTOS 必不可少的一个任务#xff0c;其他 RTOS 类系统也有空闲任务其他 RTOS 类系统也有空闲任务比如 UCOS。顾名思义空闲任务就是处理器空闲的时候去运行的一个任务当系统中没有其他就绪任务的时候空闲任务就会开始运行空闲任务最重要的作用就是让处理器在无事可做的时候找点事做防止处理器无聊因此空闲任务的优先级必然是最低的。当然是实际中必然不会如此浪费宝贵的处理器资源FreeRTOS 空闲任务中也会执行一些其他的处理。
1. 空闲任务详解
1.1 空闲任务简介 当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务这样就可以确保至少有一个任务可以运行。但是这个空闲任务使用最低优先级如果应用中有其他高优先级任务处于就绪态的话这个空闲任务就不会跟高优先级的任务抢占 CPU 资源。空闲任务还有另外一个重要的职责如果某个任务要调用函数 vTaskDelete() 删除自身那么这个任务的任务控制块 TCB 和任务堆栈等这些由 FreeRTOS 系统自动分配的内存需要在空闲任务中释放掉如果删除的是别的任务那么相应的内存就会被直接释放掉不需要在空闲任务中释放。因此一定要给空闲任务执行的机会除此以外空闲任务就没有什么特别重要的功能了所以可以根据实际情况减少空闲任务使用 CPU 的时间比如当 CPU 运行空闲任务的时候使处理器进入低功耗模式。 用户可以创建与空闲任务优先级相同的应用任务当宏 configIDLE_SHOULD_YIELD 为 1 的话应用任务就可以使用空闲任务的时间片也就是说空闲任务会让出时间片给同优先级的应用程序。
1.2 空闲任务的创建 当调用函数 vTaskStartScheduler() 启动任务调度器的时候此函数就会自动创建空闲任务代码如下
void vTaskStartScheduler( void )
{ BaseType_t xReturn; //创建空闲任务使用最低优先级 #if( configSUPPORT_STATIC_ALLOCATION 1 ) (1) 使用静态方法创建空闲任务{ StaticTask_t *pxIdleTaskTCBBuffer NULL; StackType_t *pxIdleTaskStackBuffer NULL; uint32_t ulIdleTaskStackSize; vApplicationGetIdleTaskMemory( pxIdleTaskTCBBuffer, pxIdleTaskStackBuffer,ulIdleTaskStackSize ); xIdleTaskHandle xTaskCreateStatic( prvIdleTask, IDLE, ulIdleTaskStackSize, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), pxIdleTaskStackBuffer, pxIdleTaskTCBBuffer ); if( xIdleTaskHandle ! NULL ) { xReturn pdPASS; } else { xReturn pdFAIL; } } #else (2) 使用动态方法创建空闲任务空闲任务的任务函数为 prvIdleTask()任务堆栈大小为 configMINIMAL_STACK_SIZE任务堆栈大小可以在 FreeRTOSConfig.h 中修改。任务优先级为 tskIDLE_PRIORITY宏 tskIDLE_PRIORITY 为 0说明空闲任务优先级最低用户不能随意修改空闲任务的优先级{ xReturn xTaskCreate( prvIdleTask, IDLE, configMINIMAL_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), xIdleTaskHandle ); } #endif /* configSUPPORT_STATIC_ALLOCATION */ /*********************************************************************/
/**************************省略其他代码*******************************/
/*********************************************************************/
}
1.3 空闲任务函数 空闲任务的任务函数为 prvIdleTask()但是实际上是找不到这个函数的因为它是通过宏定义来实现的在文件 portmacro.h 中有如下宏定义
#define portTASK_FUNCTION(vFunctionpvParameters) void vFunction(void *pvParameters) 其中portTASK_FUNCTION() 在文件 tasks.c 中有定义它就是空闲任务的任务函数源码如下
static portTASK_FUNCTION( prvIdleTask, pvParameters ) (1) 将此行展开就是 static void prvIdleTask(void *pvParameters)创建空闲任务的时候任务函数名就是 prvIdleTask()
{ ( void ) pvParameters; //防止报错 //本函数为 FreeRTOS 的空闲任务任务函数当任务调度器启动以后空闲任务会自动创建 for( ;; ) { //检查是否有任务要删除自己如果有的话就释放这些任务的任务控制块 TCB 和 //任务堆栈的内存 prvCheckTasksWaitingTermination(); (2) 调用函数 prvCheckTasksWaitingTermination 检查是否有需要释放内存的被删除任务当有任务调用函数 vTaskDelete() 删除自身的话此任务就会添加到列表 xTasksWaitingTermination 是否为空如果不为空的话就依次将列表中所有任务对应的内存释放掉任务控制块 TCB 和任务堆栈的内存#if ( configUSE_PREEMPTION 0 ) { //如果没有使用抢占式内核的话就强制进行一次任务切换查看是否有其他 //任务有效如果有使用抢占式内核的话就不需要这一步因为只要有任 //何任务有效(就绪)之后都会自动的抢夺 CPU 使用权 taskYIELD(); } #endif /* configUSE_PREEMPTION */ #if ( ( configUSE_PREEMPTION 1 ) ( configIDLE_SHOULD_YIELD 1 ) ) (3) 使用抢占式内核并且 configIDLE_SHOULD_YIELD 为 1说明空闲任务需要让出时间片给同优先级的其他就绪任务{ //如果使用抢占式内核并且使能时间片调度的话当有任务和空闲任务共享 //一个优先级的时候并且此任务处于就绪态的话空闲任务就应该放弃本时 //间片将本时间片剩余的时间让给这个就绪任务。如果在空闲任务优先级 //下的就绪列表中有多个用户任务的话就执行这些任务。 if( listCURRENT_LIST_LENGTH( (4) 检查优先级为 tskIDLE_PRIORITY(空闲任务优先级)的就绪任务列表是否为空如果不为空的话就调用函数 taskYIELD() 进行一次任务切换。( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) ( UBaseType_t ) 1 ) { taskYIELD(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif #if ( configUSE_IDLE_HOOK 1) { extern void vApplicationIdleHook( void ); //执行用户定义的空闲任务钩子函数注意钩子函数里面不能使用任何 //可以引起阻塞空闲任务的 API 函数。vApplicationIdleHook(); (5) 如果使能了空闲任务钩子函数的话就执行这个钩子函数空闲任务钩子函数的函数名为 vApplicationIdleHook()这个函数需要用户自行编写在编写这个钩子函数的时候一定不能调用任何可以阻塞空闲任务的 API 函数。} #endif /* configUSE_IDLE_HOOK */ //如果使能了 Tickless 模式的话就执行相关的处理代码 #if ( configUSE_TICKLESS_IDLE ! 0 ) (6) configUSE_TICKLESS_IDLE 不为 0说明使能了 FreeRTOS 的低功耗 Tickless 模式。{ TickType_t xExpectedIdleTime; xExpectedIdleTime prvGetExpectedIdleTime(); (7) 调用函数 prvGetExpectedIdleTime() 获取处理器进入低功耗模式的时长此值保存在变量 xExpectedIdleTime 中单位为时钟节拍数if( xExpectedIdleTime configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) (8) xExpectedIdleTime 的值要大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才有效{ vTaskSuspendAll(); (9) 处理 Tickless 模式挂起任务调度器其实就是起到临界段代码保护功能{ //调度器已经被挂起重新采集一次时间值这次的时间值可以 //使用 configASSERT( xNextTaskUnblockTime xTickCount ); xExpectedIdleTime prvGetExpectedIdleTime(); (10) 重新获取一次时间值这次的时间值是直接用于 portSUPPRESS_TICKS_AND_SLEEP() 的if( xExpectedIdleTime configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) { traceLOW_POWER_IDLE_BEGIN(); portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); (11) 调用 portSUPPRESS_TICKS_AND_SLEEP() 进入低功耗 Tickless 模式traceLOW_POWER_IDLE_END(); } else { mtCOVERAGE_TEST_MARKER(); } } ( void ) xTaskResumeAll(); (12) 恢复任务调度器} else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TICKLESS_IDLE */ }
}
2. 空闲任务钩子函数详解
2.1 钩子函数 FreeRTOS 中有多个钩子函数钩子函数类似于回调函数当某个功能函数执行的时候就会调用钩子函数至于钩子函数的具体内容就由用户来编写。如果不需要使用钩子函数的话就什么也不用管钩子函数是一个可选功能可以通过宏定义来选择使用哪个钩子函数。
宏定义 描述
configUSE_IDLE_HOOK 空闲任务钩子函数空闲任务会调用此钩子函数
configUSE_TICK_HOOK 时间片钩子函数xTaskIncrementTick()会调用此钩子函数。此钩子函数最终会被节拍中断服务函数用对于 STM32 来说就是 滴答定时器中断服务函数
configUSE_MALLOC_FAILED_HOOK 内存申请失败钩子函数当使用函数 pvPortMalloc() 申请内存失败的时候就会调用此钩子函数
configUSE_DAEMON_TASK_STARTUP_HOOK 守护Daemon任务启动钩子函数守护任务也就是定时器服务任务 钩子函数的使用方法基本相同用户使能相应的钩子函数然后自行根据实际需求编写钩子函数的内容。
2.2 空闲任务钩子函数 在每个空闲任务运行周期都会调用空闲任务钩子函数如果想在空闲任务优先级下处理某个任务有两种选择
1. 在空闲任务钩子函数中处理任务 不管什么时候都要保证系统中至少有一个任务可以运行因此绝对不能在空闲任务钩子函数中调用任何可以阻塞空闲任务的 API 函数比如 vTaskDelay()或者其他带有阻塞时间的信号量或队列操作函数。
2. 创建一个与空闲任务优先级相同的任务 创建一个任务是最好的解决方法但是这种方法也消耗更多的 RAM。 要使用空闲任务钩子函数首先要在 FreeRTOSConfig.h 中将宏 configUSE_IDLE_HOOK 改为 1然后编写空闲任务钩子函数 vApplicationIdleHook()。通常在空闲任务钩子函数中将处理器设置为低功耗模式来节省电能为了与 FreeRTOS 自带的 Tickless 模式做区别这里将这种低功耗的实现方法称之为通用低功耗模式。因为几乎所有的 RTOS 系统都可以使用这种方法实现低功耗。 上图中总共有三个任务它们分别是一个空闲任务(Idle)两个用户任务(Task1 和 Task2)其中空闲任务一共运行了三次分别为1、2和3其中 T1 到 T12 是 12 个时刻下面我们分别从这两种低功耗的实现方法去分析一下整个过程。
1. 通用低功耗模式 如果使用通用低功耗模式的话每个滴答定时器中断都会将处理器从低功耗模式中唤醒以1为例在 T2 时刻处理器从低功耗模式中唤醒但是接下来由于没有就绪的其他任务所以处理器又再一次进入低功耗模式。T2、T3 和 T4 这三个时刻都一样反复的进入低功耗、退出低功耗最理想的情况应该是从 T1 时刻就进入低功耗然后在 T5 时刻退出。 在2中空闲任务只工作了两个时钟节拍但是也执行了低功耗模式的进入和退出显然这个意义不大因为进出低功耗也是需要时间的。 3中空闲任务在 T12 时刻被某个外部中断唤醒中断的具体处理过程在任务 2 使用信号量实现中断与任务之间的同步。
2. 低功耗 Tickless 模式 在1中的 T1 时刻处理器进入低功耗模式在 T5 时刻退出低功耗模式。相比通用低功耗模式少了 3 次进出低功耗模式的操作。 在2中由于空闲任务只运行了两个时钟节拍所以就没必要进入低功耗模式。说明在 Tickless 模式中只有空闲任务要运行时间超过某个最小阈值的时候才会进入低功耗模式此阈值通过 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 来设置。 3中的情况和通用低功耗模式一样。 可以看出相对于通用低功耗模式FreeRTOS 自带的 Tickless 模式更加合理有效所以如果有低功耗设计需求的话大家尽量使用 FreeRTOS 自带的 Tickless 模式。
3. 空闲任务钩子函数实验 本实验学习如何在 FreeRTOS 空闲任务钩子函数中实现低功耗。在空闲任务钩子函数中使用 WFI 指令使处理器进入睡眠模式。
3.1 main.c
#include stm32f4xx.h
#include FreeRTOS.h //这里注意必须先引用FreeRTOS的头文件然后再引用task.h
#include task.h //存在一个先后的关系
#include LED.h
#include LCD.h
#include Key.h
#include usart.h
#include delay.h
#include string.h
#include beep.h
#include malloc.h
#include timer.h
#include queue.h
#include semphr.h//任务优先级
#define START_TASK_PRIO 1 //用于创建其他两个任务
//任务堆栈大小
#define START_STK_SIZE 256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define TASK1_TASK_PRIO 2 //控制 LED0 闪烁提示系统正在运行
//任务堆栈大小
#define TASK1_STK_SIZE 256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);//任务优先级
#define DATAPROCESS_TASK_PRIO 3 //指令处理函数
//任务堆栈大小
#define DATAPROCESS_STK_SIZE 256
//任务句柄
TaskHandle_t DataProcess_Handler;
//任务函数
void DataProcess_task(void *pvParameters);//二值信号量句柄
SemaphoreHandle_t BinarySemaphore; //二值信号量句柄//用于命令解析用的命令值
#define LED1ON 1
#define LED1OFF 2
#define BEEPON 3
#define BEEPOFF 4
#define COMMANDERR 0xFF//进入低功耗模式前需要处理的事件
//ulExpectedIdleTime低功耗模式运行时间
void PreSleepProcessing(void) //因为二值信号量实验用到了串口所以对GPIOB~H时钟不使能!
{//关闭某些低功耗模式下不使用的外设时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,DISABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,DISABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,DISABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,DISABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,DISABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,DISABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,DISABLE);
}//退出低功耗模式以后需要处理的事情
//ulExpectedIdleTime低功耗模式运行时间
void PostSleepProcessing(void)
{//退出低功耗模式以后打开那些被关闭的外设时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,ENABLE);
}//空闲任务钩子函数
void vApplicationIdleHook(void)
{__disable_irq();__dsb(portSY_FULL_READ_WRITE );__isb(portSY_FULL_READ_WRITE );PreSleepProcessing(); //进入睡眠模式之前需要处理的事情__wfi(); //进入睡眠模式PostSleepProcessing(); //退出睡眠模式之后需要处理的事情__dsb(portSY_FULL_READ_WRITE );__isb(portSY_FULL_READ_WRITE );__enable_irq();
}//函数 LowerToCap 用于将串口发送过来的命令中的小写字母统一转换成大写字母
//这样就可以在发送命令的时候不用区分大小写因为开发板会统一转换成大写。
//将字符串中的小写字母转换为大写
//str要转换的字符串
//len字符串长度
void LowerToCap(u8 *str,u8 len)
{u8 i;for(i0;ilen;i){//判断字符串的ASCII码是否位于96到123之间if((96str[i])(str[i]123)) //小写字母{//ASCII码是一种用于表示字符的编码系统。在ASCII码中每个字符都被赋予一个唯一的整数值。//大写字母的ASCII码值是65到90//小写字母的ASCII码值是97到122 所以一旦确定ASCII码值位于小写字母的范畴内只需要将ASCII码值减去32即可转换为大写str[i] str[i] - 32; //转换为大写}}
}//函数 CommandProcess 用于将接收到的命令字符串转换成命令值比如说命令“LED1ON”转换成命令值就是 0(宏LED1ON为 0)
//命令处理函数将字符串命令转换成命令值
//str命令
//返回值0xFF命令错误其他值命令值
u8 CommandProcess(u8 *str)
{u8 CommandValue COMMANDERR;if(strcmp((char*)str,LED1ON)0) //strcmp 字符串比较函数//这个函数会比较两个参数比较时会以字符的ASCII值进行比较//如果str1的ASCII码值小于str2返回一个负数反之返回一个正数//如果str1的ASCII码值等于str2返回 0此时if判断语句成立CommandValue LED1ON; //设置的LED1ON的宏为1也就是在串口输入1if判断语句成立else if(strcmp((char*)str,LED1OFF)0)CommandValue LED1OFF; //在串口输入2if判断语句成立else if(strcmp((char*)str,BEEPON)0)CommandValue BEEPON; //在串口输入3if判断语句成立else if(strcmp((char*)str,BEEPOFF)0)CommandValue BEEPOFF; //在串口输入4if判断语句成立return CommandValue;
}int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级delay_init(168);uart_init(115200);LED_Init();KEY_Init();BEEP_Init();LCD_Init();my_mem_init(SRAMIN); //初始化内部内存池POINT_COLORRED;LCD_ShowString(10,10,200,16,16,ATK STM32F407);LCD_ShowString(10,30,200,16,16,FreeRTOS Example);LCD_ShowString(10,50,200,16,16,Binary Semaphore);LCD_ShowString(10,70,200,16,16,Command Data:);//创建开始任务xTaskCreate((TaskFunction_t )start_task, //任务函数(const char* )start_task, //任务名称(uint16_t )START_STK_SIZE, //任务堆栈大小(void* )NULL, //传递给任务函数的参数(UBaseType_t )START_TASK_PRIO, //任务优先级(TaskHandle_t* )StartTask_Handler); //任务句柄 vTaskStartScheduler(); //开启任务调度
}//开始任务任务函数
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); //进入临界区//创建二值信号量,也就是创建一个长度为1的队列BinarySemaphore xSemaphoreCreateBinary(); //xSemaphoreCreateBinary函数为动态创建二值信号量函数//返回 NULL二值信号量创建失败返回其他值表示创建成功的二值信号量的句柄//所以BinarySemaphore表示创建成功的二值信号量的句柄//创建Task1任务xTaskCreate((TaskFunction_t )task1_task, //任务函数(const char* )task1_task, //任务名称(uint16_t )TASK1_STK_SIZE, //任务堆栈大小(void* )NULL, //传递给任务函数的参数(UBaseType_t )TASK1_TASK_PRIO, //任务优先级(TaskHandle_t* )Task1Task_Handler); //任务句柄 //创建Task2任务xTaskCreate((TaskFunction_t )DataProcess_task, //任务函数(const char* )DataProcess_task, //任务名称(uint16_t )DATAPROCESS_STK_SIZE, //任务堆栈大小(void* )NULL, //传递给任务函数的参数(UBaseType_t )DATAPROCESS_TASK_PRIO, //任务优先级(TaskHandle_t* )DataProcess_Handler); //任务句柄 vTaskDelete(StartTask_Handler); //删除开始任务taskEXIT_CRITICAL(); //退出临界区
}//Task1任务
//控制 LED0 闪烁提示系统正在运行
void task1_task(void *pvParameters)
{while(1){LED0!LED0;vTaskDelay(500); //延时500ms也就是500个时钟节拍}
}//DataProcess_task函数
//指令处理任务根据接收到的指令来控制不同的外设
void DataProcess_task(void *pvParameters)
{u8 len0;u8 CommandValueCOMMANDERR;BaseType_t errpdFALSE;u8 *CommandStr;while(1){errxSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量函数返回值pdTURE获取信号量成功pdFALSE获取信号量失败//第一个参数要获取的信号量句柄//第二个参数阻塞时间这里设置为portMAX_DEALY译为无限等待直至获得信号量if(errpdTRUE) //获取信号量成功{lenUSART_RX_STA0x3fff; //得到此次接收到的数据长度//接收状态//bit15 接收完成标志//bit14 接收到0x0d//bit13~0 接收到的有效字节数目CommandStrmymalloc(SRAMIN,len1); //申请内存 指针指向申请内存的首地址sprintf((char*)CommandStr,%s,USART_RX_BUF); //打印接收缓存区把接收缓存区的数据保存到CommandStr中CommandStr[len]\0; //加上字符串结尾符号//CommandStr 是个指针长度为len数组是从下角标 0 开始的所以len就表示数组的最后一个LowerToCap(CommandStr,len); //将字符串转换成大写CommandValueCommandProcess(CommandStr); //命令解析也就是获取上面定义的宏 1 2 3 4if(CommandValue!COMMANDERR)//if判断语句成立表示CommandValue不等于0xFF那也就是 LED1ON、LED1OFF、BEEPON、BEEPOFF 其中一个指令{printf(命令为%s\r\n,CommandStr); switch(CommandValue){case LED1ON:LED10;break;case LED1OFF:LED11;break;case BEEPON:BEEP1;break;case BEEPOFF:BEEP0;break;}}else{//当命令错误的时候开发板会向串口调试助手发送命令错误的提示信息//比如我们发送 LED1_off 这个命令串口助手会显示无效的命令请重新输入!!printf(无效的命令请重新输入!!\r\n);}USART_RX_STA 0;memset(USART_RX_BUF,0,USART_REC_LEN); //串口接收缓冲区清零myfree(SRAMIN,CommandStr); //释放内存}}
}