1.FreeRTOS实时系统

目录

FreeRTOS实时系统

一.FreeRTOS介绍

1.什么是FreeRTOS?

free即免费,RTOS的全称是Real time operating system,即实时操作系统。

注意:RTOS不是指一个确定的系统,而是指一类的系统。比如:us/OS,FreeOS,RTX,RT-Thread等,这些都是RTOS类操作系统。

FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间
管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需
要。
由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少
数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系
统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植
到各种单片机上运行,其最新版本为10.4.4版。
(以上来自百度百科)

2.为什么选择 FreeRTOS ?

FreeRTOS 是免费的;

很多半导体厂商产品的SDK(Software Development Kit)软件开发工具包,就使用FreeRTOS作为其

操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块。

简单,因为FreeRTOS的文件数量很少。

二、FreeRTOS移植到STM32芯片上

使用Cubemx快速移植,

创建一个模板项目

打开mcu

选中芯片

选中system core

配置SYS

配置RCC

配置串口,用于打印调试

选中FreeRTOS系统

项目生成配置

选择generate code,生成代码

选择open project

在usart.c中加入

/* USER CODE BEGIN 0 */
#include "stdio.h"
int fputc(int ch, FILE *f)
{
    unsigned char temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,0xffff);
    return ch;
}
/* USER CODE END 0 */

在main中测试是否打印到串口上

#include "stdio.h"
/* USER CODE BEGIN 2 */
  printf("hello world!\r\n");
/* USER CODE END 2 */

一些常见问题:

1.Timebase source为什么不能设置为systick?

裸机版的时钟默认是SysTick,但是开启FreeRTOS后,FreeRTOS会占用SysTick(用来生成1ms的定时用于任务调度),所以需要为其他总线提供另外的时钟源。

2.FreeRTOS版本问题

V2的内核版本更高,更能更多,多数情况下V1版本内核完全够用

3.FreeRTOS 各配置选项卡的解释

  • Events:事件相关的创建
  • Task and Queues: 任务与队列的创建
  • Timers and Semaphores: 定时器和信号量的创建
  • Mutexes: 互斥量的创建
  • FreeRTOS Heap Usage: 用于查看堆使用情况
  • config parameters: 内核参数设置,用户根据自己的实际应用来裁剪定制FreeRTOS 内核
  • Include parameters: FreeRTOS 部分函数的使能
  • User Constants: 相关宏的定义,可以自建一些常量在工程中使用
  • Advanced settings:高级设置

4. 内核配置、函数使能的一些翻译

三.任务的创建与删除

1.什么是任务?

任务可以理解为进程或者是一个任务,会在内存开辟一个空间用于执行任务。

比如,在电脑打开记事本,微信等都是一个任务。

任务通常含有一个while(1)死循环

2.任务创建与删除相关函数

函数名称 函数作用
xTaskCreate() 动态方式创建任务
xTaskCreateStatic() 静态方式创建任务
xTaskDelete() 删除任务

动态创建与静态创建的区别:

动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。

大多数情况使用动态创建任务。

xTaskCreate()函数原型

  1. pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);2. pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;
  2. pvParameters:指定的任务栈的大小;
  3. uxPriority:任务优先级,数值越大,优先级越大;
  4. pxCreatedTask:用于返回已创建任务的句柄可以被引用。
返回值 描述
PdPass 任务创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 任务创建失败

官方案例:

/* Task to be created. */
void vTaskCode( void * pvParameters )
{
	/* The parameter value is expected to be 1 as 1 is passed in the
	pvParameters value in the call to xTaskCreate() below.
	configASSERT( ( ( uint32_t ) pvParameters ) == 1 );
	for( ;; )
	{
	/* Task code goes here. */
	}
}
/* Function that creates a task. */
void vOtherFunction( void )
{
	BaseType_t xReturned;
	TaskHandle_t xHandle = NULL;
	/* Create the task, storing the handle. */
	xReturned = xTaskCreate(
        vTaskCode, /* Function that implements the task. */
        "NAME", /* Text name for the task. */
        STACK_SIZE, /* Stack size in words, not bytes. */
        ( void * ) 1, /* Parameter passed into the task. */
        tskIDLE_PRIORITY,/* Priority at which the task is created. */
        &xHandle ); /* Used to pass out the created task's handle.
        */
	if( xReturned == pdPASS )
	{
        /* The task was created. Use the task's handle to delete the task. */
        vTaskDelete( xHandle );
	}
}

vTaskDelete函数原型:

void vTaskDelete(TaskHandle_t xTaskToDelete);

只需将待删除的任务句柄传入该函数,即可将该任务删除。

当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。

3.实现

四、任务调度

什么是任务调度器?

调度器就是使用相关的调度算法来决定当前执行到哪个任务了

FreeRTOS中任务调度器的函数是vTaskStartScheduler(),在FreeRTOS被封装为osKernelStart()

