STM32Cube高效开发教程<高级篇><FreeRTOS>(五)-----FreeRTOS的任务管理工具函数及多任务编程示例二

   声明:本人水平有限,博客可能存在部分错误的地方,请广大读者谅解并向本人反馈错误。
   本专栏博客参考《STM32Cube高效开发教程(高级篇)》,有意向的读者可以购买正版书籍辅助学习,本书籍由王维波老师、鄢志丹老师、王钊老师倾力打造,书籍内容干货满满。

一、 任务管理工具函数

1.1 相关函数概述

  FreeRTOS中有一些API函数,用于操作任务或获取任务信息,这些函数及其基本功能简介见下表。要在程序中使用这些函数,某些“config”参数或“INCLUDE_”参数需要设置为1。本系列博客不逐一介绍对应的参数设置了,读者在用到时请查看FreeRTOS中函数源代码的预编译条件,即可知道相应的条件参数设置。
在这里插入图片描述
在这里插入图片描述

1.2 获取任务句柄

  对单个任务进行操作的函数,一般需要一个TaskHandle_t类型的表示任务句柄的变量作为参数。在使用函数osThreadNew()创建任务时,会返回一个osThreadId_t类型的变量作为任务句柄。这两个类型的定义其实是一样的,定义如下:

typedef void *osThreadId_ttypedef void *TaskHandle_t;

  所以用osThreadNew()创建任务获得的任务句柄变量,可以作为FreeRTOS任务操作函数的任务句柄输入参数。
  FreeRTOS中还有3个用于获取任务句柄的函数。

  • 函数xTaskGetCurrentTaskHandle(),用于获得当前任务的句柄,其原型定义如下:
TaskHandle t xTaskGetCurrentTaskHandle(void);

  要使用这个函数,需要将参数INCLUDE_xTaskGetCurrentTaskHandle设置为1(默认值为0),可以在CubeMX里设置。

  • 函数xTaskGetldleTaskHandle(),用于获得空闲任务的句柄,其原型定义如下:
TaskHandle t xTaskGetIdleTaskHandle(void);

  要使用这个函数,需要将参数INCLUDE_xTaskGetIdleTaskHandle设置为1(默认值为0)。这个参数在CubeMX里不能设置,需要用户在FreeRTOSConfig.h文件中自己添加宏定义,添加到文件FreeRTOSConfig.h的用户定义代码沙箱段,示例如下:

/*USER CODE BEGIN Defines */
/* 在这里添加定义,例如,用于覆盖FreeRTOS.h中的默认定义 */
#define INCLUDE_xTaskGetIdleTaskHandle    1
/*USER CODE END Defineg */
  • 函数xTaskGetHandle(),用于通过任务名称获得任务句柄,其原型定义如下:
TaskHandle_t xTaskGetHandle( const char *pcNameToQuery );

  参数pcNameToQuery是任务名称字符串。这个函数运行时间相对较长,不宜大量使用。如果两个任务具有相同的任务名称,则函数返回的结果是不确定的。函数使用示例代码如下:

const char *taskName =“Task_LED1”;
TaskHandle_t taskHandle = xTaskGetHandle(taskName);

  要使用这个函数,需要将参数INCLUDE_xTaskGetHandle设置为1(默认值为0)。这个参数可以在CubeMX里设置。

1.3 单个任务的操作

1.3.1.获取和设置任务的优先级

  程序在运行时,可以获取或改变一个任务的优先级,相关3个函数的原型定义如下。要使用这3个函数,需要将参数INCLUDE_uxTaskPriorityGet或INCLUDE_vTaskPrioritySet设置为1(默认值都是1)——可以在CubeMX里设置。

UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask);			//返回一个任务的优先级
UBaserype_t uxTaskPriorityGetFromISR(TaskHandle_t xTask);	//函数的ISR版本
void vTaskPrioritySet(TaskHandle_t xTask,UBaseType_t uxNewPriority);//设置优先级

  在这3个函数中,优先级用UBaseTypet类型的数表示,而在文件cmsis_os2.h中,定义了优先级的枚举类型osPriority_t,其部分定义如下:

typedef enum {
  osPriorityNone          =  0,         ///< No priority (not initialized).
  osPriorityIdle          =  1,         ///< Reserved for Idle thread.
  osPriorityLow           =  8,         ///< Priority: low
  osPriorityLow1          =  8+1,       ///< Priority: low + 1
  osPriorityLow2          =  8+2,       ///< Priority: low + 2
  osPriorityLow3          =  8+3,       ///< Priority: low + 3
  osPriorityLow4          =  8+4,       ///< Priority: low + 4
  osPriorityLow5          =  8+5,       ///< Priority: low + 5
  osPriorityLow6          =  8+6,       ///< Priority: low + 6
  osPriorityLow7          =  8+7,       ///< Priority: low + 7
  osPriorityBelowNormal   = 16,         ///< Priority: below normal
  osPriorityBelowNormal1  = 16+1,       ///< Priority: below normal + 1
  osPriorityBelowNormal2  = 16+2,       ///< Priority: below normal + 2
  osPriorityBelowNormal3  = 16+3,       ///< Priority: below normal + 3
  osPriorityBelowNormal4  = 16+4,       ///< Priority: below normal + 4
  osPriorityBelowNormal5  = 16+5,       ///< Priority: below normal + 5
  osPriorityBelowNormal6  = 16+6,       ///< Priority: below normal + 6
  osPriorityBelowNormal7  = 16+7,       ///< Priority: below normal + 7
  osPriorityNormal        = 24,         ///< Priority: normal
  osPriorityNormal1       = 24+1,       ///< Priority: normal + 1
  osPriorityNormal2       = 24+2,       ///< Priority: normal + 2
  osPriorityNormal3       = 24+3,       ///< Priority: normal + 3
  osPriorityNormal4       = 24+4,       ///< Priority: normal + 4
  osPriorityNormal5       = 24+5,       ///< Priority: normal + 5
  osPriorityNormal6       = 24+6,       ///< Priority: normal + 6
  osPriorityNormal7       = 24+7,       ///< Priority: normal + 7
  osPriorityAboveNormal   = 32,         ///< Priority: above normal
  osPriorityAboveNormal1  = 32+1,       ///< Priority: above normal + 1
  osPriorityAboveNormal2  = 32+2,       ///< Priority: above normal + 2
  osPriorityAboveNormal3  = 32+3,       ///< Priority: above normal + 3
  osPriorityAboveNormal4  = 32+4,       ///< Priority: above normal + 4
  osPriorityAboveNormal5  = 32+5,       ///< Priority: above normal + 5
  osPriorityAboveNormal6  = 32+6,       ///< Priority: above normal + 6
  osPriorityAboveNormal7  = 32+7,       ///< Priority: above normal + 7
  osPriorityHigh          = 40,         ///< Priority: high
  osPriorityHigh1         = 40+1,       ///< Priority: high + 1
  osPriorityHigh2         = 40+2,       ///< Priority: high + 2
  osPriorityHigh3         = 40+3,       ///< Priority: high + 3
  osPriorityHigh4         = 40+4,       ///< Priority: high + 4
  osPriorityHigh5         = 40+5,       ///< Priority: high + 5
  osPriorityHigh6         = 40+6,       ///< Priority: high + 6
  osPriorityHigh7         = 40+7,       ///< Priority: high + 7
  osPriorityRealtime      = 48,         ///< Priority: realtime
  osPriorityRealtime1     = 48+1,       ///< Priority: realtime + 1
  osPriorityRealtime2     = 48+2,       ///< Priority: realtime + 2
  osPriorityRealtime3     = 48+3,       ///< Priority: realtime + 3
  osPriorityRealtime4     = 48+4,       ///< Priority: realtime + 4
  osPriorityRealtime5     = 48+5,       ///< Priority: realtime + 5
  osPriorityRealtime6     = 48+6,       ///< Priority: realtime + 6
  osPriorityRealtime7     = 48+7,       ///< Priority: realtime + 7
  osPriorityISR           = 56,         ///< Reserved for ISR deferred thread.
  osPriorityError         = -1,         ///< System cannot determine priority or illegal priority.
  osPriorityReserved      = 0x7FFFFFFF  ///< Prevents enum down-size compiler optimization.
} osPriority_t;

  用户可以在函数vTaskPrioritySet()中使用枚举类型,例如:

TaskHandle_t taskHandle = xTaskGetCurrentTaskHandle ();			//获取当前任务的句柄
rTaskPrioritySet(taskHandle,(UBaseType_t)osPriorityAboveNormal); //改置优先级

1.3.2.函数vTaskGetlnfo()

  vTaskGetnfo()用于获取一个任务的信息,要使用这个函数,必须将参数configUSE_TRACE_FACILITY设置为1(默认值为1),可在CubeMX里设置。这个函数的原型定义如下,各参数的意义见注释:

void vTaskGetInfo(	TaskHandle_t xTask,  			//任务的句柄
					Taskstatus_t *pxTaskstatus,		//用于存储任务状态信息的造构体指针
					BaseType_t xGetFreeStackSpace,	//是否返回截空间高水位值
					eTaskState estate);				//指定任务的状态

  参数xTask是需要查询的任务的句柄。参数pxTaskStatus是用于存储返回信息的TaskStats_t结构体指针,这个结构体的定义如下,成员变量表示了任务的各种信息:

typedef struct XTASK_STATUS
{
	TaskHandle_t xHandle;//任务的包柄
	const char   *pcTaskName;//任务的名称
	UBaserype_t  xTaskNumber;//任务的唯一编号
	eTaskState   eCurrentstate;//任务的状态
	UBaseType_t  uxCurrentPriority;//任务的优先级
	UBaseType_t  uxBasepriority;
	/*任务运行的总时间,configGENERATERUN_TIME_STATS设置为1时才有意义*/
	uint32_t     ulRunTimeCounter;
	StackType_t  *pxStackBase;//指向栈空间的低地址
	uint16_t     usStackHighwaterMark;//栈空间的高水位值,单位是字
}Taskstatus_t;

  其中的任务状态eCurrentState是枚举类型eTaskState,这个枚举类型表示了任务的运行、就绪、阻塞、挂起等状态,其定义如下:

typedef enum
{
	eRunning = 0,		//运行状态
	eReady,				//就绪状态
	eBlocked,			//阻塞状态
	eSuspended,			//挂起状态,或无限等待时间的阻塞状态
	eDeleted,			//任务被删除,但是其任务控制块(TCB)还没有被释放
	eInvalid			//无效状态
}eTaskState;

  函数vTaskGetlnfo()中的参数xGetFreeStackSpace,表示是否在结构体TaskStatus_t中返回栈空间的高水位值usStacklfighWaterMark,如果xGetFreeStackSpace是pdTRUE,就返回高水位值。因为返回任务的高水位值需要较长的时间,若xGetFreeStackSpace设置为pdFALSE,就可以忽略此过程。
  函数vTaskGetlnfo()中的参数eState用于指定查询信息时的任务状态,虽然结构体TaskStatus_t中有获取任务状态的成员变量,但是不如直接赋值快。如果需要函数vTaskGetInfo()自动获取任务的状态,将参数eState设置为枚举值eInvalid即可。

1.3.3.函数pcTaskGetName()

  函数pcTaskGetName()用于返回一个任务的任务名称字符串,其原型定义如下:

char *pcTaskGetName(TaskHandle_t xTaskToQuery);

  如果要查询任务自己的任务名称,将参数xTaskToQuery设置NULL即可。

1.3.4.函数uxTaskGetStackHighWaterMark()

  函数uxTaskGetStackHighWaterMark()用于获取一个任务的高水位值,其原型定义如下:

UBaseType t uxTaskGetstackHighNaterMark(TaskHandle t xTask);

   如果要查询任务自己的高水位值,将参数xTask设置为NULL即可。
  若要使用这个函数,必须将参数INCLUDE_uxTaskGetStackHighWaterMark设置为1(默认值为1)–可在CubeMX里设置。高水位值实际上就是任务的栈空间最少可用剩余空间的大小,单位是字(word)。这个值越小,表示任务的栈空间越容易溢出。

1.3.5.函数eTaskGetState()

  函数eTaskGetState()返回一个任务的当前状态,其原型定义如下:

eTaskState eTaskGetstate(TaskHandle_t xTask);

  返回值是枚举类型eTaskState,表示任务的就绪、运行、阻塞、挂起等状态。
  若要使用这个函数,需要将参数INCLUDE_eTaskGetState或configUSE_TRACE_FACILITY设置为1,这两个参数默认值都是1,且都可以在CubeMX里设置。

1.4 内核信息统计

1.4.1.函数uxTaskGetNumberOfTasks()

  函数uxTaskGetNumberOfTasks()返回内核当前管理的任务的总数,包括就猪的、阻塞的、挂起的任务,也包括虽然删除了但还没有在空闲任务里释放的任务:其原型定义如下:

UBaseType_t uxTaskGetNumberofTasks(void);

1.4.2.函数vTaskList()

  函数vTaskList()返回内核中所有任务的字符串列表信息,包括每个任务的名称、状态、优先级、高水位值、任务编号等。其原型定义如下:

void vTaskList(char * pcWriteBuffer);

  参数pcWriteBufter是预先创建的一个宇符数组的指针,用于存储返回的字符串信息。这个字符数组必须足够大,FreeRTOS不会检查这个数组的大小。这个函数的使用示例代码如下:

char infoBuffer[300];
vTaskList(infoBuffer);

  返回的数据存储在字符数组infoBuffer中,使用了“\t”“\n”等转义字符,以便用表格方式显示。例如,本博客后面的一个示例程序中,通过vTaskList()函数获取的系统的任务列表字符串内容如下。为了直观显示,这里保留了其中的“\t”“\r\n”“\0”等转义字符,将换行符“\n”表示的一行单独作为一行显示。
  Task_LED1 \tX\t8\t50\t2\r\n
  Tmr Sve \tR\t2\t246\t4\r\n
  IDLE \tR\t0\t118\t3\r\n
  Task_ADC \tB\t24\t134\t1\r\n\0
  每行字符串的第一部分是任务名称,这里除了用户的两个任务Task_LED1和Task_ADC,还有系统自动创建的空闲任务IDLE和定时器服务任务TmrSvc。
  每行字符串的第二部分是用目“\t”分隔的多个参数,依次为状态、优先级、栈空间高水值和任务编号。其中,任务状态用字母表示,各字母的意义如下。

  • X,运行状态,也就是调用函数vTaskList()的任务的状态。
  • B,阻塞状态。
  • R,就猪状态。
  • S挂起状态,或无根等待时间的阻塞状态。
  • D,被删除的任务,但是空闲任务还没有释放其使用的内存。

  例如,对于任务Task_LED1,其状态字符串是“\tX\t8\t50\t2\r\n”,表示它处于运行状态,优先级为8,栈空间高水位值为50,任务编号为2。
  vTaskList()的代码实现用到了函数sprintf(),会使编译后的应用的大小明显增大。所以,这个函数一般只在调试时使用,不要在发布版本里使用。要使用这个函数,需要将以下3个参数都设置为1。

  • configUSE_TRACE_FACILITY,默认值为1,可在CubeMX里设置。
  • configUSE_STATS_FORMATTING_FUNCTION,默认值为0,可在CubeMX里设置。
  • configSUPPORT_DYNAMIC_ALLOCATION,默认值为1,不能在CubeMX里设置。

1.4.3.函数uxTaskGetSystemState()

  要使用这个函数,需要将参数configUSE_TRACE_FACILITY配置为1(默认值为1)—可在CubeMX里配置这个参数。
  这个函数用于获得系统内所有任务的状态,为每个任务返回一个TaskStatus_t结构体数据,此结构体在函数bTaskGetInfo()部分介绍过。函数uxTaskGetSystemState()的原型定义如下:

UBaserype_t uxTaskGetSystemState(TaskStatus_t * const pxTaskstatusArray,const
UBaserype_t uxArraySize, uint32_t * const pulTotalRunTime);
  • 参数pxTaskStahusArray是一个数组的指针,成员是结构体类型TaskStatus_t。需预先分配数组大小,必须大于或等于FecRIQS内的任务数返回的数据就存储在这个数组里每个任务对应一个数组成员
  • 参数uxAnaySize是数组pxTaskShahsAry的大小,表示数组pxTaskStatusArray的成员个数。
  • 参数pulTotalRunTime用于返回FreeRTOS启动后总的运行时间,如果设置为NULL,则不返回这个数据。只有参数configGENERATE_RUN_TIME_STATS设置为1,才会返回这个数据,默认值为0,可在CubeMX里设置。

  函数的返回值是uxTaskGetSystemState()实际获取的任务信息的条数,也就是FreeRTOS中实际任务的个数,与函数uxTaskGetNumberOfTasks()返回的任务个数相同。

1.4.4.函数yTaskGetRunTimeStats()

  要使用这个函数,必须将以下两个参数都设置为1。

  • configGENERATE_RUN_TIME_STATS,默认值为0,可在CubeMX里设置。
  • configUSE_STATS_FORMATTING_FUNCTIONS,默认值为0,可在CubeMX里设置。

  函数vTaskGetRunTimeStats()用于统计系统内每个任务的运行时间,包括绝对时间和占用CPU的百分比。其原型定义如下:

void vTaskGetRunTimeStats(char*pcWriteBuffer);

  参数peWriteBufer用于存储返回数据的台字符数组,返回的数据以文字表格的形式表示,函数vTaskList()返回结果的方式类似。
  注意,函数vTaskGetRunTimeStats()运行时,会禁止所有中断,所以,不要在程序正常运行时使用这个函数,应该只在程序调试阶段使用。函数vTaskGetRunTimeStats()内部会调用uxTaskGetSystemState(),将其中的任务运行数据转换为更易阅读的绝对运行时间和百分比时间函数vTaskGetRunTimeStats()依赖于函数sprintf(),会导致编译后的代码量增加,所以在发布的程序中,不要使用此函数

1.4.5.函数xTaskGetSchedulerState()

  这个函数返回调度器的状态,当以下两个参数中的某一个设置为1时,此函数就可用。

  • INCLUDE_xTaskGetSchedulerState,默认值为1,可在CubeMX里修改
  • configUSE_TIIMERS,默认值为1,CubeMX里有这个参数,但不允许修改

  函数xTaskGetSchedulerState()返回任务调度器当前的状态,其原型定义如下:

BaseType_t xTaskGetSchedulerstate( void );

  返回值用如下的三个宏定义常数表示任务调度器的状态:

#define taskSCHEDULER_SUSPENDED		((Baserype_t)0)  	//被挂起
#define taskSCHEDULER_NOT_STARTED	((BaseType_t)1)		//未启动
#define taskSCHEDULER_RUNNING		((BaseType_t)2)		//正在运行

二、 多任务编程示例二

2.1 示例功能与CubeMX项目设置

  在本博客中会创建一个示例,演示FreeRTOS中一些任务管理函数的使用。在本示例中,会在FreeRTOS中创建2个任务:任务Task_ADC通过ADC1的IN5通道周期性采集电位器的电压值,并在LCD上显示;任务Task_Info用于测试任务信息统计的一些工具函数,统计信息在LCD上显示。
在本示例中,将会用到LCD和LED,然后在SYS组件中,设置TIM6为HAL基础时钟,这是因为要使用FreeRTOS。

2.1.1.ADC的设置

  我们要用到ADC1的IN5输入通道。开发板上用一个电位器调压作为IN5的输入电压,电路如下图所示。要使用ADC1输入,还需要将端子【3-3】的跳线设置到ADC输入模式,即下图中的跳线J1的1和2要用跳线帽短接。
在这里插入图片描述
  ADC1的模式设置中只需勾选IN5通道,其参数设置结果如下图所示。采用加重独立模式、12位精度、数据右对齐、软件触发常规转换,无须开启ADC1的硬件中断。ADC各参数的意义和软件触发ADC转换的原理详见STM32Cube高效开发教程<基础篇>(十二)----ADC的示例。

2.1.2.FreeRTOS的设置

  启用FreeRTOS,使用CMSIS_V2接口。Config parameters 和 Include parameters两个设置页面的参数都保持默认值。在FreeRTOS中创建2个任务,设置任务的参数,如下图所示。因为两个任务的优先级不同,所以我们将校空间大小都修改为256,并设置它们都使用动态分配内存方式。若栈空间大小使用默认值128,本示例程序将无法正常运行。
在这里插入图片描述

  上图中两个任务的栈空间大小并不是随意给出的,是在程序运行过程中,通 过统计栈空间的高水位值,给出的一个比较安全合理的值。栈空间太小,会导致栈空间溢出,程序无法正常运行,栈空间太大,会浪费内存。要设置合理的栈空间大 小,最好在调试阶段统计一下任务的高水位值。

2.2 程序功能实现

2.2.1.主程序

  完成设置后,CubeMX自动生成代码。在CubeIDE里打开项目,将PublicDrivers目录下的TFT_LCD和KEY_LED目录添加到项目的头文件和源程序搜索路径。在主程序中添加少量用户代码,完成后的主程序代码如下:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "adc.h"
#include "gpio.h"
#include "fsmc.h"

/* Private includes ----------------------------------------------------------*/
#include "tftlcd.h"

/* Private function prototypes -----------------------------------------------*/
void MX_FREERTOS_Init(void);

int main(void)
{
	HAL_Init();
	SystemClock_Config();
	
	/* Initialize all configured peripherals */
	MX_GPIO_Init();
	MX_FSMC_Init();
	MX_ADC1_Init();
	/* USER CODE BEGIN 2 */
	TFTLCD_Init();
	LCD_ShowStr(10,10,(uint8_t *)"Demo2_2:Task Utilities");
	/* USER CODE END 2 */

	/* Init scheduler */
	osKernelInitialize();  /* Call init function for freertos objects (in freertos.c) */
	MX_FREERTOS_Init();
	/* Start scheduler */
	osKernelStart();
	/* We should never get here as control is now taken by the scheduler */
	while (1)
	{
	}
}

  在外设初始化部分,上述程序执行了以下3个外设初始化函数。

  添加的两行用户代码,用于调用函数TFTLCD_Init()进行LCD的软件初始化,然后在LCD上显示项目文字信息。之后,仍然是依次调用3个函数进行FreeRTOS内核初始化、对象初始化和内核启动,与具体示例相关的就是函数MX_FREERTOS_Init(),它创建项目里定义的任务。

2.2.2.FreeRTOS对象初始化

  CubeMX自动生成的文件freertos.c包含两个任务的相关定义、FreeRTOS对象初始化函数MX_FREERTOS_Init(),以及两个任务函数的框架。对象初始化函数MX_FREERTOS_Init()相关代码如下,文件开头部分增加了两个任务函数中要用的头文件,两个任务函数的代码在后面再介绍:

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/   
#include	"tftlcd.h"
#include	"keyled.h"
#include	"adc.h"

