FreeRTOS学习篇九:任务通知

1.任务通知基本概念

FreeRTOS 从V8.2.0版本开始提供任务通知这个功能,每个任务都有一个32位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组, 也可以替代长度为1的队列(可以保存一个32位整数或指针值)。相对于以前使用FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。 按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等ICP通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用GCC编译器, -o2 优化级别),任务通知的使用无需创建队列。 想要使用任务通知,必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS设置为 1,其实默认是为1的, 所以任务通知是默认使能的。

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

  • 发送通知给任务, 如果有通知未读,不覆盖通知值。
  • 发送通知给任务,直接覆盖通知值。
  • 发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用。
  • 发送通知给任务,递增通知值,可以当做计数信号量使用。

通过对以上任务通知方式的合理使用,可以在一定场合下替代FreeRTOS的信号量,队列、事件组等。当然,凡是都有利弊,不然的话FreeRTOS还要内核的IPC通信机制干嘛,消息通知虽然处理更快,RAM开销更小,但也有以下限制 :

  • 只能有一个任务接收通知消息, 因为必须指定接收通知的任务。。
  • 只有等待通知的任务可以被阻塞, 发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。

《FreeRTOS内核实现与应用开发实战——基于STM32》 P361

(IPC,InterProcess Communication,进程间通信方式)

2.运作机制

  1. 任务被创建的时候,任务通知也会被初始化。
  2. 可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知。
  3. 只能在任务中可以等待通知,不能在中断中等待通知。
  4. 任务等待通知时处于阻塞状态,可以设置阻塞超时时间。

3.常用函数

①发送任务通知

FreeRTOS中发送任务通知的函数有6个,但实际只能完成三种发送操作,这三种操作又分为了带中断保护的和不带中断保护的。任务中发送任务通知的函数均是调用xTaskGenericNotify()函数来进行发送通知的。

a.xTaskNotifyGive()

在这里插入图片描述
该函数可以作为二值信号量和计数信号量的一种轻量型的实现,速度更快, 在这种情况下对象任务在等待任务通知的时候应该是使用函 数ulTaskNotifyTake() 而不是 xTaskNotifyWait() 。

/* 使用示例 */
/* 任务声明 */
static void prvTask1(void *pvParameters);
static void prvTask2(void *pvParameters);
/*定义任务句柄 */
static TaskHandle_t xTask1 = NULL;
static TaskHandle_t xTask2 = NULL;
/* 主函数:创建两个任务,然后开始任务调度 */
void main( void )
{
	xTaskCreate(prvTask1, "Task1", 200, NULL, tskIDLE_PRIORITY, &xTask1);
	xTaskCreate(prvTask2, "Task2", 200, NULL, tskIDLE_PRIORITY, &xTask2);
	vTaskStartScheduler();
}
static void prvTask1( void *pvParameters )
{
	for ( ;; ) {
		/* 向 prvTask2()发送一个任务通知,让其退出阻塞状态 */
		xTaskNotifyGive( xTask2 );

		/* 等待通知 */
		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
	}
}
static void prvTask2( void *pvParameters )
{
	for ( ;; ) {
		/* 等待通知 */
		ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
		/* 向 prvTask1()发送一个任务通知,让其退出阻塞状态 */
   		 xTaskNotifyGive( xTask1 );
	}
}

b.vTaskNotifyGiveFromISR()

在这里插入图片描述
vTaskNotifyGiveFromISR()是vTaskNotifyGive()的中断保护版本。

c.xTaskNotify()

在这里插入图片描述
在这里插入图片描述

/* 使用示例 */
/* 将任务 xTask1Handle 的任务通知值的位 8 设置为 1*/
xTaskNotify( xTask1Handle, ( 1UL << 8UL ), eSetBits );

/* 向任务 xTask2Handle 发送一个任务通知,有可能会解除该任务的阻塞状态,
但是并不会更新该任务自身的任务通知值 */
xTaskNotify( xTask2Handle, 0, eNoAction );

/* 向任务 xTask3Handle 发送一个任务通知并把该任务自身的任务通知值更新为 0x50,
尽管没有读取该任务的上一次的任务通知,也就是覆盖并写入任务通知 */
xTaskNotify( xTask3Handle, 0x50, eSetValueWithOverwrite );

