stm32 hal库 RCC初始化函数SystemClock_Config()梳理分析、初步细致学习(一)

这篇具有很好参考价值的文章主要介绍了stm32 hal库 RCC初始化函数SystemClock_Config()梳理分析、初步细致学习(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、PLL主时钟初始化

1.1 时钟使能

 1.2 配置好主时钟配置结构体

1.3 将配置好的值写入到对应的寄存器、初始化PLL主时钟;

1.3.1 __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState)分析: 

1.3.2 给PLL相关寄存器赋值:

二、外设时钟初始化

2.1等待周期的验证和写入;

2.2 HCLK配置

2.3 SYSCLK配置、时钟源选择

2.3.1  PLL时钟就绪检测和__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY)宏定义的分析

2.3.2 系统时钟源选择

2.4PCLK1和PCLK2配置

2.5 更新hal库参数

三、总结


本人使用的单片机stm32f407vg,代码来源stm32CubeMx。

时钟配置如下

systemclock_config,stm32,学习

 主函数调用SystemClock_Config();,这也是RCC初始化函数;

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage 
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 144;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

一、PLL主时钟初始化

1.1 时钟使能

__HAL_RCC_PWR_CLK_ENABLE();

#define __HAL_RCC_PWR_CLK_ENABLE()     do { \
                                    __IO uint32_t tmpreg = 0x00U; \
                                    SET_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
                                    /* Delay after an RCC peripheral clock enabling */ \
                                    tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
                                    UNUSED(tmpreg); \
                                      } while(0U)

#define SET_BIT(REG, BIT)     ((REG) |= (BIT))

 #define READ_BIT(REG, BIT)    ((REG) & (BIT))

#define RCC_APB1ENR_PWREN_Pos              (28U)                               
#define RCC_APB1ENR_PWREN_Msk              (0x1UL<<RCC_APB1ENR_PWREN_Pos) 
#define RCC_APB1ENR_PWREN                  RCC_APB1ENR_PWREN_Msk    

systemclock_config,stm32,学习

把0x1左移28位,即使能电源接口时钟;

__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

这个函数是调压器输出电压级别选择,选择级别1模式(默认的模式),暂时不了解有什么作用;

 1.2 配置好主时钟配置结构体

systemclock_config,stm32,学习

 32的时钟树,暂时用到这些;

 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
 RCC_OscInitStruct.HSEState = RCC_HSE_ON;

#define RCC_OSCILLATORTYPE_NONE            0x00000000U
#define RCC_OSCILLATORTYPE_HSE             0x00000001U
#define RCC_OSCILLATORTYPE_HSI             0x00000002U
#define RCC_OSCILLATORTYPE_LSE             0x00000004U
#define RCC_OSCILLATORTYPE_LSI             0x00000008U

#define RCC_HSE_OFF                      0x00000000U
#define RCC_HSE_ON                       RCC_CR_HSEON
#define RCC_HSE_BYPASS               ((uint32_t)(RCC_CR_HSEBYP | RCC_CR_HSEON))

分别是配置时钟源为(HSE\HSI\LSE\LSI); 选择HSE时钟状态(使能、关闭、旁路)

  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 144;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;

#define RCC_PLL_NONE                      ((uint8_t)0x00)
#define RCC_PLL_OFF                       ((uint8_t)0x01)
#define RCC_PLL_ON                        ((uint8_t)0x02)

#define RCC_PLLSOURCE_HSI                RCC_PLLCFGR_PLLSRC_HSI
#define RCC_PLLSOURCE_HSE                RCC_PLLCFGR_PLLSRC_HSE

#define RCC_PLLCFGR_PLLSRC_HSE_Pos         (22U)                               
#define RCC_PLLCFGR_PLLSRC_HSE_Msk   

                                                                 (0x1UL <<RCC_PLLCFGR_PLLSRC_HSE_Pos) 
#define RCC_PLLCFGR_PLLSRC_HSE             RCC_PLLCFGR_PLLSRC_HSE_Msk

 #define RCC_PLLP_DIV2                  0x00000002U
#define RCC_PLLP_DIV4                  0x00000004U
#define RCC_PLLP_DIV6                  0x00000006U
#define RCC_PLLP_DIV8                  0x00000008U

分别为PLL时钟(使能、关闭);PLL时钟源选择(HSE\HSI);PLL M\N\P,Q分频器赋值;

1.3 将配置好的值写入到对应的寄存器、初始化PLL主时钟;

  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

调用这个函数进行赋值,并通过返回值判断是否初始化成功;

代码很长,只留下HSE和PLL配置部分;

__weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef  *RCC_OscInitStruct)
{
  uint32_t tickstart, pll_config;

  /* Check Null pointer */
  if(RCC_OscInitStruct == NULL)
  {
    return HAL_ERROR;
  }

  /* Check the parameters */
  assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
  /*------------------------------- HSE Configuration ------------------------*/
  if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)
  {
    /* Check the parameters */
    assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));
    /* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled */
    if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE) ||\
      ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))
    {
      if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
      {
        return HAL_ERROR;
      }
    }
    else
    {
      /* Set the new HSE configuration ---------------------------------------*/
      __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);

      /* Check the HSE State */
      if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)
      {
        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till HSE is ready */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
      else
      {
        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till HSE is bypassed or disabled */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
    }
  }
/*-------------------------------- PLL Configuration -----------------------*/
  /* Check the parameters */
  assert_param(IS_RCC_PLL(RCC_OscInitStruct->PLL.PLLState));
  if ((RCC_OscInitStruct->PLL.PLLState) != RCC_PLL_NONE)
  {
    /* Check if the PLL is used as system clock or not */
    if(__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_CFGR_SWS_PLL)
    {
      if((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_ON)
      {
        /* Check the parameters */
        assert_param(IS_RCC_PLLSOURCE(RCC_OscInitStruct->PLL.PLLSource));
        assert_param(IS_RCC_PLLM_VALUE(RCC_OscInitStruct->PLL.PLLM));
        assert_param(IS_RCC_PLLN_VALUE(RCC_OscInitStruct->PLL.PLLN));
        assert_param(IS_RCC_PLLP_VALUE(RCC_OscInitStruct->PLL.PLLP));
        assert_param(IS_RCC_PLLQ_VALUE(RCC_OscInitStruct->PLL.PLLQ));

        /* Disable the main PLL. */
        __HAL_RCC_PLL_DISABLE();

        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till PLL is ready */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }

        /* Configure the main PLL clock source, multiplication and division factors. */
        WRITE_REG(RCC->PLLCFGR, (RCC_OscInitStruct->PLL.PLLSource                                            | \
                                 RCC_OscInitStruct->PLL.PLLM                                                 | \
                                 (RCC_OscInitStruct->PLL.PLLN << RCC_PLLCFGR_PLLN_Pos)             | \
                                 (((RCC_OscInitStruct->PLL.PLLP >> 1U) - 1U) << RCC_PLLCFGR_PLLP_Pos) | \
                                 (RCC_OscInitStruct->PLL.PLLQ << RCC_PLLCFGR_PLLQ_Pos)));
        /* Enable the main PLL. */
        __HAL_RCC_PLL_ENABLE();

        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till PLL is ready */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
        {
          if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
      else
      {
        /* Disable the main PLL. */
        __HAL_RCC_PLL_DISABLE();

        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till PLL is ready */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
    }
    else
    {
      /* Check if there is a request to disable the PLL used as System clock source */
      if((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_OFF)
      {
        return HAL_ERROR;
      }
      else
      {
        /* Do not return HAL_ERROR if request repeats the current configuration */
        pll_config = RCC->CFGR;
        if((READ_BIT(pll_config, RCC_PLLCFGR_PLLSRC) != RCC_OscInitStruct->PLL.PLLSource) ||
           (READ_BIT(pll_config, RCC_PLLCFGR_PLLM) != RCC_OscInitStruct->PLL.PLLM) ||
           (READ_BIT(pll_config, RCC_PLLCFGR_PLLN) != RCC_OscInitStruct->PLL.PLLN) ||
           (READ_BIT(pll_config, RCC_PLLCFGR_PLLP) != RCC_OscInitStruct->PLL.PLLP) ||
           (READ_BIT(pll_config, RCC_PLLCFGR_PLLQ) != RCC_OscInitStruct->PLL.PLLQ))
        {
          return HAL_ERROR;
        }
      }
    }
  }
  return HAL_OK;
}

首先是很长一堆判断是否出错的语句,不用管;

1.3.1 __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState)分析: 

      /* Set the new HSE configuration ---------------------------------------*/
      __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState); 


而函数本身如下

#define __HAL_RCC_HSE_CONFIG(__STATE__)                         \
                    do { if ((__STATE__) == RCC_HSE_ON)  

                                      SET_BIT(RCC->CR, RCC_CR_HSEON);                                       \
                           else if ((__STATE__) == RCC_HSE_BYPASS)   \
                           {         SET_BIT(RCC->CR, RCC_CR_HSEBYP);        \
                                     SET_BIT(RCC->CR, RCC_CR_HSEON);    }                                         \                      else                                      \
                          {           CLEAR_BIT(RCC->CR, RCC_CR_HSEON);       \
                                       CLEAR_BIT(RCC->CR, RCC_CR_HSEBYP);  }                                                   } while(0U)

简单看就是判断HSE使能还是旁路。然后给RCC_CR对应位赋值;

SET_BIT(RCC->CR, RCC_CR_HSEON);  

#define SET_BIT(REG, BIT)     ((REG) |= (BIT))

之前初始化的语句是:

RCC_OscInitStruct.HSEState = RCC_HSE_ON;

对应的值如下

#define RCC_CR_HSEON_Pos                   (16U)                               
#define RCC_CR_HSEON_Msk                   (0x1UL << RCC_CR_HSEON_Pos)          
#define RCC_CR_HSEON                       RCC_CR_HSEON_Msk  

即1左移16位;

systemclock_config,stm32,学习

而,SET_BIT(RCC->CR, RCC_CR_HSEBYP);

#define RCC_CR_HSEBYP_Pos                  (18U)                               
#define RCC_CR_HSEBYP_Msk                  (0x1UL << RCC_CR_HSEBYP_Pos)         
#define RCC_CR_HSEBYP                      RCC_CR_HSEBYP_Msk 

即1左移18位;

systemclock_config,stm32,学习

暂时没用过HSE旁路,好像是不用晶振、用外部信号输入代替晶振。

1.3.2 给PLL相关寄存器赋值:

先关闭PLL时钟:

__HAL_RCC_PLL_DISABLE();

#define __HAL_RCC_PLL_DISABLE() (*(__IO uint32_t *) RCC_CR_PLLON_BB = DISABLE)

#define RCC_PLLON_BIT_NUMBER       0x18U
#define RCC_CR_PLLON_BB            (PERIPH_BB_BASE + (RCC_CR_OFFSET * 32U) + (RCC_PLLON_BIT_NUMBER * 4U))
#define PERIPH_BB_BASE        0x42000000UL
/* --- CR Register --- */
#define RCC_CR_OFFSET              (RCC_OFFSET + 0x00U)

#define RCC_OFFSET                 (RCC_BASE - PERIPH_BASE)

简单点看就是在RCC_CR寄存器里。

0x18转换为10进制就是24,即bit24;

systemclock_config,stm32,学习

 赋0就是关闭PLL;使能PLL同理

主要赋值语句

WRITE_REG(RCC->PLLCFGR, (RCC_OscInitStruct->PLL.PLLSource | \
RCC_OscInitStruct->PLL.PLLM | \
                                 (RCC_OscInitStruct->PLL.PLLN << RCC_PLLCFGR_PLLN_Pos)             | \       (((RCC_OscInitStruct->PLL.PLLP >> 1U) - 1U) << RCC_PLLCFGR_PLLP_Pos) | \
                                 (RCC_OscInitStruct->PLL.PLLQ << RCC_PLLCFGR_PLLQ_Pos)));

#define WRITE_REG(REG, VAL)   ((REG) = (VAL))

首先是RCC_PLLSOURCE_HSE之前写结构体如下:

RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

#define RCC_PLLSOURCE_HSE                RCC_PLLCFGR_PLLSRC_HSE

#define RCC_PLLCFGR_PLLSRC_HSE_Pos         (22U)                               

#define RCC_PLLCFGR_PLLSRC_HSE_Msk         (0x1UL << RCC_PLLCFGR_PLLSRC_HSE_Pos) /*!< 0x00400000 */
#define RCC_PLLCFGR_PLLSRC_HSE             RCC_PLLCFGR_PLLSRC_HSE_Msk  

即1左移22位,在RCC_PLLCFGR寄存器中作用选择HSE;

systemclock_config,stm32,学习

 寄存器其他位和分频器计算如下:

systemclock_config,stm32,学习

#define RCC_PLLCFGR_PLLN_Pos               (6U) 

#define RCC_PLLP_DIV2                  0x00000002U
#define RCC_PLLP_DIV4                  0x00000004U
#define RCC_PLLP_DIV6                  0x00000006U
#define RCC_PLLP_DIV8                  0x00000008U

#define RCC_PLLCFGR_PLLP_Pos               (16U) 

#define RCC_PLLCFGR_PLLQ_Pos               (24U)   

PLLM不移动,PLLN左移6位,PLLQ左移24

PLLP分频的值右移1位、即除以2再减1,2~8分频对应00~11两位,PLLP左移16位;

最后把各个数相或,数值写到寄存器。

完毕后使能PLL时钟。

这儿使用write,是因为操作的位比较多,直接相与再写入比较方便。

二、外设时钟初始化

 systemclock_config,stm32,学习

时钟树如上,结构体成员赋值如下:

RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

#define RCC_CLOCKTYPE_SYSCLK             0x00000001U
#define RCC_CLOCKTYPE_HCLK               0x00000002U
#define RCC_CLOCKTYPE_PCLK1              0x00000004U
#define RCC_CLOCKTYPE_PCLK2              0x00000008U

#define RCC_SYSCLKSOURCE_HSI             RCC_CFGR_SW_HSI
#define RCC_SYSCLKSOURCE_HSE             RCC_CFGR_SW_HSE
#define RCC_SYSCLKSOURCE_PLLCLK          RCC_CFGR_SW_PLL

#define RCC_SYSCLK_DIV1                  RCC_CFGR_HPRE_DIV1
#define RCC_SYSCLK_DIV2                  RCC_CFGR_HPRE_DIV2

...
#define RCC_SYSCLK_DIV512                RCC_CFGR_HPRE_DIV512

#define RCC_HCLK_DIV1                    RCC_CFGR_PPRE1_DIV1
#define RCC_HCLK_DIV2                    RCC_CFGR_PPRE1_DIV2
...
#define RCC_HCLK_DIV16                   RCC_CFGR_PPRE1_DIV16

分别是要使能的外设时钟(HCLK\SYSCLK\PCLK1\PCLK2);

系统时钟源选择(HSI\HSE\PLLCLK);AHB分频器APB1分频器,APB2分频器

将配置好的结构体成员写到对应的寄存器内;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }

调用这个函数并且判断是否出错;

函数主体如下: 

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef  *RCC_ClkInitStruct, uint32_t FLatency)
{
  uint32_t tickstart;

  /* Check Null pointer */
  if(RCC_ClkInitStruct == NULL)
  {
    return HAL_ERROR;
  }

  /* Check the parameters */
  assert_param(IS_RCC_CLOCKTYPE(RCC_ClkInitStruct->ClockType));
  assert_param(IS_FLASH_LATENCY(FLatency));

  /* To correctly read data from FLASH memory, the number of wait states (LATENCY)
    must be correctly programmed according to the frequency of the CPU clock
    (HCLK) and the supply voltage of the device. */

  /* Increasing the number of wait states because of higher CPU frequency */
  if(FLatency > __HAL_FLASH_GET_LATENCY())
  {
    /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
    __HAL_FLASH_SET_LATENCY(FLatency);

    /* Check that the new number of wait states is taken into account to access the Flash
    memory by reading the FLASH_ACR register */
    if(__HAL_FLASH_GET_LATENCY() != FLatency)
    {
      return HAL_ERROR;
    }
  }

  /*-------------------------- HCLK Configuration --------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_HCLK) == RCC_CLOCKTYPE_HCLK)
  {
    /* Set the highest APBx dividers in order to ensure that we do not go through
       a non-spec phase whatever we decrease or increase HCLK. */
    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
    {
      MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_HCLK_DIV16);
    }

    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
    {
      MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, (RCC_HCLK_DIV16 << 3));
    }

    assert_param(IS_RCC_HCLK(RCC_ClkInitStruct->AHBCLKDivider));
    MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_ClkInitStruct->AHBCLKDivider);
  }

  /*------------------------- SYSCLK Configuration ---------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_SYSCLK) == RCC_CLOCKTYPE_SYSCLK)
  {
    assert_param(IS_RCC_SYSCLKSOURCE(RCC_ClkInitStruct->SYSCLKSource));

    /* HSE is selected as System Clock Source */
    if(RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_HSE)
    {
      /* Check the HSE ready flag */
      if(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
      {
        return HAL_ERROR;
      }
    }
    /* PLL is selected as System Clock Source */
    else if((RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLCLK)   ||
            (RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLRCLK))
    {
      /* Check the PLL ready flag */
      if(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
      {
        return HAL_ERROR;
      }
    }
    /* HSI is selected as System Clock Source */
    else
    {
      /* Check the HSI ready flag */
      if(__HAL_RCC_GET_FLAG(RCC_FLAG_HSIRDY) == RESET)
      {
        return HAL_ERROR;
      }
    }

    __HAL_RCC_SYSCLK_CONFIG(RCC_ClkInitStruct->SYSCLKSource);

    /* Get Start Tick */
    tickstart = HAL_GetTick();

    while (__HAL_RCC_GET_SYSCLK_SOURCE() != (RCC_ClkInitStruct->SYSCLKSource << RCC_CFGR_SWS_Pos))
    {
      if ((HAL_GetTick() - tickstart) > CLOCKSWITCH_TIMEOUT_VALUE)
      {
        return HAL_TIMEOUT;
      }
    }
  }

  /* Decreasing the number of wait states because of lower CPU frequency */
  if(FLatency < __HAL_FLASH_GET_LATENCY())
  {
     /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
    __HAL_FLASH_SET_LATENCY(FLatency);

    /* Check that the new number of wait states is taken into account to access the Flash
    memory by reading the FLASH_ACR register */
    if(__HAL_FLASH_GET_LATENCY() != FLatency)
    {
      return HAL_ERROR;
    }
  }

  /*-------------------------- PCLK1 Configuration ---------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
  {
    assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB1CLKDivider));
    MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider);
  }

  /*-------------------------- PCLK2 Configuration ---------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
  {
    assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB2CLKDivider));
    MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, ((RCC_ClkInitStruct->APB2CLKDivider) << 3U));
  }

  /* Update the SystemCoreClock global variable */
  SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> RCC_CFGR_HPRE_Pos];

  /* Configure the source of time base considering new system clocks settings */
  HAL_InitTick (uwTickPrio);

  return HAL_OK;
}