osThreadId_t Task_ADCHandle;
const osThreadAttr_t Task_ADC_attributes = {
  .name = "Task_ADC",
  .priority = (osPriority_t) osPriorityNormal,
  .stack_size = 256 * 4
};
/* Definitions for Task_Info */
osThreadId_t Task_InfoHandle;
const osThreadAttr_t Task_Info_attributes = {
  .name = "Task_Info",
  .priority = (osPriority_t) osPriorityLow,
  .stack_size = 256 * 4
};
/* Private function prototypes -----------------------------------------------*/
void AppTask_ADC(void *argument);
void AppTask_Info(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

void MX_FREERTOS_Init(void) {
  /* creation of Task_ADC */
  Task_ADCHandle = osThreadNew(AppTask_ADC, NULL, &Task_ADC_attributes);
  /* creation of Task_Info */
  Task_InfoHandle = osThreadNew(AppTask_Info, NULL, &Task_Info_attributes);

}

  本示例中的两个任务都采用了动态分配内存方式。在本书后面的示例中,如无特殊需要我们一般都采用动态分配内存方式创建任务。两个任务的优先级不同,栈空间大小是256字,比默认的128字大。注意,在CubeMX中设置的参数configMINIMAL_STACK_SIZE,是系统自动创建的空闲任务的线空间大小,其默认值是128字。

2.2.3.任务TaskADC的功能实现

  在任务TaskADC里,我们对ADC1的IN5通道用轮询方式进行数据采集,并且使用vTaskDelayUntil()函数实现比较精确的周期性采集。添加用户功能代码后,这个函数的完整代码如下:

/* USER CODE END Header_AppTask_ADC */
void AppTask_ADC(void *argument)
{
  /* USER CODE BEGIN AppTask_ADC */
	LCD_ShowStr(10, 40, (uint8_t *)"Task_ADC: ADC by polling");
	LCD_ShowStr(10, 40+LCD_SP10, (uint8_t *)"ADC Value(mV)= ");
	uint16_t	ADCX=LCD_CurX;	//记录X坐标位置

	TickType_t previousWakeTime=xTaskGetTickCount();	//获取嘀嗒信号计数值
	for(;;)
	{
		HAL_ADC_Start(&hadc1);	//启动ADC转换
		if (HAL_ADC_PollForConversion(&hadc1,100)==HAL_OK)   //轮询方式等待转换完成
		{
			uint32_t  val=HAL_ADC_GetValue(&hadc1);	//读取ADC转换原始数据
			uint32_t  Volt=3300*val;	//mV
			Volt=Volt>>12;				//除以2^12
			LCD_ShowUint(ADCX,40+LCD_SP10,Volt);	//转换为mV显示
		}
		//	  HAL_ADC_Stop(&hadc1);		//无需每次都停止
		vTaskDelayUntil(&previousWakeTime, pdMS_TO_TICKS(500));
	}
  /* USER CODE END AppTask_ADC */
}

  对ADC以轮询方式进行数据采集的方法是:以HAL_ADC_Start()函数启动转换,然后调用函数HAL_ADC_PollForConversion()以轮询方式等待转换完成,并设置最多等待100ms。转换完成后,调用函数HAL_ADC_GetValue()读取ADC转换原始数值,ADC转换结果是12位有效右对齐数据,然后再转换为毫伏电压值显示。
  上述程序使用了vTaskDelayUntil()函数,保证了ADC数据采集的周期是比较准确的500ms。

2.2.4.任务Task_Info的功能实现

  任务TaskInfo主要用来测试一些任务管理函数,添加代码后的任务函数代码如下:

/* USER CODE END Header_AppTask_Info */
void AppTask_Info(void *argument)
{
  /* USER CODE BEGIN AppTask_Info */
	//====获取单个任务的信息=====
	//	TaskHandle_t taskHandle=xTaskGetCurrentTaskHandle();	//获取当前任务句柄
		TaskHandle_t taskHandle=xTaskGetIdleTaskHandle();		//获取空闲任务句柄
	//	TaskHandle_t taskHandle=xTaskGetHandle("Task_ADC");		//通过任务名称获取任务句柄
	//	TaskHandle_t taskHandle=Task_ADCHandle;					//直接使用任务句柄变量

	TaskStatus_t taskInfo;
	BaseType_t getFreeStackSpace=pdTRUE;  	//是否获取高水位值,
	eTaskState taskState=eInvalid; 			//当前的状态
	vTaskGetInfo(taskHandle, &taskInfo, getFreeStackSpace, taskState);	//获取任务信息

	taskENTER_CRITICAL();		//开始临界代码段,不允许任务调度

	LcdFRONT_COLOR=lcdColor_WHITE;
	LCD_ShowStr(10, 100, (uint8_t *)"Task_Info: Show task info");
	LCD_ShowStr(20, LCD_CurY+LCD_SP10, (uint8_t *)"Get by vTaskGetInfo() ");
	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Task Name= ");
	LCD_ShowStr(LCD_CurX+10, LCD_CurY, (uint8_t *)taskInfo.pcTaskName);

	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Task Number= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, taskInfo.xTaskNumber);

	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Task State= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, taskInfo.eCurrentState);

	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Task Priority= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, taskInfo.uxCurrentPriority);

	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"High Water Mark= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, taskInfo.usStackHighWaterMark);