/* 向任务 xTask4Handle 发送一个任务通知,并把该任务自身的任务通知值更新为 0xfff
但是并不会覆盖该任务之前接收到的任务通知值 */
if(xTaskNotify(xTask4Handle, 0xfff, eSetValueWithoutOverwrite)==pdPASS )
{
	/* 任务 xTask4Handle 的任务通知值已经更新 */
} else
{
	/* 任务 xTask4Handle 的任务通知值没有更新,即上一次的通知值还没有被取走*/
}

d.xTaskNotifyFromISR()

在这里插入图片描述

e.xTaskNotifyAndQuery()

xTaskNotifyAndQuery()与 xTaskNotify()很像,都是调用通用的任务通知发送函数xTaskGenericNotify()来实现通知的发送,不同的是多了一个附加的参数pulPreviousNotifyValue,该参数用于回传接收任务的上一个通知值。
在这里插入图片描述

/* 使用示例 */
uint32_t ulPreviousValue;
/* 设置对象任务 xTask1Handle 的任务通知值的位 8 为 1,
在更新位 8 的值之前把任务通知值回传存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQuery( xTask1Handle, ( 1UL << 8UL ), eSetBits,&ulPreviousValue );


/* 向对象任务 xTask2Handle 发送一个任务通知,有可能解除对象任务的阻塞状态
但是不更新对象任务的通知值,并将对象任务的通知值存储在变量 ulPreviousValue 中 */
xTaskNotifyAndQuery( xTask2Handle, 0, eNoAction, &ulPreviousValue );
/* 覆盖式设置对象任务的任务通知值为 0x50
且对象任务的任务通知值不用回传,则最后一个形参设置为 NULL */
xTaskNotifyAndQuery( xTask3Handle, 0x50, eSetValueWithOverwrite, NULL );

/* 设置对象任务的任务通知值为 0xfff,但是并不会覆盖对象任务通过
xTaskNotifyWait()和 ulTaskNotifyTake()这两个函数获取到的已经存在
的任务通知值。对象任务的前一个任务通知值存储在变量 ulPreviousValue 中*/
if ( xTaskNotifyAndQuery( xTask4Handle,0xfff,eSetValueWithoutOverwrite,
	&ulPreviousValue ) == pdPASS )
{
	/* 任务通知值已经更新 */
} else
{
	/* 任务通知值没有更新 */
}

f.xTaskNotifyAndQueryFromISR()

在这里插入图片描述