调度器规则:

FreeRTOS是一个实时操作系统,需要遵循一些调度规则:

  1. 抢占式调度:高优先级的抢占低优先级的,永远都是高优先级的优先执行
  2. 时间片调度:同等优先级的,谁先抢占到CPU资源,谁先执行。

抢占式调度运行过程

时间片调度运行过程

任务的状态

FreeRTOS中有4种状态:

  • Running运行态:当前任务正在使用CPU的资源,任务正在执行。
  • Ready就绪态:当前任务等待CPU的调度,任务等待运行。
  • Blocked阻塞态:当前任务因为一些延时、或等待信号量、消息队列、事件标记组而处于一个阻塞的状态。
  • Suspended挂起态:类似暂停,通过调用vTaskSuspend()对指定的任务进行挂起。需用通过调用vTaskResume()才能恢复。

总结:

  1. 只有就绪态才能变为运行态
  2. 其他状态只有调用指定的函数,先成功就绪态才能变为运行态。

任务小实验:

需求

建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:

taskLED1:间隔 500ms 闪烁 LED1;

taskLED2:间隔 1000ms 闪烁 LED2;

taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;

taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2

cubeMX配置

代码实现:

/* USER CODE BEGIN Header_StartTaskLED1 */
/**
  * @brief  Function implementing the taskLED1 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTaskLED1 */
void StartTaskLED1(void const * argument)
{
  /* USER CODE BEGIN StartTaskLED1 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
    osDelay(500);
  }
  /* USER CODE END StartTaskLED1 */
}

/* USER CODE BEGIN Header_StartTaskLED2 */
/**
* @brief Function implementing the taskLED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
  /* USER CODE BEGIN StartTaskLED2 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
    osDelay(1000);
  }
  /* USER CODE END StartTaskLED2 */
}

/* USER CODE BEGIN Header_StartTaskKEY1 */
/**
* @brief Function implementing the taskKEY1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskKEY1 */
void StartTaskKEY1(void const * argument)
{
  /* USER CODE BEGIN StartTaskKEY1 */
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            printf("KEY1按下了!\r\n");
            if(taskLED1Handle == NULL)
            {
                printf("任务1不存在,开始创建\r\n");
                osThreadDef(taskLED1, StartTaskLED1, osPriorityNormal, 0, 128);
                taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);
                if(taskLED1Handle != NULL)
                    printf("任务1创建成功\r\n");
            }
            else
            {
                osThreadTerminate(taskLED1Handle);
                taskLED1Handle = NULL;
                printf("任务1已删除\r\n");
            }
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskKEY1 */
}

/* USER CODE BEGIN Header_StartTaskKEY2 */
/**
* @brief Function implementing the taskKEY2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskKEY2 */
void StartTaskKEY2(void const * argument)
{
  /* USER CODE BEGIN StartTaskKEY2 */
    
    static int flag = 0;
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            if(flag == 0)
            {
                osThreadSuspend(taskLED2Handle);
                printf("任务2已暂停\r\n");
                flag = 1;
            }
            else
            {
                osThreadResume(taskLED2Handle);
                printf("任务2已恢复\r\n");
                flag = 0;
            }
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskKEY2 */
}

五、队列

什么队列?

队列又称消息队列,是任务通信的一种数据结构,队列可以在任务与任务之间中断和任务间的传递消息。

为啥不能使用全局变量呢?

全局的变量,在多任务修改的情况下,获取的不是最新的值,容易导致数据的错读。

队列的特点

1.入队出队方式

通常先进先出(FIFO),数据先进去的先被读取。

还有一种后进先出(LIFO),比较少用,一般有于栈。

2.数据传递方式

采用实际值传递,则将数据拷贝到队列进行传递,也可以使用指针,在数据比较大的时候通常采用指针传递。

3、多任务访问

队列不属于某个任务,任何任务都可以使用队列发送跟接收数据。

4、出队、入队阻塞

当向一个队列发送消息是,可以指定一个阻塞时间,假设次数当队列已满无法入队时,可以设置一个阻塞时间:

  • 0:直接返回,不阻塞。
  • 0-portMAX_DELAY:设置指定的时间,超过该时间还没进入队列直接返回。
  • portMAX_DELAY:死等,等到队列有空闲位置为止。

队列相关API

1.创建队列

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );

参数:

  • uxQueueLength:队列的可容纳的长度
  • uxItemSize:一个队列容纳的大小

返回值:

如何一个队列创建成功,会分配到内存,反之失败则为NULL

2.写队列API

