【FreeRTOS学习 - 零散记录】

跟着韦东山老师FreeRTOS教学资料的学习记录
FreeRTOS全部项目代码链接(更新中)
https://gitee.com/chenshao777/free-rtos_-study


[FreeRTOS学习 - 分节记录]

  • 一、常用函数介绍
  • 二、一些细节

一、常用函数介绍

1. 创建任务函数

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask )

BaseType_t 实际上是 long 类型
xTaskCreate 函数的参数含义依次是
pxTaskCode : 任务函数(函数指针)
pcName : 具有描述性的任务名
usStackDepth : 栈深度
pvParameters : 任务优先级
pxCreatedTask : 任务句柄


2. 任务调度函数

vTaskStartScheduler();          //开启任务调度

通常写在main函数最后,用于开启任务调度器
任务调度器里会开启空闲任务和初始化定时器服务等工作


3. 相对延时函数

void vTaskDelay( const TickType_t xTicksToDelay )

TickType_t 实际上是 uint32_t 的宏
如果定时10ms,可以这么写

void Task(void *pvParameters)
{
	const TickType_t xDelay_10ms = 10 / portTICK_PERIOD_MS;
	for(;;)
	{
		// ......
		// 该任务执行到下面这句,会阻塞10ms
		vTaskDelay(xDelay_10ms);
	}
}

portTICK_PERIOD_MS 代表一个时间片的时间 , 单位毫秒
portTICK_PERIOD_MS 在 portmacro.h 中有定义

#define portTICK_PERIOD_MS			( ( TickType_t ) 1000 / configTICK_RATE_HZ )

configTICK_RATE_HZ 在 FreeRTOSConfig.h 中定义,表示RTOS的系统节拍频率,即1秒内运行的次数,这里是 1000次,即1ms

//RTOS系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度
#define configTICK_RATE_HZ						  (( TickType_t )1000)

所以这里 RTOS 的 Tick 是1ms


4. 绝对延时函数

/* 获取当前系统时间 */
void Task(void *pvParameters)
{
	/* 获取当前系统时间 */
	u32 lastWakeTime = xTaskGetTickCount();  
	const TickType_t xDelayUntil_10ms = 10 / portTICK_PERIOD_MS;
	for(;;)
	{
		// ......
		// 该任务会以每10ms运行一次的频率运行
		vTaskDelayUntil(&lastWakeTime, xDelayUntil_10ms); //100Hz(10ms控制一次)
	}
}

vTaskDelayUntil 与 vTaskDelay 的区别

  • 相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束;
  • 绝对延时是指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务,即以固定频率运行。

举例:创建多个任务

#include "stm32f10x.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"

/*
 * 环境检测
*/
void Huanjing_Task(void *pvParameters)
{
	const TickType_t xDelay_10ms = 10 / portTICK_PERIOD_MS;
	for(;;)
	{
		/* 循环执行任务 */
		// ......
		// ......
		
		vTaskDelay(xDelay_10ms);  //阻塞10ms
	}
}

/*
 * LED闪烁,表示系统正在运行
*/
void LED_Task(void *pvParameters)
{
	const TickType_t xDelay_1000ms = 1000 / portTICK_PERIOD_MS;
	for(;;)
	{
		/* LED灯翻转 */
		GPIOC->ODR ^= (1<<13);
		vTaskDelay(xDelay_1000ms);  //阻塞1000ms
	}
}

/*
 * 处理UWB数据
*/
void LED_Task(void *pvParameters)
{
	const TickType_t xDelay_10ms = 10 / portTICK_PERIOD_MS;
	for(;;)
	{
		/* 处理UWB数据 */
		// ......
		// ......
		vTaskDelay(xDelay_10ms);  //阻塞10ms
	}
}

int main()
{
	systemInit();   //初始化各个模块
	
	// DHT11温湿度模块
	xTaskCreate(Huanjing_Task, "Huanjing_Task", 200, NULL, 2, NULL);
	
	// LED 显示系统正在运行 
	xTaskCreate(LED_Task, "LED_Task", 200, NULL, 1, NULL);
	
	// 处理UWB数据
	xTaskCreate(UWB_vSemphr_Task, "UWB_vSemphr_Task", 200, NULL, 3, NULL);
	
	vTaskStartScheduler();          //开启任务调度
}
  • LED_Task 会使LED以一定频率闪烁,优先级最低,只能在另外两个任务阻塞期间得以执行;
  • Huanjing_Task 任务负责检测环境数据,只能在 UWB_vSemphr_Task 任务阻塞期间执行;
  • UWB_vSemphr_Task 任务优先级最高,延时结束后会抢占其他的任务而执行

5. 队列创建函数

xQueueCreate( const UBaseType_t uxQueueLength, 
			  const UBaseType_t uxItemSize )

xQueueCreate函数的参数含义依次是
uxQueueLength: 队列长度
uxItemSize : 队列数据单元的长度


示例:

/* 1. 创建队列返回句柄 */
QueueHandle_t Queue_t;

//......
//......

int main()
{
	//......
	
	/*  创建队列, 每个数据单元类型为 int */
	Queue_t = xQueueCreate(20, sizeof(int));
	if(Queue_t != NULL)
		printf("队列创建成功!\r\n");
		
	//......
}


5.1 写队列函数

// 写队列(写到队尾)
xQueueSendToBack( QueueHandle_t xQueue, 
				  const void * const pvItemToQueue, 
				  TickType_t xTicksToWait )