/* 使用示例 */
void vAnISR( void )
{
	/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE */
	BaseType_t xHigherPriorityTaskWoken = pdFALSE.
	uint32_t ulPreviousValue;

	/* 设置目标任务 xTask1Handle 的任务通知值的位 8 为 1
	在任务通知值的位 8 被更新之前把上一次的值存储在变量 ulPreviousValue 中*/
	xTaskNotifyAndQueryFromISR( xTask1Handle, ( 1UL << 8UL ),eSetBits,
		&ulPreviousValue,
		&xHigherPriorityTaskWoken );
	/* 如果任务 xTask1Handle 阻塞在任务通知上,那么现在已经被解锁进入就绪态
	如果其优先级比当前正在运行的任务的优先级高,则 xHigherPriorityTaskWoken
	会被设置为 pdRTUE,然后在中断退出前执行一次上下文切换,在中断退出后则去
	执行这个被唤醒的高优先级的任务 */
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

②获取任务通知

获取任务通知函数只能用在任务中,没有带中断保护版本,因此只有两个API函 数: ulTaskNotifyTake() 和
xTaskNotifyWait ()。前者是为代替二值信号量和计数信号量而专门设计的,它和发送通知API 函数 xTaskNotifyGive()、 vTaskNotifyGiveFromISR()配合使用;后者是全功能版的等待通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队列。
所有的获取任务通知 API 函数都带有指定阻塞超时时间参数,当任务因为等待通知而进入阻塞时,用来指定任务的阻塞时间,这些超时机制与 FreeRTOS 的消息队列、信号量、事件等的超时机制一致。

1、ulTaskNotifyTake():
在这里插入图片描述
当一个任务使用其自身的任务通知值作为二值信号量或者计数信号量时, 其他任务应该使用函数xTaskNotifyGive()或者xTaskNotify(( xTaskToNotify), ( 0 ), eIncrement )来向其发送信号量。 如果是在中断中,则应该使用他们的中断版本函数。

/* 使用示例 */
/* 中断 */
void vANInterruptHandler( void )
{
	BaseType_t xHigherPriorityTaskWoken;
	/* 清除中断 */
	prvClearInterruptSource();
	/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE
	如果调用 vTaskNotifyGiveFromISR()会解除 vHandlingTask 任务的阻塞状态,
	并且 vHandlingTask 任务的优先级高于当前处于运行状态的任务,
	则 xHigherPriorityTaskWoken 将会自动被设置为 pdTRUE */
	xHigherPriorityTaskWoken = pdFALSE;

	/* 发送任务通知,并解锁阻塞在该任务通知下的任务 */
	vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );

	/* 如果被解锁的任务优先级比当前运行的任务的优先级高
	则在中断退出前执行一次上下文切换,在中断退出后去执行
	刚刚被唤醒的优先级更高的任务*/
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

/* 任务:阻塞在一个任务通知上 */
void vHandlingTask( void *pvParameters )
{
	BaseType_t xEvent;
	for ( ;; ) {
		/* 一直阻塞(没有时间限制,所以没有必要检测函数的返回值)
		这里 RTOS 的任务通知值被用作二值信号量,所以在函数退出
		时,任务通知值要被清 0 。要注意的是真正的应用程序不应该
		无限期的阻塞*/
		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
		/* RTOS 任务通知被当作二值信号量使用当处理完所有的事情后继续等待下一个任务通知*/
		do {
			xEvent = xQueryPeripheral();
			if ( xEvent != NO_MORE_EVENTS ) {
				vProcessPeripheralEvent( xEvent );
			}
		} while ( xEvent != NO_MORE_EVENTS );
	}
}

2、xTaskNotifyWait():
在这里插入图片描述

/* 使用示例 */
void vAnEventProcessingTask(void* pvParameters)
{
	uint32_t ulNotifiedValue;
    for (;;){
        /* 等待通知 */
        xTaskNotifyWait(0x00, /* 在进入的时候不清除通知值的任何位 */
		ULONG_MAX, /* 在退出的时候复位通知值为 0 */
		&ulNotifiedValue, /* 任务通知值传递到变量ulNotifiedValue 中*/
		portMAX_DELAY ); /* 一直等待 */
        /* 根据任务通知值里面的各个位的值处理事情 */
		if ( ( ulNotifiedValue & 0x01 ) != 0 ) {
			/* 位 0 被置 1 */
			prvProcessBit0Event();
		}
        if ( ( ulNotifiedValue & 0x02 ) != 0 ) {
			/* 位 1 被置 1 */
			prvProcessBit0Event();
		}
        if ( ( ulNotifiedValue & 0x04 ) != 0 ) {
			/* 位 2 被置 1 */
			prvProcessBit0Event();
		}
    }
}

4.使用示例1-替代消息队列

示例:通过PA0的按键0来控制任务通知的发送,将任务通知发送到接收任务1;通过PA2的按键1来控制任务通知的发送,将任务通知发送到接收任务2;接收任务1、接收任务2等待任务通知的来临,接收到任务通知就往串口发送一组数据表示接收到通知了。

结果:
在这里插入图片描述
代码:

0、在FreeRTOSConfig.h配置文件中添加以下:

#define configSUPPORT_DYNAMIC_ALLOCATION 1

1、STM32F103C8_BSP.c

#include "stm32f10x.h"                  // Device header

void USART1_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStruct);
    
    USART_Cmd(USART1, ENABLE);
    
}
void KEY_GPIO_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void USART1_SendByte(uint8_t byte){
    USART_SendData(USART1, byte);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
uint32_t Serial_Pow(uint32_t X, uint8_t Y){
	uint32_t Result = 1;
	while(Y--)	
		Result *= X;
	return Result;
}
/* 取出数字各位的数并转为char形式数据来发送 */
void USART1_SendStringNumber(uint32_t Number, uint8_t Length)
{
	uint16_t i;
	for(i = 0; i < Length; i++) {
		/* Number=321 Length=3,则会将3、2、1的ASCII码依次发送出去 */
		USART1_SendByte(Number / Serial_Pow(10,Length - i - 1) % 10 + '0');
	}
}
void USART1_SendString(char* str)
{
    uint16_t i;
    for(i = 0; str[i] != '\0'; i++){
        USART1_SendByte(*(str+i));
    }
}

uint8_t KEY_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1){
        while(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1);
        return 1;
    }
    return 0;
}

2、main.c

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"

/* 定义任务句柄,任务通知需要使用到句柄 */
TaskHandle_t Receive1Task_Handle = NULL;
TaskHandle_t Receive2Task_Handle = NULL;
TaskHandle_t SendTask_Handle = NULL;
/* 声明任务 */
void Receive1Task(void);
void Receive2Task(void);
void SendTask(void);

