Longan Nano 보드의 LED
Longan Nano 보드는 빨간색, 초록색, 파란색 LED를 하나씩 내장하고 있습니다. 이번 글에서는 GPIO를 이용해 세 LED를 제어하는 펌웨어를 만들어보겠습니다.
먼저 각 LED는 다음 GPIO 포트와 핀에 연결되어 있습니다.
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)은 필드MDy
및CTLy
를 통해 설정을 바꿀 수 있습니다. - GPIOx_OCTL, GPIOx_BOP, GPIOx_BC
GPIOx_OCTL
레지스터는 필드OCTLy
를 가지며 각각 핀의 출력(0 또는 1)을 설정합니다.GPIOx_BOP
와GPIOx_BC
는 쓰기 전용 레지스터로, 필드BOPy
에 1을 쓰면OCTLy
가 1이 되고,BCy
에 1을 쓰면OCTLy
가 0이 됩니다. 속도상 출력을 설정할 땐GPIOx_BOP
와GPIOx_BC
를 사용하는 것이 좋습니다.
각 필드의 자세한 설정값은 다음 표와 같습니다. (유저 매뉴얼 표 7.1) 입력 모드에서 pull-down과 pull-up 설정법이 STM32와 다르다는 점에 주의하세요. 여기서 AFIO(alternative function input/output)는 해당 핀을 GPIO가 아닌 UART, SPI 등의 페리페럴 입출력 핀으로 사용하겠다는 의미이며 입출력을 직접 제어할 수 없을 뿐 기타 설정은 동일합니다.
모드 | 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와 같은 방식으로 헤더 파일을 만들었습니다.
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
// ...
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를 켭니다. 자세한 필드 설정값은 유저 매뉴얼을 참고하시기 바랍니다.
#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
명령어를 반복하도록 구현하였습니다.