函数 描述
xQueueSend() 往队列的尾部写入消息
xQueueSendToBack() 同xQueueSend()
xQueueSendToFront() 往队列的头部写入消息
xQueueOverwrite() 覆写消息队列(只能用于长度1的情况)
xQueueSendFromISR() 在中断中往消息队列尾部写入消息
xQueueSendToBackFromISR() 同xQueueSendFromISR()
xQueueSendToFrontFromISR() 在中断中往队列的头部写入消息
xQueueOverwriteFromISR() 在中断中覆写消息队列(只能用于长度1的情况)
BaseType_t xQueueSend(
    QueueHandle_t xQueue,
    const void * pvItemToQueue,
    TickType_t xTicksToWait
);

参数:

  • xQueue:队列的句柄,数据项将发送到此队列。
  • pvItemToQueue:待写入数据
  • xTicksToWait:阻塞超时时间

返回值:

如果成功写入数据,返回 pdTRUE,否则返回 errQUEUE_FULL。

3、读队列API

函数 描述
xQueueReceive() 从队列头部读取消息,并删除消息
xQueuePeek() 从队列头部读取消息,但并不删除消息
xQueueReceiveFromISR() 在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR() 在中断中从队列头部读取消息,但并不删除消息
BaseType_t xQueueReceive(
    QueueHandle_t xQueue,
    void *pvBuffer,
    TickType_t xTicksToWait
);

参数:

  • xQueue:待读取的队列
  • pvItemToQueue:数据读取缓冲区
  • xTicksToWait:阻塞超时时间

返回值:

成功返回 pdTRUE,否则返回 pdFALSE。

实验:

创建一个队列,按下 KEY1 向队列发送数据,按下 KEY2 向队列读取数据。

cubeMX配置

代码实现

/* USER CODE BEGIN Header_StartTaskSend */
/**
  * @brief  Function implementing the taskSend thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
  /* USER CODE BEGIN StartTaskSend */
    uint16_t buf = 100;
    BaseType_t status;
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET)
        {
            status = xQueueSend(myQueue01Handle,&buf,0);
            if(status == pdTRUE)
                printf("数据发送成功:%d \r\n",buf);
            else
                printf("数据发送失败\r\n");
        }
        while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskSend */
}

/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the taskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{
  /* USER CODE BEGIN StartTaskReceive */
    uint16_t buf = 100;
    BaseType_t status;
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET)
        {
            status = xQueueReceive(myQueue01Handle,&buf,0);
            if(status == pdTRUE)
                printf("数据接收成功:%d \r\n",buf);
            else
                printf("数据接收失败\r\n");
        }
        while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskReceive */
}

六、二值信号量

什么是信号量?

信号量(semaphore)实在多任务环境下使用的一种机制,是可以用来保证两个或者多个关键代码段不被并发调用。

信号量可以拆分为信号和量,信号起到了通知的作用,而量表示资源的数量,当我们的量只有0和1的时候,我们称为二值信号量,只有两种状态,当我们那个量没有限制时,称为计数型信号量。

什么是二值信号量?

二值信号量其实是一个长度为1,大小为1的队列,只有0和1两种状态,通常用它进行互斥访问或任务同步。

二值信号量API

函数 描述
xSemaphoreCreateBinary() 使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic() 使用静态方式创建二值信号量
xSemaphoreGive() 释放信号量
xSemaphoreGiveFromISR() 在中断释放信号量
xSemaphoreTake() 获取信号量
xSemaphoreTakeFromISR() 在中断获取信号量

创建二值信号量

SemaphoreHandle_t xSemaphoreCreateBinary( void )

参数:

返回值:

成功,返回对应二值信号量的句柄;

失败,返回 NULL 。

释放二值信号量

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )

参数:

xSemaphore:要释放的信号量句柄

返回值:

成功,返回 pdPASS ;

失败,返回 errQUEUE_FULL 。

获取二值信号量

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );

参数:

xSemaphore:要获取的信号量句柄

xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;

返回值:

成功,返回 pdPASS ;

失败,返回 errQUEUE_FULL 。

实现:

创建一个二值信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。

cubeMX配置

代码实现

  osSemaphoreDef(myBinarySem);
  //myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);//使用这个默认会创建一个二值信号量
    myBinarySemHandle = xSemaphoreCreateBinary();//需要手动创建



/* USER CODE BEGIN Header_StartTaskSemaphoreGive */
/**
  * @brief  Function implementing the taskSemaphoreGi thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTaskSemaphoreGive */
void StartTaskSemaphoreGive(void const * argument)
{
  /* USER CODE BEGIN StartTaskSemaphoreGive */
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            if(xSemaphoreGive(myBinarySemHandle) == pdTRUE)
                printf("二极信号放入成功\r\n");
            else
                printf("二极信号放入失败\r\n");
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskSemaphoreGive */
}

/* USER CODE BEGIN Header_StartTaskSemaphoreTake */
/**
* @brief Function implementing the taskSemaphoreTa thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSemaphoreTake */
void StartTaskSemaphoreTake(void const * argument)
{
  /* USER CODE BEGIN StartTaskSemaphoreTake */
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            if(xSemaphoreTake(myBinarySemHandle, 0) == pdTRUE)
                printf("二极信号获取成功\r\n");
            else
                printf("二极信号获取失败\r\n");
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskSemaphoreTake */
}