2.1等待周期的验证和写入;

  if(FLatency > __HAL_FLASH_GET_LATENCY())
  {
    /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
    __HAL_FLASH_SET_LATENCY(FLatency);

    /* Check that the new number of wait states is taken into account to access the Flash
    memory by reading the FLASH_ACR register */
    if(__HAL_FLASH_GET_LATENCY() != FLatency)
    {
      return HAL_ERROR;
    }
  }

初步看是判断等待周期的,

#define FLASH_LATENCY_2                FLASH_ACR_LATENCY_2WS  

                                                                 /*!< FLASH Two Latency cycles      */

#define FLASH_ACR_LATENCY_2WS          0x00000002U   

#define __HAL_FLASH_GET_LATENCY()     (READ_BIT((FLASH->ACR), FLASH_ACR_LATENCY))

systemclock_config,stm32,学习

 #define __HAL_FLASH_SET_LATENCY(__LATENCY__) (*(__IO uint8_t *)ACR_BYTE0_ADDRESS = (uint8_t)(__LATENCY__))

#define ACR_BYTE0_ADDRESS           0x40023C00U 

我们主要操作RCC_CFGR寄存器,所以在切换时钟源时要插入1、2个等待周期。

所以做法是调用函数__HAL_FLASH_GET_LATENCY()

