【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_堆栈

一、osThreadDef是一个宏定义
#define osThreadDef(name, thread, priority, instances, stacksz)  \
const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz), NULL, NULL }

所以

osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
//相当于
const   osThreadDef_t   os_thread_def_defaultTask = { "defaultTask", (StartDefaultTask), (osPriorityNormal), (0), (128)  }

相当于定义了一个结构体类型为osThreadDef_t的结构体常量os_thread_def_defaultTask常量,并且赋值。

宏定义中,##的作用就是把2个宏参数连接为1个数,或实现字符串的连接。
#的作用就是将#后面的宏参数进行字符串的操作,也就是将#后面的参数两边加上一对双引号使其成为字符串。

osThreadDef_t 是一个结构体定义,记住在单片机的库函数中一般变量名后缀是_t结尾的都是一个结构体。
【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_stm32_02
在这个结构体中

typedef struct os_thread_def  {
  char                   *name;        //线程名,字符串止指针类型
  os_pthread             pthread;      //线程函数的起始地址,函数指针
  osPriority             tpriority;    //初始化线程优先级,结构体类型
  uint32_t               instances;    //该线程函数的最大实例数,一般为0
  uint32_t               stacksize;    //堆栈大小要求(字节);0是默认堆栈大小
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
  uint32_t               *buffer;      //用于静态分配的堆栈缓冲区;动态分配为空
  osStaticThreadDef_t    *controlblock;  //控制块,用于保存用于静态分配的线程数据;动态分配为空
#endif
} osThreadDef_t;

解释:使用typedef将结构体os_thread_def一个osThreadDef_t的别名,以后就用osThreadDef_t代替结构体os_thread_def。当使用动态创建函数也就是采用动态内存分配时有5个成员变量,当使用静态创建函数也就是采用动态内存分配时有7个成员变量。一般在工程应用中采用动态内存分配,简单一些,不用自己设置堆栈大小和控制块。

  • *name:指定一个名字,比如指定为 defaultTask。在定义结构体变量时,会使用##(连接符) 自动在 defaultTask 前面加os_thread_def_前缀,最终的结构体变量名为 os_thread_def_defaultTask。

  • hread:指定线程函数,创建线程时该函数就会被注册为线程函数,运行线程时就是在执行线程函数的代码,不过如果以普通方式来调用线程函数的话,线程函数就是一个普通的函数。线程函数的格式是固定的,为void 函数名(void const * 参数名),函数名和参数名可以自己定,但是返回值和参数的类型必须是voidvoid const *。宏的第二个参数thread就应该写为 StartDefaultTask。
    疑问:谁会传递参数给线程函数?
    答:后面讲 osThreadCreate 函数时再介绍。

  • priority:指定线程的优先级。
    有关线程优先级,后面讲枚举类型osPiority时会详细介绍,一般的话会指定为普通优
    先级osPriorityNormal即可。

  • instances:线程实例
    0:表示 osThreadDef 宏所定义的结构体变量(数据结构)只能用来创建一个线程(线程实例)
    其它值>0 的值:比如 3,表示使用该数据结构可以创建 3 个线程,这三个线程使用的都是相同的线程函数,后面会具体举例。
    疑问:一个线程函数可以被多个线程使用吗?
    答:当然可以,虽然几个线程所用的线程函数都是一样的,但是各自运行各自的,互不
    干扰。

  • stacksz:指定线程栈的大小
    一般指定为 128 字节就够了。由于线程栈比较小,因此当线程函数要使用很大的数组时,最好定义为全局变量或者动态开辟在堆中,否者可能会导致线程栈的空间不够用

函数指针 void (*os_pthread)

typedef void (*os_pthread) (void const *argument);

【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_堆栈_03

结构体 osPriority

typedef enum  {
  osPriorityIdle          = -3,          ///< priority: idle (lowest)
  osPriorityLow           = -2,          ///< priority: low
  osPriorityBelowNormal   = -1,          ///< priority: below normal
  osPriorityNormal        =  0,          ///< priority: normal (default)
  osPriorityAboveNormal   = +1,          ///< priority: above normal
  osPriorityHigh          = +2,          ///< priority: high
  osPriorityRealtime      = +3,          ///< priority: realtime (highest)
  osPriorityError         =  0x84        ///< system cannot determine priority or thread has illegal priority
} osPriority;

【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_stm32_04

二、osThreadCreate创建任务

在前面的案例中使用osThreadDef宏定义了一个结构体变量,变量最终的名字为os_thread_def_defaultTask,创建任务(线程)时会使用该结构体变量,但是我们在使用 osThreadDef宏时所指定的名字叫defaultTask,因此需要在前面加os_thread_def_前缀,以构建出完整的名字,然后才能访问该结构体变量。

osThread的作用即是在前面加os_thread_def_前缀并加上取地址符&,表示对结构体变进行取地址。
【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_堆栈_05

defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

其中的参数osThread(defaultTask)也是一个宏定义。
【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_字符串_06
所以

defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
相当于
defaultTaskHandle = osThreadCreate(os_thread_def_defaultTask, NULL);

其中osThreadCreate函数定义如下
【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_stm32_07

osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
{
  TaskHandle_t handle;
  if (xTaskCreate((TaskFunction_t)thread_def->pthread,
				 (const portCHAR *)thread_def->name,
                 thread_def->stacksize, argument, 
                 makeFreeRtosPriority(thread_def->tpriority),&handle) 
                 != pdPASS)  
  {
    return NULL;
  }     
  
  return handle;
}

函数原型

osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)

功能:使用 osThreadDef 宏所定义的结构体变量来创建一个线程。创建好线程后,然后进入 READY 状态,等待任务管理来调度运行。

参数

  • 参数 1:指定osThreadDef所定义结构体变量的指针,通过该指针即可访问结构体变量,然后使用里面的信息来创建线程。由于结构体变量名字的前面有一个os_thread_def_前缀,所以需要使用 osThread宏来添加前缀,如果指定的名字为task1的话,第一个参数应该写为osThread(task1),进行宏替换后的最终效果为&os_thread_def_task1。实际上我们完全可以将第一个参数直接写为&os_thread_def_task1,不过使用osThread宏显然会更方便一些。

  • 参数 2:传递给线程函数的参数。线程函数的参数 argument 的值就来自于这里,如果没有什么参数要传递的,就设置为NULL。

返回值:

  • 函数调用成功就返回唯一标识线程的线程 ID(句柄),如果失败就返回 NULL。

其中优先级还要经过计算才得到

【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_动态内存分配_08

如果想直接用数字定义优先级,可以通过修改以下2处实现
【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_动态内存分配_09【FreeRTOS】FreeRTOS学习笔记(10)— FreeRTOS的osThreadDef创建任务(CMSIS_API)_宏定义_10

osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

相当于

const   osThreadDef_t   os_thread_def_defaultTask = { "defaultTask", (StartDefaultTask), (osPriorityNormal), (0), (128)  }
defaultTaskHandle = osThreadCreate(os_thread_def_defaultTask, NULL);