七、计数型信号量

计量型信号量相当于长度大于1的队列,能容纳更多资源,在创建时确定了长度大小。

应用场景:

一个停车场有固定的停车位,当一辆车开进来时,车位减一,一辆车开走时,车位加一,当车位满了,表示队列装满了,此时再进来只能等待或者是开走。

计数型相关API函数

函数 描述
xSemaphoreCreateCounting() 使用动态方式创建计数型信号量
xSemaphoreCreateCountingStatic() 使用静态方式创建计数型信号量
xSemaphoreGetCount() 获取计数型信号量

计数型信号量的释放和获取与二值信号量完全相同 !

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount);

参数:

uxMaxCount:可以达到的最大计数值

uxInitialCount:创建信号量时分配给信号量的计数值

返回值:

成功,返回对应计数型信号量的句柄;

失败,返回 NULL 。

实现案例:

创建一个计数型信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。

cubeMX配置

USE_COUNTING_SEMAPHORES的值从Disabled改成Enabled

代码实现

/* USER CODE BEGIN Header_StartTaskSemaphoreGive */
/**
  * @brief  Function implementing the taskSemaphoreGi thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTaskSemaphoreGive */
void StartTaskSemaphoreGive(void const * argument)
{
  /* USER CODE BEGIN StartTaskSemaphoreGive */
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            if(xSemaphoreGive(myCountingSemaHandle) == pdTRUE)
                printf("计数信号量放入成功\r\n");
            else
                printf("计数信号量放入失败\r\n");
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskSemaphoreGive */
}

/* USER CODE BEGIN Header_StartTaskSemaphoreTake */
/**
* @brief Function implementing the taskSemaphoreTa thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSemaphoreTake */
void StartTaskSemaphoreTake(void const * argument)
{
  /* USER CODE BEGIN StartTaskSemaphoreTake */
  /* Infinite loop */
  for(;;)
  {
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            if(xSemaphoreTake(myCountingSemaHandle, 0) == pdTRUE)
                printf("计数信号量获取成功\r\n");
            else
                printf("计数信号量获取失败\r\n");
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTaskSemaphoreTake */
}

八、互斥量

什么是互斥量?

在多数情况下,互斥型信号量与二值型信号量非常相似,但功能上,二值型信号量用于同步,而互斥型信号量用于资源保护。

互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象。

什么是优先级翻转?

如图上所示,当低优先级获得CPU资源时,还没执行完就会被高优先级的任务打断,如果执行重要的东西时,容易发送错误,所以互斥型信号量就起到作用了,低优先级 在获取到CPU资源时,不会被其他高优先级的任务打断,直到释放信号。

互斥量相关API

互斥信号量不能用于中断服务函数中!

函数 描述
xSemaphoreCreateMutex() 使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic() 使用静态方法创建互斥信号量。
SemaphoreHandle_t xSemaphoreCreateMutex( void )

参数:

返回值:

成功,返回对应互斥量的句柄;

失败,返回 NULL 。

实现

  1. 演示优先级翻转
  2. 使用互斥量优化优先级翻转问题

cubeMX配置

代码实现

创建了三个任务StartTaskH、StartTaskM、StartTaskL

/* USER CODE BEGIN Header_StartTaskH */
/**
  * @brief  Function implementing the TaskH thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTaskH */
void StartTaskH(void const * argument)
{
  /* USER CODE BEGIN StartTaskH */
  /* Infinite loop */
  for(;;)
  {
    xSemaphoreTake(myMutexHandle,portMAX_DELAY);
    printf("TaskH:我在上厕所...\r\n");
    HAL_Delay(1000);
    printf("TaskH:我出来了...\r\n");
    xSemaphoreGive(myMutexHandle);
    osDelay(1000);
  }
  /* USER CODE END StartTaskH */
}

/* USER CODE BEGIN Header_StartTaskM */
/**
* @brief Function implementing the TaskM thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskM */
void StartTaskM(void const * argument)
{
  /* USER CODE BEGIN StartTaskM */
  /* Infinite loop */
  for(;;)
  {
      printf("TaskM:我占用CPU资源\r\n");
    osDelay(1000);
  }
  /* USER CODE END StartTaskM */
}

/* USER CODE BEGIN Header_StartTaskL */
/**
* @brief Function implementing the TaskL thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskL */
void StartTaskL(void const * argument)
{
  /* USER CODE BEGIN StartTaskL */
  /* Infinite loop */
  for(;;)
  {
    xSemaphoreTake(myMutexHandle,portMAX_DELAY);
    printf("TaskL:我在上厕所...\r\n");
    HAL_Delay(3000);
    printf("TaskL:我出来了...\r\n");
    xSemaphoreGive(myMutexHandle);
    osDelay(1000);
  }
  /* USER CODE END StartTaskL */
}

九、事件标志组

什么是事件标志组?

事件标志位:表示某件事是否发生,联想:全局变量flag,通常每个位表示一个事件(高8位不算)

事件标志组:是一组事件标志位的集合,也就是一个整数

事件标志组本质是一个 16 位或 32 位无符号的数据类型 EventBits_t ,由 configUSE_16_BIT_TICKS 决定。

虽然使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高8位用作存储事件标志组的控制信息,

低 24 位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!

事件标志组API

函数 描述
xEventGroupCreate() 使用动态方式创建事件标志组
xEventGroupCreateStatic() 使用静态方式创建事件标志组
xEventGroupClearBits() 清零事件标志位
xEventGroupClearBitsFromISR() 在中断中清零事件标志位
xEventGroupSetBits() 设置事件标志位
xEventGroupSetBitsFromISR() 在中断中设置事件标志位
xEventGroupWaitBits() 等待事件标志位
1.创建事件标志组
EventGroupHandle_t xEventGroupCreate( void );

参数:

返回值:

成功,返回对应事件标志组的handle;

失败,返回 NULL 。

2.设置事件标志位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );

参数:

xEventGroup:对应事件组句柄。

uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。

返回值:

设置之后事件组中的事件标志位值。

3.清除事件标志位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );

参数:

xEventGroup:对应事件组句柄。

uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。

返回值:

清零之前事件组中事件标志位的值。

4.等待事件标志位
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );

参数:

xEventGroup:对应的事件标志组handle

uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值

xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除

xWaitForAllBits:pdTRUE——所有等待事件位全为1(逻辑与),pdFALSE——等待的事件位有一个为1

(逻辑或)

xTicksToWait:超时

返回值:

等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位

其他值:等待事件标志位失败,返回事件组中的事件标志位

实现:

创建一个事件标志组和两个任务( task1 和 task2),task1 检测按键,如果检测到 KEY1 和 KEY2 都按过,
则执行 task2 。

/* USER CODE BEGIN Variables */
EventGroupHandle_t eventgroup_handle;
/* USER CODE END Variables */

/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
eventgroup_handle = xEventGroupCreate();
/* USER CODE END RTOS_THREADS */


/* USER CODE BEGIN Header_StartTask1 */
/**
  * @brief  Function implementing the Task1 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask1 */
void StartTask1(void const * argument)
{
  /* USER CODE BEGIN StartTask1 */
  /* Infinite loop */
  for(;;)
  {

    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            xEventGroupSetBits(eventgroup_handle, 0x01);
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
    }
    
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
    {
        osDelay(20);
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            xEventGroupSetBits(eventgroup_handle, 0x02);
        }
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
    }
    osDelay(10);
  }
  /* USER CODE END StartTask1 */
}

/* USER CODE BEGIN Header_StartTask2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask2 */
void StartTask2(void const * argument)
{
  /* USER CODE BEGIN StartTask2 */
    EventBits_t result;
  /* Infinite loop */
  for(;;)
  {
      
    result = xEventGroupWaitBits(eventgroup_handle, 0x01 | 0x02 , pdTRUE, pdFALSE , portMAX_DELAY);
    printf("同意请假了:%#x \r\n",result);
    osDelay(1);
  }
  /* USER CODE END StartTask2 */
}

十、任务通知

什么是任务通知?

FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS

官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队

列)。

在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可

以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!

任务通知值的更新方式

FreeRTOS 提供以下几种方式发送通知给任务 :

  • 发送消息给任务,如果有通知未读, 不覆盖通知值
  • 发送消息给任务,直接覆盖通知值
  • 发送消息给任务,设置通知值的一个或者多个位
  • 发送消息给任务,递增通知值

通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。

任务通知的优势和劣势

任务通知的优势

  1. 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
  2. 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。

任务通知的劣势

  1. 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
  2. 通知只能一对一,因为通知必须指定任务。
  3. 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
  4. 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数

据。

任务通知相关API函数

1.发送通知

函数 描述
xTaskNotify() 发送通知,带有通知值
xTaskNotifyAndQuery() 发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGive() 发送通知,不带通知值
xTaskNotifyFromISR() 在中断中发送通知,带有通知值
xTaskNotifyAndQueryFromISR() 在中断中发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGiveFromISR() 在中断中发送通知,不带通知值
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );

参数:

xTaskToNotify:需要接收通知的任务handle;

ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;

eAction:一个枚举,代表如何使用任务通知的值;

枚举值 描述
eNoAction 发送通知,但不更新值(参数ulValue未使用)
eSetBits 被通知任务的通知值按位或ulValue。(某些场景下可代替事件组, 效率更高)
eIncrement 被通知任务的通知值增加1(参数ulValue未使用),相当于 xTaskNotifyGive
eSetValueWithOverWrite 被通知任务的通知值设置为 ulValue。(某些场景下可代替 xQueueOverwrite ,效率更高)
eSetValueWithoutOverwrite 如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。如果被通知任务没有取走上一个通知,又接收到了一个通知,则这次通知值丢弃,在这种情况下视为调用失败并返回 pdFALSE(某些场景下可代替 xQueueSend ,效率更高)

返回值:

如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他

情况均返回pdPASS。

BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );

参数:

xTaskToNotify:需要接收通知的任务handle;

ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;

eAction:一个枚举,代表如何使用任务通知的值;

pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价

于函数 xTaskNotify()。

返回值:

如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他

情况均返回pdPASS。

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

参数:

xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。

返回值:

总是返回 pdPASS。

2.等待通知

等待通知API函数只能用在任务,不可应用于中断中!

函数 描述
ulTaskNotifyTake() 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当 任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。
xTaskNotifyWait() 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );

参数:

xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零(二值信号

量);pdFALSE:把通知值减一(计数型信号量);

xTicksToWait:阻塞等待任务通知值的最大时间;

返回值:

0:接收失败

非0:接收成功,返回任务通知的通知值

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );

ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。

ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被

保存到形参*pulNotificationValue 中。

pulNotificationValue:用于保存接收到的任务通知值。 如果 不需要使用,则设置为 NULL 即可 。

xTicksToWait:等待消息通知的最大等待时间。

实现:

  1. 模拟二值信号量

    /* USER CODE BEGIN Header_StartTaskSend */
    /**
      * @brief  Function implementing the TaskSend thread.
      * @param  argument: Not used
      * @retval None
      */
    /* USER CODE END Header_StartTaskSend */
    void StartTaskSend(void const * argument)
    {
      /* USER CODE BEGIN StartTaskSend */
      /* Infinite loop */
      for(;;)
      {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
            {
                xTaskNotifyGive(TaskReceiveHandle);
                printf("任务通知:二值信号量数据发送成功! \r\n");
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
        }
        osDelay(10);
      }
      /* USER CODE END StartTaskSend */
    }
    
    /* USER CODE BEGIN Header_StartTaskReceive */
    /**
    * @brief Function implementing the TaskReceive thread.
    * @param argument: Not used
    * @retval None
    */
    /* USER CODE END Header_StartTaskReceive */
    void StartTaskReceive(void const * argument)
    {
      /* USER CODE BEGIN StartTaskReceive */
      uint32_t rev = 0;
      /* Infinite loop */
      for(;;)
      {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
            {
                rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
                if(rev != 0)
                    printf("任务通知:二值信号量数据接收成功!rev=%d \r\n",rev);
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
        }
        osDelay(10);
      }
      /* USER CODE END StartTaskReceive */
    }
    
  2. 模拟计数型信号量

    /* USER CODE BEGIN Header_StartTaskSend */
    /**
      * @brief  Function implementing the TaskSend thread.
      * @param  argument: Not used
      * @retval None
      */
    /* USER CODE END Header_StartTaskSend */
    void StartTaskSend(void const * argument)
    {
      /* USER CODE BEGIN StartTaskSend */
      /* Infinite loop */
      for(;;)
      {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
            {
                xTaskNotifyGive(TaskReceiveHandle);
                printf("任务通知:计数信号量数据发送成功! \r\n");
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
        }
        osDelay(10);
      }
      /* USER CODE END StartTaskSend */
    }
    
    /* USER CODE BEGIN Header_StartTaskReceive */
    /**
    * @brief Function implementing the TaskReceive thread.
    * @param argument: Not used
    * @retval None
    */
    /* USER CODE END Header_StartTaskReceive */
    void StartTaskReceive(void const * argument)
    {
      /* USER CODE BEGIN StartTaskReceive */
      uint32_t rev = 0;
      /* Infinite loop */
      for(;;)
      {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
            {
                rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
                if(rev != 0)
                    printf("任务通知:计数信号量数据接收成功!rev=%d \r\n",rev);
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
        }
        osDelay(10);
      }
      /* USER CODE END StartTaskReceive */
    }
    
  3. 模拟事件标志组

    /* USER CODE BEGIN Header_StartTaskSend */
    /**
      * @brief  Function implementing the TaskSend thread.
      * @param  argument: Not used
      * @retval None
      */
    /* USER CODE END Header_StartTaskSend */
    void StartTaskSend(void const * argument)
    {
      /* USER CODE BEGIN StartTaskSend */
      /* Infinite loop */
      for(;;)
      {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
            {
                printf("按下了KEY1,发送0x01\r\n");
                xTaskNotify(TaskReceiveHandle,0x01,eSetBits);
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
        }
        
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
            {
                printf("按下了KEY2,发送0x02\r\n");
                xTaskNotify(TaskReceiveHandle,0x02,eSetBits);
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
        }
        osDelay(10);
      }
      /* USER CODE END StartTaskSend */
    }
    
    /* USER CODE BEGIN Header_StartTaskReceive */
    /**
    * @brief Function implementing the TaskReceive thread.
    * @param argument: Not used
    * @retval None
    */
    /* USER CODE END Header_StartTaskReceive */
    void StartTaskReceive(void const * argument)
    {
      /* USER CODE BEGIN StartTaskReceive */
      uint32_t notify_val = 0, event_bit = 0;
      /* Infinite loop */
      for(;;)
      {
        xTaskNotifyWait(0,0xFFFFFFFF,&notify_val, portMAX_DELAY);
        if(notify_val & 0x01)
            event_bit |= 0x01;
        if(notify_val & 0x02)
            event_bit |= 0x02;
        
        if(event_bit == (0x01 | 0x02))
        {
            event_bit = 0;
            printf("请假成功\r\n");
        }
    
        osDelay(10);
      }
      /* USER CODE END StartTaskReceive */
    }
    
  4. 模拟邮箱

    /* USER CODE BEGIN Header_StartTaskSend */
    /**
      * @brief  Function implementing the TaskSend thread.
      * @param  argument: Not used
      * @retval None
      */
    /* USER CODE END Header_StartTaskSend */
    void StartTaskSend(void const * argument)
    {
      /* USER CODE BEGIN StartTaskSend */
      /* Infinite loop */
      for(;;)
      {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET)
            {
                printf("按下了KEY1\r\n");
                xTaskNotify(TaskReceiveHandle,1,eSetValueWithOverwrite);
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET);
        }
        
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
        {
            osDelay(20);
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET)
            {
                printf("按下了KEY2\r\n");
                xTaskNotify(TaskReceiveHandle,0x02,eSetValueWithOverwrite);
            }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == RESET);
        }
        osDelay(10);
      }
      /* USER CODE END StartTaskSend */
    }
    
    /* USER CODE BEGIN Header_StartTaskReceive */
    /**
    * @brief Function implementing the TaskReceive thread.
    * @param argument: Not used
    * @retval None
    */
    /* USER CODE END Header_StartTaskReceive */
    void StartTaskReceive(void const * argument)
    {
      /* USER CODE BEGIN StartTaskReceive */
      uint32_t notify_val = 0;
      /* Infinite loop */
      for(;;)
      {
        xTaskNotifyWait(0,0xFFFFFFFF,&notify_val, portMAX_DELAY);
        printf("接收到的通知值为:%d \r\n",notify_val);
        osDelay(10);
      }
      /* USER CODE END StartTaskReceive */
    }
    

