RTThread学习记录13——关于要使用一个外设,RTThread与cubemx到底要怎么配合?

一、遇到的问题

我学习RTThread到IO设备模型时,先是学习了怎么使用UART和PIN设备,再学习了ADC和PWM这种外包型外设,就遇到一个疑惑,为什么突然到初始化ADC时就要使用到cubemx生成代码了?为什么生成代码之后要放到board.c?为什么UART和PIN设备就不需要呢?

还有我最近也在搞RTThread相关的项目,就遇到一个问题,就是我按照我的理解,我用到了pwm,我需要线在RTThread里打开pwm,然后在board.h里面#define要用到的pwm及其通道,然后使用cubemx生成pwm的引脚和时钟硬件初始化代码,再把这些代码粘贴到board.c里面去,但是我就遇到了一个问题,就是编译的时候,报错提示:pwm的引脚和时钟硬件初始化代码重复定义了,这和我之前遇到的情况完全不一样,之前学习到pwm时,也是类似的操作,这次却出问题,为什么呢。后面我用DeepSeekv4.1Flash,让它带我看懂底层逻辑,我才搞懂。

二、为什么 UART 和 PIN 不用 CubeMX,而 ADC 和 PWM 要用?

首先我们要明白,大部分的外设要使用分成底层驱动方向和软件驱动方向,底层驱动就类似于配置某个外设的时钟和引脚,偏向硬件和底层的驱动/初始化,而软件驱动方向就类似于设置这个参数的初始值、配置相关的调用这个外设的函数等等,偏向代码层面,不太涉及直接控制硬件寄存器。

所以UART和PIN外设不需要cubemx配置,是因为在RTThread中,UART和PIN外设是直接被完全配置的,而ADC和PWM外设的底层驱动(对硬件引脚和时钟的初始化,也就是**HAL_xxx_MspPostInit()和HAL_xxx_MspInit()**这2个函数),RTThread是没有配置的,RTThread只配置了软件驱动,cubemx负责底层驱动。

为什么要怎么做?

三、为什么官方教程需要复制那2个函数?其本意是什么?

前面我们知道,RTThread要用一个外设需要两个方向的驱动,但是在硬件引脚和时钟方面的初始化RTThread有2种方式。

第一种就是非常简单直接的,比如你要初始化定时器3的PWM,通道是4,那就找到初始化定时器3的PWM,通道为4的时钟及其硬件引脚的代码也就是下面的代码,把它粘贴到board.c里面,这样就能让RTThread自己初始化硬件引脚和时钟了。

cpp 复制代码
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm)
{
  if(htim_pwm->Instance==TIM3)
  {
    /* USER CODE BEGIN TIM3_MspInit 0 */

    /* USER CODE END TIM3_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();
    /* USER CODE BEGIN TIM3_MspInit 1 */

    /* USER CODE END TIM3_MspInit 1 */

  }

}

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(htim->Instance==TIM3)
  {
    /* USER CODE BEGIN TIM3_MspPostInit 0 */

    /* USER CODE END TIM3_MspPostInit 0 */

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**TIM3 GPIO Configuration
    PB1     ------> TIM3_CH4
    */
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* USER CODE BEGIN TIM3_MspPostInit 1 */

    /* USER CODE END TIM3_MspPostInit 1 */
  }

}

其实这种方式也就是和board.h里面的指示意思一样,就是需要你自己找到初始化这个外设的底层驱动函数,这样RTThread就能自己脱离cubemx使用外设了。

缺点就是这个初始化底层的代码需要自己找,要么ai,要么查资料。

但是最方便的方式其实就是使用cubemx生成某个外设的全套初始化代码,包括初始化这个外设的时钟、这个外设的硬件引脚,这个外设的初始值设置,这个外设其他的管理函数等等.....

但是RTThread其实只需要初始化这个外设的时钟、初始化这个外设的硬件引脚 的代码,其他的初始化RTThread自己能搞定,所以就需要RTThread排除掉cubemx生成的无用的东西,不让无用的代码参与编译,然后把初始化这个外设的时钟、初始化这个外设的硬件引脚的代码复制粘贴到board.c里面,这样就能正常使用一个外设了!这才是board.h里那4个步骤的本意!

四、cubemx到底有什么用,为什么RTThread需要用到cubemx?

cubemx对于RTThread目前我总结出有2个作用。

1.RTThread独立,不依赖cubemx

首先,cubemx能帮助RTThread生成外设的底层初始化的代码(也就是**HAL_xxx_MspPostInit()和HAL_xxx_MspInit()**这2个函数),省得RTThread又去找。就像上面举的例子,就是cubemx生成代码,RTThread取自己需要的就行,不依赖cubemx。

2.RTThread配合cubemx,依赖于cubemx

其次,就是直接由cubemx接管所以的硬件初始化,简单来说,就是不管什么外设,UART、PWM、PIN、ADC等等,全部在cubemx里面配置好参数,通道,模式等等,直接生成代码,由cubemx接管外设的初始化。RTThread只需要在RTThread-setting里面打开要用到外设就行,此时RTThread就是深度依赖于cubemx了。

