Up

GPIO 기초

2023년 3월 8일

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와 동일합니다.

RCU_APB2EN

GD32VF103의 GPIO는 APB2 버스에 연결되어 있으므로, GPIO에 클럭을 공급하려면 APB2에서 설정해주어야 합니다. RCU_APB2EN 레지스터는 APB2에 연결된 장치들에 클럭을 공급하는 데 사용하는 레지스터로, 각 GPIO 포트는 필드 PAEN부터 PEEN까지에 해당합니다. 필드의 값이 0이면 클럭을 공급하지 않고, 1이면 클럭을 공급합니다.

GPIOx_CTL0, GPIOx_CTL1

이 두 레지스터는 각 포트(x=A…E)의 설정을 저장하는 레지스터입니다. CTL0는 핀 0부터 7까지, CTL1은 핀 8부터 15까지를 담당합니다. 각 핀(y=0…15)은 필드 MDyCTLy를 통해 설정을 바꿀 수 있습니다.

GPIOx_OCTL, GPIOx_BOP, GPIOx_BC

GPIOx_OCTL 레지스터는 필드 OCTLy를 가지며 각각 핀의 출력(0 또는 1)을 설정합니다. GPIOx_BOPGPIOx_BC는 쓰기 전용 레지스터로, 필드 BOPy에 1을 쓰면 OCTLy가 1이 되고, BCy에 1을 쓰면 OCTLy가 0이 됩니다. 속도상 출력을 설정할 땐 GPIOx_BOPGPIOx_BC를 사용하는 것이 좋습니다.

헤더 파일 만들기

각 레지스터에 접근할 수 있도록 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 명령어를 반복하도록 구현하였습니다.