学习两章的笔记:
-----------------------------------------------------
FreeRTOS的任务管理:
/*任务与调度器的基本概念
任务的状态
FreeRTOS的任务相关函数*/
任务:一个while(1)的函数,可认为是一系列独立任务的集合。每个任务在自己的环境中运行
调度器:在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。
抢占式、时间片轮转
任务状态:就绪Reday
运行Running
阻塞Blocked
挂起Suspended (挂起的任务对调度器是不可见的,让一个任务进入挂起状态的唯一方法:vTaskSuspend()函数 )
常见任务函数:
1.任务延时函数: void vTaskDelay( const TickType_t xTicksToDelay )
用于阻塞延时,调用该函数后,任务将进入阻塞状态(进入阻塞态的任务将让出 CPU 资源)。
想使用 FreeRTOS 中的 vTaskDelay()函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能
形参xTicksToDelay延时的时长,比如系统的时钟节拍周期为 1ms,那么调用 vTaskDelay(1)的延时时间则为 1ms
该函数的延时是相对的(它的延时是等 vTaskDelay ()调用完毕后开始计算的)。
2.绝对延时函数: void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );
它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的
3.任务挂起函数: void vTaskSuspend( TaskHandle_t xTaskToSuspend )
4.任务恢复函数: void vTaskResume( TaskHandle_t xTaskToResume )
5.任务删除函数: void vTaskDelete( TaskHandle_t xTaskToDelete )
嵌入式开发任务设计要点:
对以下了如指掌:任务优先级,任务与中断的处理,任务的运行时间、逻辑、状态,任务运行的上下文环境
FreeRTOS 中程序运行的上下文包括:中断服务函数、 普通任务、空闲任务
中断服务函数:在这个上下文环境中不能使用挂起当前任务的操作,不允许调用任何会阻塞运行的 API 函数接口
最好保持精简短小,快进快出,一般在中断服务函数中只做标记事件的发生,然后通知任务,让对应任务去执行相关处理
(因为中断服务函数的优先级高于任何优先级的任务,如果中断处理时间过长,将会导致整个系统的任务无法正常运行)
任务:不能出现没有阻塞机制的死循环。因为死循环的时候,任务不会主动让出 CPU,低优先级的任务是不可能得到CPU使用权
所以在进行任务设计时,就应该保证任务在不活跃的时候,任务可以进入阻塞态以交出 CPU 使用权,这就需要我们自己明确知道什么情况下让任务进入阻塞态,保证低优先级任务可以正常运行
空闲任务(idle任务):是 FreeRTOS 系统中没有其他工作进行时自动进入的系统任务。
当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环 。
空闲任务是唯一一个不允许出现阻塞情况的任务,因为 FreeRTOS 需要保证系统永远都有一个可运行的任务。
----------------------------------------------------------------------------------------
消息队列:任务间通信的数据结构,异步通信,不固定长度的消息
任务/中断服务函数将一条/多条消息放入消息队列。同样也可以得到消息
实现异步通信:(如下特点)
消息支持先进先出方式排队,支持异步读写工作方式。
读写队列均支持超时机制。
消息支持后进先出方式排队,往队首发送消息(LIFO)。
可以允许不同长度(不超过队列节点最大值)的任意类型消息。
一个任务能够从任意一个消息队列接收和发送消息。
多个任务能够从同一个消息队列接收和发送消息。
当队列使用结束后,可以通过删除队列函数进行删除。
运作机制:
创建消息队列
发送消息队列
读取消息:读不到会阻塞(阻塞时间用户来制定)
消息队列不再被使用时,应该删除它以释放资源
阻塞机制:
出队
入队
消息队列控制块:
消息的对应内存空间
头指针pcHead
尾指针pcTail
消息大小uxItemSize
队列长度uxLength
当前队列消息个数uxMessagesWaiting
应用场景:FreeRTOS中任务和任务通信主要靠队列
发送队列的消息都是拷贝进行了,所以消息都是源消息而不是消息的引用
常用函数:
1.消息队列创建函数 xQueueCreate()
函数原型:QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );
功能:用于创建一个新的队列
参数:uxQueueLength 队列能够存储的最大消息单元数目,即队列长度。
uxItemSize 队列中消息单元的大小,以字节为单位。
返回值:如果创建成功则返回一个队列句柄,用于访问创建的队列。
如果创建不成功则返回NULL,可能原因是创建队列需要的 RAM 无法分配成功。
xQueueCreate() -> xQueueGenericCreate() -> prvInitialiseNewQueue()
分配消息队列内存
void vInitQueue() //用户自定义创建消息队列函数
{
QueueHandle_t Test_Queue =NULL;
#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建 Test_Queue */
Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
if (NULL != Test_Queue)
printf("创建 Test_Queue 消息队列成功!\r\n");
taskEXIT_CRITICAL();
}
有静态创建和动态创建,静态创建麻烦需要手动配置空间,动态创建简单些需要将config中的宏配置为1
2.消息队列删除函数 vQueueDelete()
根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列
3.向消息队列发送消息函数
xQueueSend()与 xQueueSendToBack()
xQueueSendFromISR()与 xQueueSendToBackFromISR()
xQueueSendToFront()
xQueueSendToFrontFromISR()
通用消息队列发送函数 xQueueGenericSend()(任务)
消息队列发送函数 xQueueGenericSendFromISR()(中断)
4.从消息队列读取消息函数
xQueueReceive()与 xQueuePeek()
xQueueReceiveFromISR()与 xQueuePeekFromISR()
从队列读取消息函数 xQueueGenericReceive()
注意事项:
1. 使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等这些函数之前应先
创建需消息队列,并根据队列句柄进行操作。
2. 队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。当
然也 FreeRTOS 也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进
队列的数据。
3. 在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数
据区域大小不小于消息大小,否则,很可能引发地址非法的错误。
4. 无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将
消息的地址作为消息进行发送、接收。
5. 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同
一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读
出倒是用的比较少。
实验:实验一的两个任务代码 实验二的两个任务代码
实验一:一个LED任务:就是流水灯闪烁
一个KEY任务,按键1将LED任务挂起,按键2将LED任务恢复
实验二:一个发送消息任务,按键1发送520,按键2发送1314
一个接受消息任务,接受所有发送者的消息
//FreeRTOS头文件
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "stm32f10x.h"
//外设头文件
#include "bsp_usart.h"
#include "bsp_led.h"
#include "bsp_key.h"
/***************************************************************
宏定义
***************************************************************/
#define QUEUE_LEN 4 //队列的长度,最大可包含多少个消息
#define QUEUE_SIZE 4 //队列中每个消息大小(字节)
//一个指针,用于指向任务;一个任务创建好之后就会有一个句柄,如果以后要操作这个任务就对这个句柄进行操作
/***************************************************************
任务句柄
***************************************************************/
//创建任务句柄
static TaskHandle_t AppTaskCreate_Handle = NULL;
//LED任务句柄
static TaskHandle_t LED_Task_Handle = NULL;
//Key任务句柄
static TaskHandle_t KEY_Task_Handle = NULL;
//接受/发送 消息的任务句柄
static TaskHandle_t Receive_Task_Handle = NULL;
static TaskHandle_t Send_Task_Handle = NULL;
/*内核对象句柄:信号量 消息队列 事件标志组 软件定时器*/
//内核对象:就是全局的数据结构,通过调用内核对象的函数实现相应功能
//消息队列 任务句柄
QueueHandle_t Test_Queue_Handle = NULL;
/***************************************************************
全局变量声明
***************************************************************/
/***************************************************************
函数声明
***************************************************************/
//硬件外设的初始化函数声明 所有外设
static void BSP_Init();
//定义led任务函数:led流水灯的任务主体
static void LED_Task();
//定义key任务函数:Key用来检测按下,如果K1按下就挂起LED任务,按K2就恢复led任务
static void KEY_Task();
//创建任务函数:所有的任务创建,统一放在这里
static void AppTaskCreate();
//接受消息任务
static void Receive_Task();
//发送消息任务
static void Send_Task();
//实验一的两个任务:LED和KEY 任务挂起和恢复
static void TEST_1_LED_KEY();
//实验二的任务:消息队列 接受函数和发送函数(按下 KEY1 或者 KEY2 发送队列消息,Receive 任务接收到消息在串口回显)
static void TEST_2_Receive_Send();
/***************************************************************
主函数
***************************************************************/
int main(void)
{
//硬件外设初始化
BSP_Init();
printf("This is Queue_Send_Receive Test.\n\n");
BaseType_t xReturn = NULL;
//创建AppStackCreate任务
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate,
(const char* )"AppTaskCreate",//任务名称
(uint32_t )512, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )1, //任务优先级
(TaskHandle_t* )&AppTaskCreate_Handle); //任务控制块指针
if(NULL != xReturn) //创建成功,则进入调度
{
printf("AppTask Create OK !\n");
vTaskStartScheduler();
}
//不会执行到这里了
while (1);
}
/***************************************************************
函数实现
***************************************************************/
static void BSP_Init()
{
//中断优先级分组为4
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
LED_GPIO_Config();
Key_GPIO_Config();
USART_Config();
}
static void TEST_1_LED_KEY()
{
BaseType_t xReturn = NULL;
//创建LED任务
xReturn = xTaskCreate( (TaskFunction_t) LED_Task,//任务入口
(const char*) "LED_Task",//任务名称
(uint32_t ) 128, //任务堆栈的大小
(void *) NULL, //传给任务函数的参数
(UBaseType_t) 2, //任务优先级
(TaskHandle_t*) &LED_Task_Handle //任务句柄
);
if(NULL != xReturn)
printf("LED_Task create OK!\n");
else
printf("LED_Task create Error!\n");
xReturn = NULL;
//创建Key任务
xReturn = xTaskCreate( (TaskFunction_t) KEY_Task,
(const char *) "KEY_Task",
(uint32_t) 256,
(void*) NULL,
(UBaseType_t) 3,
(TaskHandle_t* ) &KEY_Task_Handle
);
if(NULL != xReturn)
printf("KEY_Task create OK!\n");
else
printf("KEY_Task create Error!\n");
}
static void TEST_2_Receive_Send()
{
//创建消息队列
Test_Queue_Handle = xQueueCreate( (UBaseType_t) QUEUE_LEN, (UBaseType_t) QUEUE_SIZE);
if(NULL != Test_Queue_Handle)
{
printf("xQueueCreate OK!\n");
}
else
{
printf("xQueueCreate ERROR!\n");
}
BaseType_t xReturn = NULL;
//创建接受消息任务
xReturn = xTaskCreate( (TaskFunction_t) Receive_Task, //任务入口
(const char*) "Receive_Task",//任务名称
(uint32_t ) 256,
(void*) NULL,
(UBaseType_t) 2,
(TaskHandle_t* ) &Receive_Task_Handle );
if(NULL != xReturn)
printf("Receive_Task create OK!\n");
else
printf("Receive_Task create Error!\n");
xReturn = NULL;
//创建发送消息任务
xReturn = xTaskCreate( (TaskFunction_t) Send_Task, //任务入口
(const char*) "Send_Task",//任务名称
(uint32_t ) 256,
(void*) NULL,
(UBaseType_t) 3,
(TaskHandle_t* ) &Send_Task_Handle );
if(NULL != xReturn)
printf("Send_Task create OK!\n");
else
printf("Send_Task create Error!\n");
}
static void AppTaskCreate()
{
//进入临界区
taskENTER_CRITICAL();
//创建 实验一的任务
TEST_1_LED_KEY();
//创建 实验二的任务
TEST_2_Receive_Send();
vTaskDelete(AppTaskCreate_Handle);//删除APPTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
static void LED_Task()
{
while(1)
{
LED_RED;
vTaskDelay (500);//1000个tick
LED_BLUE;
vTaskDelay(500);//1000个tick
LED_GREEN;
vTaskDelay(500);//1000个tick
}
}
static void KEY_Task()
{
while(1)
{
if(KEY_ON == Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN))
{
vTaskSuspend(LED_Task_Handle);
printf("Suspend LED_Task...\n");
}
if(KEY_ON == Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN))
{
vTaskResume(LED_Task_Handle);
printf("Resume LED_Task...\n");
}
vTaskDelay(50);//这个延时不能省略
}
}
static void Receive_Task()
{
printf("come in [static void Receive_Task())]\n\n");
BaseType_t xReturn = NULL;//创建信息返回值
uint32_t recv_queue;//接受消息的变量
while(1)
{
//消息队列句柄 接受信息 等待时间:一直等
xReturn = xQueueReceive(Test_Queue_Handle,&recv_queue,portMAX_DELAY);
if(xReturn == pdTRUE)
{
printf("Receive_Task receive data:%d\n",recv_queue);
}
else
{
printf("Receive_Task receive ERROR :0x%lx\n",xReturn);
}
}
}
static void Send_Task()//按下KET1 发送data1 ,按下KEY2 发送data2
{
printf("come in [static void Send_Task()]\n\n");
BaseType_t xReturn = NULL;//创建信息返回值
uint32_t send_data1 = 520;
uint32_t send_data2 = 1314;
while(1)
{
if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
printf("sending data1...\n");
xReturn = xQueueSend(Test_Queue_Handle,&send_data1,0);
if(pdPASS == xReturn)
{
printf("data1 send OK!\n");
}
}
if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON)
{
printf("sending data2...\n");
xReturn = xQueueSend(Test_Queue_Handle,&send_data2,0);
if(pdPASS == xReturn)
{
printf("data2 send OK!\n");
}
}
vTaskDelay(50);
}
}
一个BUG改了一天...我去...
使用消息队列的函数时,需要在参数中添加消息队列的内核句柄,结果写错了这个参数成了一个任务句柄,导致运行时进入HardFault_Handle(硬件错误:我理解的是Linux下的段错误 ,越界访问非法内存...)
这几天在开发板上移植了一个FreeRTOS,感受就是和Linux很像,在Linux中写程序需要了解Linux的很多系统调用,Linux中有信号量、消息队列、进程、线程、锁.....等等这些;FreeRTOS就像一个简化版的Linux,不过它是实时操作系统强调实时性,这个系统中也有一些信号量、消息队列、任务...等等,编程的时候也是要了解系统的这些函数(相当于系统调用),学会API的是使用时第一步,之后最好将系统中的这个函数实现一步一步了解.....
这样一想,我确实只是学了个皮毛,就会个调用函数而已,而且就会调用一部分。。。。。。
我不禁思考,学习FreeRTOS的之后的路线是怎样的?了解很多API?在公司的项目中能够运用按时完善的写出代码完成任务?