十一、延时函数

什么是延时函数?

延时函数是延迟执行

延时函数分类

相对延时:vtaskDelay

不能在延迟时间中发生中断

绝对延时:vTaskDelayUntil

延时时间能在中断中,直到延时结束

vtaskDelay与HAL_DELAY区别

vTaskDelay 作用是让任务阻塞,任务阻塞后,RTOS系统调用其它处于就绪状态的优先级最高的任务来执

行。

HAL_Delay 一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出,故其占用了全部CPU时

间。

十二、软件定时器

什么是定时器?

简单可以理解为闹钟,到达指定一段时间后,就会响铃。

STM32 芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成 PWM 、输入捕获、输出

比较,等等,功能强大,但是由于硬件的限制,个数有限。

软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理信息。

软件定时器优缺点

优点:

  1. 简单、成本低;
  2. 只要内存足够,可创建多个;

缺点:

精度较低,容易受中断影响。在大多数情况下够用,但对于精度要求比较高的场合不建议使用。

软件定时器原理

定时器是一个可选的、不属于 FreeRTOS 内核的功能,它是由定时器服务任务来提供的。

在调用函数 vTaskStartScheduler() 开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个

任务就叫做软件定时器服务任务。

  1. 负责软件定时器超时的逻辑判断
  2. 调用超时软件定时器的超时回调函数
  3. 处理软件定时器命令队列

FreeRTOS提供了很多定时器有关的API函数,这些API函数大多都使用FreeRTOS的队列发送命令给定时器服

务任务。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS的软件定时器使用的,用户不能直接访问

软件定时器相关配置

软件定时器有一个定时器服务任务和定时器命令队列,这两个东西肯定是要配置的,相关的配置也是放到文

件FreeRTOSConfig.h中的,涉及到的配置如下:

1、configUSE_TIMERS

如果要使用软件定时器的话宏configUSE_TIMERS一定要设置为1,当设置为1的话定时器服务任务就会在启

动FreeRTOS调度器的时候自动创建。

2、configTIMER_TASK_PRIORITY

设置软件定时器服务任务的任务优先级,可以为0~(configMAX_PRIORITIES-1)。优先级一定要根据实际的应

用要求来设置。如果定时器服务任务的优先级设置的高的话,定时器命令队列中的命令和定时器回调函数就

会及时的得到处理。

3、configTIMER_QUEUE_LENGTH

此宏用来设置定时器命令队列的队列长度。

4、configTIMER_TASK_STACK_DEPTH

此宏用来设置定时器服务任务的任务堆栈大小。

单次定时器和周期定时器

单次定时器: 只超时一次,调用一次回调函数。可手动再开启定时器;

周期定时器: 多次超时,多次调用回调函数。

软件定时器相关API函数

函数 描述
xTimerCreate() 动态方式创建软件定时器
xTimerCreateStatic() 静态方式创建软件定时器
xTimerStart() 开启软件定时器定时
xTimerStop() 停止软件定时器定时
xTimerReset() 复位软件定时器定时
xTimerChangePeriod() 更改软件定时器的定时超时时间
xTimerStartFromISR() 在中断中开启软件定时器定时
xTimerStopFromISR() 在中断中停止软件定时器定时
xTimerResetFromISR() 在中断中复位软件定时器定时
xTimerChangePeriodFromISR() 在中断中更改定时超时时间

1. 创建软件定时器

TimerHandle_t xTimerCreate
( const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );

参数:

pcTimerName:软件定时器名称

xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍。宏 pdMS_TO_TICKS() 可用于将以毫秒为单位

指定的时间转换为以 tick 为单位指定的时间。

uxAutoReload:定时器模式, pdTRUE:周期定时器, pdFALSE:单次定时器

pvTimerID:软件定时器 ID,用于多个软件定时器公用一个超时回调函数

pxCallbackFunction:软件定时器超时回调函数

返回值:

成功:定时器handle

失败:NULL

2. 开启软件定时器

BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xBlockTime );

参数:

xTimer:待开启的软件定时器的句柄

xTickToWait:发送命令到软件定时器命令队列的最大等待时间

返回值:

pdPASS:开启成功

pdFAIL:开启失败

3. 停止软件定时器

BaseType_t xTimerStop( TimerHandle_t xTimer,
TickType_t xBlockTime );

参数与返回值同上。

4. 复位软件定时器

BaseType_t xTimerReset( TimerHandle_t xTimer,
TickType_t xBlockTime );

参数与返回值同上。

该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时。

5. 更改软件定时器定时时间

xNewPeriod:新的定时超时时间,单位:系统时钟节拍。

其余参数与返回值同上。

实现:

创建两个定时器:

定时器1,周期定时器,每1秒打印一次 liangxu shuai

定时器2,单次定时器,启动后 2 秒打印一次 laochen shuai

cubeMX配置

software timer ope修改USE_TIMERS的值为Enable

代码实现:

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  osTimerStart(myTimer01Handle, 1000);
  //xTimerChangePeriod(myTimer01Handle,pdMS_TO_TICKS(1000),0); //使用原始的时钟节拍 pdMS_TO_TICKS ,单位为毫秒
  osTimerStart(myTimer02Handle, 2000);
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}

/* Callback01 function */
void Callback01(void const * argument)
{
  /* USER CODE BEGIN Callback01 */
    printf("callback01\r\n");
  /* USER CODE END Callback01 */
}

/* Callback02 function */
void Callback02(void const * argument)
{
  /* USER CODE BEGIN Callback02 */
    printf("callback02\r\n");
  /* USER CODE END Callback02 */
}

十三、中断管理

中断定义

中断(Interrupt)是一种由硬件或软件触发的信号,用于通知处理器需要立即处理某个事件。中断机制允许处理器暂停当前任务,转而去执行高优先级的任务(中断服务程序,ISR),处理完成后恢复原任务。

中断优先级

任何中断的优先级都大于任务!

在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的优先级并不是从 0~15 ,默认情况下它是从 5~15 ,0~4 这 5 个中断优先级不是 FreeRTOS 控制的(5是取决于 configMAX_SYSCALL_INTERRUPT_PRIORITY)。

相关注意

  1. 在中断中必需使用中断相关的函数;
  2. 中断服务函数运行时间越短越好。

实现

创建一个队列及一个任务,按下按键 KEY1 触发中断,在中断服务函数里向队列里发送数据,任务则阻塞接

收队列数据。

cubeMX配置

代码实现

stm32f1xx_it.c

/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 1 */


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    //往队列里面放入数据
    uint32_t send_data = 1;
    xQueueSendFromISR(myQueue01Handle,&send_data,NULL);
}
/* USER CODE END 1 */

freetos.c

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN StartDefaultTask */
    uint32_t rev;
  /* Infinite loop */
  for(;;)
  {
    if(xQueueReceive(myQueue01Handle, &rev, portMAX_DELAY) == pdTRUE)
        printf("rev = %d \r\n",rev);
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}
posted @ 2025-05-28 10:09  站着说话不腰疼  阅读(511)  评论(0)    收藏  举报