//======用函数uxTaskGetStackHighWaterMark()单独获取每个任务的高水位值=====
	LcdFRONT_COLOR=lcdColor_YELLOW;
	LCD_ShowStr(20, LCD_CurY+LCD_SP15, (uint8_t *)"High Water Mark of tasks");

	taskHandle=xTaskGetIdleTaskHandle();	//获取空闲任务句柄
	UBaseType_t  hwm=uxTaskGetStackHighWaterMark(taskHandle);
	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Idle Task= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, hwm);

	taskHandle=Task_ADCHandle;				//Task_ADC的任务句柄
	hwm=uxTaskGetStackHighWaterMark(taskHandle);
	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Task_ADC= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, hwm);

	taskHandle=Task_InfoHandle;				//Task_Info的任务句柄
	hwm=uxTaskGetStackHighWaterMark(taskHandle);
	LCD_ShowStr(30, LCD_CurY+LCD_SP10, (uint8_t *)"Task_Info= ");
	LCD_ShowUint(LCD_CurX+10, LCD_CurY, hwm);

//=======获取内核的信息==========
	LcdFRONT_COLOR=lcdColor_GREEN;
	LCD_ShowStr(10, LCD_CurY+LCD_SP15, (uint8_t *)"Kernel Info ");

	UBaseType_t  taskNum=uxTaskGetNumberOfTasks();  //获取系统任务个数
	LCD_ShowStr(20, LCD_CurY+LCD_SP10, (uint8_t *)"uxTaskGetNumberOfTasks()= ");
	LCD_ShowUint(LCD_CurX, LCD_CurY, taskNum);

	uint16_t lastRow=LCD_CurY;
	taskEXIT_CRITICAL();		//结束临界代码段,重新允许任务调度

	//	char infoBuffer[300];
	//	vTaskList(infoBuffer);  //返回一个字符串表格,用 \t  \n  做表
	//	LCD_ShowStr(10, LCD_CurY+20, infoBuffer);

	UBaseType_t loopCount=0;
	for(;;)
	{
		loopCount++;
		LED1_Toggle();		//LED1闪烁
		vTaskDelay(pdMS_TO_TICKS(300));

		if (loopCount==10)   //正常循环次数
			break;
	}

	LcdFRONT_COLOR=lcdColor_RED;
	LCD_ShowStr(10, lastRow+LCD_SP20, (uint8_t *)"Task_Info is deleted");
	vTaskDelete(NULL);	//删除任务自己
  /* USER CODE END AppTask_Info */
}

  这段程序主要测试了以下几个功能:
  (1)使用函数vTaskGetinfo()获取一个任务的信息。首先要获取任务句柄,程序用了多种方法获取任务句柄,即程序中的如下几行语句:

	//	TaskHandle_t taskHandle=xTaskGetCurrentTaskHandle();	//获取当前任务句柄
		TaskHandle_t taskHandle=xTaskGetIdleTaskHandle();		//获取空闲任务句柄
	//	TaskHandle_t taskHandle=xTaskGetHandle("Task_ADC");		//通过任务名称获取任务句柄
	//	TaskHandle_t taskHandle=Task_ADCHandle;					//直接使用任务句柄变量

  只需要使用其中的一条语句获取任务句柄,其他语句需注释掉。另外,使用函数xTaskGeddleTaskHandle()时,可能会出现编译错误,显示这个函数未定义。这是因为在源程序tasks.c中,这个函数有个预编译条件,只有当参数INCLUDE_xTaskGetldleTaskHandle值为1时,才编译这个函数。CubeMX中没有这个参数的对应设置项,而文件FreeRTOS.h中,这个参数的默认值为0。我们需要将这个参数的值修改为1。但是要注意,不能在文件FreeRTOS.h中直接修改这个参数的值,而要在文件FreeRTOSConfig.h的用户代码沙箱段内,重新定义这个宏,即下面的代码:

/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instancer to override default
ones in FreeRTOS.h)在这个部分可以添加用定义参数,例如,用于覆盖FreeRTOS.h中的默认参数定义*/
#define INCLUDE_xTaskGetIdleTaskHandle   1
/* USER CODE END Defines */

  这个沙箱段在文件FreeRTOSConfig.h中的最下方,就是用于重新定义一些无法在CubeMX中可视化设置的参数,用于替换其在文件FreeRTOS.h中的默认定义。

  使用FreeRTOS时,如果编译时遇到函数未定义的错误,要注意查看其源代码里有没有预编译条件。有的函数在头文件里有定义,但源程序里不一定编译,例 如,函数xTaskGetIdleTaskHandle()。

  调用函数vTaskGetlnfo()的语句如下:

vTaskGetInfo(taskHandle,&taskInfo,getFreestackspace,taskstate);

  函数返回的任务信息存储在结构体变量taskInfo里。参数getFreeStackSpace确定是否获取任务栈空间的高水位值。若参数taskState指定为某种状态,就返回任务在这种状态下的参数,若taskState为eInvalid,就返回任务实际所处状态的信息。
  vTaskGetInfo()获取的任务信息包括任务编号、名称、优先级等,还有找空间的高水位值。高水位值表示任务栈空间的最小可用剩余空间,这个值越小,就说明任务栈空间越容易溢出。在本示例调试程序的过程中发现,如果栈空间设置得太小,程序将无法正常运行。所以,在程序调试阶段,检查任务的高水位值是非常必要的
  (2)使用函数uxTaskGetStackHighWaterMark()获取一个任务的高水位值。用户可以通过函数uxTaskGetStackHighWaterMark()直接获取一个任务的高水位值(单位是字)。程序使用该函数分别获取了空闲任务、TaskADC、TaskInfo这3个任务的高水位值并加以显示。
  (3)获取内核其他信息。函数uxTaskGetNumberOfTasks()可获取FreeRTOS中当前管理的任务数本示例程序运行时,这个函数返回的值是4。除了创建的2个用户任务,还有系统自动创建的空闲任务和定时器服务任务。
  函数vTaskList()可以获取管理任务的列表信息。其返回结果是字符串,但是因为LCD的驱动程序无法显示转义字符,在LCD上无法以整齐的表格形式显示信息,所以将实际程序注释掉。
  (4)定义关键代码段。程序使用函数taskENTER_CRITICAL()和taskEXIT_CRITICAL()定义了临界代码段。在开始LCD显示之前,使用了函数taskENTER_CRITICALO定义临界代码段的开始,这样会暂停任务调度,使后面的代码段在执行时不会被其他任打断;在进入for循环之前,使用函数taskEXIT_CRITICAL()定义临界代码段的结束,恢复任务调度。
  在这里定义临界代码段的原因是这段代码使用了大量的LCD显示函数,需要用到全局变量LCD_Curx和LCD_CurY。如果不定义临界代码段,执行这段代码时,可能会被任务Task_ADC抢占执行,因为Task_ADC的优先级是osPriorityNormal,而任务Task_Info的优先级是
osPriorityLow。此外,任务Task_ADC的任务函数里也使用了LCD显示函数,会改变全局变量LCD_CurX和LCD_CurY的值。在任务Task_Info的任务函数中定义临界代码段,可以保证这段代码的执行不会被打断,也就保证了全局变量LCD_Curx和LCD_CurY的值不被外部修改,否则可能导致LCD显示位置混乱。
  (5)删除任务。任务函数的主体一般是一个无限循环,在任务函数中不允许出现return语句。如果跳出了无限循环,需要在任务函数返回之前执行vTaskDelete(NULL)删除任务自己
  程序中的for循环只执行了10次,使LED1闪烁。退出for循环之后,改变前景色为红色,
显示信息字符串“Task_Info is deleted”之后,执行vTaskDelete(NULL)删除了任务自己

2.2.5.运行与测试

  构建项目后,将代码下载到开发板上并运行,可以在LCD上看到各种信息。任务Task_ADC周期性地通过ADC1采集电压值,并在LCD上显示;任务Task_Info读取的信息在LCD上显示,LED1闪烁几次后,任务Task_Info被删除,LED1不再闪烁。
  程序运行时,LCD上显示了以下各任务的高水位值(单位是字)。

  • 空闲任务的高水位值是118。
  • 任务Task_ADC的高水位值是136。
  • 任务Task_Info的高水位值是124。

  空闲任务的栈空间大小是128字,由参数configMINIMAL_STACK_SIZE决定。任务Task_ADC和Task_Info的栈空间大小被设置为256字,如果使用默认的大小128字,则任务Task_Info会发生栈溢出,导致程序无法正常运行。

三、往期回顾

STM32Cube高效开发教程<高级篇><FreeRTOS>(一)-----FreeRTOS基础
STM32Cube高效开发教程<高级篇><FreeRTOS>(二)-----FreeRTOS的文件组成和基本原理
STM32Cube高效开发教程<高级篇><FreeRTOS>(三)-----FreeRTOS的任务相关概念及任务调度
STM32Cube高效开发教程<高级篇><FreeRTOS>(四)-----FreeRTOS的任务管理相关函数及多任务编程示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

致虚守静~归根复命

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

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

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

打赏作者

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

抵扣说明:

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

余额充值