读取FLASH_ACR寄存器LATENCY位。当等待周期小于2时要进行处理。

方法就是给LATENCY地址写值,写的值就是FLASH_LATENCY_2。

至于为什么没有用很多基地址组合,因为FLASH下的寄存器很重要,能自主读写的很少。

systemclock_config,stm32,学习

2.2 HCLK配置

  /*-------------------------- HCLK Configuration --------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_HCLK) == RCC_CLOCKTYPE_HCLK)
  {
    /* Set the highest APBx dividers in order to ensure that we do not go through
       a non-spec phase whatever we decrease or increase HCLK. */
    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
    {
      MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_HCLK_DIV16);
    }

    if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
    {
      MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2, (RCC_HCLK_DIV16 << 3));
    }

    assert_param(IS_RCC_HCLK(RCC_ClkInitStruct->AHBCLKDivider));
   MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_ClkInitStruct->AHBCLKDivider);
  }

之前结构体HCLK和其他三个相或,所以这儿的if判断都要进入里面;

后面两个if语句都是判断欲开启的外设时钟有没有PCLK1、PCLK2,都有,然后赋值16分频,具体的后面详解;

这部分主要是

MODIFY_REG(RCC->CFGR, RCC_CFGR_HPRE, RCC_ClkInitStruct->AHBCLKDivider);

#define MODIFY_REG(REG, CLEARMASK, SETMASK)  WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))

这儿括号很多,一开始看错了,看成前面的寄存器和后面一堆相与,怎么也不对,其实是寄存器和中间那部分相与,再和后面那部分相或;这个宏定义作用是给这个寄存器写数值,后面一堆按位运算的作用是只写给想修改的位,其他的保持不变。

READ_REG(REG)就是读RCC_CFGR寄存器的值;

#define RCC_CFGR_HPRE_Pos                  (4U)                                
#define RCC_CFGR_HPRE_Msk                  (0xFUL << RCC_CFGR_HPRE_Pos)        

                                                                                /*!< 0x000000F0 */
#define RCC_CFGR_HPRE                      RCC_CFGR_HPRE_Msk  

RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;

#define RCC_SYSCLK_DIV1                  RCC_CFGR_HPRE_DIV1

#define RCC_CFGR_HPRE_DIV1                 0x00000000U                         
#define RCC_CFGR_HPRE_DIV2                 0x00000080U                       
#define RCC_CFGR_HPRE_DIV4                 0x00000090U                        
#define RCC_CFGR_HPRE_DIV8                 0x000000A0U                      
...                     
#define RCC_CFGR_HPRE_DIV512               0x000000F0U 

这个宏定义的逻辑就是寄存器本来的值和(0x000000f0取反)也就是0xffffff0f相与,得到的值是bit[5:8]清零,其他位不变,这个值再和分频的值相或,0的位置变为想写入的值。

systemclock_config,stm32,学习

2.3 SYSCLK配置、时钟源选择

