GPIO 기초

2023년 3월 13일

Longan Nano 보드의 LED

Longan Nano 보드는 빨간색, 초록색, 파란색 LED를 하나씩 내장하고 있습니다. 이번 글에서는 GPIO를 이용해 세 LED를 제어하는 펌웨어를 만들어보겠습니다.

먼저 각 LED는 다음 GPIO 포트와 핀에 연결되어 있습니다.

Longan Nano 보드의 LED 핀맵
LED 포트
Red C 13
Green A 1
Blue A 2

또한 LED의 반대편은 3.3V에 연결되어 있으므로 출력을 1로 설정하면 LED가 꺼지고, 0으로 설정하면 LED가 켜집니다.

GPIO 출력을 위한 레지스터

GD32VF103의 유저 매뉴얼을 보면 GPIO를 구동하는 데 필요한 레지스터들을 찾을 수 있습니다. 이들 중 GPIO 출력을 위한 레지스터들은 다음과 같습니다. 거의 대부분 STM32와 동일합니다.

각 필드의 자세한 설정값은 다음 표와 같습니다. (유저 매뉴얼 표 7.1) 입력 모드에서 pull-down과 pull-up 설정법이 STM32와 다르다는 점에 주의하세요. 여기서 AFIO(alternative function input/output)는 해당 핀을 GPIO가 아닌 UART, SPI 등의 페리페럴 입출력 핀으로 사용하겠다는 의미이며 입출력을 직접 제어할 수 없을 뿐 기타 설정은 동일합니다.

GPIO 레지스터 설정표
모드 CTL MD OCTL
Input Analog 00 00 Don't care
Floating 01
Pull-down 10 0
Pull-up 1
Output Push-pull 00 01: Speed up to 10 MHz
10: Speed up to 2 MHz
11: Speed up to 50 MHz
0: Low
1: High
Open-drain 01
AFIO Push-pull 10 Don't care
Open-drain 11

헤더 파일 만들기

각 레지스터에 접근할 수 있도록 STM32와 같은 방식으로 헤더 파일을 만들었습니다.

gd32vf103_gpio.h

typedef struct {
    volatile uint32_t CTL0;             /* Port control register 0,             Address offset: 0x00. */
    volatile uint32_t CTL1;             /* Port control register 1,             Address offset: 0x04. */
    volatile uint32_t ISTAT;            /* Port input status register,          Address offset: 0x08. */
    volatile uint32_t OCTL;             /* Port output control register,        Address offset: 0x0C. */
    volatile uint32_t BOP;              /* Port bit operation register,         Address offset: 0x10. */
    volatile uint32_t BC;               /* Port bit clear register,             Address offset: 0x14. */
    volatile uint32_t LOCK;             /* Port configuration lock register,    Address offset: 0x18. */
} GPIO_TypeDef;

#define GPIOA                           ((GPIO_TypeDef *)0x40010800)
#define GPIOB                           ((GPIO_TypeDef *)0x40010C00)
#define GPIOC                           ((GPIO_TypeDef *)0x40011000)
#define GPIOD                           ((GPIO_TypeDef *)0x40011400)
#define GPIOE                           ((GPIO_TypeDef *)0x40011800)

#define GPIO_CTL0_MD0_Pos               (0U)
#define GPIO_CTL0_MD0_Msk               (0x3UL << GPIO_CTL0_MD0_Pos)
#define GPIO_CTL0_MD0                   GPIO_CTL0_MD0_Msk
#define GPIO_CTL0_CTL0_Pos              (2U)
#define GPIO_CTL0_CTL0_Msk              (0x3UL << GPIO_CTL0_CTL0_Pos)
#define GPIO_CTL0_CTL0                  GPIO_CTL0_CTL0_Msk
#define GPIO_CTL0_MD1_Pos               (4U)
#define GPIO_CTL0_MD1_Msk               (0x3UL << GPIO_CTL0_MD1_Pos)
#define GPIO_CTL0_MD1                   GPIO_CTL0_MD1_Msk
#define GPIO_CTL0_CTL1_Pos              (6U)
#define GPIO_CTL0_CTL1_Msk              (0x3UL << GPIO_CTL0_CTL1_Pos)
#define GPIO_CTL0_CTL1                  GPIO_CTL0_CTL1_Msk
// ...
gd32vf103_rcu.h