static void STM32F103_Init(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    USART1_Init();
    KEY_GPIO_Init();
}
int main(void)
{
    STM32F103_Init();
    taskENTER_CRITICAL();
    
    xTaskCreate((TaskFunction_t)Receive1Task, "Receive1Task", 512, NULL, 2,
    (TaskHandle_t *)&Receive1Task_Handle);
    
    xTaskCreate((TaskFunction_t)Receive2Task, "Receive2Task", 512, NULL, 3,
    (TaskHandle_t *)&Receive2Task_Handle);
    
    xTaskCreate((TaskFunction_t)SendTask, "SendTask", 512, NULL, 4,
    (TaskHandle_t *)&SendTask_Handle);
    
    vTaskStartScheduler();
    taskEXIT_CRITICAL();
    while(1);
}

void Receive1Task(void)
{
    BaseType_t xReturn = pdTRUE;
    uint32_t r_num;
    while(1){
        /* 等待任务通知,一直等待 */
        xReturn=xTaskNotifyWait(0x0, 0xFFFFFFFF, &r_num, portMAX_DELAY);
        if(pdTRUE == xReturn){
            USART1_SendString("Receive1_Task Task notification is ");
            USART1_SendStringNumber(r_num, 1);
            USART1_SendString(".\n\n");
        }
    }
}
void Receive2Task(void)
{
    BaseType_t xReturn = pdTRUE;
    uint32_t r_num;
    while(1){
        /* 等待任务通知,一直等待 */
        xReturn=xTaskNotifyWait(0x0, 0xFFFFFFFF, &r_num, portMAX_DELAY);
        if(pdTRUE == xReturn){
            USART1_SendString("Receive2_Task Task notification is ");
            USART1_SendStringNumber(r_num, 1);
            USART1_SendString(".\n\n");
        }
    }
}
void SendTask(void)
{
    BaseType_t xReturn = pdPASS;
    uint32_t send1 = 1;
    uint32_t send2 = 2;
    while(1){
        if(KEY_Scan(GPIOA, GPIO_Pin_0) == 1){
            /* 往接收任务1发送任务通知 */
            xReturn = xTaskNotify(Receive1Task_Handle, send1, eSetValueWithOverwrite);
            if(pdTRUE == xReturn){
                USART1_SendString("KEY0 ===> Receive1Task_Handle : Task notification successfully released!\n");
            }
        }
        /* 往接收任务2发送任务通知 */
        if(KEY_Scan(GPIOA, GPIO_Pin_2) == 1){
            xReturn = xTaskNotify(Receive2Task_Handle, send2, eSetValueWithOverwrite);
            if(pdTRUE == xReturn){
                USART1_SendString("KEY1 ===> Receive2Task_Handle : Task notification successfully released!\n");
            }
        }
        vTaskDelay(20);
    }
}

5.使用示例2-替代二值信号量

示例:通过PA0的按键0来控制任务通知的发送,将任务通知发送到接收任务1;通过PA2的按键1来控制任务通知的发送,将任务通知发送到接收任务2;接收任务1、接收任务2等待任务通知的来临,接收到任务通知就往串口发送一组数据表示接收到通知了。

结果:

在这里插入图片描述

代码:

0、在FreeRTOSConfig.h的配置,和示例1一样。

1、STM32F103C8_BSP.c:和示例1一样

2、main.c

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"

/* 定义任务句柄,任务通知需要使用到句柄 */
TaskHandle_t Receive1Task_Handle = NULL;
TaskHandle_t Receive2Task_Handle = NULL;
TaskHandle_t SendTask_Handle = NULL;
/* 声明任务 */
void Receive1Task(void);
void Receive2Task(void);
void SendTask(void);

static void STM32F103_Init(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    USART1_Init();
    KEY_GPIO_Init();
    LED_GPIO_Init();
}
int main(void)
{
    STM32F103_Init();
    taskENTER_CRITICAL();
    
    xTaskCreate((TaskFunction_t)Receive1Task, "Receive1Task", 512, NULL, 2,
    (TaskHandle_t*)&Receive1Task_Handle);
    
    xTaskCreate((TaskFunction_t)Receive2Task, "Receive2Task", 512, NULL, 3,
    (TaskHandle_t*)&Receive2Task_Handle);
    
    xTaskCreate((TaskFunction_t)SendTask, "SendTask", 512, NULL, 4,
    (TaskHandle_t*)&SendTask_Handle);
    
    vTaskStartScheduler();
    taskEXIT_CRITICAL();
    while(1);
}

