RTT内核启动之rt_hw_board_init硬件板级初始化函数

RTT内核启动流程补充

在上一篇文章RTT内核启动流程-优快云博客,只是简单的介绍了rtthread_startup函数中所调用的一些函数的作用,好有一个大概的认知,这篇文章想详细的介绍一下rt_hw_board_init这个函数的具体执行过程。

rt_hw_board_init函数

rtthread_startup函数所调用的第二个函数是rt_hw_board_init板级初始化函数,跳转到定义我们可以看到rt_hw_board_init函数的具体内容:
在这里插入图片描述

HAL_Init

第一个函数是HAL_Init函数(有可能会因为RTT版本的改变这里面的函数会发生一些变化),我们转到定义可以看到他的具体内容:
在这里插入图片描述

这句代码==#if (PREFETCH_ENABLE != 0)==是说如果定义了PREFETCH_ENABLE这个宏,那么会执行下面这些代码块:

#if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \
    defined(STM32F102x6) || defined(STM32F102xB) || \
    defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \
    defined(STM32F105xC) || defined(STM32F107xC)

  /* Prefetch buffer is not available on value line devices */
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif

这个代码块会对单片机的型号进行一些判断,因为有些单片机的型号是支持指令预取功能,而另一些是不支持的。所以这个代码块会在支持指令预取功能的型号上启动指令预取功能。
(也就是上面注释的第一行:Configure the Flash prefetch.)

有关于指令预取:
![[Pasted image 20240609182700.png]]

接着是HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);,正如上面的注释一样,进行中断优先级的分组,讲其设置为GROUP_4。

下面是HAL_InitTick(TICK_INT_PRIORITY);,这个函数的作用在注释中也提到了,配置系统滴答数来产生一个频率为1000Hz的中断,在目前这个阶段,时钟还没有被配置因此系统是被一个频率位16MHz的高速内部时钟信号驱动(HSI:High Speed Internal Clock)。
(后面可能会出一篇文章去具体介绍配置过程)

最后调用了HAL_MspInit();,函数去初始化一些底层硬件,在这里贴一张图,我也不太懂这个函数的具体作用以及内部运行的细节。
在这里插入图片描述

SystemClock_Config

跳转到定义可以发现这个函数紧挨着HAL_MspInit这个函数,这个函数的作用就是配置系统时钟信号
![[Pasted image 20240609185726.png]]

其实就是打通了红线这么一条路。

rt_system_heap_init

这个函数在此先不作介绍,会在后面研究RTT的内存管理的时候去介绍。跳转到定义根据函数的注释可以知道这个函数的作用是初始化操作系统的自己的堆内存。

rt_components_board_init

具体的函数内容如下:
![[Pasted image 20240609192157.png]]

根据注释可知,这个函数的作用是对板级组件的初始化。