xQueueSendToBack 函数的参数含义依次是
xQueue : 队列句柄
pvItemToQueue : 发送数据的指针
xTicksToWait : 阻塞超时时间


5.2 读队列函数

xQueueReceive( QueueHandle_t xQueue, 
			   void * const pvBuffer, 
			   TickType_t xTicksToWait )

xQueueReceive函数的参数含义依次是
xQueue : 队列句柄
pvBuffer : 接收数据的指针
xTicksToWait : 阻塞超时时间


举例:写队列 - 读队列

#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"

/* 创建队列返回句柄 */
QueueHandle_t Queue_t;

/*
  写队列
*/
void TaskSend(void *pvParameters)
{
	int param;
	BaseType_t status;
	/* 该任务被创建了两个实例,需将传递参数强转为 int 型 */
	param = (int)pvParameters;

	for(;;)
	{
		/* 写队列,参数:目标队列的句柄, 发送数据的指针, 阻塞超时时间*/
		status = xQueueSendToBack(Queue_t, &param, 0);
		
		/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */ 
		taskYIELD(); 
		
		/* 不能使用该延时函数,否则当队列满后,只有第一个写任务可以得到调度,第二个写任务会被饿死 */
//		vTaskDelay(1); 
	}
}

/*
  读队列
*/
void TaskRead(void *pvParameters)
{
	int read_data;
	BaseType_t result;
	BaseType_t count;
	for(;;)
	{
		/* 查询队列中数据个数 */
		count = uxQueueMessagesWaiting(Queue_t);
		printf("c = %d\r\n", count);
		
		/* 读队列 */
		result = xQueueReceive(Queue_t, &read_data, 50);
		if(result == pdPASS)
			printf("Read:  %d\r\n", read_data);
		else
			printf("Read NULL\r\n");
	}
}

int main()
{
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");
	
	/*  创建队列, 每个数据单元类型为 int */
	Queue_t = xQueueCreate(20, sizeof(int));
	if(Queue_t != NULL)
		printf("队列创建成功!\r\n");
	
	/* 创建三个任务写队列 */
	xTaskCreate(TaskSend, "taskSend1", 200, (void*)100, 1, NULL);
	xTaskCreate(TaskSend, "taskSend2", 200, (void*)110, 1, NULL);
	xTaskCreate(TaskSend, "taskSend3", 200, (void*)120, 1, NULL);
	
	/* 创建一个任务读队列 */
	xTaskCreate(TaskRead, "TaskRead", 200, NULL, 2, NULL);
	
	/* 启动调度器,任务开始执行 */
	vTaskStartScheduler();          //开启任务调度
}

PS:
1. 同一个任务函数可以复用,只需要任务名不同即可
2. taskYIELD(): 当任务运行完后直接通知调度器切换其他任务,无需等待本任务时间片耗尽

6. 二值信号量

vSemaphoreCreateBinary( QueueHandle_t xSemaphore )

xSemaphore : 创建的信号量

举例:

QueueHandle_t xSemaphore;      // 定义信号量句柄

//......
//......

/* 初始化二值信号量 */
vSemaphoreCreateBinary(xSemaphore);  
/* 经过测试,初始化二值信号量后,默认信号量是满状态,可以先Take一下,把信号量清空 */
xSemaphoreTake(xSemaphore, 0);   

获取二值信号量

/* 获取信号量结果变量 */
BaseType_t result;  

//......
//......

/*
 * portMAX_DELAY:表示没有超时限制
 * 尝试获取信号量,如果没有获取到则进入阻塞态
 * 如果设置了超时时间,超时时间内获取到了信号量,则返回pdPASS,如果没有获取到,则返回pdFALSE
*/
result = xSemaphoreTake(xSemaphore, portMAX_DELAY);  
if(result == pdPASS)
{
		printf("读取到二值信号量\r\n");
}

6.1 串口中断采用二值信号量同步

二值信号量可以在某个特殊的中断发生,让任务解除阻塞

串口中给出二值信号量代码(中断中需要使用 xSemaphoreGiveFromISR 函数)

串口初始化时,要将抢占优先级设置成大于等于5,因为在串口中断里使用了FreeRTOS的函数操作

/*
	串口初始化时,要将抢占优先级设置成大于等于5,因为在串口中断里使用了FreeRTOS的操作
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口2中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=9;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;		//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化NVIC寄存器
*/

int USART2_IRQHandler(void)                	//串口2中断服务程序
{
	uint8_t r;
	BaseType_t xHigherPriorityTaskWoken;
	
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)   //接收中断
	{
		USART_ClearFlag(USART2, USART_FLAG_RXNE);
		r = USART2->DR;        //读取接收到的数据
		
		//......
		//......
		
		xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);  //给出二值信号量,解除UWB任务
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
	}
}

二、一些细节

1. 串口中断里不要加 printf,不然会导致大量数据收不到,因为printf会占用比较多的资源;
2. 串口接收大量数据时,可以接收完一帧数据后,再给出用二值信号量,唤醒等待信号量的任务,再进行数据处理;
3. 串口接收大量数据时,也可以使用消息队列,数据帧判断和处理在任务中进行,消息队列要设置的大一些,防止满了而丢失数据;
4. 在main函数里初始化NVIC:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

你可能感兴趣的:(FreeRTOS,FreeRTOS,嵌入式硬件,STM32,RTOS,学习记录)