💌 所属专栏:【BES2500x系列】
😀 作 者:我是夜阑的狗🐶
🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询!
💖 欢迎大家:这里是CSDN,我总结知识的地方,喜欢的话请三连,有问题请私信 😘 😘 😘
您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!🤩 🤩 🤩
文章目录
- 前言
- 1 系统初始化
- 1.1 邮箱线程的诞生
- 1.2 app_os_init()
- 2 Battery模块
- 2.1 app_mailbox_init()
- 2.2 osMailQDef()
- 2.3 osMailQ()
- 2.4 osThreadDef
- 2.5 app_thread()
- 2.6 osThread
- 总结
前言
大家好,又见面了,我是夜阑的狗🐶,本文是专栏【BES2500x系列】专栏的第13篇文章;
今天开始学习BES2500x系列的一天💖💖💖,开启新的征程,记录最美好的时刻🎉,每天进步一点点。
专栏地址:【BES2500x系列】, 此专栏是我是夜阑的狗对BES2500x系列开发过程的总结,希望能够加深自己的印象,以及帮助到其他的小伙伴😉😉。对了,前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
1 系统初始化
前面学习了嵌入式系统启动的基本流程,可以分为 引导程序 和 系统初始化程序 这两部分,并了解系统初始化中各个函数作用,但是看到这里我们估计还是一头雾水,此时就有大佬说“不妨从 Battery 模块入手了解该 SDK 框架”。那接下来就学习 Battery 模块具体是怎么跑的吧。话不多说,让我们原文再续,书接上回吧。
1.1 邮箱线程的诞生
从上一篇文章可以了解到系统的 main
函数是如何跑到的,并且在讲解 main 函数中各个函数的作用,还知道了 main
中的 app_init
应用初始化涉及到了蓝牙、存储、系统状态、电源管理以及音频等方面的初始化和配置。知道这些初始化流程之后,接下来就深入了解一下 Battery
模块是怎么运转的吧,可以在 app.cpp
文件中看到该 app_init 函数。
这里由于 app_init
函数代码过多,这里不就放出来了,只讲解一下其中的各个函数作用。app_init
函数中 个别函数 讲解
1.2 app_os_init()
这段代码是一个应用程序操作系统初始化函数的实现,我们可以看到了线程创建,这是 CMSIS 的风格,相信开发过 STM32 的大佬应该比较熟悉,用来定义线程、定时器和邮箱通讯等。
- 代码
/**
* 初始化操作系统环境
*
* 该函数负责初始化应用程序的操作系统环境它首先尝试初始化邮箱,然后创建一个应用线程
* 这是应用程序启动的入口点,确保操作系统环境正确初始化,以便应用程序能够正常运行
*
* @return 返回-1表示初始化失败,0表示成功
*/
int app_os_init(void)
{
// 初始化邮箱系统,如果失败则返回-1
if (app_mailbox_init())
return -1;
// 创建应用线程,如果创建失败则返回0
app_thread_tid = osThreadCreate(osThread(app_thread), NULL);
if (app_thread_tid == NULL) {
TRACE(0,"Failed to Create app_thread\n");
return 0;
}
return 0;
}
- 参数/函数讲解
序号 | 参数/函数 | 说明 |
---|---|---|
1 | app_mailbox_init() | 初始化应用程序的邮箱,邮箱通常用于线程间的通信和消息传递。如果初始化失败,函数会返回非零值。 |
2 | osThreadCreate() | 创建了一个名为 app_thread 的线程,并将其标识符保存在 app_thread_tid 变量中。osThreadCreate 是操作系统提供的函数,用于创建一个新的线程。osThread 宏用于指定要创建的线程的名称。 |
2 Battery模块
在系统初始化中,我们可以看到调用邮箱线程初始化函数,接下来看看里面都做了些什么吧
2.1 app_mailbox_init()
- 代码
/**
* 初始化应用程序邮箱
*
* 本函数用于创建一个邮箱对象,并初始化相关计数器。如果创建失败,则会打印错误信息并返回负值。
*
* @param 无
* @return int 返回0表示成功,返回-1表示失败。
*/
static int app_mailbox_init(void)
{
// 创建邮箱对象,如果创建失败,则返回-1
app_mailbox = osMailCreate(osMailQ(app_mailbox), NULL);
if (app_mailbox == NULL) {
TRACE(0,"Failed to Create app_mailbox\n");
return -1;
}
// 初始化邮箱使用计数器
app_mailbox_cnt = 0;
return 0;
}
本函数用于创建一个邮箱对象,并初始化相关计数器。如果创建失败,则会打印错误信息并返回负
- 参数/函数讲解
序号 | 参数/函数 | 说明 |
---|---|---|
1 | osMailCreate | 创建邮箱队列 |
2 | osMailQ | 该宏通过指定的邮箱队列名称,生成一个指向该邮箱队列定义的指针。 |
2.2 osMailQDef()
一般在文件开头会看到这样的定义:osMailQDef
,
- 代码
/**
* 定义一个邮箱队列。
*
* 该函数用于定义一个邮箱队列,邮箱队列名为app_mailbox,最大容量为APP_MAILBOX_MAX,消息块类型为APP_MESSAGE_BLOCK。
* 这是一个静态邮箱队列定义,意味着它在程序运行期间一直存在。
*
* @param app_mailbox 邮箱队列的名称。
* @param APP_MAILBOX_MAX 邮箱队列的最大容量。
* @param APP_MESSAGE_BLOCK 消息块的类型。
*/
osMailQDef (app_mailbox, APP_MAILBOX_MAX, APP_MESSAGE_BLOCK);
// 静态邮箱队列的ID初始化为NULL。
static osMailQId app_mailbox = NULL;
该函数用于定义一个邮箱队列,邮箱队列名为 app_mailbox
,最大容量为APP_MAILBOX_MAX
,消息块类型为 APP_MESSAGE_BLOCK
。这是一个静态邮箱队列定义,意味着它在程序运行期间一直存在。这个邮箱队列可以用于多线程或任务之间的数据通信,确保数据安全地传递。app_mailbox
是一个全局变量,用于存储邮箱队列的标识符,方便后续操作。
/**
* 定义一个邮箱队列。
*
* 该宏用于静态定义一个邮箱队列以及相关配置,以便在系统运行时使用。邮箱队列可以用来在任务之间传递消息。
*
* @param name 邮箱队列的名称。
* @param queue_sz 邮箱队列中邮件的最大数量。
* @param type 邮件中数据的类型。
*
* @note 此宏定义了一个静态数组来存储邮件,以及一个osMailQDef_t类型的结构体来描述邮箱队列。
*/
#define osMailQDef(name, queue_sz, type) \
static uint32_t os_mailQ_m_##name[3+((sizeof(type)+3)/4)*(queue_sz)]; \
osMailQDef_t os_mailQ_def_##name = \
{ {(queue_sz), sizeof(type), (os_mailQ_m_##name)}, NULL, {NULL} }
- 参数/函数讲解
该C语言宏 osMailQDef
用于在 FreeRTOS
操作系统中创建和初始化一个邮箱(Mail Queue)结构体。具体功能如下:
1、定义内存缓冲区:首先,它创建一个名为 os_mailQ_m_name
的静态 uint32_t
类型的数组。数组的大小基于参数 queue_sz
(队列容量)和 type
(邮件的数据类型)计算得出。计算方式是 (sizeof(type) + 3) / 4,确保数组大小能完整容纳任何type类型的对象,并且对4字节对齐。
2、初始化MailQ结构体:然后,它定义了一个 osMailQDef_t
类型的变量os_mailQ_def_name
。该结构体包含以下字段:
序号 | 参数/函数 | 说明 |
---|---|---|
1 | mailQ | 队列的大小(元素个数),由 queue_sz 提供。 |
2 | mailSize | 单个邮件的大小,由 sizeof(type) 提供,用于确保正确分配和传递邮件数据。 |
3 | pool | 指向上面定义的内存缓冲区的指针,即os_mailQ_m_name 。 |
4 | list和mutex | 这两个字段通常与 FreeRTOS 的内部管理有关,用于同步和保护邮箱操作,这里它们被初始化为NULL 。 |
通过这个宏,用户可以在编译时创建邮箱队列,为实时操作系统中的任务间安全通信提供支持。
2.3 osMailQ()
- 代码
/**
* 宏定义用于获取指定名称的邮箱队列的地址。
*
* 该宏通过指定的邮箱队列名称,生成一个指向该邮箱队列定义的指针。
*
* @param name 邮箱队列的名称。此参数将被直接用于构建邮箱队列指针的名称。
* @return 返回一个指向指定名称的邮箱队列定义的指针。
*/
#define osMailQ(name) \
&os_mailQ_def_##name
- 参数/函数讲解
序号 | 参数/函数 | 说明 |
---|---|---|
1 | osMailQ | 该宏通过指定的邮箱队列名称,生成一个指向该邮箱队列定义的指针。 |
2.4 osThreadDef
接下来就让我们来深入看下 osThreadCreate(osThread(app_thread), NULL)
是怎么跑的吧。一般在文件开头会看到这样的定义:osThreadDef
,
- 代码
static void app_thread(void const *argument);
osThreadDef(app_thread, osPriorityHigh, 1, APP_THREAD_STACK_SIZE, "app_thread");
定义了一个名为 app_thread
的线程,具有高优先级 osPriorityHigh
,只有一个实例,栈大小为 APP_THREAD_STACK_SIZE
,线程的任务名称为 “app_thread
”。
/*
* 定义一个操作系统的线程定义。
*
* 参数:
* name: 线程名称的前缀。
* priority: 线程的优先级。
* instances: 线程实例的数量。如果为1,则创建一个静态实例;否则,创建动态实例。
* stacksz: 线程栈的大小(字节)。如果为0,则分配最小栈大小。
* task_name: FreeRTOS任务的名称。
*
* 返回值:无。
*
* 说明:此宏用于预先定义线程的栈、控制块和FreeRTOS线程定义结构体。
*/
#define osThreadDef(name, priority, instances, stacksz, task_name) \
static uint64_t os_thread_stack##name[(stacksz)?(((stacksz+7)/8)):1]; \
static StaticTask_t os_thread_cb_##name; \
const osThreadDef_t os_thread_def_##name = \
{ (name), \
{ task_name, osThreadDetached, \
(instances == 1) ? (&os_thread_cb_##name) : NULL,\
(instances == 1) ? sizeof(StaticTask_t) : 0U, \
((stacksz) && (instances == 1)) ? (&os_thread_stack##name) : NULL, \
8*((stacksz+7)/8), \
(priority), 0U, 0U } }
- 参数/函数讲解
这是一个宏定义,用于定义线程的属性。它包括了以下几个部分:
序号 | 参数/函数 | 说明 |
---|---|---|
1 | os_thread_stack##name[(stacksz)?(((stacksz+7)/8)):1] | 这行代码定义了一个名为 os_thread_stack<name> 的静态数组,用于存储线程的堆栈。堆栈的大小由 stacksz 决定,通过 (stacksz+7)/8 计算出需要的 uint64_t 类型的元素个数,并且使用三元运算符 (stacksz)?(((stacksz+7)/8)):1 确保至少有一个元素。 |
2 | static StaticTask_t os_thread_cb_##name | 这行代码定义了一个名为 os_thread_cb_<name> 的静态变量,用于存储线程控制块。 |
3 | const osThreadDef_t os_thread_def_##name | 这行代码定义了一个名为 os_thread_def_<name> 的常量,类型为 osThreadDef_t ,其中包含了线程的名称、任务名称、控制块、堆栈等属性。 |
宏定义中的最后一部分是一个结构体的初始化,具体属性如下:
序号 | 参数/函数 | 说明 |
---|---|---|
1 | (name) | 线程名称。 |
2 | { task_name, osThreadDetached, … } | 任务的属性,包括任务名称和任务的状态(这里是 osThreadDetached ,表示线程是一个分离线程)。 |
3 | (instances == 1) ? (&os_thread_cb_##name) : NULL | 如果线程只有一个实例,将指向线程控制块的指针赋给 cb_mem ,否则为 NULL 。 |
4 | (instances == 1) ? sizeof(StaticTask_t) : 0U | 如果线程只有一个实例,将线程控制块的大小赋给 cb_size ,否则为 0U。 |
5 | ((stacksz) && (instances == 1)) ? (&os_thread_stack##name) : NULL | 如果线程有堆栈且只有一个实例,将指向堆栈的指针赋给 stack_mem ,否则为 NULL 。 |
6 | 8*((stacksz+7)/8) | 堆栈的大小,保证是 uint64_t 类型的整数倍。 |
7 | (priority) | 线程的优先级 |
总的来说,这段代码定义了一个线程及其相关的属性,包括线程名称、任务名称、线程控制块、堆栈等。
/// Thread Definition structure contains startup information of a thread.
#if (osCMSIS < 0x20000U)
typedef struct os_thread_def {
os_pthread pthread; ///< start address of thread function
osPriority tpriority; ///< initial thread priority
uint32_t instances; ///< maximum number of instances of that thread function
uint32_t stacksize; ///< stack size requirements in bytes; 0 is default stack size
} osThreadDef_t;
#else
typedef struct os_thread_def {
os_pthread pthread; ///< start address of thread function
osThreadAttr_t attr; ///< thread attributes
} osThreadDef_t;
#endif
从前面线程赋值来看,这里用的第二个结构体, osThreadDef宏就是给该结构体变量初始化赋值。
2.5 app_thread()
- 代码
static void app_thread(void const *argument)
{
while(1){
APP_MESSAGE_BLOCK *msg_p = NULL; // 声明了一个指向 APP_MESSAGE_BLOCK 类型的指针 msg_p,并将其初始化为 NULL。
if (!app_mailbox_get(&msg_p)) { // 从邮箱中获取消息,并将消息的指针存储在 msg_p 中
if (msg_p->mod_id < APP_MODUAL_NUM) {
if (mod_handler[msg_p->mod_id]) { //检查模块处理函数是否存在。如果模块处理函数存在,则进入条件语句中执行后续操作。
int ret = mod_handler[msg_p->mod_id](&(msg_p->msg_body)); // 调用了模块处理函数,并将消息体作为参数传递给它。
if (ret)
TRACE(2,"mod_handler[%d] ret=%d", msg_p->mod_id, ret);
}
}
app_mailbox_free(msg_p); // 释放了消息所占用的内存,防止内存泄漏。
}
}
}
- 参数/函数讲解
序号 | 参数/函数 | 说明 |
---|---|---|
1 | app_mailbox_get() | 从邮箱中获取消息,并将消息的指针存储在 msg_p 中 |
2 | app_mailbox_free() | 释放了消息所占用的内存,防止内存泄漏 |
3 | mod_handler[] | 各模块钩子注册的数组 |
总的来说,这段代码表示一个线程不断地从邮箱中获取消息,并根据消息中的模块 ID 找到对应的模块处理函数进行处理,然后释放消息所占用的内存。
2.6 osThread
- 代码
/// Access a Thread definition.
/// \param name name of the thread definition object.
#define osThread(name) \
&os_thread_def_##name
- 参数/函数讲解
这个宏定义简单地返回了指向特定线程定义结构体的指针。具体来说,它会返回一个指针,该指针指向了以 os_thread_def_<name>
命名的线程定义结构体。通过上面一系列的说明可以知道,该 os_thread_def_<name>
结构体已经被初始化好了,所以是可以获取的。这个宏可以在代码中使用,以便获取特定线程的定义结构体指针,然后可以在操作系统中使用这个指针来管理和操作该线程。
总结
前面大费周章讲解了一通无非就是对线程函数赋值给结构体,然后用宏获取该结构来为后续创建线程做准备。到这里应该都很清晰起来了吧,来理一下具体步骤:
- Step 1、
osThreadDef
是设置线程名、优先级以及堆栈大小; - Step 2、在通过
osThread
获取配置的结构体变量的指针; - Step 3、最后作为参数转入
osThreadCreate()
创建线程;
感谢观看,这里就是 Battery模块 – 邮箱线程 的讲解,如果觉得有帮助,请给文章点个赞吧,让更多的人看到。🌹 🌹 🌹
也欢迎你,关注我。👍 👍 👍
原创不易,还希望各位大佬支持一下,你们的点赞、收藏和留言对我真的很重要!!!💕 💕 💕 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!下期再见。🎉
更多专栏订阅:
😀 【LeetCode题解(持续更新中)】
🥇 【恒玄BES】
🌼 【鸿蒙系统】
💎 【蓝牙协议栈】
🎃 【死机分析】
👑 【Python脚本笔记】
🚝 【Java Web项目构建过程】
💛 【微信小程序开发教程】
⚽ 【JavaScript随手笔记】
🤩 【大数据学习笔记(华为云)】
🦄 【程序错误解决方法(建议收藏)】
🔐 【Git 学习笔记】
🚀 【软件安装教程】
订阅更多,你们将会看到更多的优质内容!!