이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.


지난 글에서 다룬 내용을 간단히 정리해 보겠습니다.

제일 먼저 RCC->APB1ENR 레지스터의 PWREN 비트를 1로 설정하여 APB1 버스를 활성화시켰습니다.

두번째로는 PWR->CR 레지스터의 VOS 비트에 1을 기록하여 Run 모드에서 버스를 최고 속도로 동작시킬 수 있게 하였습니다.

세번째로 구조체 RCC_OscInitTypeDef에 적절한 값을 설정하여 클럭 소스를 지정하였습니다.

네번째로는 구조체 RCC_OscInitTypeDef의 구성요소 중 하나인 구조체 RCC_PLLInitTypeDef에 적절한 값들을 지정하여 시스템 클럭인 SYSCLK을 구성하였습니다. RCC_PLLInitTypeDef에 지정하는 내용은 궁극적으로는RCC_PLLCFGR 레지스터에 기록할 내용임을 살펴 보았습니다.

클럭 설정하는 부분이 AVR에 비해서 많이 복잡합니다. 처음 접하는 프로세서인데다가 구조체와 매크로 함수로 코딩되어 있어서, 내용이 더 길어졌던 것 같습니다. 아무래도 범용성을 가지는 라이브러리를 구현하려니 어쩔 수 없는 현상이겠지만, HAL 드라이버 자체는 상당히 무거운 것 같습니다. 사실 위 네가지 정도 작업한 것을 HAL 드라이버를 이용하지 않고, 직접 코딩하고 컴파일하면 훨씬 가볍고 간단한 프로그램이 나오지 않을까 하는 생각이 듭니다.

호기심에 직접 바로 레지스터에 기록하는 루틴을 넣어 봤지만 그대로 동작하지는 않았습니다. 레지스터에 값을 기록하는 순서도 고려해야할 것 같고, 값을 기록한 후에 상태를 확인하면서 기다리는 시시간도 충분히 있어야 합니다. 설정한 클럭 값을 실제로 레지스터에 기록하는 함수는 HAL_RCC_OscConfig() 함수입니다. 이 함수 내부를 보면 다음과 같은 방식으로 값을 레지스터에 기록합니다.

      /* 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;
          }
        }
      }



RCC->CR 레지스터의 HSEON 비트(비트16)에 RCC_OscInitStruct->HSEState가 가지고 있는 값을 기록하고 HSERDY 비트(비트17)가 1로 설정되기를 기다립니다. HAL_GetTick() 수가 HSE_TIMEOUT_VALUE(실제 값은 100)를 넘도록 HSERDY 비트가 1로 설정되지 않으면 HAL_TIMEOUT 값을 가지고 리턴합니다.
아직 실제로 시도해 보지는 않았지만 앞의 글에서 다룬 내용을 저런 방식으로 기록하면 될 것 같은 생각이 듭니다.


앞의 글에서 다루지 못하고 남겨 두었던 클럭 설정 부분을 마져 다루어 보도록 하겠습니다.



본 글에서는 전의 글에 있었으나 다루지 못한 다음 코드들을 분석 해 봅니다.

  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_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
  }



구조체 RCC_ClkInitTypeDef는 Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_rcc.h에 다음과 같이 정의되어 있습니다.

typedef struct
{
  uint32_t ClockType;             /*!< The clock to be configured.
                                       This parameter can be a value of @ref RCC_System_Clock_Type      */

  uint32_t SYSCLKSource;          /*!< The clock source (SYSCLKS) used as system clock.
                                       This parameter can be a value of @ref RCC_System_Clock_Source    */

  uint32_t AHBCLKDivider;         /*!< The AHB clock (HCLK) divider. This clock is derived from the system clock (SYSCLK).
                                       This parameter can be a value of @ref RCC_AHB_Clock_Source       */

  uint32_t APB1CLKDivider;        /*!< The APB1 clock (PCLK1) divider. This clock is derived from the AHB clock (HCLK).
                                       This parameter can be a value of @ref RCC_APB1_APB2_Clock_Source */

  uint32_t APB2CLKDivider;        /*!< The APB2 clock (PCLK2) divider. This clock is derived from the AHB clock (HCLK).
                                       This parameter can be a value of @ref RCC_APB1_APB2_Clock_Source */

}RCC_ClkInitTypeDef;



구조체의 멤버에서 볼 수 있듯이 이 구조체에서 정할 수 있는 클럭은 SYSCLK, AHB 버스 클럭, APB1 버스 클럭, APB2 버스의 클럭 등 4가지입니다. 이 클럭은 RCC->CFGR 레지스터에서 설정합니다. RCC->CFGR 레지스터는 앞의 글 STM32CubeMx가 만든 코드 분석하기 - clock 설정(1)에서 다룬 바 있습니다.

 



구조체의 첫 멤버인 ClockType에는 설정할 클럭들을 지정합니다. 여기에 쓰일 수 있는 값은 다음의 네 개이고, 이들은 or 연산을 할 수 있습니다.

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



본 프로젝트에서는 4개를 모두 다 or 연산해서 전달하여 4를가지의 클럭을 모두 설정합니다.

구조체의 두번째 멤버 SYSCLKSource는 이름과 같이 SYSCLK를 지정합니다. 이 멤버는 RCC->CFGR 레지스터 그림에 빨간색으로 네모 표시한 RCC->CFGR 레지스터의 SW1, SW0(비트[1:0])에 해당하는 값을 전달합니다. 각각의 비트가 갖는 값은 다음 그림과 같고, 사용할 수 있는 매크로는 stm32f4xx_hal_rcc.h에다음과 같이 정의되어 있습니다.

 


#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_SYSCLKSOURCE_PLLRCLK         ((uint32_t)(RCC_CFGR_SW_0 | RCC_CFGR_SW_1))


위에 사용한 RCC_CFGR_SW_HSI 등의 매크로는 Drivers\CMSIS\Device\ST\STM32F4xx\Include\stm32f407xx.h에 다음과 같이 정의되어 있습니다.
#define RCC_CFGR_SW_HSI                    0x00000000U                         /*!< HSI selected as system clock */
#define RCC_CFGR_SW_HSE                    0x00000001U                         /*!< HSE selected as system clock */
#define RCC_CFGR_SW_PLL                    0x00000002U                         /*!< PLL selected as system clock */


RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;은 RCC->CFGR 레지스터의 SW(bit[1:0])에 2를 기록하여 SYSCLK으로 PLL을 사용하도록 하고 있습니니다.

구조체의 세번째 멤버 AHBCLKDivider는 SYSCLK을 몇으로 나누어 AHB 버스의 클럭(HCLK)으로 사용할 것인지를 지정합니다. RCC->CFGR 레지스터 그림에 녹색 사각형으로 표시한 HPRE[3:0](비트[7:4])로 각 비트가 갖는 값은 다음 그림과 같으며, 여기에 사용할 수 있는 매크로는 stm32f4xx_hal_rcc.h 다음과 같이 정의되어 있습니다.



#define RCC_SYSCLK_DIV1                  RCC_CFGR_HPRE_DIV1
#define RCC_SYSCLK_DIV2                  RCC_CFGR_HPRE_DIV2
#define RCC_SYSCLK_DIV4                  RCC_CFGR_HPRE_DIV4
#define RCC_SYSCLK_DIV8                  RCC_CFGR_HPRE_DIV8
#define RCC_SYSCLK_DIV16                 RCC_CFGR_HPRE_DIV16
#define RCC_SYSCLK_DIV64                 RCC_CFGR_HPRE_DIV64
#define RCC_SYSCLK_DIV128                RCC_CFGR_HPRE_DIV128
#define RCC_SYSCLK_DIV256                RCC_CFGR_HPRE_DIV256
#define RCC_SYSCLK_DIV512                RCC_CFGR_HPRE_DIV512


위에 사용된 RCC_CFGR_HPREE_DIV1 등의 매크로는 stm32f407xx.h에 다음과 같이 정의되어 있습니다.

#define RCC_CFGR_HPRE_DIV1                 0x00000000U                         /*!< SYSCLK not divided    */
#define RCC_CFGR_HPRE_DIV2                 0x00000080U                         /*!< SYSCLK divided by 2   */
#define RCC_CFGR_HPRE_DIV4                 0x00000090U                         /*!< SYSCLK divided by 4   */
#define RCC_CFGR_HPRE_DIV8                 0x000000A0U                         /*!< SYSCLK divided by 8   */
#define RCC_CFGR_HPRE_DIV16                0x000000B0U                         /*!< SYSCLK divided by 16  */
#define RCC_CFGR_HPRE_DIV64                0x000000C0U                         /*!< SYSCLK divided by 64  */
#define RCC_CFGR_HPRE_DIV128               0x000000D0U                         /*!< SYSCLK divided by 128 */
#define RCC_CFGR_HPRE_DIV256               0x000000E0U                         /*!< SYSCLK divided by 256 */
#define RCC_CFGR_HPRE_DIV512               0x000000F0U                         /*!< SYSCLK divided by 512 */



RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV2;는 RCC->CFGR 레지스터의 HPRE[3:0]에 8을 넣음으로써, SYSCLK을 2로 나누어 AHB 버스의 클럭으로 사용하도록 합니다.


구조체의 네번째 멤버인 APB1CLKDivider는 AHB 버스의 클럭을 몇으로 나누어 APB1 버스의 클럭으로 사용할 것인지를 지정합니다. RCC->CFGR 레지스터 그림에 파란색 사각형으로 표시한 PPRE1[2:0](비트[12:10])로 각 비트가 갖는 값은 다음 그림과 같으며, 여기에 사용할 수 있는 매크로는 stm32f4xx_hal_rcc.h 다음과 같이 정의되어 있습니다.



#define RCC_HCLK_DIV1                    RCC_CFGR_PPRE1_DIV1
#define RCC_HCLK_DIV2                    RCC_CFGR_PPRE1_DIV2
#define RCC_HCLK_DIV4                    RCC_CFGR_PPRE1_DIV4
#define RCC_HCLK_DIV8                    RCC_CFGR_PPRE1_DIV8
#define RCC_HCLK_DIV16                   RCC_CFGR_PPRE1_DIV16


위에 사용된 RCC_CFGR_PPRE_DIV1 등의 매크로는 stm32f407xx.h에 다음과 같이 정의되어 있습니다.

#define RCC_CFGR_PPRE1_DIV1                0x00000000U                         /*!< HCLK not divided   */
#define RCC_CFGR_PPRE1_DIV2                0x00001000U                         /*!< HCLK divided by 2  */
#define RCC_CFGR_PPRE1_DIV4                0x00001400U                         /*!< HCLK divided by 4  */
#define RCC_CFGR_PPRE1_DIV8                0x00001800U                         /*!< HCLK divided by 8  */
#define RCC_CFGR_PPRE1_DIV16               0x00001C00U                         /*!< HCLK divided by 16 */


RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;는 RCC->CFGR 레지스터의 PPRE1[2:0]에 4를 넣어 APB1의 클럭을 HCLK / 2로 정하고 있습니다.

구조체의 다섯번째 멤버 APB2CLKDivider는 AHB 버스의 클럭을 몇으로 나누어 APB2 버스의 클럭으로 사용할 것인지를 지정합니다. RCC->CFGR 레지스터 그림에 갈색 사각형으로 표시한 PPRE2[2:0](비트[12:10])로 각 비트가 갖는 값은 다음 그림과 같으며, 여기에 사용할 수 있는 매크로는 위의 PPRE1에서 사용한 것과 같습니다.

 



HAL_RCC_ClockConfig() 함수는 구조체 RCC_ClkInitTypeDef로 전달받은 값을 RCC->CFGR 레지스터에 기록합니다. 실제로는 이 함수는 RCC_ClkInitTypeDef 외에 정수형 변수를 하나 더 받습니다. 이 정수형 변수는 Flash Memory를 관리하는데 필요한 지연시간입니다. 이와 관련된 사항은 나중에 Flash Memory를 다룰 때에 자세히 살펴 보겠습니다.


이상으로 총 2회에 걸친 SystemClock_Config() 함수에 대한 분석을 모두 마치겠습니다.


{후기]처음 접하는 마이크로프로세서에 관하여 글을 쓰느라고 어렵고 힘들었지만, 많은 것을 배울 수 있어서 좋았습니다. ㅎㅎㅎ. 글 두 개 쓰는데 약 2주 걸렸습니다.

 

블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,