FreeRTOS实战(二)·按键实现任务的挂起和恢复(STM32移植模版)

目录

1.  程序设计

2.  工程创建

3.  功能实现


        通过之前入门系列的学习,我们对FreeRTOS的移植已经有了一个大概的概念,那么我们下面就给其投入到实际的使用当中。

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

1.  程序设计

        创建两个任务,一个LED任务实现电平的翻转,另一任务通过按键进行LED任务的挂起和恢复。

2.  工程创建

        这里不在一一介绍每个文件夹的作用,详细可以了解:

FreeRTOS菜鸟入门(六)·移植FreeRTOS到STM32(逐步超详细移植)-CSDN博客

        根据上面的移植好的模版,我们进行后续任务相关函数的移植,首先对于LED.c的函数,我们增加一个电平翻转的功能,方便后续调用:

//红灯电平翻转
void Toggle_LED_R(void)
{
	BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));
	GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}

//绿灯电平翻转
void Toggle_LED_G(void)
{
	BitAction LED_G = (BitAction)(1 - GPIO_ReadOutputDataBit(LED2_GPIO_PORT, LED2_GPIO_PIN));
	GPIO_WriteBit(LED2_GPIO_PORT, LED2_GPIO_PIN, LED_G);
}

//蓝灯电平翻转
void Toggle_LED_B(void)
{
	BitAction LED_B = (BitAction)(1 - GPIO_ReadOutputDataBit(LED3_GPIO_PORT, LED3_GPIO_PIN));
	GPIO_WriteBit(LED3_GPIO_PORT, LED3_GPIO_PIN, LED_B);
}

        然后创建一个KEY.c函数,就是简单的按键触发函数,检测按键是否按下:

#include "Key.h"  

//初始化配置
void Key_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*开启按键端口的时钟*/
	RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK|KEY2_GPIO_CLK,ENABLE);
	
	//选择按键的引脚
	GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; 
	// 设置按键的引脚为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	//使用结构体初始化按键
	GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
	
	//选择按键的引脚
	GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; 
	//设置按键的引脚为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	//使用结构体初始化按键
	GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);	
}

//检测按键是否按下
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )  
	{	 
		/*等待按键释放 */
		while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);   
		return 	KEY_ON;	 
	}
	else
		return KEY_OFF;
}


        然后其中宏定义相关存放在 KEY.h 文件当中:

#ifndef __KEY_H
#define	__KEY_H

#include "stm32f10x.h"

//  引脚定义
#define    KEY1_GPIO_CLK     RCC_APB2Periph_GPIOA
#define    KEY1_GPIO_PORT    GPIOA			   
#define    KEY1_GPIO_PIN		 GPIO_Pin_0

#define    KEY2_GPIO_CLK     RCC_APB2Periph_GPIOC
#define    KEY2_GPIO_PORT    GPIOC		   
#define    KEY2_GPIO_PIN		  GPIO_Pin_13

 /** 按键按下标置宏
	*  按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
	*  若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可
	*/
#define KEY_ON	1
#define KEY_OFF	0

void Key_GPIO_Config(void);
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);


#endif /* __KEY_H */

        两个外设的代码创建完了,我们回到main.c文件,先完善头文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"

        然后为了方便管理初始化文件,我们创建一个函数,将初始化相关的函数都存放到这个函数中,这样主函数管理比较方便:

//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}

        我们知道创建任务为了方便管理,我们需要创建一个任务句柄,我们这里先不考虑LED和KEY的任务创建,先创建一个用于创建任务的任务句柄:

static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */

        我们创建一个用于创建别的任务的任务,这个任务的作用就是通过临界区访问将我们需要的如LED,KEY等任务能够完整的创建,不被中断或者在任务创建时被任务调度打扰,创建完成后就可以将创建任务的这个任务通过调用刚刚创建的句柄删除了,这个任务存在的意义就是保护我们所需要的任务在创建时不被打扰:

//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /***这里是一些我们需要创建的任务***/
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

对于临界区的概念,你可以把他理解为一个房子,保护内部正在做的事情,防止被外部打扰。

详细可以了解:

FreeRTOS菜鸟入门(四)·临界段的保护_freertos临界区保护-CSDN博客

        当然,虽然这个任务用一下就删除了,但是不妨碍他是一个任务,既然是任务,我们就需要给它分配,栈空间等参数,这里在主函数里进行分配:

int main(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建AppTaskCreate任务 */
		xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
													(const char*    )"AppTaskCreate",/* 任务名字 */
													(uint16_t       )512,  /* 任务栈大小 */
													(void*          )NULL,/* 任务入口函数参数 */
													(UBaseType_t    )1, /* 任务的优先级 */
													(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
		/* 启动任务调度 */           
		if(pdPASS == xReturn)
			vTaskStartScheduler();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}

        完整的main函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */


//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */

int main(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建AppTaskCreate任务 */
		xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
													(const char*    )"AppTaskCreate",/* 任务名字 */
													(uint16_t       )512,  /* 任务栈大小 */
													(void*          )NULL,/* 任务入口函数参数 */
													(UBaseType_t    )1, /* 任务的优先级 */
													(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
		/* 启动任务调度 */           
		if(pdPASS == xReturn)
			vTaskStartScheduler();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}

//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行一下没有报错,不过由于我们没有给它其他任务,所以现在也没有实验详细,对于这个工程我们先保存一下,因为后续实验都直接在这个工程上进行操作,省的每次都要创建一遍。

基于STM32F1系列FreeRTOS移植模版资源-CSDN文库

3.  功能实现

        上面我们已经把工程的框架搭好了,下面开始对功能开始实现。

        首先,我们上面也提到了,需要创建两个任务,那么就需要两个任务句柄:

static TaskHandle_t Test_Task_Handle = NULL;/* LED任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY任务句柄 */

        既然句柄有了,就需要对任务主体进行设置,创建一个LED任务,每隔500ms亮灭一次:

static void Test_Task(void* parameter)
{	
  while (1)
  {
    LED1_ON;
    printf("Test_Task Running,LED1_ON!\r\n");
    vTaskDelay(500);   /* 延时500个tick */
    
    LED1_OFF;     
    printf("Test_Task Running,LED1_OFF!\r\n");
    vTaskDelay(500);   /* 延时500个tick */
  }
}

        然后是对按键任务的编写,我们设置两个按键一个调用vTaskSuspend()用来挂起任务,一个调用vTaskResume()用来恢复任务:

static void KEY_Task(void* parameter)
{	
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {/* K1 被按下 */
      printf("挂起Test任务!\r\n");
      vTaskSuspend(Test_Task_Handle);/* 挂起LED任务 */
      printf("挂起Test任务成功!\r\n");
    } 
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {/* K2 被按下 */
      printf("恢复Test任务!\r\n");
      vTaskResume(Test_Task_Handle);/* 恢复LED任务! */
      printf("恢复Test任务成功!\r\n");
    }
    vTaskDelay(20);/* 延时20个tick */
  }
}

对于挂起简单来说就是让任务调度器看不见他,这样被挂起的任务也就无法运行了,将其从挂起恢复,就是让调度器在次看见他,这样通过调度器还能继续运行。

详细的理解可以查看,任务管理的四种状态:

FreeRTOS菜鸟入门(九)·任务管理·超详细逐步解析_freertos菜鸟教程-CSDN博客

        完整main函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */

static TaskHandle_t Test_Task_Handle = NULL;/* LED任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */


/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */


//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */

static void Test_Task(void* pvParameters);/* Test_Task任务实现 */
static void KEY_Task(void* pvParameters);/* KEY_Task任务实现 */

static void All_Function_Init(void);/* 用于初始化板载相关资源 */

int main(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建AppTaskCreate任务 */
		xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
													(const char*    )"AppTaskCreate",/* 任务名字 */
													(uint16_t       )512,  /* 任务栈大小 */
													(void*          )NULL,/* 任务入口函数参数 */
													(UBaseType_t    )1, /* 任务的优先级 */
													(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
		/* 启动任务调度 */           
		if(pdPASS == xReturn)
			vTaskStartScheduler();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}

//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Test_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Test_Task, /* 任务入口函数 */
                        (const char*    )"Test_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Test_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Test_Task任务成功!\r\n");
  /* 创建KEY_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */
                        (const char*    )"KEY_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建KEY_Task任务成功!\r\n");  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

//Test_Task任务主体
static void Test_Task(void* parameter)
{	
  while (1)
  {
    LED1_ON;
    printf("Test_Task Running,LED1_ON!\r\n");
    vTaskDelay(500);   /* 延时500个tick */
    
    LED1_OFF;     
    printf("Test_Task Running,LED1_OFF!\r\n");
    vTaskDelay(500);   /* 延时500个tick */
  }
}

//Test_Task任务主体
static void KEY_Task(void* parameter)
{	
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {/* K1 被按下 */
      printf("挂起Test任务!\r\n");
      vTaskSuspend(Test_Task_Handle);/* 挂起LED任务 */
      printf("挂起Test任务成功!\r\n");
    } 
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {/* K2 被按下 */
      printf("恢复Test任务!\r\n");
      vTaskResume(Test_Task_Handle);/* 恢复LED任务! */
      printf("恢复Test任务成功!\r\n");
    }
    vTaskDelay(20);/* 延时20个tick */
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
}



        运行结果:

        可以看到当我们将LED任务挂起后,其不在运行,只有当我们恢复后才会继续运行。

完整工程:

FreeRTOS-KEY调用挂起和恢复.zip资源-CSDN文库

FreeRTOS实战系列_时光の尘的博客-CSDN博客

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值