4. 0부터 시작하는 펌웨어 개발 - clock 설정, RCC(Reset and Clock Control)-1 내부 Clock 사용하기 (with STM32, B-L475E-IOT01A1)

728x90
반응형

2023.04.15 - [Software/운영체제 만들기] - 3. 0부터 시작하는 펌웨어 개발-프로젝트 구성 및 디바이스 드라이버 작성 준비하기 (with STM32, B-L475E-IOT01A1)

 

3. 0부터 시작하는 펌웨어 개발-프로젝트 구성 및 디바이스 드라이버 작성 준비하기 (with STM32, B-L4

지난 포스팅에서 개발준비를 모두 마쳤다. 그럼 이제 본격적으로 STM32에 대해 알아보려고 한다. 본 포스팅은 내가 개인적으로 펌웨어 개발에서 추구하는 방향이므로 정답은 아니고 하나의 방법

vuzwa.tistory.com

 

이전 포스팅을 하고 시간이 꽤 흘렀다. 귀찮고 게을러진 거지 ㅎㅎㅎ 이거 해서 뭐 하나~~~~ 이런 생각에 잠시 멈췄다가 다시 시작해 본다. 이전 포스팅과 프로젝트 이름 폴더 구성이 달라졌다. 

포스팅을 하는 시점을 기준으로 RCC 일부 기능과 GPIO, USART 일부기능을 구현했고, ITM, Systick을 구현해 놓은 상태다. 구현을 하다 보니 위 그림과 같은 폴더구조가 적절한 것 같아서 구성해 봤다. src에서 사용하는 .h 파일은 inc 폴더에 모두 모아두고 ARM 코어를 제어하는 코드들은 cortex_driver폴더에 기능별로 정리하고 mcu의 peripheral을 제어하는 코드들은 stm32_driver에 기능별로 정리했다. 이렇게 정리해 두면 다른 MCU에 적용하기도 수월할 것 같다. 

이번 포스팅의 주제인 RCC는 MCU의 전원이 켜지거나 리셋이 발생하면 시스템을 초기 상태로 되돌리는 기능을 하고 clock을 설정하는 기능을 한다. 시스템이 초기 상태로 돌아가게 되면 가장 먼저 해줘야 하는 일이 Clock을 설정하는 일이다. 그래서 Reset과 Clock을 제어하는 기능을 하나의 peripheral로 구성한 것이 아닐까?라는 생각이 든다. 

MCU를 리셋시키는 방법은 여러 가지가 있는데 이번 포스팅에서는 리셋에 대해서는 다루지 않고 Clock 제어에 대해서만 알아보도록 하겠다. 

아래 그림이 STM32L475VGT의 Clock block diagram이다. 레퍼런스 매뉴얼 154 페이지에 있다. 

clock은 외부에서 공급받는 방식과 mcu 내부에서 자체적으로 생성하는 clock을 사용하는 방법이 있다. 현재 사용하는 개발보드에는 외부 Clock이 미삽이라 테스트해 볼 수 없기 때문에 내부 Clock을 사용하는 방법에 대해 알아보려고 한다.(구매해서 구현해 볼 예정)  MCU의 Core, Flash, peripheral, Memory 등은 모두 다른 clock으로 동작을 하기 때문에 다양한 분기 과정을 거치게 되는데 여기에 공급해 주는 Clock을 만들어주는 역할을 하는 것이 RCC다. MCU가 초기화되면 clock도 내부 clock을 사용하는 설정으로 초기화된다. 아마 거의 대부분의 mcu가 동일할 것이다. 내부 Clock 이 분기되는 과정은 다음과 같다. 


STM32L475의 내부 Clock 은 16 MHz로 고정된 HSI와 100khz~48 MHz까지 사용가능한 MSI 두 가지가 있다. MCU가 리셋되면 MSI 4 MHz로 기본설정된다. 먼저 HSI를 사용하는 방법에 대해 알아보자.

HSI Clock 사용하기

RCC의 CR1 레지스터 중 bit 8,9,10,11번이 HSI에 관련된 레지스터다.

HSI를 사용하기 위해서는 HISON bit를 set 시키고 HSIRDY 핀의 상태를 읽어 HSI의 준비상태를 확인한 다음 SYSCLK를 선택해 준다.

코드로 구현해 보자!

static int8_t rcc_hsi_clock_init(rcc_config_t* config)
{
    RCC->CR |= (1 << RCC_CR_HSION_Pos);

    while(!(RCC->CR & RCC_CR_HSIRDY));

    if((RCC->CFGR & RCC_CFGR_SW) != RCC_CFGR_SW_HSI)   {
        RCC->CFGR &= ~RCC_CFGR_SW;
        RCC->CFGR |= (RCC_CFGR_SW_HSI << RCC_CFGR_SW_Pos);
    }

    RCC->CR &= ~RCC_CR_MSION;

    while(RCC->CR & RCC_CR_MSIRDY);

    mcu_clock.system_clock = HSI_CLOCK_VALUE_HZ;

    return 1;
}

3번째 줄에서 HSION bit를 set 시키고 5번째 줄에서 HSIRDY bit 가 set 될 때까지 대기한다. 5번째 줄의 코드는 HSIRDY가 set 되지 않는다면 무한루프에 빠져 시스템이 멈추는 현상이 발생할 수 있는 여지가 있어서 다른 peripheral에서 사용한다면 좋지 못한 코드지만 Clock을 초기화하는 과정에서는 사용해도 크게 무리가 없는 코드다. 7번째 줄에서 RCC의 CFGR 레지스터에서 SYSCLK mux의 설정을 변경해 HSI Clock을 사용하도록 설정한다. 

 

HSI clock으로 설정을 완료한 다음 초기에 설정되어 있던 MSI Clock을 비활성화시켜줘야 한다. clock 전환 시에는 반드시 사용하고자 하는 clock을 먼저 활성화시켜 놓은 다음 사용 중이던 clock을 비활성화시키는 순서로 진행되어야 한다. 다음으로 MSI Clock을 사용하는 방법에 대해 알아보자.

 

반응형

 

MSI Clock 사용하기

MSI Clock은 MCU 초기상태에 사용되는 Clock이다. MSI 레지스터는 HSI와 동일한 레지스터의 bit 0~7번에 위치하고 있다. 


구현한 코드로 설명해 보도록 하겠다.

static int8_t rcc_msi_clock_init(rcc_config_t* config)
{
    uint32_t sysclk = config->sysclk;
    uint32_t msi_range;

    RCC->CR |= RCC_CR_MSION;

    while(!(RCC->CR & RCC_CR_MSIRDY));

    /**
     * 1 : MSI Range is provided by MSIRANGE[3:0] in the RCC_CR register
     * 0 : MSI Range is provided by MSISRANGE[3:0] in RCC_CSR register
     */
    RCC->CR |= (1 << RCC_CR_MSIRGSEL_Pos);

    if(config->sysclk > STM32L475VGT_MSI_MAX_CLOCK)  {
        sysclk = STM32L475VGT_MSI_MAX_CLOCK;
        msi_range = RCC_CR_MSIRANGE_11;
    }
    else {
         msi_range = find_msi_clock(config->sysclk);
    }

    RCC_CR_MISRANGE_CLEAR;
    RCC->CR |= msi_range;
    
    if((RCC->CFGR & RCC_CFGR_SW) != RCC_CFGR_SW_MSI)   {
        RCC->CFGR &= ~RCC_CFGR_SW;
        RCC->CFGR |= (RCC_CFGR_SW_MSI << RCC_CFGR_SW_Pos);
    }
   
    mcu_clock.system_clock = sysclk;

    return 1;
}

6번째 줄에서 MSION bit를 set 시키고 MSIRDY bit가 set 될 때까지 대기한다. 여기까지는 HSI와 동일하다. MSI는 100kHz에서 48 MHz 사이의 clock을 선택해서 사용할 수 있고 MSI clock의 범위는 CR1의 bit4~7번의 MSIRANGE 레지스터와 RCC_CRS 레지스터의 bit8~11번의 MSISRANGE 레지스터를 통해 설정할 수 있다. MSI Clock의 Frequency를 조절하기 위해서는 MSIRGSEL bit가 set 되어 있어야 한다. 

MSISRANGE는 Stanby mode와 관련 있는 내용으로 보인다. 이 부분은 향후 전원모드 관련내용을 구현할 때 진행해 봐야겠다. 25번째줄이 MSIRANGE를 설정하는 부분이고 이후는 HSI와 동일하다. 아래 코드는 MSI를 사용해 설정할 수 있는 clock의 주파수를 enum 으로 정렬한 코드다.

typedef enum {
    MSI_100_KHZ = 0,
    MSI_200_KHZ,
    MSI_400_KHZ,
    MSI_800_KHZ,
    MSI_1_MHZ,
    MSI_2_MHZ,
    MSI_4_MHZ,
    MSI_8_MHZ,
    MSI_16_MHZ,
    MSI_24_MHZ,
    MSI_32_MHZ,
    MSI_48_MHZ,
    MSI_CLOCK_NUM,
}   msi_clock_range_t;

 

여기까지가 내부 Clock인 MSI, HSI를 사용하는 방법이다. 이제 Flash, Peripheral 등으로 분기되는 clock의 prescaler를 설정하는 방법에 대해 알아보자. 

Clock prescaler 설정

CFGR의 SW bit를 통해 설정된 SYSCLK는 AHB Prescaler를 거쳐 분기되고 APB1, APB2 Prescaler를 통해 한번더 나눠져 Flash, Peripheral등에 공급된다.

AHB는 CFGR의 bit4~7번 레지스터로 설정한다. 

코드로 구현은 아래와 같다. 

static uint8_t rcc_hclk_init(hclk_div_t div)
{
    uint32_t div_table[HCLK_DIV_NUM] = {RCC_CFGR_HPRE_DIV1, RCC_CFGR_HPRE_DIV2, RCC_CFGR_HPRE_DIV4, RCC_CFGR_HPRE_DIV8, RCC_CFGR_HPRE_DIV16, RCC_CFGR_HPRE_DIV64, RCC_CFGR_HPRE_DIV128, RCC_CFGR_HPRE_DIV256, RCC_CFGR_HPRE_DIV512};
    uint16_t div_value[HCLK_DIV_NUM] = {1, 2, 4, 8, 16, 64, 128, 256, 512};

    RCC->CFGR &= ~RCC_CFGR_HPRE;
    RCC->CFGR |= div_table[div];

    mcu_clock.hclk = mcu_clock.system_clock / div_value[div];

    return 1;
}

uint32_t div_table[HCLK_DIV_NUM] 의 초기화 값은 STM32;475XX.H 파일에 있는 DEFINE 값이다. APB1, APB2의 Prescaler를 설정하는 코드는 아래와 같다. 

static uint8_t rcc_apb_init(apb_name_t apb_num, hclk_div_t div)
{
    uint32_t div_table[APB_DIV_NUM] = {RCC_CFGR_PPRE1_DIV1, RCC_CFGR_PPRE1_DIV2, RCC_CFGR_PPRE1_DIV4, RCC_CFGR_PPRE1_DIV8, RCC_CFGR_PPRE1_DIV16};
    uint16_t div_value[APB_DIV_NUM] = {1, 2, 4, 8, 16};

    switch(apb_num) {
        case APB1 :
            RCC->CFGR &= ~RCC_CFGR_PPRE1;
            RCC->CFGR |= div_table[div];
            mcu_clock.apb1_peripheral = mcu_clock.hclk / div_value[div];
            mcu_clock.apb1_timer = mcu_clock.apb1_peripheral;
            break;

        case APB2 :
            RCC->CFGR &= ~RCC_CFGR_PPRE2;
            RCC->CFGR |= (div_table[div] << (RCC_CFGR_PPRE2_Pos - RCC_CFGR_PPRE1_Pos));
            mcu_clock.apb2_peripheral = mcu_clock.hclk / div_value[div];
            mcu_clock.apb2_timer = mcu_clock.apb2_peripheral;
            break;
        default : break;
    }

    return 1;
}



SMT32L475의 Peripheral에서 사용할 수 있는 최대 clock을 80MHz를 만들어내기 위해서는 내부, 외부 Clock 사용여부에 관계없이 반드시 PLL을 사용해야한다. 현재 구현내용에는 PLL에 대한 구현이 포함되어 있지 않다. USART까지 포스팅을 하고 PLL에 대한 구현을 추가할 계획이다.

포스팅에서 설명한 코드는 모두 git을 통해 공개할 계획이다. 어느 정도 정리가 되면 :)

틀린부분이 있거나 질문이 있으시면 언제든지 댓글 부탁드립니다 ! 

- 끝 -

728x90
반응형