堆和栈
堆
- 堆即为一块空闲的内存,从这块内存中来取出一部分用完之后再把它释放回去
char heap_buf[1024];
int pos = 0;
void * my_malloc(int size)
{
int old_pos = pos;
pos += size;
return &heap_buf[old_pos];
}
void my_free(void * size)
{
}
int main(void)
{
int i;
char * buf = my_malloc(100);
unsigned char uch = 200;
for(i=0;i<26;i++)
{
buf[i] = 'A' + i;
}
}
此时heap_buf就有数据了,并且下一次分配空间的时候,是从地址100开始分配的,因为pos指向100
栈
- 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
void c_fun(void)
{
;
}
void b_fun(void)
{
;
}
int a_fun(int val)
{
int a = 8;
a += val;
b_fun();
c_fun();
return a;
}
int main(void)
{
a_fun(40);
return 0;
}
基本知识:
- 返回地址:可以理解为返回地址就是该函数执行结束后的下一条指令
在main函数中,a函数执行完之后将会返回到return 0中
a函数中调用函数b,当b函数执行之后将会返回到c函数调用前,c函数执行完之后返回到return a中
在c语言中,上面过程我们一目了然就知道了它返回的地址是谁,但这个返回地址保存在哪里呢?
1、返回地址保存在哪?
- 返回地址保存在栈中
- main在调用a_fun前会做两两件事情:
- 将a的返回地址(return 0的地址)保存到一个寄存器里面LR(link Register)
- 调用a_fun
- 那么a_fun里面调用b_fun之前,就要先把b_fun的返回地址(c_fun)保存到LR里面,之后在调用b_fun
2.那么在a_fun里面保存的LR会不会覆盖之前LR的数据呢?如果不会,LR里面是如何处理的?
- LR并不会被覆盖
- 在a_fun内部会做一件事情:
- 把LR的值(main里面的return 0的地址)存入栈中
- 同样道理,b_fun的内部也会将LR的值(c_fun的地址)存入栈中,b_fun执行完就会开始执行c_fun
- 当c_fun执行完,就会取出c_fun保存的LR值,并跳过去执行(return a),a函数也一样,最终跳到main函数的return 0
C函数开头:
- 划分栈(LR等寄存器、局部变量)
- 将LR等寄存器存入栈
- 执行代码
- 如代码里面有a = 8的话,会先把a在栈里面划分空间,之后再把8这个值写到栈中
官方精简的第1个FreeRtos程序
-
下载
-
删减目录
-
编译、执行
-
添加串口打印功能
serial.c
#include "FreeRTOS.h"
#include "queue.h"
#include "semphr.h"
#include <stdio.h>
#include "stm32f10x_lib.h"
#include "serial.h"
void SerialPortInit(void)
{
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init( GPIOA, &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init( GPIOA, &GPIO_InitStructure );
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_Clock = USART_Clock_Disable;
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init( USART1, &USART_InitStructure );
USART_Cmd( USART1, ENABLE );
}
int fputc( int ch, FILE *f )
{
USART_TypeDef * USARTx = USART1;
while ( (USARTx->SR & (1<<7)) == 0);
USARTx->DR = ch;
return ch;
}
main.c
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
vTaskStartScheduler();
return 0;
}
static void prvSetupHardware( void )
{
RCC_DeInit();
RCC_HSEConfig( RCC_HSE_ON );
while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
{
}
*( ( unsigned long * ) 0x40022000 ) = 0x02;
RCC_HCLKConfig( RCC_SYSCLK_Div1 );
RCC_PCLK2Config( RCC_HCLK_Div1 );
RCC_PCLK1Config( RCC_HCLK_Div2 );
RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
RCC_PLLCmd( ENABLE );
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
while( RCC_GetSYSCLKSource() != 0x08 )
{
}
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
SerialPortInit();
}
第1个FreeRtos程序及引申
1.创建两个打印任务
- FreeRtos是多任务系统,这个多任务在我们人类感觉上来说是同时执行的,但其实是交叉执行的
void task1Function(void * Parameters)
{
while(1)
{
printf("1");
}
}
void task2Function(void * Parameters)
{
while(1)
{
printf("2");
}
}
int main( void )
{
TaskHandle_t xTaskHandle1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
vTaskStartScheduler();
return 0;
}
2.FreeRtos源码结构
结构
以Keil工具下STM32F103芯片为例,它的FreeRTOS的目录如下:
主要涉及2个目录:
-
Demo
-
Source
- 根目录下是核心文件,这些文件是通用的
- portable目录下是移植时需要实现的文件
- 目录名为:[compiler]/[architecture]
- 比如:RVDS/ARM_CM3,这表示cortexM3架构在RVDS工具上的移植文件
核心文件
FreeRTOS的最核心文件只有2个:
- FreeRTOS/Source/tasks.c
- FreeRTOS/Source/list.c
其他文件的作用也一起列表如下:
FreeRTOS/Source/下的文件 | 作用 |
---|
tasks.c | 必需,任务操作 |
list.c | 必须,列表 |
queue.c | 基本必需,提供队列操作、信号量(semaphore)操作 |
timer.c | 可选,software timer |
event_groups.c | 可选,提供event group功能 |
croutine.c | 可选,过时了 |
3.编程规范
数据类型
每个移植的版本都含有自己的 portmacro.h 头文件,里面定义了2个数据类型:
- TickType_t:
- FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt
- 每发生一次中断,中断次数累加,这被称为tick count
- tick count这个变量的类型就是TickType_t
- TickType_t可以是16位的,也可以是32位的
- FreeRTOSConfig.h中定义configUSE_16_BIT_TICKS时,TickType_t就是uint16_t
- 否则TickType_t就是uint32_t
- 对于32位架构,建议把TickType_t配置为uint32_t
- BaseType_t:
- 这是该架构最高效的数据类型
- 32位架构中,它就是uint32_t
- 16位架构中,它就是uint16_t
- 8位架构中,它就是uint8_t
- BaseType_t通常用作简单的返回值的类型,还有逻辑值,比如 pdTRUE/pdFALSE
变量名
变量名有前缀:
变量名前缀 | 含义 |
---|
c | char |
s | int16_t,short |
l | int32_t,long |
x | BaseType_t,其他非标准的类型:结构体、task handle、queue handle等 |
u | unsigned |
p | 指针 |
uc | uint8_t,unsigned char |
pc | char指针 |
函数名
函数名的前缀有2部分:返回值类型、在哪个文件定义
函数名前缀 | 含义 |
---|
vTaskPrioritySet | 返回值类型:void 在task.c中定义 |
xQueueReceive | 返回值类型:BaseType_t 在queue.c中定义 |
pvTimerGetTimerID | 返回值类型:pointer to void 在tmer.c中定义 |
宏的名
宏的名字是大小,可以添加小写的前缀。前缀是用来表示:宏在哪个文件中定义。
宏的前缀 | 含义:在哪个文件里定义 |
---|
port (比如portMAX_DELAY) | portable.h或portmacro.h |
task (比如taskENTER_CRITICAL()) | task.h |
pd (比如pdTRUE) | projdefs.h |
config (比如configUSE_PREEMPTION) | FreeRTOSConfig.h |
err (比如errQUEUE_FULL) | projdefs.h |
通用的宏定义如下:
宏 | 值 |
---|
pdTRUE | 1 |
pdFALSE | 0 |
pdPASS | 1 |
pdFAIL | 0 |
创建任务详解
动态_静态创建任务
-
对于每个任务都会有一个TCB_t结构体,对于TCB_t结构体我们可以选择动态分配或者静态分配
-
使用xTaskCreate函数创建的任务和栈都是动态分配的
-
一个任务可以简单的理解为是一个函数,在xTaskCreate函数中,我们还指定了栈的大小,因为在函数中有各种局部变量,以及各种调用,所以每一个任务的栈应该都不一样的,否则它们的栈会互相冲突,并且这个栈我们还可以静态分配
代码
void task1Function(void * Parameters)
{
while(1)
{
printf("1");
}
}
void task2Function(void * Parameters)
{
while(1)
{
printf("2");
}
}
void task3Function(void * Parameters)
{
while(1)
{
printf("3");
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
TaskHandle_t xTaskHandle1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
vTaskStartScheduler();
return 0;
}
运行结果
总结
动态内存的使用
静态创建任务
进一步实验
优先级实验
高优先级的任务先执行,同优先级的任务交替执行
- 定义任务标记用来表示任务是否运行
- 将变量添加到逻辑分析仪中
- 首先运行到main函数中
- 变量名右键 ——> add ‘变量名’ to ——> logic Analyzer
- 逻辑分析仪中右键变量名改为Bit
同优先级实验
在下面逻辑分析仪中可以看出同优先级的任务他们的交叉执行
代码
static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;
void task1Function(void * Parameters)
{
while(1)
{
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
printf("1");
}
}
void task2Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");
}
}
void task3Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
TaskHandle_t xTaskHandle1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
vTaskStartScheduler();
return 0;
}
运行结果
不同优先级实验
将上面代码中task3Function改为优先级2,其他两个任务的优先级均为1
可以看出只有taskFunction在运行
高优先级的任务先执行,如果高优先级的任务没有主动放弃执行的话,其他低优先级的任务将不能执行
删除任务实验
我们创建一个任务,并且传入了一个xTaskHandle1,让我们可以引用这个任务
想要删除任务,即必须要通过xTaskHandlex
vTaskDelete
- 功能:删除xTaskCreate和xTaskCreateStatic创建的任务
- 参数:传入一个TaskHandle_t(句柄)
代码
TaskHandle_t xTaskHandle1;
TaskHandle_t xTaskHandle3;
static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;
void task1Function(void * Parameters)
{
while(1)
{
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
printf("1");
}
}
void task2Function(void * Parameters)
{
int i;
while(1)
{
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");
if(i++ == 100)
{
vTaskDelete(xTaskHandle1);
}
if(i == 200)
{
vTaskDelete(xTaskHandle3);
}
if(i == 300)
{
vTaskDelete(NULL);
}
}
}
void task3Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskHandle3 = xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
vTaskStartScheduler();
return 0;
}
运行结果
Hello World
3333333333331111111111122222222222233333333333111111111111222222222223333333333331111111111122222222222233333333333111111111111222222222223333333333331111111111122222222222233333333333111111111111222222222223333333333331111111111122222222222233333333333111111111111222222222223333333333331111111111122222222222233333333333222222222222333333333332222222222223333333333322222222222233333333333222222222222333333333332222222222223333333333322222222222233333333333222222222222333333333332222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
使用同一个任务函数创建多个任务
代码
TaskHandle_t xTaskHandle1;
TaskHandle_t xTaskHandle3;
static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;
void task1Function(void * Parameters)
{
while(1)
{
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
printf("1");
}
}
void task2Function(void * Parameters)
{
int i;
while(1)
{
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");
if(i++ == 100)
{
vTaskDelete(xTaskHandle1);
}
if(i == 200)
{
vTaskDelete(xTaskHandle3);
}
if(i == 300)
{
vTaskDelete(NULL);
}
}
}
void task3Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}
void taskGenericFunction(void * Para)
{
int val = (int)Para;
while(1)
{
printf("%d",val);
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskHandle3 = xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
xTaskCreate(taskGenericFunction,"Task4",100,(void *)4,1,NULL);
xTaskCreate(taskGenericFunction,"Task5",100,(void *)5,1,NULL);
vTaskStartScheduler();
return 0;
}
运行结果
为什么同一个函数能够创建不同的任务呢?
- 因为它们的栈是不一样的
- 执行任务时传入不同的参数,首先它们的参数就不一样,其次,每个任务有自己的栈,这些局部变量都保存到不同的栈中,使得它们在运行的时候互不影响
栈大小实验
栈的分配操作:(具体细节在内存管理章节)
- 如下图,heap是一块空闲的内存
- 假设分配了一块空间长度为len
- 那么,在free的时候并没有传入长度,是怎么释放掉的呢?
- free传入了buff,知道了buff的首地址
- 在buff首地址前面有一个结构体,这个结构体至少存了buff的len信息
- 当使用free函数来释放buff的时候,就可以反过来知道buff的长度是多少
- 使用malloc(len)分配内存的时候,首先会分配一个头部,再去分配len字节的buff,最终返回头部后一个(Site)地址给buff
- 当我们释放内存时,从buff地址往前推,找出头部,获得头部的长度信息,从而就可以释放掉buff
- xTaskCreate(task1Function,“Task1”,100,NULL,1,&xTaskHandle1);
- xTaskCreate创建任务时会分配一个TCB结构体,还会分配栈
- 先分配TCB1的栈和头部
- 之后分配Task1的栈(100 * 4)和头部
- 同样道理,创建Tsak2时也是一样的
我们知道栈里面会保存各种寄存器和局部变量,我们知道栈是从高地址往下增长的(绿色线),当我们使用大量的局部变量之后就会破坏掉头和TCB1以及TCB1的头部,此时就会出现不可预计的后果
代码
void task1Function(void * Parameters)
{
volatile char buf[500];
int i;
while(1)
{
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
printf("1");
for (i=0; i<500; i++)
buf[i] = 0;
}
}
void task2Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");
}
}
void task3Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}
void taskGenericFunction(void * Para)
{
int val = (int)Para;
while(1)
{
printf("%d",val);
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskHandle3 = xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
xTaskCreate(taskGenericFunction,"Task4",100,(void *)4,1,NULL);
xTaskCreate(taskGenericFunction,"Task5",100,(void *)5,1,NULL);
vTaskStartScheduler();
return 0;
}
运行结果
HardFault_Handler硬件错误
任务管理
任务状态理论
- 任务切换的基础:tick中断
- 定时器,每隔1毫秒产生中断
- tick中断处理函数中会判断是否需要切换任务,若需要切换的话则会切换
- 这个1毫秒的时间是在FreeRTOSConfig.h文件中定义的,若想要更改时间的话可以更改configTICK_RATE_HZ宏
-
- 在FreeRtos中我们可以指定任务的tick次数,如Task1执行10次tick,Task2执行5次Tick
- 有哪些任务状态?状态切换图
- Running:正在运行状态
- Ready:准备状态(可随时运行)
- Blocked:阻塞状态(等待某些事情发生,才可以继续运行)
- suspended:暂停状态(主动休息 / 被命令去休息)
- 状态转换图:
-
- 怎么管理不同状态的任务:放在不同链表里
- 阻塞状态(Blocked)举例:vTaskDelay函数
- 暂停状态(Suspended)举例:vTaskSuspend / vTaskResume
任务状态实验
代码
- 创建三个任务,分别是:Task1、Task2、Task3
- 让Task1调用vTaskSuspend( Task3 )函数,让Task3进入到暂停状态(Suspend)
- 等待一段时间让Task1调用vTaskResume( Task3 )函数,让Task3从暂停状态进入准备状态(Ready)
- 注意:Task3必须要让其他正在运行的任务来调用vTaskResume函数来解除暂停状态
- 让Task2调用vTaskDelay函数(等待某个时间),主动进入阻塞状态(Blocked)
注意:
-
这里的时钟是72MHz,并且PLLCLK9倍频,所以要把外部晶振设置为8MHz
-
设置方法:
- 点击魔术棒 --> Target --> 在STMicroelectronics STM32F13VB处改为8.0
TaskHandle_t xTaskHandle1;
TaskHandle_t xTaskHandle3;
static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;
void task1Function(void * Parameters)
{
TickType_t tStart = xTaskGetTickCount();
TickType_t t;
int flag = 0;
printf("打印了1次\r\n");
while(1)
{
t = xTaskGetTickCount();
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
printf("1");
if( !flag && (t >= tStart + 10) )
{
vTaskSuspend(xTaskHandle3);
flag = 1;
}
if( t >= tStart + 20)
{
vTaskResume( xTaskHandle3 );
}
}
}
void task2Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");
vTaskDelay(10);
}
}
void task3Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskHandle3 = xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
vTaskStartScheduler();
return 0;
}
运行结果
VTaskDelay和vTaskDelayUntil
有两个Delay函数:
- vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态。
- vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪状态。
如图,vTaskDelay是固定等待时间 N * Tick,如前面有一个不固定时间的程序,那么t1 到 t2 的时间就不是固定的
如图,从t1 到 t2 的时间称为△t ,当调用vTaskDelayUntil时传入t1 和 △t(终点),那么无论vTaskDelayUntil在t1的什么时候调用,t1 到 t2的时间都是不变的,变得只是阻塞时间(Blocked)
总结:
- vTaskDelay:固定Blocked等待时间,动态Tick中断时间
- vTaskDelayUntil:动态Blocked时间,固定Tick中断时间
代码
TaskHandle_t xTaskHandle1;
TaskHandle_t xTaskHandle3;
static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;
static int radns[] = {3,53,45,110,12};
void task1Function(void * Parameters)
{
TickType_t tStart = xTaskGetTickCount();
int i,j=0;
while(1)
{
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
for(i=0; i<radns[j]; i++)
printf("1");
j++;
if(j == 5)
j = 0;
#if 0
vTaskDelay(8);
#else
vTaskDelayUntil(&tStart,8);
#endif
}
}
void task2Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");
}
}
void task3Function(void * Parameters)
{
while(1)
{
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}
int main( void )
{
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,2,&xTaskHandle1);
xTaskCreate(task2Function,"Task2",100,NULL,1,NULL);
xTaskHandle3 = xTaskCreateStatic(task3Function,"Task3",100,NULL,1,xTask3Stack,&xTask3TCB);
vTaskStartScheduler();
return 0;
}
运行结果
当调用vTaskDelay函数时:
- 可以看到Task1的高电平时间(运行时间)是不一样的,但是休眠时间是一样的
当调用vTaskDelayUntil函数时:
- 此时t1在1.04ms时刻调用 --> t2:6.38 --> t3:11.71
- 从t1到t2,t2到t3的的间隔时间都差不多为5.34
空闲任务
- 删除任务后的清理工作,是在空闲任务(Idle,优先级为0)中完成的,比如释放任务的内存
- 空闲任务何时才能执行?
- 空闲任务只能处于这2个状态之一:Running、Ready
实验一
代码
- 在main函数里面创建Task1
- 在Task1里面创建Task2,并且删除Task2
void task2Function(void * Parameters);
void task1Function(void * Parameters)
{
TaskHandle_t xTaskHandle2;
BaseType_t xReturn;
while(1)
{
printf("1");
xReturn = xTaskCreate(task2Function,"Task2",1024,NULL,2,&xTaskHandle2);
if( xReturn != pdPASS )
printf("xTaskCreate err");
vTaskDelete(xTaskHandle2);
}
}
void task2Function(void * Parameters)
{
while(1)
{
printf("2");
vTaskDelay(2);
}
}
int main( void )
{
TaskHandle_t xTaskHandle1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
vTaskStartScheduler();
return 0;
}
运行结果
理论:
- Task1会不断的创建和删除Task2,但是Task1在不断的运行,导致Idle(空闲任务,优先级为0)没办法执行
- 也就是说没办法去清理Task2,意味着在Task1里面会不断地消耗内存,但是清理工作没办法执行,从而导致创建Task2失败
事实:
- 但是,运行结果并没有打印 “xTaskCreate err”,也就是说在空闲任务里面执行清理工作,那么这个结论需要修正一下
实验二 *
- 在main函数里面创建Task1
- 在Task1里面创建Task2
- 在Task2中将Task2删除(自杀)
代码
void task2Function(void * Parameters);
void task1Function(void * Parameters)
{
TaskHandle_t xTaskHandle2;
BaseType_t xReturn;
while(1)
{
printf("1");
xReturn = xTaskCreate(task2Function,"Task2",1024,NULL,2,&xTaskHandle2);
if( xReturn != pdPASS )
printf("xTaskCreate err");
}
}
void task2Function(void * Parameters)
{
while(1)
{
printf("2");
vTaskDelete(NULL);
}
}
int main( void )
{
TaskHandle_t xTaskHandle1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,1,&xTaskHandle1);
vTaskStartScheduler();
return 0;
}
运行结果
可以看到程序很快就出现了内存不够的情况
钩子函数
空闲任务钩子函数
- 执行一些低优先级的、后台的、需要连续执行的函数
- 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率
- 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了
- 绝对不能导致任务进入Blocked、Suspended状态
- 如果你会使用vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存
使用钩子函数的前提:
- 把这个宏定义为1:configUSE_IDLE_HOOK
- 实现 vApplicationIdleHook 函数
代码
static int task1Flagrun = 0;
static int task2Flagrun = 0;
static int taskidleFlagrun = 0;
void task2Function(void * Parameters);
void task1Function(void * Parameters)
{
TaskHandle_t xTaskHandle2;
BaseType_t xReturn;
while(1)
{
task1Flagrun = 1;
task2Flagrun = 0;
taskidleFlagrun = 0;
printf("1");
xReturn = xTaskCreate(task2Function,"Task2",1024,NULL,2,&xTaskHandle2);
if( xReturn != pdPASS )
printf("xTaskCreate err\r\n");
}
}
void task2Function(void * Parameters)
{
while(1)
{
task1Flagrun = 0;
task2Flagrun = 1;
taskidleFlagrun = 0;
printf("2");
vTaskDelete(NULL);
}
}
void vApplicationIdleHook( void )
{
task1Flagrun = 0;
task2Flagrun = 0;
taskidleFlagrun = 1;
printf("0");
}
int main( void )
{
TaskHandle_t xTaskHandle1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello World\r\n");
xTaskCreate(task1Function,"Task1",100,NULL,0,&xTaskHandle1);
vTaskStartScheduler();
return 0;
}
运行结果
- 此时空闲任务是有机会执行的,它一旦有机会执行就会去做清理工作,内存就不会被耗尽
任务调度算法
状态与事件
正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理器系统中,任何时间里只能有一个任务处于运行状态。
非运行状态的任务,它处于这3种状态之一:
- 阻塞(Blocked)
- 暂停(Suspended)
- 就绪(Ready)
就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。
阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。
事件分为两类:
- 时间相关的事件
- 所谓时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。
- 使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。
- 同步事件
- 同步事件就是:某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。
- 怎么"发送信息"?方法很多
- 任务通知(task notification)
- 队列(queue)
- 事件组(event group)
- 信号量(semaphoe)
- 互斥量(mutex)等
- 这些方法用来发送同步信息,比如表示某个外设得到了数据。
调度算法
从3个角度统一理解多种调度算法:
- 可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)
- 可以:被称作"可抢占调度"(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。
- 不可以:不能抢就只能协商了,被称作"合作调度模式"(Co-operative Scheduling)
- 当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让 出CPU资源。
- 其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点
- 可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)
- 轮流执行:被称为"时间片轮转"(Time Slicing),同优先级的任务轮流执行,你执行一个时间 片、我再执行一个时间片
- 不轮流执行:英文为"without Time Slicing",当前任务会一直执行,直到主动放弃、或者被 高优先级任务抢占
- 在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项: configIDLE_SHOULD_YIELD)
- 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务
- 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊
列表如下:
配置项 | A | B | C | D | E |
---|
configUSE_PREEMPTION | 1 | 1 | 1 | 1 | 0 |
configUSE_TIME_SLICING | 1 | 1 | 0 | 0 | x |
configIDLE_SHOULD_YIELD | 1 | 0 | 1 | 0 | x |
说明 | 常用 | 很少用 | 很少用 | 很少用 | 几乎不用 |
注:
- A:可抢占+时间片轮转+空闲任务让步
- B:可抢占+时间片轮转+空闲任务不让步
- C:可抢占+非时间片轮转+空闲任务让步
- D:可抢占+非时间片轮转+空闲任务不让步
- E:合作调度
是否可抢占
配置此配置项可决定是否可抢占
代码
static volatile int flagIdleTaskrun = 0;
static volatile int flagTask1run = 0;
static volatile int flagTask2run = 0;
static volatile int flagTask3run = 0;
void vTask1( void *pvParameters )
{
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 1;
flagTask2run = 0;
flagTask3run = 0;
printf("T1\r\n");
}
}
void vTask2( void *pvParameters )
{
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 1;
flagTask3run = 0;
printf("T2\r\n");
}
}
void vTask3( void *pvParameters )
{
const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 1;
printf("T3\r\n");
vTaskDelay( xDelay5ms );
}
}
void vApplicationIdleHook(void)
{
flagIdleTaskrun = 1;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 0;
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
vTaskStartScheduler();
return 0;
}
运行结果
当configUSE_PREEMPTION为0时(不可抢占):
- 当任务3进入阻塞状态时,任务1就会一直执行,即使任务3从阻塞状态中退出,任务1仍然再运行
- 若不允许抢占,可以在任务做完某件事之后主动放弃CPU资源
当configUSE_PREEMPTION为1时(允许抢占):
- 此处Task3的优先级最高,所以当Task3从阻塞中出来之后,就会立马执行任
- 其他任务优先级均为0,则交替执行
是否轮流执行
配置此配置项可决定同优先级是否可轮流执行
代码
static volatile int flagIdleTaskrun = 0;
static volatile int flagTask1run = 0;
static volatile int flagTask2run = 0;
static volatile int flagTask3run = 0;
void vTask1( void *pvParameters )
{
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 1;
flagTask2run = 0;
flagTask3run = 0;
printf("T1\r\n");
}
}
void vTask2( void *pvParameters )
{
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 1;
flagTask3run = 0;
printf("T2\r\n");
}
}
void vTask3( void *pvParameters )
{
const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 1;
printf("T3\r\n");
vTaskDelay( xDelay5ms );
}
}
void vApplicationIdleHook(void)
{
flagIdleTaskrun = 1;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 0;
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
vTaskStartScheduler();
return 0;
}
运行结果
此处配置可抢占,不可轮流执行
- 可以看出当任务3放弃CPU资源后,某个任务就一直在执行(除了空闲),直到任务3重新抢回CPU资源
空闲任务是否礼让其他任务
此配置项可配置空闲任务是否礼让其他任务
ldieTask任务内部流程
IdleTask(){
while(1)
{
xxxx
钩子();
if: YIELD == 1
触发一次调度
endif
}
}
代码
static volatile int flagIdleTaskrun = 0;
static volatile int flagTask1run = 0;
static volatile int flagTask2run = 0;
static volatile int flagTask3run = 0;
void vTask1( void *pvParameters )
{
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 1;
flagTask2run = 0;
flagTask3run = 0;
printf("T1\r\n");
}
}
void vTask2( void *pvParameters )
{
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 1;
flagTask3run = 0;
printf("T2\r\n");
}
}
void vTask3( void *pvParameters )
{
const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 1;
printf("T3\r\n");
vTaskDelay( xDelay5ms );
}
}
void vApplicationIdleHook(void)
{
flagIdleTaskrun = 1;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 0;
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
vTaskStartScheduler();
return 0;
}
运行结果
此处为configIDLE_SHOULD_YIELD = 0时,即空闲任务不礼让其他任务
此处为configIDLE_SHOULD_YIELD = 1时,即空闲任务不礼让其他任务
同步互斥与通信概述
同步互斥的概念
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
- 什么叫同步?就是:哎哎哎,我正在用厕所,你等会。
- 什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
- 同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用 完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?
再举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完 成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经 理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒 我。这就是使用"同步"来实现"互斥"。
有时候看代码更容易理解,伪代码如下:
void 抢厕所(void)
{
if (有人在用)
我眯一会;
用厕所;
喂,醒醒,有人要用厕所吗;
}
假设有A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也 就愉快地上厕所了。 在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机 制实现了“临界资源”的“互斥访问”。
同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临 界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时, 应该是这样:A用完,B再用;B用完,A再用。
同步的例子(有缺陷)
- 让Task1执行比较大的运算
- 当Task1在执行时,Task2会不断的判断Task1是否执行完毕
- 缺点:太浪费CPU资源,Task1在运行的过程中,Task2应该处于阻塞状态,不应该去抢占CPU资源
代码
static int sum = 0;
static volatile int flagCalcEnd = 0;
void vTask1( void *pvParameters )
{
volatile int i;
while(1)
{
for(i=0; i<10000000; i++)
sum++;
flagCalcEnd = 1;
vTaskDelete(NULL);
}
}
void vTask2( void *pvParameters )
{
while(1)
{
if(flagCalcEnd == 1)
printf("sum = %d\r\n",sum);
}
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
vTaskStartScheduler();
return 0;
}
运行结果
- 当程序运行到6s的时候,Task2才开始执行
- Task1在不断累加,Task2也在跟Task2竞争CPU资源
- 这种同步方式,虽然可以执行,但是太浪费CPU资源
不创建Task2,仅创建Task1的运行结果:
- 可以看到i从0执行到10000000只花了3s,比上面快了一半
互斥的例子(有缺陷)
- 编写一个通用函数,向串口打印当前正在运行的任务
- 用通用函数来创建两个任务
代码
void vTaskGenericFunction(void * para)
{
while(1)
{
if(!flagUSERused)
{
flagUSERused = 1;
printf("%s\r\n",(char *)para);
flagUSERused = 0;
vTaskDelay(1);
}
}
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTaskGenericFunction,"Task3",1000,"This is Task3 run",1,NULL);
xTaskCreate(vTaskGenericFunction,"Task4",1000,"This is Task4 run",1,NULL);
vTaskStartScheduler();
return 0;
}
运行结果
void vTaskGenericFunction(void * para)
{
while(1)
{
printf("%s\r\n",(char *)para);
}
}
void vTaskGenericFunction(void * para)
{
while(1)
{
if(!flagUSERused)
{
flagUSERused = 1;
printf("%s\r\n",(char *)para);
flagUSERused = 0;
}
}
}
void vTaskGenericFunction(void * para)
{
while(1)
{
if(!flagUSERused)
{
flagUSERused = 1;
printf("%s\r\n",(char *)para);
flagUSERused = 0;
vTaskDelay(1);
}
}
}
- 这种写法就解决了上面两个问题
- 但是,这个代码仍然会有一个隐患
- 当Task3和Task4都执行vTaskGenericFunction时,可能会出现同一时刻执行到if(!flagUSERused)语句,且都成功进入if语句里面,这时再去设置flagUSERused为1已经无济于事
- 此代码的问题在于 判断 和 设置 的间隔时间太长
FreeRTOS的解决方案
队列的使用
队列的理论讲解
队列的读写操作
队列的简化操如入下图所示,从此图可知:
- 队列可以包含若干个数据:
- 队列中有若干项,这被称为"长度"(length)
- 每个数据大小固定 创建队列时就要指定长度、数据大小
- 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
- 也可以强制写队列头部:覆盖头部数据
队列的结构体
Queue{
*head:指向一块真正用来存放数据的缓冲区
*xTasksWaitingToReceive:等待读数据的任务;若队列中无数据,则进入阻塞状态,若队列有数据,则退出阻塞状态,并执行读操作
*xTasksWaitingToSend:等待写数据的任务;若队列已满,则进入阻塞状态,若队列有空间,则退出阻塞状态,并执行写操作
}
typedef struct QueueDefinition
{
int8_t * pcHead;
int8_t * pcWriteTo;
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u;
List_t xTasksWaitingToSend;
List_t xTasksWaitingToReceive;
} xQUEUE;
队列创建
动态分配内存:xQueueCreate,队列的内存在函数内部动态分配
- uxQueueLength:队列长度,最多能存放多少个数据(item)
- uxItemSize:每个数据(item)的大小:以字节为单位
- 返回值:非0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为内存不足
写队列
写入尾部
往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait:
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait );
- 当pcWirteTo指针写完n-1个字节之后,pcWirteTo会条会头部(0)
写入头部
往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
参数
- xQueue:队列句柄,要写哪个队列
- pvItemToQueue:数据指针,这个数据的值会被复制进队列, 复制多大的数据?在创建队列时已经指定了数据大小
- xTicksToWait:如果队列满则无法写入新数据,可以让任务进入阻塞状态, xTicksToWait表示阻塞的最大时间(Tick Count)。 如果被设为0,无法写入数据时函数会立刻返回; 如果被设为portMAX_DELAY,则会一直阻塞(调用此函数的任务)直到有空间可写
- 返回值:
- pdPASS:数据成功写入了队列
- errQUEUE_FULL:写入失败,因为队列满了。
读队列
使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait );
- xQueue:队列句柄,要读哪个队列
- pvBuffer:bufer指针,队列的数据会被复制到这个buffer 复制多大的数据?在创建队列时已经指定了数据大小
- xTicksToWait:如果队列空则无法读出数据,可以让任务进入阻塞状态, xTicksToWait表示阻塞的最大时间(Tick Count)。 如果被设为0,无法读出数据时函数会立刻返回; 如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写
- 返回值:
- pdPASS:从队列读出数据入
- errQUEUE_EMPTY:读取失败,因为队列空了。
队列的阻塞访问
只要知道队列的句柄,谁都可以读、写该队列。任务、ISR都可读、写队列。
可以多个任务读写队列。 任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地说,就是可以定 个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞的时间。如果队 列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间到之后它也会进入就绪态。
既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入阻塞状态:有多 个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪态?
- 优先级最高的任务
- 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还可以指定阻塞的 时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有空间,则时间到之后它也会 进入就绪态。
既然写队列的任务个数没有限制,那么当多个任务写"满队列"时,这些任务都会进入阻塞状态:有多个 任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
- 优先级最高的任务
- 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
队列的常规使用
用队列实现同步
此代码改进了之前同步的例子,让task1在运行的时候,task2不会去抢占task1的cpu资源
运行流程:
- 先运行task2任务,当task2任务执行到xQueueReceive的时候,发现队列中没有数据,则进入blocked状态,直到有数据
- 当task2进入blocked状态执行,task1运行,当task1执行完10000000次之后,将结果写入队列中,此时task2将会退出blocked状态,并将队列的数据取出,打印到串口中
代码
static int sum = 0;
static int task2flagrun;
static QueueHandle_t xQueueCalcHandle;
void vTask1( void *pvParameters )
{
int i;
while(1)
{
for(i = 0;i<10000000;i++)
sum++;
xQueueSend(xQueueCalcHandle,&sum,portMAX_DELAY);
}
}
void vTask2( void *pvParameters )
{
int num;
while(1)
{
task2flagrun = 0;
xQueueReceive(xQueueCalcHandle,&num,portMAX_DELAY);
printf("num = %d\r\n",num);
task2flagrun = 1;
}
}
int main( void )
{
prvSetupHardware();
xQueueCalcHandle = xQueueCreate(2,sizeof(int));
if(xQueueCalcHandle == NULL)
{
printf("create queue error!!");
}
xTaskCreate(vTask1,"Task1",1000,NULL,1,NULL);
xTaskCreate(vTask2,"Task2",1000,NULL,1,NULL);
vTaskStartScheduler();
return 0;
}
运行结果
用队列实现互斥
代码
static QueueHandle_t xQueueLockHandle;
int InitUSARTLock(void)
{
int val;
xQueueLockHandle = xQueueCreate(1,sizeof(int));
if(xQueueLockHandle == NULL)
{
printf("Init lock error");
return 1;
}
xQueueSend(xQueueLockHandle,&val,portMAX_DELAY);
return 0;
}
void getUSARTLock(void)
{
int val;
xQueueReceive(xQueueLockHandle,&val,portMAX_DELAY);
}
void putUSARTLock(void)
{
int val;
xQueueSend(xQueueLockHandle,&val,portMAX_DELAY);
}
void vtaskGenericFunction(void * para)
{
while(1)
{
getUSARTLock();
printf("%s\r\n",(char *)para);
putUSARTLock();
taskYIELD();
}
}
int main( void )
{
prvSetupHardware();
InitUSARTLock();
xTaskCreate(vtaskGenericFunction,"Task1",1000,"This is Task1 run",1,NULL);
xTaskCreate(vtaskGenericFunction,"Task2",1000,"This is Task2 run",1,NULL);
vTaskStartScheduler();
return 0;
}
运行结果
当优先级相同且没有调用taskYIELD()的运行结果
邮箱
FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:
-
它是一个队列,队列长度只有1
-
写邮箱:新数据覆盖旧数据,在任务中使用 xQueueOverwrite() ,在中断中使用 xQueueOverwriteFromISR() 。
既然是覆盖,那么无论邮箱中是否有数据,这些函数总能成功写入数据。
-
读邮箱:读数据时,数据不会被移除;在任务中使用 xQueuePeek() ,在中断中使用 xQueuePeekFromISR() 。
这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱时总能成功。
main函数中创建了队列(队列长度为1)、创建了发送任务、接收任务:
- 发送任务的优先级为2,它先执行
- 接收任务的优先级为1
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
xQueue = xQueueCreate( 1, sizeof(uint32_t) );
if( xQueue != NULL )
{
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
vTaskStartScheduler();
}else
{
}
return 0;
}
队列集
skStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
#### 运行结果
<img src="https://s2.loli.net/2022/12/01/IGkE9S8tafC6FhB.png" alt="image-20221201142922726" style="zoom: 80%;" />
- 可以看到这次运行速度相比之前要快上一倍
### 用队列实现互斥
#### 代码
```c
static QueueHandle_t xQueueLockHandle;
/*-----------------------------------------------------------*/
//创建锁
int InitUSARTLock(void)
{
int val;
xQueueLockHandle = xQueueCreate(1,sizeof(int));
if(xQueueLockHandle == NULL)
{
printf("Init lock error");
return 1;
}
//写入一个数据,用来表示当前无任务执行
xQueueSend(xQueueLockHandle,&val,portMAX_DELAY);
return 0;
}
//读取锁
void getUSARTLock(void)
{
int val;
//如果队列能读取到数据,则表示无其他任务在运行
xQueueReceive(xQueueLockHandle,&val,portMAX_DELAY);
}
//释放锁
void putUSARTLock(void)
{
int val;
//写入一个数据,用来表示当前无任务执行
xQueueSend(xQueueLockHandle,&val,portMAX_DELAY);
}
void vtaskGenericFunction(void * para)
{
while(1)
{
getUSARTLock();
printf("%s\r\n",(char *)para);
//到这里task1还在等待
putUSARTLock(); /* task1 --> Ready 但是 task2 --> Running,所以task1不会打印 */
/*
解决方法:
1.使用taskYIELD()发起一次任务切换
2.使用taskDelay()让当前任务进入Blocked状态(不推荐)
*/
taskYIELD();
}
}
int main( void )
{
prvSetupHardware();
InitUSARTLock();
xTaskCreate(vtaskGenericFunction,"Task1",1000,"This is Task1 run",1,NULL);
xTaskCreate(vtaskGenericFunction,"Task2",1000,"This is Task2 run",1,NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
运行结果
[外链图片转存中…(img-D65rbcHE-1675569506983)]
当优先级相同且没有调用taskYIELD()的运行结果
[外链图片转存中…(img-z5DjFpLF-1675569506984)]
邮箱
FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:
-
它是一个队列,队列长度只有1
-
写邮箱:新数据覆盖旧数据,在任务中使用 xQueueOverwrite() ,在中断中使用 xQueueOverwriteFromISR() 。
既然是覆盖,那么无论邮箱中是否有数据,这些函数总能成功写入数据。
-
读邮箱:读数据时,数据不会被移除;在任务中使用 xQueuePeek() ,在中断中使用 xQueuePeekFromISR() 。
这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱时总能成功。
main函数中创建了队列(队列长度为1)、创建了发送任务、接收任务:
- 发送任务的优先级为2,它先执行
- 接收任务的优先级为1
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
xQueue = xQueueCreate( 1, sizeof(uint32_t) );
if( xQueue != NULL )
{
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
vTaskStartScheduler();
}else
{
}
return 0;
}
队列集
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)