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()函数了。

相关推荐
外收内放5 小时前
面向对象高级(实战案例)
开发语言·python·学习
·present·6 小时前
CSAPP(lecture 4_01)
c语言·c++
薛定e的猫咪6 小时前
(ICLR 2020)MAML 靠的不是快速学习,而是特征复用
人工智能·深度学习·学习·算法·机器学习
薛定e的猫咪7 小时前
(ICLR 2026)DF-META:面向 FJSP 的无导数元学习实例自适应调度
人工智能·深度学习·学习·算法·机器学习
沉淀的.晴天9 小时前
can总线
stm32
桀人9 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
本地化文档10 小时前
llvm-libc-docs-l10n
c语言·github·gitcode·sphinx·llvm·crowdin
别动我齐刘海10 小时前
机器人 C++ / 系统联调补强2——异常处理、Protobuf
linux·c++·人工智能·学习·算法·机器学习·机器人
一直C10 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
承渊政道11 小时前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp