Chapter 3: GPIO를 제어해보자! (ARM Assembly)

2019-03-04 14:07:03 +0000 -
Simple ARM Operating System

Source Code는 https://github.com/LeeKyuHyuk/Simple-ARM-Operating-System/tree/raspberry-pi-zero/Chapter-3/src에 있습니다.


ARM Assembly로 Raspberry Pi Zero의 GPIO를 제어하여 ACT LED를 켜고 끄는 것을 만들어보겠습니다.
Raspberry Pi Zero ACT LED
Raspberry Pi Zero의 ACT LED는 GPIO 47을 사용하고 있습니다.

GPIO (General Purpose Input/Output)

모든 GPIO 핀은 소프트웨어로 입력 또는 출력 핀으로 지정 될 수 있으며 다양한 목적으로 사용됩니다.
이번 예제에서는 GPIO 4번에 연결된 LED를 켜보겠습니다.

우선 코드를 작성하기전에 라즈베리파이의 Memory Map부터 보도록 하겠습니다.
BCM2835-ARM-Peripherals.pdf의 5페이지를 보면 라즈베리파이1의 Memory Map은 아래와 같다고 설명하고 있습니다.
BCM2835 ARM Peripherals
그림을 보면 BCM2835를 사용하는 Raspberry Pi Zero의 I/O Peripherals의 물리 주소는 0x20000000에서 시작한다고 합니다.

  • 시스템 메모리는 ARM과 VC 파트로 분할되며, 부분적으로 공유됩니다. (예를 들면, Frame Buffer)
  • ARM의 메모리 매핑 된 레지스터는 0x20000000부터 시작합니다.
    • BCM2835 메뉴얼에 있는 0x7E000000 오프셋을 0x20000000로 사용하면 됩니다.

Raspberry Pi Zero의 GPIO는 아래와 같습니다.
Raspberry Pi Zero GPIO

GPIO 핀의 번호 배정은 숫자 순서가 아닙니다.
GPIO 핀 01은 라즈베리파이 보드의 물리적 핀 2728에 있지만 고급 사용을 위해 예약되어 있습니다

BCM2835-ARM-Peripherals.pdf의 90 페이지를 보면, GPIO는 아래와 같이 41개의 레지스터를 가지고 있습니다.

Address Field Name Description Size Read/Write
0x 7E20 0000 GPFSEL0 GPIO Function Select 0 32 R/W
0x 7E20 0004 GPFSEL1 GPIO Function Select 1 32 R/W
0x 7E20 0008 GPFSEL2 GPIO Function Select 2 32 R/W
0x 7E20 000C GPFSEL3 GPIO Function Select 3 32 R/W
0x 7E20 0010 GPFSEL4 GPIO Function Select 4 32 R/W
0x 7E20 0014 GPFSEL5 GPIO Function Select 5 32 R/W
0x 7E20 0018 - Reserved - -
0x 7E20 001C GPSET0 GPIO Pin Output Set 0 32 W
0x 7E20 0020 GPSET1 GPIO Pin Output Set 1 32 W
0x 7E20 0024 - Reserved - -
0x 7E20 0028 GPCLR0 GPIO Pin Output Clear 0 32 W
0x 7E20 002C GPCLR1 GPIO Pin Output Clear 1 32 W
0x 7E20 0030 - Reserved - -
0x 7E20 0034 GPLEV0 GPIO Pin Level 0 32 R
0x 7E20 0038 GPLEV1 GPIO Pin Level 1 32 R
0x 7E20 003C - Reserved - -
0x 7E20 0040 GPEDS0 GPIO Pin Event Detect Status 0 32 R/W
0x 7E20 0044 GPEDS1 GPIO Pin Event Detect Status 1 32 R/W
0x 7E20 0048 - Reserved - -
0x 7E20 004C GPREN0 GPIO Pin Rising Edge Detect Enable 0 32 R/W
0x 7E20 0050 GPREN1 GPIO Pin Rising Edge Detect Enable 1 32 R/W
0x 7E20 0054 - Reserved - -
0x 7E20 0058 GPFEN0 GPIO Pin Falling Edge Detect Enable 0 32 R/W
0x 7E20 005C GPFEN1 GPIO Pin Falling Edge Detect Enable 1 32 R/W
0x 7E20 0060 - Reserved - -
0x 7E20 0064 GPHEN0 GPIO Pin High Detect Enable 0 32 R/W
0x 7E20 0068 GPHEN1 GPIO Pin High Detect Enable 1 32 R/W
0x 7E20 006C - Reserved - -
0x 7E20 0070 GPLEN0 GPIO Pin Low Detect Enable 0 32 R/W
0x 7E20 0074 GPLEN1 GPIO Pin Low Detect Enable 1 32 R/W
0x 7E20 0078 - Reserved - -
0x 7E20 007C GPAREN0 GPIO Pin Async. Rising Edge Detect 0 32 R/W
0x 7E20 0080 GPAREN1 GPIO Pin Async. Rising Edge Detect 1 32 R/W
0x 7E20 0084 - Reserved - -
0x 7E20 0088 GPAFEN0 GPIO Pin Async. Falling Edge Detect 0 32 R/W
0x 7E20 008C GPAFEN1 GPIO Pin Async. Falling Edge Detect 1 32 R/W
0x 7E20 0090 - Reserved - -
0x 7E20 0094 GPPUD GPIO Pin Pull-up/down Enable 32 R/W
0x 7E20 0098 GPPUDCLK0 GPIO Pin Pull-up/down Enable Clock 0 32 R/W
0x 7E20 009C GPPUDCLK1 GPIO Pin Pull-up/down Enable Clock 1 32 R/W
0x 7E20 00A0 - Reserved - -
0x 7E20 00B0 - Test 4 R/W

