声明:本文所述的所有实例代码仅在stm32f103c6tb上跑过,其余单片机理论上都能使用,但未经实测
文章目录
深入学习请点此处
操作系统启动步骤
1.定义任务函数
模块化,写在自己的.c文件中
/**
* @brief LED_Task任务主体
* @param parameter
* @retval void
*/
static void LED_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延时500个tick */
printf("LED_Task Running,LED1_ON\r\n");
LED1_OFF;
vTaskDelay(500); /* 延时500个tick */
printf("LED_Task Running,LED1_OFF\r\n");
}
}
2.空闲任务与定时器任务堆栈函数实现
当我们使用了静态创建任务的时候,configSUPPORT_STATIC_ALLOCATION 这个宏 定 义 必 须为 1 (在 FreeRTOS.h 文 件 中 ) , 并且 我 们需 要 实 现两 个 函数 : vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory(),这两个函数是用 户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而 不能是动态分配
写在main.c,函数在main函数前声明
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
/**
* @brief 获取空闲任务的任务堆栈和任务控制块内存
* @param ppxIdleTaskTCBBuffer : 任务控制块内存
* @param ppxIdleTaskStackBuffer : 任务堆栈内存
* @param pulIdleTaskStackSize : 任务堆栈大小
* @retval void
*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */
*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}
/**
* @brief 获取定时器任务的任务堆栈和任务控制块内存
* @param ppxTimerTaskTCBBuffer : 任务控制块内存
* @param ppxTimerTaskStackBuffer : 任务堆栈内存
* @param pulTimerTaskStackSize : 任务堆栈大小
* @retval void
*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}
3.定义任务栈、控制块、句柄
写在main.c
/* AppTaskCreate任务堆栈 */
static StackType_t AppTaskCreate_Stack[128];
/* LED任务堆栈 */
static StackType_t LED_Task_Stack[128];
/* AppTaskCreate 任务控制块 */
static StaticTask_t AppTaskCreate_TCB;
/* AppTaskCreate 任务控制块 */
static StaticTask_t LED_Task_TCB;
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle;
4.编写AppTaskCreate任务
这个任务是用于创建用户任务,为了方便管理,我们的所有的任务创建都统一放在这个函数中,在这个函数中创建成功的任务就可以直接参与任务调度了
写在main.c,在main函数前声明
static void AppTaskCreate(void)
{
taskENTER_CRITICAL(); //进入临界区
/* 创建LED_Task任务 */
LED_Task_Handle = xTaskCreateStatic((TaskFunction_t )LED_Task, //任务函数
(const char* )"LED_Task", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )4, //任务优先级
(StackType_t* )LED_Task_Stack, //任务堆栈
(StaticTask_t* )&LED_Task_TCB); //任务控制块
if(NULL != LED_Task_Handle)/* 创建成功 */
printf("LED_Task任务创建成功!\n");
else
printf("LED_Task任务创建失败!\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
5.创建任务
静态创建任务
xTaskCreateStatic
在main函数内执行创建 AppTaskCreate 任务
/* 创建 AppTaskCreate 任务 */
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t )AppTaskCreate, //任务函数
(const char* )"AppTaskCreate", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )3, //任务优先级
(StackType_t* )AppTaskCreate_Stack, //任务堆栈
(StaticTask_t* )&AppTaskCreate_TCB); //任务控制块
在AppTaskCreate任务中创建其余任务
/* 创建LED_Task任务 */
LED_Task_Handle = xTaskCreateStatic((TaskFunction_t )LED_Task, //任务函数
(const char* )"LED_Task", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )4, //任务优先级
(StackType_t* )LED_Task_Stack, //任务堆栈
(StaticTask_t* )&LED_Task_TCB); //任务控制块
if(LED_Task_Handle != NULL)/* 创建成功 */
printf("LED_Task任务创建成功!\n");
else
printf("LED_Task任务创建失败!\n");
动态创建任务
xTaskCreate
程序跑起来内存会溢出,未查明原因
在AppTaskCreate任务中创建其余任务
/* 创建Receive_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任务入口函数 */
(const char* )"Receive_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&Receive_Task_Handle);/* 任务控制块指针 */
6.启动任务
//这里建议先判断AppTaskCreate任务是否创建成功
跟在创建任务语句后
if(AppTaskCreate_Handle != NULL)/* 创建成功 */
vTaskStartScheduler(); /* 开启调度器 */
任务管理
任务状态切换
(1):创建任务→就绪态:
(2):就绪态→运行态:
(3):运行态→就绪态:
(4):运行态→阻塞态:(挂起、延时、 读信号量等待)
(5):阻塞态→就绪态:(任务恢复、延时时间超时、读 信号量超时或读到信号量等),
(6) (7) (8):就绪态、阻塞态、运行态→挂起态:调用 vTaskSuspend() 或 vTaskSuspendAll()
(9):挂起态→就绪态:调用 vTaskResume() 或 vTaskResumeFromISR()
删除任务
调用 vTaskDelete() 形参为要删除任 务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。
任务轮转
任务轮转即将运行态任务转就绪态,并运行最高优先级就绪态任务
以下是几个触发轮转的条件(满足一个即可)
- 任务由运行态转阻塞态(挂起、延时、 读信号量等待)
- 运行态→挂起态
延时函数
vTaskDelay 和 vTaskDelayUntil 函数的参数是以系统节拍为单位的延时时间,而不是以毫秒为单位。要将毫秒转换为系统节拍,可以使用portTICK_PERIOD_MS常量,例如vTaskDelay(500 / portTICK_PERIOD_MS)表示延时500毫秒。
相对延时vTaskDelay()
指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束。
绝对延时vTaskDelayUntil
指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。
消息队列使用步骤
要使用消息队列功能需要引入头文件
#include “queue.h”
1.创建消息队列
静态创建队列
xQueueCreateStatic()
使 用 xQueueCreateStatic()创建队列时,使用的是静态内存分配,所以要想使用该函数必须在 FreeRTOSConfig.h 中把 configSUPPORT_STATIC_ALLOCATION 定义为 1 来使能。
/* 创建一个可以最多可以存储 10 个 64 位变量的队列 */
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint64_t )
/* 该变量用于存储队列的数据结构 */
static StaticQueue_t xStaticQueue;
/* 该数组作为队列的存储区域,大小至少有 uxQueueLength * uxItemSize 个字节 */
uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];
void vATask( void *pvParameters ){
QueueHandle_t xQueue;
/* 创建一个队列 */
xQueue = xQueueCreateStatic(QUEUE_LENGTH, /* 队列深度 */
ITEM_SIZE, /* 队列数据单元的单位 */
ucQueueStorageArea,/* 队列的存储区域 */
&xStaticQueue ); /* 队列的数据结构 */
/* 剩下的其他代码 */
while(1){
}
}
这里解释下 ucQueueStorageArea 和 xStaticQueue的区别:
ucQueueStorageArea:就是一个数组,存放队列的实际内容
xStaticQueue:是一个结构体变量,内涵了队列的属性、ucQueueStorageArea的首地址,后续操作队列实际上也就是操作xStaticQueue
动态创建队列
程序跑起来内存会溢出,未查明原因
xQueueCreate()
使用 xQueueCreate()创建队列时,使用的是动态内存分配,所以要想使用该函数必须在 FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 来使能,这 是个用于使能动态内存分配的宏,通常情况下,在 FreeRTOS 中,凡是创建任务,队列, 信号量和互斥量等内核对象都需要使用动态内存分配,所以这个宏默认在 FreeRTOS.h 头文 件中已经使能(即定义为 1)。
在AppTaskCreate中执行
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(); //退出临界区
2.读队列
使用 xQueueReceive() 或 xQueueReceiveFromISR() 函数读队列,读到一个数据后,队列中该数据会被移除。两个函数分别为:在任务中使用、在ISR中使用。
这里举出xQueueReceive函数的应用实例
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdTRUE */
uint32_t r_queue; /* 定义一个接收消息的变量 */
while (1)
{
xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */
&r_queue, /* 发送的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if(pdTRUE == xReturn)
printf("本次接收到的数据是%d\n\n",r_queue);
else
printf("数据接收出错,错误代码0x%lx\n",xReturn);
}
}
3.写列队
可以把数据写到队列头部,也可以写到尾部
xQueueSend 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
xQueueSendToBack 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
xQueueSendToBackFromISR 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
xQueueSendToFront 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
xQueueSendToFrontFromISR 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
阻塞时间表示着等待队列空闲的最大超时时间。如果队列满并且xTicksToWait 被设置成0,函数立刻返回
xQueueSend()函数使用实例
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
uint32_t send_data1 = 1;
uint32_t send_data2 = 2;
while (1)
{
if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
{/* K1 被按下 */
printf("发送消息send_data1!\n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data1,/* 发送的消息内容 */
0 ); /* 等待时间 0 */
if(pdPASS == xReturn)
printf("消息send_data1发送成功!\r\n");
}
if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
{/* K2 被按下 */
printf("发送消息send_data2!\n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data2,/* 发送的消息内容 */
0 ); /* 等待时间 0 */
if(pdPASS == xReturn)
printf("消息send_data2发送成功!\r\n");
}
vTaskDelay(20);/* 延时20个tick */
}
}
4.消息队列删除函数
需要注意的是调用删除消息队列函 数前,系统应存在 xQueueCreate()或 xQueueCreateStatic()函数创建的消息队列。此外 vQueueDelete()也可用于删除信号量。如果删除消息队列时,有任务正在等待消息,则不应 该进行删除操作。
消息队列删除函数 vQueueDelete()使用实例
#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE 4
int main( void )
{
QueueHandle_t xQueue;
/* 创建消息队列 */
xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_ITEM_SIZE );
if ( xQueue == NULL ) {
/* 消息队列创建失败 */
} else {
/* 删除已创建的消息队列 */
vQueueDelete( xQueue );
}
}
5.复位
xQueueReset()
队列刚被创建时,里面没有数据;使用过程中可以调用xQueueReset()把队列恢复为初始状态
6.查询
可以查询队列中有多少个数据、有多少空余空间。
/*返回队列中可用数据的个数*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*返回队列中可用空间的个数*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
一些奇怪的发现
如果有大佬知道我这个问题出在哪,劳请赐教
- 挂起一个任务后,再重新解挂,这个任务函数会从头执行
- stm32f103c6芯片动态创建任务或队列都会导致ZI-data区达到20000多,程序直接炸