void Receive1Task(void)
{
    while(1){
        /* 获取任务通知,一直等待 */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        USART1_SendString("Receive1_Task ===> Task notification obtained successfully!\n\n");
    }
}
void Receive2Task(void)
{
    while(1){
        /* 获取任务通知,一直等待 */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        USART1_SendString("Receive2_Task ===> Task notification obtained successfully!\n\n");
    }
}
void SendTask(void)
{
    BaseType_t xReturn = pdPASS;
    while(1){
        if(KEY_Scan(GPIOA, GPIO_Pin_0) == 1){
            xReturn = xTaskNotifyGive(Receive1Task_Handle);
            /* 此函数只会返回 pdPASS */
            if (xReturn == pdTRUE){
                USART1_SendString("KEY0 ===> Receive1_Task Task notification successfully released!\n");
            }
        }
        if(KEY_Scan(GPIOA, GPIO_Pin_2) == 1){
            xReturn = xTaskNotifyGive(Receive2Task_Handle);
            /* 此函数只会返回 pdPASS */
            if (xReturn == pdTRUE){
                USART1_SendString("KEY1 ===> Receive2_Task Task notification successfully released!\n");
            }
        }
        vTaskDelay(20);
    }
}

6.使用示例3-替代计数信号量

示例:

  • 通过PA2的按键1来控制任务通知的发送,发送任务通知到TakeTask任务。
  • 通过PA0的按键0来控制任务通知的接收,接收从GiveTask任务发送过来的任务通知。
  • 保存任务通知数。

结果:
在这里插入图片描述

代码:

0、在FreeRTOSConfig.h的配置,和示例1一样。

1、STM32F103C8_BSP.c:和示例1一样

2、main.c

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"

/* 定义任务句柄,任务通知需要使用到句柄 */
TaskHandle_t TakeTask_Handle = NULL;
TaskHandle_t GiveTask_Handle = NULL;

/* 声明任务 */
void TakeTask(void);
void GiveTask(void);
static uint32_t give_num = 0; /* 用于记录发送的任务通知次数 */

static void STM32F103_Init(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    USART1_Init();
    KEY_GPIO_Init();
    LED_GPIO_Init();
}
int main(void)
{
    STM32F103_Init();
    taskENTER_CRITICAL();
    
    xTaskCreate((TaskFunction_t)TakeTask, "TakeTask", 512, NULL, 2,
    (TaskHandle_t*)&TakeTask_Handle);
    
    xTaskCreate((TaskFunction_t)GiveTask, "GiveTask", 512, NULL, 3,
    (TaskHandle_t*)&GiveTask_Handle);
    
    
    vTaskStartScheduler();
    taskEXIT_CRITICAL();
    while(1);
}
void TakeTask(void)
{
    uint32_t take_num = 1;
    while(1){
        if(KEY_Scan(GPIOA, GPIO_Pin_0) == 1) {
            /* 获取任务通知,并且获取不到不进入阻塞态 */
            take_num = ulTaskNotifyTake(pdFALSE, 0);
            if(take_num > 0){
                give_num--;
                USART1_SendString("KEY0 pressed,The number obtained is: ");
                USART1_SendStringNumber(take_num - 1, 1);
                USART1_SendString("\n");
            }else {
                USART1_SendString("======KEY0 pressed,But no numbers======\n\n");
            }
            vTaskDelay(20);
        }
    }
}
void GiveTask(void)
{
    BaseType_t xReturn = pdPASS;
    while(1){
        if(KEY_Scan(GPIOA, GPIO_Pin_2) == 1) {
            /* 往任务1发送通知 */
            xReturn = xTaskNotifyGive(TakeTask_Handle);
            if (pdPASS == xReturn){
                give_num++;
                USART1_SendString("KEY1 pressed,Release a number!Now there are ");
                USART1_SendStringNumber(give_num, 1);
                USART1_SendString(" numbers\n");
            }
        }
        vTaskDelay(20);
    }
}

7.使用示例4-替代事件组

示例:PA0接按键0,PA2接按键2,按键0、按键1都按下了为一个事件组,都按下时LED亮。按键按下时设置LED等为熄灭状态;通过串口发送提示信息。

结果:单独将按键0按下,LED灭,再单独按下按键1,此时两个按键都按下了一次,事件组发生,LED亮。