GPFSEL, GPSET, GPCLR와 같이 다양한 레지스터가 있습니다.

우선 BCM2835-ARM-Peripherals.pdf의 91페이지를 보면, GPFSEL 레지스터는 GPIO 기능을 선택하는 레지스터라고 설명하고 있습니다.
GPFSEL0은 GPIO 0번~9번을 담당하고, [2:0] 비트는 GPIO 0번, [5:3] 비트는 GPIO 1번을 담당하고 있습니다.
3비트씩 GPIO핀을 담당하고 있으며, 000을 지정하면 입력(Input), 001은 출력(Output)을 설정하게 됩니다.
GPIO 47번 핀을 출력으로 설정하려면 GPFSEL4 레지스터의 [23:21] 비트를 001로 설정하면 됩니다.

BCM2835-ARM-Peripherals.pdf의 95페이지를 보면 GPSET에 대해 설명하고 있습니다.
GPSET은 GPIO 핀의 출력을 설정하는 레지스터인데, GPSET0GPSET1이 있습니다. GPSET 레지스터의 n번 비트는 GPIO n번 핀을 정의하며, 0을 쓰는 것은 아무 효과가 없습니다.
GPSET0은 GPIO 0번 ~ 31번, GPSET1은 GPIO 32번 ~ 53번을 담당합니다.
GPIO 47번 핀을 출력(Output)으로 설정했고, LED를 켜고 싶으니 GPSET015번 비트를 1로 설정하면 됩니다. 메뉴얼을 잘 읽을줄만 알면 작업하기가 편해집니다.

boot.S:

.equ PERI_BASE ,0x20000000             @ Peripheral Base Address
.equ GPIO_BASE ,PERI_BASE + 0x00200000 @ GPIO Base Address

.section ".text.boot"
.globl _start

_start:
    @--------------------------------------------------
    @ ACT LED Blink
    @--------------------------------------------------
    LDR R0 ,=GPIO_BASE                 @ R0에 GPIO_BASE 주소를 넣습니다.
    MOV R1 ,#1                         @ R1에 1을 넣습니다.
    LSL R1 ,#21                        @ R1을 21번 Left Shift하여
                                       @ R1의 21번 비트를 1로 설정합니다
                                       @ 이렇게 하면, GPFSEL0의 [23:21]
                                       @ 비트를 001로 설정하게 됩니다.
    STR R1 ,[R0, #0x0010]              @ R0(0x20200000) + GPFSEL4(0x0010)에
                                       @ R1의 값을 넣습니다.

    MOV R1 ,#1                         @ R1에 1을 넣습니다.
    LSL R1 ,#15                        @ 15번 Left Shift하여 R1의 15번 비트를
                                       @ 1로 설정합니다.
    STR R1 ,[R0, #0x0020]              @ R0(0x20200000) + GPSET1(0x0020)에
                                       @ R1의 값을 넣습니다.
                                       @ GPIO 47번을 LOW로 설정하면 LED가 켜집니다.

    b .                                @ 프로그램이 끝나지 않도록 제자리에서 무한루프

    .end

linker.ld:

SECTIONS
{
    . = 0x8000;
    .text : { *(.text.boot) }
}

Makefile:

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
CFLAGS = -O2 -Wall -Wextra -fpic -ffreestanding -std=gnu99

all: clean kernel.bin

boot.o: boot.S
	arm-none-eabi-gcc $(CFLAGS) -c boot.S -o boot.o

kernel.bin: boot.o $(OBJS)
	arm-none-eabi-gcc -T linker.ld -o kernel.elf -ffreestanding -O2 -nostdlib boot.o
	arm-none-eabi-objcopy kernel.elf -O binary kernel.bin
	arm-none-eabi-objdump -D kernel.elf > kernel.dump

clean:
	rm kernel.elf kernel.bin *.o >/dev/null 2>/dev/null || true

작성한 코드를 빌드하여 실제 Raspberry Pi Zero에서 실행해보자!

위와 같이 Makefile까지 모두 작성했다면 터미널에 make를 입력하여 빌드합니다.
빌드가 완료되면 kernel.bin 파일이 생성됩니다.

SDCard를 FAT32로 포맷합니다.
그리고, /References/boot에 있는 bootcode.bin, config.txt, start.elf를 모두 복사합니다.
방금 빌드한 kernel.bin도 함께 복사합니다.
SDCard files

SDCard에 있는 config.txt에는 kernel=kernel.bin이라는 설정만 있습니다. 이 설정은 kernel.bin을 사용하여 부팅하겠다는 뜻입니다.

SDCard를 Raspberry Pi Zero에 넣고, 부팅하면 아래와 같이 ACT LED가 켜지는 것을 확인할 수 있습니다.
Raspberry Pi Zero ACT LED On