클럭 설정

2023년 3월 26일

클럭 트리

GD32VF103 유저 매뉴얼에 따르면 클럭 트리는 다음과 같습니다.

GD32VF103 클럭 트리

클럭 트리에서 볼 수 있다시피 GD32VF103은 내부 클럭 소스 두 개를 가지고, 외부 클럭 소스 두 개를 추가로 연결할 수 있습니다.

IRC8M :내부 RC 진동자(8 MHz) IRC40K :내부 RC 진동자(40 kHz) HXTAL :외부 고속 결정 진동자(4–32 MHz) LXTAL :외부 저속 결정 진동자(32.768 kHz)

Longan Nano에는 8 MHz짜리 HXTAL과 LXTAL이 연결되어 있습니다.

시스템 클럭(CK_SYS)를 결정하는 SCS[1:0]의 초기값은 00이기 때문에 리셋 후 시스템 클럭 소스는 IRC8M입니다. 이전까지는 별다른 클럭 설정을 하지 않았으므로 코어가 8 MHz로 동작했던 거죠.

시스템 클럭을 108 MHz로 설정하기

GD32VF103이 지원하는 시스템 클럭의 최댓값은 108 MHz입니다. 클럭 안정성은 내부 RC 진동자보다는 결정 진동자가 더 좋기 때문에 Longan Nano의 8 MHz HXTAL을 이용해 시스템 클럭을 108 MHz로 설정해보겠습니다. HXTAL 클럭(CK_HXTAL)을 시스템 클럭으로 공급하는 방법에는 세 가지가 있습니다.

  1. HXTAL 클럭을 시스템 클럭으로 직접 공급
  2. HXTAL 클럭을 PREDV0와 PLLMF를 거쳐 공급
  3. HXTAL 클럭을 PREDV1, PLL1MF, PREDV0, PLLMF를 거쳐 공급

첫 번째 방법은 HXTAL 클럭이 108 MHz가 아니기 때문에 여기선 불가능합니다. 두 번째와 세 번째 방법은 분주기와 PLL을 통해 클럭을 조절할 수 있으므로 선택 가능한 방법입니다. 8 MHz HXTAL을 108 MHz로 공급하기 위해서는 PREDV0에서 2로 나누고 PLLMF에서 27을 곱하는 두 단계만으로 충분하니까 두 번째 방법을 택하겠습니다.

시스템 클럭은 다시 AHB 및 다양한 주변 장치에 공급됩니다. AHB 클럭의 최댓값은 시스템 클럭과 동일하므로 최대 속도를 내려면 AHB 분주비를 1로 설정합니다. 또한 APB1과 APB2의 최대 클럭은 각각 54 MHz와 108 MHz이므로, APB1과 APB2의 분주비는 각각 2와 1로 설정합니다. ADC는 최대 클럭이 14 MHz이고 APB2에서 클럭을 공급받으므로 ADC의 분주비는 8로 설정합니다.

최종적으로 시스템 클럭을 108 MHz로 설정하는 방법은 다음과 같습니다.

  1. PLLMF를 27로 설정하고 PLL 입력을 PREDV0로 설정
  2. PREDV0를 2로 설정하고 그 입력을 HXTAL로 설정
  3. AHB, APB1, APB2, ADC 분주비 설정
  4. PLL 활성화
  5. 시스템 클럭 입력을 PLL로 설정

PLL을 활성화할 때는 PLL이 안정화될 때까지 대기해야 합니다. 자세한 내용은 유저 매뉴얼과 아래 코드를 참조하세요.

구현

아래 코드는 시스템 클럭을 108 MHz로 설정한 뒤 빨간색 LED를 깜빡입니다. 클럭 설정을 없앤 상태에서 컴파일하고 업로드했을 때 깜박이는 주기가 어떻게 되는지 확인해보시기 바랍니다. 헤더 파일은 저장소에서 찾을 수 있습니다.


#include "gd32vf103.h"

/* Red: C13. */

#define DELAY_COUNT (1000000U)

static void delay(uint32_t count);

int main(void) {
    /* PLL multiplier = 27. */
    RCU->CFG0 |= ((10U << RCU_CFG0_PLLMF_Pos) | (1U << RCU_CFG0_PLLMF_4_Pos));
    /* PLL source = PREDV0. */
    RCU->CFG0 |= (0U << RCU_CFG0_PLLSEL_Pos);

    /* PREDV0 = 2. */
    RCU->CFG1 |= (1U << RCU_CFG1_PREDV0_Pos);
    /* PREDV0 source = HXTAL. */
    RCU->CFG1 |= (0U << RCU_CFG1_PREDV0SEL_Pos);

    /* AHB prescaler = 1. */
    RCU->CFG0 |= (0U << RCU_CFG0_AHBPSC_Pos);
    /* APB1 prescaler = 2. */
    RCU->CFG0 |= (4U << RCU_CFG0_APB1PSC_Pos);
    /* APB2 prescaler = 1. */
    RCU->CFG0 |= (0U << RCU_CFG0_APB2PSC_Pos);
    /* ADC prescaler = 8. */
    RCU->CFG0 |= (3U << RCU_CFG0_ADCPSC_Pos);

    /* Enable HXTAL. */
    RCU->CTL |= RCU_CTL_HXTALEN;
    while ((RCU->CTL & RCU_CTL_HXTALSTB) == 0U) {}

    /* Enable PLL. */
    RCU->CTL |= RCU_CTL_PLLEN;
    while ((RCU->CTL & RCU_CTL_PLLSTB) == 0U) {}

    /* System clock source = PLL. */
    RCU->CFG0 |= (2U << RCU_CFG0_SCS_Pos);
    while ((RCU->CFG0 & RCU_CFG0_SCSS) == 0U) {}

    /* Enable GPIOC. */
    RCU->APB2EN |= RCU_APB2EN_PCEN;

    /* Output, max speed 50 MHz, push-pull. */
    GPIOC->CTL1 &= ~(GPIO_CTL1_MD13 | GPIO_CTL1_CTL13);
    GPIOC->CTL1 |= (0x3U << GPIO_CTL1_MD13_Pos);

    while (1) {
        /* Turn on red. */
        GPIOC->BC = GPIO_BC_CR13;
        delay(DELAY_COUNT);

        /* Turn off red. */
        GPIOC->BOP = GPIO_BOP_BOP13;
        delay(DELAY_COUNT);
    }
}

static void delay(uint32_t count) {
    for (uint32_t i = 0U; i < count; i++) {
        __asm__("nop");
    }
}