if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_SYSCLK) == RCC_CLOCKTYPE_SYSCLK)
  {
    assert_param(IS_RCC_SYSCLKSOURCE(RCC_ClkInitStruct->SYSCLKSource));


    /* PLL is selected as System Clock Source */
    else if((RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLCLK)   ||
            (RCC_ClkInitStruct->SYSCLKSource == RCC_SYSCLKSOURCE_PLLRCLK))
    {
      /* Check the PLL ready flag */
      if(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
      {
        return HAL_ERROR;
      }
    }

    __HAL_RCC_SYSCLK_CONFIG(RCC_ClkInitStruct->SYSCLKSource);

    /* Get Start Tick */
    tickstart = HAL_GetTick();

    while (__HAL_RCC_GET_SYSCLK_SOURCE() != (RCC_ClkInitStruct->SYSCLKSource << RCC_CFGR_SWS_Pos))
    {
      if ((HAL_GetTick() - tickstart) > CLOCKSWITCH_TIMEOUT_VALUE)
      {
        return HAL_TIMEOUT;
      }
    }
  }

  /* Decreasing the number of wait states because of lower CPU frequency */
  if(FLatency < __HAL_FLASH_GET_LATENCY())
  {
     /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
    __HAL_FLASH_SET_LATENCY(FLatency);

    /* Check that the new number of wait states is taken into account to access the Flash
    memory by reading the FLASH_ACR register */
    if(__HAL_FLASH_GET_LATENCY() != FLatency)
    {
      return HAL_ERROR;
    }
  }

代码很长,去掉一部分HSE、HSI作为时钟源的代码原理和PLL的一致。

2.3.1  PLL时钟就绪检测和__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY)宏定义的分析

首先是时钟就绪标志位的检测,就比如我要PLL作为主时钟的时钟源。那么在开启主时钟之前必须要保证PLL是打开的,软件通过CR寄存器下PLLRDY判断。

systemclock_config,stm32,学习

 通过函数     

if(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
      {
        return HAL_ERROR;
      }

#define __HAL_RCC_GET_FLAG(__FLAG__) (((((((__FLAG__) >> 5U) == 1U)? RCC->CR :((((__FLAG__) >> 5U) == 2U) ? RCC->BDCR :((((__FLAG__) >> 5U) == 3U)? RCC->CSR :RCC->CIR))) & (1U << ((__FLAG__) & RCC_FLAG_MASK)))!= 0U)? 1U : 0U)
这个宏定义很长,下面是__FLAG__可以选的值

/* Flags in the CR register */
#define RCC_FLAG_HSIRDY                  ((uint8_t)0x21)
#define RCC_FLAG_HSERDY                  ((uint8_t)0x31)
#define RCC_FLAG_PLLRDY                  ((uint8_t)0x39)
#define RCC_FLAG_PLLI2SRDY               ((uint8_t)0x3B)

/* Flags in the BDCR register */
#define RCC_FLAG_LSERDY                  ((uint8_t)0x41)

/* Flags in the CSR register */
#define RCC_FLAG_LSIRDY                  ((uint8_t)0x61)
#define RCC_FLAG_BORRST                  ((uint8_t)0x79)
#define RCC_FLAG_PINRST                  ((uint8_t)0x7A)
#define RCC_FLAG_PORRST                  ((uint8_t)0x7B)
#define RCC_FLAG_SFTRST                  ((uint8_t)0x7C)
#define RCC_FLAG_IWDGRST                 ((uint8_t)0x7D)
#define RCC_FLAG_WWDGRST                 ((uint8_t)0x7E)
#define RCC_FLAG_LPWRRST                 ((uint8_t)0x7F)

#define RCC_FLAG_MASK  ((uint8_t)0x1FU)

将宏定义拆开来看:

if((((((__FLAG__) >> 5U) == 1U)? RCC->CR :((((__FLAG__) >> 5U) == 2U) ? RCC->BDCR :((((__FLAG__) >> 5U) == 3U)? RCC->CSR :RCC->CIR))) & (1U << ((__FLAG__) & RCC_FLAG_MASK)))!= 0U)   return 1;

else       return 0;

把((__FLAG__) >> 5U) 看成 b;就是

if(b == 1)

{

        if(RCC_CR & (1U << ((__FLAG__) & RCC_FLAG_MASK)) != 0u)

                return 1;

        else

                return 0;

}

else if (b == 2)

{

        

        if(RCC_BDCR & (1U << ((__FLAG__) & RCC_FLAG_MASK)) != 0u)

                return 1;

        else

                return 0;

}

...

#define RCC_FLAG_PLLRDY                  ((uint8_t)0x39)

对于PLL标准位,0x39右移5位等于1,0x39和0x1f相与,也就是取后5位,得到0x19;1左移0x19位是1左移25位,也就是PLLRDY位,其他位都是0。再和RCC_CR相与,就是取第25位是0,还是1.

当是1时,也就是PLL开启时得到的结果不等于0;当未硬件置一时,0和1相与也是0,得到的结果就是0;

2.3.2 系统时钟源选择

__HAL_RCC_SYSCLK_CONFIG(RCC_ClkInitStruct->SYSCLKSource);

#define __HAL_RCC_SYSCLK_CONFIG(__RCC_SYSCLKSOURCE__) MODIFY_REG(RCC->CFGR, RCC_CFGR_SW, (__RCC_SYSCLKSOURCE__))
 

#define RCC_CFGR_SW_Pos                    (0U)                                
#define RCC_CFGR_SW_Msk                    (0x3UL << RCC_CFGR_SW_Pos)          

#define RCC_CFGR_SW                        RCC_CFGR_SW_Msk          /*!< 0x00000003 */

#define RCC_SYSCLKSOURCE_PLLCLK          RCC_CFGR_SW_PLL

#define RCC_CFGR_SW_HSI                    0x00000000U                         
#define RCC_CFGR_SW_HSE                    0x00000001U                         
#define RCC_CFGR_SW_PLL                    0x00000002U                         
函数逻辑和之前一样,给最后两位赋值,其他位保持不变。

systemclock_config,stm32,学习

  if(FLatency < __HAL_FLASH_GET_LATENCY())
  {
     /* Program the new number of wait states to the LATENCY bits in the FLASH_ACR register */
    __HAL_FLASH_SET_LATENCY(FLatency);

    /* Check that the new number of wait states is taken into account to access the Flash
    memory by reading the FLASH_ACR register */
    if(__HAL_FLASH_GET_LATENCY() != FLatency)
    {
      return HAL_ERROR;
    }
  }

这部分是减少等待周期;大于2时,写入到FLASH_ACR_LATENCY,和之前一样。

2.4PCLK1和PCLK2配置

 /*-------------------------- PCLK1 Configuration ---------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
  {
    assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB1CLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider);
  }

  /*-------------------------- PCLK2 Configuration ---------------------------*/
  if(((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK2) == RCC_CLOCKTYPE_PCLK2)
  {
    assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB2CLKDivider));
    MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE2,

                                                        ((RCC_ClkInitStruct->APB2CLKDivider) << 3U));
  }

RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;

#define RCC_CFGR_PPRE1_Pos                 (10U)                               
#define RCC_CFGR_PPRE1_Msk                 (0x7UL << RCC_CFGR_PPRE1_Pos)      

                                                                                         /*!< 0x00001C00 */
#define RCC_CFGR_PPRE1                     RCC_CFGR_PPRE1_Msk 

#define RCC_CFGR_PPRE2_Pos                 (13U)                               
#define RCC_CFGR_PPRE2_Msk                 (0x7UL << RCC_CFGR_PPRE2_Pos)      

                                                                                         /*!< 0x0000E000 */
#define RCC_CFGR_PPRE2                     RCC_CFGR_PPRE2_Msk

#define RCC_HCLK_DIV1                    RCC_CFGR_PPRE1_DIV1
#define RCC_HCLK_DIV2                    RCC_CFGR_PPRE1_DIV2
...
#define RCC_HCLK_DIV16                   RCC_CFGR_PPRE1_DIV16

#define RCC_CFGR_PPRE1_DIV1                0x00000000U                        
#define RCC_CFGR_PPRE1_DIV2                0x00001000U                        
...                       
#define RCC_CFGR_PPRE1_DIV16               0x00001C00U 

分别是给RCC_CFGR,bit[10:12]和bit[13:15]写入数据,数据内容是分频值。

对于PCLK2分频值用到PPRE1的值,所以要左移三位。

在这儿可以直接用PPER2的分频值,但不知道为什么没有用;

#define RCC_CFGR_PPRE2_DIV1                0x00000000U                       
#define RCC_CFGR_PPRE2_DIV2                0x00008000U                       
...                     
#define RCC_CFGR_PPRE2_DIV16               0x0000E000U 

systemclock_config,stm32,学习

2.5 更新hal库参数

  /* Update the SystemCoreClock global variable */
  SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> RCC_CFGR_HPRE_Pos];

  /* Configure the source of time base considering new system clocks settings */
  HAL_InitTick (uwTickPrio);

翻译下来的意思是:更新SystemCoreClock全局变量;

                                鉴于新的系统时钟设置来配置时基。

这儿涉及到hal库,目前还不太懂,希望有大佬补充。

三、总结

时钟的配置最核心的还是看懂时钟树,知道时钟源怎么选,时钟这个路怎么走的,到哪个外设。至于配置完全可以利用软件的便利,节省大量的工作,对于函数的分析其实是笔者较真了。但是一点点梳理下来,还是学习到很多函数的写法,宏定义的运用。同时,以后可能面对的时钟配置远超过CubeMX软件使用的复杂度,掌握配置时钟整体的思路至关重要。

参考资料:stm32f4xx中文参考手册文章来源地址https://www.toymoban.com/news/detail-616752.html

