跟着韦东山老师FreeRTOS教学资料的学习记录
FreeRTOS全部项目代码链接(更新中)
https://gitee.com/chenshao777/free-rtos_-study
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 的区别
举例:创建多个任务
#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(); //开启任务调度
}
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, ¶m, 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);