typedef struct {
    volatile uint32_t CTL;              /* Control register,                    Address offset: 0x00. */
    volatile uint32_t CFG0;             /* Clock configuration register 0,      Address offset: 0x04. */
    volatile uint32_t INT;              /* Clock interrupt register,            Address offset: 0x08. */
    volatile uint32_t APB2RST;          /* APB2 reset register,                 Address offset: 0x0C. */
    volatile uint32_t APB1RST;          /* APB1 reset register,                 Address offset: 0x10. */
    volatile uint32_t AHBEN;            /* AHB enable register,                 Address offset: 0x14. */
    volatile uint32_t APB2EN;           /* APB2 enable register,                Address offset: 0x18. */
    volatile uint32_t APB1EN;           /* APB1 enable register,                Address offset: 0x1C. */
    volatile uint32_t BDCTL;            /* Backup domain control register,      Address offset: 0x20. */
    volatile uint32_t RSTSCK;           /* Reset source/clock register,         Address offset: 0x24. */
    volatile uint32_t AHBRST;           /* AHB reset register,                  Address offset: 0x28. */
    volatile uint32_t CFG1;             /* Clock configuration register 1,      Address offset: 0x2C. */
    uint32_t RESERVED0;                 /* Reserved: 0x30. */
    volatile uint32_t DSV;              /* Deep-sleep mode voltage register,    Address offset: 0x34. */
} RCU_TypeDef;

#define RCU                             ((RCU_TypeDef *)0x40021000)
// ...

전체 내용은 저장소에서 확인할 수 있습니다.

구현

이제 헤더 파일을 사용하여 LED를 깜빡이는 코드를 작성해보겠습니다. 먼저 포트 A와 C에 클럭을 공급한 다음, 각 핀을 출력 모드(최대 속도 50 MHz), push-pull로 설정합니다. 그리고 일정 주기로 빨간색–초록색–파란색 순으로 LED를 켭니다. 자세한 필드 설정값은 유저 매뉴얼을 참고하시기 바랍니다.

main.c

#include <stdbool.h>
#include "gd32vf103.h"

/* Red: C13. */
/* Green: A1. */
/* Blue: A2. */

#define DELAY_COUNT (1000000UL)

static void delay(uint32_t count);

int main(void) {
    /* Enable GPIOA and GPIOC. */
    RCU->APB2EN |= RCU_APB2EN_PAEN | RCU_APB2EN_PCEN;

    /* Output, max speed 50 MHz, push-pull. */
    GPIOC->CTL1 &= ~(GPIO_CTL1_MD13 | GPIO_CTL1_CTL13);
    GPIOC->CTL1 |= ((0x3UL << GPIO_CTL1_MD13_Pos));
    GPIOA->CTL0 &= ~(GPIO_CTL0_MD1 | GPIO_CTL0_CTL1 | GPIO_CTL0_MD2 | GPIO_CTL0_CTL2);
    GPIOA->CTL0 |= ((0x3UL << GPIO_CTL0_MD1_Pos) | (0x3UL << GPIO_CTL0_MD2_Pos));

    /* Turn off all LEDs. */
    GPIOC->BOP = GPIO_BOP_BOP13;
    GPIOA->BOP = GPIO_BOP_BOP1;
    GPIOA->BOP = GPIO_BOP_BOP2;

    while (1) {
        /* Red only. */
        GPIOC->BC = GPIO_BC_CR13;
        GPIOA->BOP = GPIO_BOP_BOP1;
        GPIOA->BOP = GPIO_BOP_BOP2;
        delay(DELAY_COUNT);

        /* Green only. */
        GPIOC->BOP = GPIO_BOP_BOP13;
        GPIOA->BC = GPIO_BC_CR1;
        GPIOA->BOP = GPIO_BOP_BOP2;
        delay(DELAY_COUNT);

        /* Blue only. */
        GPIOC->BOP = GPIO_BOP_BOP13;
        GPIOA->BOP = GPIO_BOP_BOP1;
        GPIOA->BC = GPIO_BC_CR2;
        delay(DELAY_COUNT);
    }
}

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

아직 타이머 같은 것들을 설정하지 않았기 때문에 delay 함수는 단순히 nop 명령어를 반복하도록 구현하였습니다.