到了这里,关于stm32 hal库 RCC初始化函数SystemClock_Config()梳理分析、初步细致学习(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32 cubemx CAN STM32 CAN初始化详解

    接收用到的结构体如下: CAN概念:         全称Controller Area Network,是一种半双工,异步通讯。 物理层:         闭环:允许总线最长40m,最高速1Mbps,规定总线两端各有一个120Ω电阻,闭环        开环:最大传输距离1Km,最高速125Kbps,规定每根线串联一个2.2kΩ的电阻,

    2024年02月13日
    浏览(58)
  • STM32 串口的初始化(内附详细代码)

    首先我们先要根据原理图来确认我们用的串口接到了那个引脚  我这边的串口1为例,接收端是PA10,发送端是PA9首先我们需要配置PA9和PA10. 把接受端配置成浮空输入,完全靠引脚来判断。把发送端配置成复用推挽模式,并打开GPIOA的时钟和复用时钟多的看代码吧,我把注释都写

    2024年02月13日
    浏览(53)
  • STM32的GPIO初始化配置-学习笔记

            由于刚开始没有学懂GPIO的配置原理,导致后面学习其它外设的时候总是产生阻碍,因为其它外设要使用前,大部分都要配置GPIO的初始化,因此这几天重新学习了一遍GPIO的配置,记录如下。         首先我们要知道芯片上的引脚,并不是只有GPIO的功能,还能复用成

    2024年04月17日
    浏览(55)
  • STM32 GPIO设置(GPIO初始化)学习笔记

    GPIO 都知道是 通用输入输出接口 的意思就不详细解释 那么我们就直接进入怎么设置GPIO接口: 这里我的编译软件是keil5,相信大家都应该知道stm32有各种的工作模式上拉、下拉、推挽、开漏等等。如果想要了解具体的工作模式原理这里我推荐大家看:推挽 开漏 高阻 这都是谁

    2024年03月28日
    浏览(58)
  • STM32—TIM定时器初始化结构体详解

      注:高级控制定时器可以用到所有初始化结构体,通用定时器不能使用 TIM_BDTRInitTypeDef 结构体,基本定时器只能使用时基结构体。    时基结构体TIM_TimeBaseInitTypeDef用于定时器基础参数设置,与TIM_TimeBaseInit函数配合使用完成配置。 (1) TIM_Prescaler:定时器预分频器设置,

    2024年02月02日
    浏览(48)
  • STM32单片机同时初始化GPIOA和GPIOB

    要同时初始化STM32F1xx的GPIOA和GPIOB,您可以按照以下步骤进行: 首先,在代码中包含stm32f1xx.h头文件 , 例如: 然后,使能GPIOA和GPIOB的时钟 ,例如: 这将使能GPIOA和GPIOB的时钟,以便进行配置和使用。需要注意的是,STM32F103C8T6使用APB2总线驱动GPIOA和GPIOB。 接下来,设置GPIOA和

    2024年02月14日
    浏览(46)
  • STM32/GD32学习指南-踩坑之(一)外部晶振配置,初始化失败,不起振

    GD32使用外部有源晶振和无源晶振的问题,型号为GD32 F450 一、GD32配置使用外部晶振 1.使用外部无源晶振 找到startup_gd32f450_470.s汇编文件,找到SystemInit()函数跳转进去 在底部找到system_clock_config()函数,再次跳转进去 选中宏定义:__SYSTEM_CLOCK_200M_PLL_IRC16M,跳转,如图 将内部时钟

    2024年02月13日
    浏览(52)
  • STM32CubeMX v6.9.0 BUG:FLASH_LATENCY设置错误导致初始化失败

    今天在调试外设功能时,发现设置了使用外部时钟之后程序运行异常,进行追踪调试并与先前可以正常运行的项目进行对比之后发现这个问题可能是由于新版本的STM32CubeMX配置生成代码时的BUG引起的。 MCU: STM32H750VBT6 STM32CubeIDE: Version: 1.13.0 Build: 17399_20230707_0829 (UTC) STM32CubeMX: v

    2024年02月15日
    浏览(55)
  • 【STM32&RT-Thread零基础入门】 5. 线程创建应用(线程创建、删除、初始化、脱离、启动、睡眠)

    硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线 本章主要讲线程的工作机制和管理方法,通过实例讲解如何使用多线程完成多任务开发。 RT-Thread用线程控制块来描述和管理一个线程,一个线程对应一个线程控制块。线程控

    2024年02月12日
    浏览(55)
  • 通用输入输出端口GPIO,及其初始化(HAL库)

    我在学习STM32时候呢,是直接先接触的STM32CubeMX软件,更着网上各种教程迷迷糊糊学了一大堆没用的东西,于是先一步步来吧,我总结了很长时间,希望对正在学习相关知识的朋友们有帮助。 可以先去看看STM32CubeMX如何配置:传送门 读完以上我写的文章基本上是蒙的,因为我

    2024年02月09日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包