在这里插入图片描述

代码:

0、在FreeRTOSConfig.h的配置,和示例1一样。

1、STM32F103C8_BSP.c

#include "stm32f10x.h"                  // Device header

void USART1_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStruct);
    
    USART_Cmd(USART1, ENABLE);
    
}
void KEY_GPIO_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void LED_GPIO_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStruct);
    GPIO_SetBits(GPIOC, GPIO_Pin_13);
}

void USART1_SendByte(uint8_t byte){
    USART_SendData(USART1, byte);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
uint32_t Serial_Pow(uint32_t X, uint8_t Y){
	uint32_t Result = 1;
	while(Y--)	
		Result *= X;
	return Result;
}
// 取出数字各位的数并转为char形式数据来发送
void USART1_SendStringNumber(uint32_t Number, uint8_t Length)
{
	uint16_t i;
	for(i = 0; i < Length; i++) {
		// Number=321 Length=3,则会将3、2、1的ASCII码依次发送出去
		USART1_SendByte(Number / Serial_Pow(10,Length - i - 1) % 10 + '0');
	}
}
void USART1_SendString(char* str)
{
    uint16_t i;
    for(i = 0; str[i] != '\0'; i++){
        USART1_SendByte(*(str+i));
    }
}

uint8_t KEY_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1){
        while(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1);
        return 1;
    }
    return 0;
}

2、main.c

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"
#include "event_groups.h"

/* 定义事件组句柄 */
EventGroupHandle_t Event_Handle = NULL;
/* 定义事件掩码 */
#define KEY0_EVENT (0x01 << 0) /* 位0 */
#define KEY1_EVENT (0x01 << 1) /* 位1 */
/* 定义任务句柄 */
TaskHandle_t LEDTask_Handle = NULL;
TaskHandle_t KEYTask_Handle = NULL;

/* 声明任务 */
void LEDTask(void);
void KEYTask(void);

static void STM32F103_Init(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    USART1_Init();
    KEY_GPIO_Init();
    LED_GPIO_Init();
}
int main(void)
{
    STM32F103_Init();
    taskENTER_CRITICAL();
    /* 创建事件组 */
    Event_Handle = xEventGroupCreate();
    
    xTaskCreate((TaskFunction_t)LEDTask, "LEDTask", 512, NULL, 2,
    (TaskHandle_t*)&LEDTask_Handle);
    
    xTaskCreate((TaskFunction_t)KEYTask, "KEYTask", 512, NULL, 3,
    (TaskHandle_t*)&KEYTask_Handle);
    
    
    vTaskStartScheduler();
    taskEXIT_CRITICAL();
    while(1);
}

void LEDTask(void)
{
    uint32_t r_event = 0;   /* 用于接收事件 */
    uint32_t last_event = 0;/* 用于保存事件 */
    BaseType_t xReturn = pdTRUE;
    while(1){
        /* 等待通知 */
        xReturn = xTaskNotifyWait(0x0,0xFFFFFFFF, &r_event, portMAX_DELAY);
        /* 实现事件组,通过两个变量保存通知,此时的通知可以看作是事件状态标志 */
        if (pdTRUE == xReturn) {
            last_event |= r_event;
            /* 如果接收完成并且正确 */
            if (last_event == (KEY0_EVENT | KEY1_EVENT)) {
                last_event = 0; /* 将上一次的事件清零 */
                USART1_SendString("Both button 1 and button 2 have been pressed.LED OPEN!\n");
                GPIO_ResetBits(GPIOC, GPIO_Pin_13);
            } else 
                last_event = r_event; 
        }
    }
}
void KEYTask(void)
{
    while(1){
        if(KEY_Scan(GPIOA, GPIO_Pin_0) == 1){
            USART1_SendString("Key 0 pressed!\n");
            /* 往任务LEDTask发送任务通知:事件KEY0_EVENT */
            xTaskNotify(LEDTask_Handle, KEY0_EVENT, eSetBits);
            GPIO_SetBits(GPIOC, GPIO_Pin_13);
        }
        if(KEY_Scan(GPIOA, GPIO_Pin_2) == 1){
            USART1_SendString("Key 1 pressed!\n");
            /* 往任务LEDTask发送任务通知,事件KEY1_EVENT */
            xTaskNotify(LEDTask_Handle, KEY1_EVENT, eSetBits);
            GPIO_SetBits(GPIOC, GPIO_Pin_13);            
        }
        vTaskDelay(20);
    }
}

END

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值