五、为什么之前能成功,现在不行?

关键在于RTThread屏蔽的代码有哪些 和 cubemx生成代码放哪里的问题。

在以前使用cubemx开发时,我们会经常要勾选

"Generate peripheral initialization as a pair of .c/.h files per peripheral"

目的是为了把每个外设的初始化文件(xxx.c/xxx.h)分开放,不要全部放到stm32f1xx_hal_msp.c文件里,能够方便我们管理。

在RTThread,RTThread恰好屏蔽的代码文件就包括每个外设自己的的初始化文件(xxx.c/xxx.h),而不会屏蔽stm32f1xx_hal_msp.c这个文件,因为里面还包含的是整个板子HAL库及底层硬件的初始化代码,RTThread不能屏蔽!

同时,在cubemx眼里,你不勾选**"Generate peripheral initialization as a pair of .c/.h files per peripheral",意思就是要把所有外设的初始化代码(包含了HAL_xxx_MspPostInit()和HAL_xxx_MspInit()** 这2个函数及该外设的初始值、模式、通道初始化)放到stm32f1xx_hal_msp.c 这个文件里,这样,因为RTThread没有屏蔽stm32f1xx_hal_msp.c这个文件,就会调用里面对该外设的底层驱动初始化的代码(也就是HAL_xxx_MspPostInit()和HAL_xxx_MspInit() 这2个函数),这也是第四个问题里,RTThread依赖cubemx的实现原理,就是cubemx在生成代码时不勾选,然后把外设所有的初始化代码放一起让RTThread自己调用。


那么,RTThread不依赖cubemx,自己初始化外设的原理就和上面相反了,就是勾选**"Generate peripheral initialization as a pair of .c/.h files per peripheral",让外设的初始化代码分别存放,这样我们需要用到哪个外设就直接去对于外设的初始化代码,把里面的HAL_xxx_MspPostInit()和HAL_xxx_MspInit()** 函数复制粘贴出来放到board.c里面就行了。cubemx在这单纯只是一个代码生成器,没有其他作用了。

所以总的来说,为什么我之前的项目能成功,但是现在却不行了?

成功的原因就是我之前的项目勾选了,RTThread就自动屏蔽掉cubemx生成的代码了,然后我就按照board.h里面指示的,复制HAL_xxx_MspPostInit()和HAL_xxx_MspInit() 这2个函数到board.c里面,完全符合流程和原理,就没出现问题。

失败的原因有2个,我一开始遇到的问题是说HAL_xxx_MspPostInit()和HAL_xxx_MspInit() 这2个函数重复定义了,是因为我在cubemx里面没有勾选,然后我又把HAL_xxx_MspPostInit()和HAL_xxx_MspInit() 这2个函数复制到board.c里面,导致RTThread在编译时发现HAL_xxx_MspPostInit()和HAL_xxx_MspInit() 这2个函数定义了两次,就报了这个错。

第二个问题就是,

六、总结与拓展

总的来说,这次有加深了我对于IO设备,从应用上的理解,DeepSeek确实也是一个很好的自学工具,让我很快理解了教程里一带而过但是深有其意的细节之处,它还给我拓展了很多的知识。

拓展部分

关于弱定义函数:RT-Thread 里像 ADC、PWM这类外设,执行和他们有关的库函数时,会去找一个固定名字的配套初始化函数来开时钟、配引脚。一般cubemx生成的代码在RTThread里都会被加载成弱定义函数,比如_weak int main()函数就是典型例子,cubemx也会生成main函数,但是会加上_weak前缀表示是弱定义,这样就只会执行RTThread里面的main()函数了。

相关推荐
phltxy1 小时前
C 语言动态内存管理:从申请空间到安全释放
c语言·开发语言·ui
上位机妹子2 小时前
C 语言 数组删除指定元素(快慢指针法)
c语言·数据结构·算法
心中有你02142 小时前
【路径规划】A*寻路算法最通俗易懂讲解(C语言完整实现+详细注释)
c语言·开发语言·算法
喜欢打篮球的普通人2 小时前
MiniMind 学习笔记(十二):Pretrain 实操——从版本梳理到 8GB 显卡上的真实训练
人工智能·笔记·学习
大圣编蚕3 小时前
C语言入门:基本语法汇总
c语言·开发语言
m4Rk_3 小时前
【论文阅读】Agent 记忆机制(87):VizoMem——把文本历史转化为可检索的视觉记忆
论文阅读·人工智能·学习·开源·github
en.en..4 小时前
C语言---宏
c语言·开发语言
坤坤子吖4 小时前
算法学习——高精度加减乘除
c++·笔记·学习·算法
HY小宝F4 小时前
树莓派智能摄像头系统 30 天重学计划之 Day 03 · 网络基础:建立“包“的思维 —— 学习总结
网络·学习