如果我们这个宏RT_DEBUG_INIT的值为1,也就是使用调试功能(#if和#else都是C语言中的预处理指令,即根据条件的真假来选择编译哪些代码块),将执行以下代码块:

int result;
    const struct rt_init_desc *desc;
    for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
    {
        rt_kprintf("initialize %s", desc->fn_name);
        result = desc->fn();
        rt_kprintf(":%d done\n", result);
    }

![[Pasted image 20240609193003.png]]

但是在跳转定义的时候却发现这个结构体没有被定义,于是我尝试在rtdef.h(这个头文件中保存了绝大部分的定义)文件中ctrl+F寻找这个结构体,找到了这个结构体的原型
![[Pasted image 20240609193521.png]]

因为这个结构体前面有一个#if RT_DEBUG_INIT条件预处理指令,而在我下载的这个版本的RTT中,这个宏的值被定为了0,所以编译器在编译的时候也就自然不会去编译这个结构体。

在这个结构体内部,有一个字符指针,指针名为fn_name,并且还有一个函数指针,其原型在第二行:typedef int (*init_fn_t)(void);

下面定义了一个宏:

#define INIT_EXPORT(fn, level)                                                       \
            const char __rti_##fn##_name[] = #fn;                                            \
            RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
            { __rti_##fn##_name, fn};

这个宏的作用是:
![[Pasted image 20240609200450.png]]

如果有兴趣去学习这个宏具体为什么能实现这个功能,可以看一下这篇文章:
RT-Thread INIT_EXPORT宏-优快云博客

在这个函数中定义出来的rt_init_desc结构体实际上是对板级组件初始化函数的一个描述(desc实际上是description,也就是对rt_init函数的描述)。接下来就是一个for循环,for循环的第一步先对我们定义出来的desc结构体进行了一个初始化赋值,但是**__rt_init_desc_rti_board_start**这是一个什么东西呢?

我们在components.c文件中可以可以找到这样一些函数:
![[Pasted image 20240611131746.png]]

如果我们尝试将自己代入INIT_EXPORT(rti_board_start, “0.end”); 这个宏的执行过程的话,它会被展开这样:

const char __rti_rti_board_start_name[] = #rti_board_start;
RT_USED const struct rt_init_desc __rt_init_desc_rti_board_start SECTION(".rti_fn.0.end") = {__rti_rti_board_start_name, rti_board_start};

可以看出,rti_board_start这个函数指针和rti_board_start这个字符串被打包成了一个名为
__rt_init_desc_rti_board_start的结构体,并且这个结构体题被放在了.rti_fn.0.end这个段中,而在这个段中,存放了其他板级初始化的结构体,并且这个结构体是所有结构体的“头”,如果我们得到了这个结构体的地址,我们也就可以继续寻址访问其他初始化函数描述的结构体。

所以在这个for循环中,desc作为游标结点,被赋值为了这个段的首地址,当地址偏移到 __rt_init_desc_rti_board_end时,结束这个循环,在循环的过程中会打印正在初始化的函数,并在结束调用初始化函数后返回一个状态,这个状态也会在调试窗口被打印出来。

如果我们这个宏RT_DEBUG_INIT的值为0,也就是不使用调试功能,INIT_EXPORT这个宏会做出对应的改变
![[Pasted image 20240611134847.png]]

这时他不在是一个结构体,而只是一个单纯的函数指针。
![[Pasted image 20240611134952.png]]

但是这两个for循环的本质都是一样的,就是自动调用被放在段中的初始化函数。

rt_console_set_device(RT_CONSOLE_DEVICE_NAME); 这个函数将会放在其他文章中去讲解。

<think>好的,用户需要关于RT-Thread 5.0.2版本中rt_mq_recv函数的使用说明、文档和示例。首先,我应该先回顾已有的引用资料,确保信息准确。 根据引用[1]和[2],rt_mq_recv的函数接口是: rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout) 参数包括消息队列句柄、接收缓冲区、消息大小和超时时间。返回值为RT_EOK表示成功,RT_ETIMEOUT超时,RT_ERROR失败。引用[3]提到消息队列的工作机制是从队头取出消息块,处理完后将其转为空闲块插入空闲链表尾部。这说明接收消息是先进先出的,且操作会影响消息队列的结构。 接下来需要确认这些信息是否适用于5.0.2版本。由于用户提供的引用中没有明确提到版本差异,但RT-Thread的API通常保持向后兼容,所以可以推测5.0.2版本的rt_mq_recv函数参数和返回值与之前版本一致。但为了准确,建议用户查阅官方文档或版本更新日志确认。 使用说明部分应包括参数详细解释、返回值含义、超时机制。比如timeout为0可能表示立即返回,负数可能阻塞等待,正数表示等待的ticks数。需要注意线程上下文,确保调用在线程中,并处理可能的错误情况。 示例代码需要展示如何创建消息队列、发送消息、接收消息。需要包含必要的头文件,初始化步骤,以及错误处理。例如,在接收时使用RT_WAITING_FOREVER表示无限等待,接收后检查返回值,处理数据。 此外,注意事项应强调缓冲区大小匹配,避免内存溢出,正确处理超时,以及线程安全。可能还需要提到在中断服务例程中不能使用带有阻塞的rt_mq_recv,应使用非阻塞方式。 最后,生成相关问题时,要考虑用户可能的后续疑问,如如何创建消息队列、阻塞与非阻塞的区别、线程安全等。确保问题覆盖常见的使用场景和潜在问题。</think>### RT-Thread 5.0.2 rt_mq_recv 函数说明 #### 一、函数定义 ```c rt_err_t rt_mq_recv(rt_mq_t mq, void* buffer, rt_size_t size, rt_int32_t timeout); ``` **参数说明**: - `mq`:消息队列对象句柄(需预先通过`rt_mq_create`或`rt_mq_init`创建) - `buffer`:接收消息的缓冲区指针(需保证内存空间足够存储消息) - `size`:期望接收的消息大小(必须与发送方`rt_mq_send`的消息长度一致) - `timeout`:超时时间(单位:系统时钟节拍): - `0`:立即返回 - `RT_WAITING_FOREVER`(负数):永久阻塞 - 其他正整数值:等待指定节拍数[^2] #### 二、返回值 - `RT_EOK`:成功接收消息 - `RT_ETIMEOUT`:超时未收到消息 - `RT_ERROR`:参数错误或消息队列被删除[^1] #### 三、工作机制 1. 从消息队列头部提取消息块 2. 将消息内容复制到`buffer`指向的内存 3. 释放的消息块会被移动到空闲消息链表尾部[^3] #### 四、代码示例 ```c #include <rtthread.h> /* 定义消息队列控制块和缓冲区 */ static rt_mq_t test_mq; static char mq_pool[256]; int main(void) { /* 创建消息队列:名称/test_mq,消息长度8字节,缓冲区大小256,等待方式FIFO */ test_mq = rt_mq_create("/test_mq", 8, 16, RT_IPC_FLAG_FIFO); /* 接收消息 */ char recv_buf[8]; rt_err_t result = rt_mq_recv(test_mq, recv_buf, sizeof(recv_buf), RT_WAITING_FOREVER); if (result == RT_EOK) { rt_kprintf("Received: %s\n", recv_buf); } return 0; } ``` #### 五、注意事项 1. 必须保证`size`参数与发送方定义的消息长度严格一致 2. 在中断服务例程中调用时,`timeout`必须设置为`0` 3. 多线程接收时需注意优先反转问题 4. 接收缓冲区建议使用静态内存或线程栈空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值