상세 컨텐츠

본문 제목

[디바이스마트 이벤트] RDK X3 후기

기타

by 전산거북이 2026. 6. 12. 11:21

본문

안녕하세요? 이번에는 디바이스마트에서 진행중인 RDK X3 지원 이벤트 후기에 대해 적어보려고 합니다.

 

https://www.devicemart.co.kr/page/event?seq=304

 

전자부품, 아두이노, 라즈베리파이, 공구 및 로봇까지 한곳에서 국내 최대 전자부품 전문몰 | 디

디바이스마트

www.devicemart.co.kr

 

라즈베리파이는 조금 만져봤는데 RDK X3는 또 처음 만져보는거 같네요.

계속 리뷰를 하려고 했는데, 충전기가 없어서 맞는 충전기도 구입하고 하다보니 조금 늦어졌습니다.ㅋㅋㅋ쿠ㅜㅜㅜ

 

상품은 이렇게 박스에 담겨져 오는데, 꽤 이쁩니다. 뭔가 고급스럽네요!

사진이 크게 찍힌거 같지만, 손바닥만합니다. 라즈베리파이와 동일한 사이즈라고 생각하심 될 듯 합니다.

 

라즈베리파이와 규격 자체는 비슷할 듯 합니다. 아쉽게도 100% 일치하지는 않아서 제가 가지고 있던 라즈베리파이용 케이스에 담지는 못했습니다.

 

GPIO 핀은 라즈베리파이와 완전 동일하다고 하니 참고하시면 될 듯 합니다.

원래는 카메라 테스트를 해보고 싶었지만 현재 카메라가 없어 이는 다음으로 미루도록 하겠습니다.

대신 간단한 커널 프로그램을 올려보면서 GPIO 작동을 확인해보았습니다.

 

우선 확인해야 할 것은 커널 버전과 아키텍처입니다.

커널은 리눅스 4.14.87 버전을 쓰며, aarch64 아키텍쳐를 사용중입니다.

이제 커널 프로그래밍을 해 보도록 합시다.

커널 프로그래밍을 할 줄 알아야 이걸로 각종 디바이스를 제어하기 용이합니다. 나중에 카메라를 붙이는 것도 중요하지만, 카메라만 붙일건 아니니까요. GPIO 핀이 괜히 있는건 아니라고 생각합니다ㅎㅎ

 

GPIO 번호를 확인하기 위해서는 wiringPi를 사용하면 좋지만, 그건 라즈베리파이에서 작동하는 라이브러리라 여기서 작동할지도 모르고, 어차피 커널 프로그래밍을 하기 위해서는 어플리케이션 라이브러리는 사용할 수 없기 때문에 직접 번호를 확인해주는 것이 좋습니다.

다행히도 예제 코드가 들어있어 쉽게 확인할 수 있었습니다. 예제 코드가 있는지도 몰랐어서 삽질을 좀 했지만, 여러분들은 그러지 마세요...ㅎㅎ

 

보시다시피 40pin_sample 이라는 폴더 안에 test_all_pins.py 가 있습니다. 코드 내부를 볼까요? 길지 않아서 코드로 보여드리겠습니다.

#!/usr/bin/env python3

from __future__ import print_function
import sys
import time
import Hobot.GPIO as GPIO

# 获取所有可以控制的管脚 BOARD 编号
all_pins = list(GPIO.all_pin_data['BOARD'].keys())
all_pins.sort()

# 从命令行参数里面获取需要测试的管脚号序列
if len(sys.argv) > 1:
    all_pins = map(int, sys.argv[1:])

print("All gpio pins: ", all_pins)

# 禁用警告信息
GPIO.setwarnings(False)

# 开始测试,依次拉高拉低管脚,电平保持250毫秒,键盘输入 CTRL-C 测试下一个管脚
for pin in all_pins:
    print("Testing pin %d as OUTPUT; CTRL-C to test next pin" % pin)
    try:
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(pin, GPIO.OUT)
        while True:
            GPIO.output(pin, GPIO.HIGH)
            time.sleep(0.25)
            GPIO.output(pin, GPIO.LOW)
            time.sleep(0.25)
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()

 

중국에서 제작한 디바이스라서 그런지 한자가 많이 보이네요. 지금은 넘어가도 괜찮을 듯 합니다. 중요한건 아니니까요.

단순히 확인해보면, gpio 번호들을 쭉 매핑을 해서 어떤 위치에 있는 gpio가 몇 번인지 테스트를 하는 내용입니다. CTRL-C 을 이용하여 다음 핀으로 넘어가게 되어있다고 하네요.

 

암튼 이를 이용해서 저는 40번 핀에 연결한 것을 확인하였습니다. 아마 각각 다른 부분에 연결을 했을 거라 생각합니다. LED를 이용하여 테스트를 하였는데, 연결된 핀에 gpio 신호가 가면 LED가 깜빡깜빡 할 것입니다.

이 상태에서!!! 반드시 확인을 해 주어야 하는게 있습니다. 저희가 단순히 어플리케이션을 만드는 게 아니라 디바이스 드라이버 모듈을 만들어 커널에 붙인다고 했잖아요? 그래서 저 번호를 그대로 사용할 수 없습니다.

 

해당 명령어를 통해 볼 수 있는 내용들은 "현재 사용중인 gpio" 입니다. 위 명령어는 40번이 작동할 때이고, 아래는 작동을 멈추었을 때 입니다.

그럼 보이죠? 빨간색으로 박스를 쳐놓은 gpio-108 번이 추가되었습니다. 즉, 40번 핀은 gpio-108이라는 것입니다.

이제 gpio를 이용하여 커널 드라이버를 만들 수 있을 것입니다.

저는 우선 home 디렉토리에 led_driver 라는 폴더를 만들었습니다.

그 밑에 led.c와 테스트를 위한 led_app.c를 만들 것 입니다.

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define MY_MAJOR 200
#define MY_MINOR_BASE 0
#define MY_DEV_COUNT 1
#define MY_DEV_NAME "my_led_dev"
#define BUFFER_SIZE 1000

static dev_t dev_num;
static struct cdev led_cdev;
static struct class *led_class;
static struct device *led_device;

/* 모듈 메타데이터 설정 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sunrise");  
MODULE_DESCRIPTION("Skeleton Device Driver");

#define GPIO_LED (108)

volatile unsigned *gpio; /* I/O 접근을 위한 volatile 변수 */


static char *led_devnode(struct device *dev, umode_t *mode)
{
	if(mode)
	{
		*mode = 0666;
	}
	return NULL;
}

// open
static int led_open(struct inode *inode, struct file *file)
{
	pr_info("LED Device opened!\n");

	gpio_direction_output(GPIO_LED, 0);
	return 0;
}

// release
static int led_release(struct inode *inode, struct file *file)
{
	pr_info("LED Device closed!\n");
	return 0;
}

// write
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	char kernel_buffer[BUFFER_SIZE];
	int retval;

	if(count > BUFFER_SIZE - 1)
	{
		count = BUFFER_SIZE - 1;
	}

	retval = copy_from_user(kernel_buffer, buf, count);
	if(retval != 0)
	{
		pr_err("Failed to receive data from user\n");
		return -EFAULT;
	}

	kernel_buffer[count] = '\0';
	pr_info("Received from user: %s\n", kernel_buffer);

	(!strcmp(kernel_buffer, "0")) ? gpio_set_value(GPIO_LED, 0) : gpio_set_value(GPIO_LED, 1);

	return count;
}

static const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.write = led_write,
};

/* 모듈이 커널에 로드될 때 호출되는 함수 */
static int __init led_init(void)
{
	int retval;

	pr_info("LED Module is up...\n");

	retval = alloc_chrdev_region(&dev_num, 0, 1, "LED_Driver");

	if(retval < 0)
	{
		pr_err("Failed to allocate device number!\n");
		return retval;
	}
	cdev_init(&led_cdev, &led_fops);
	led_cdev.owner = THIS_MODULE;

	retval = cdev_add(&led_cdev, dev_num, 1);
	if(retval < 0)
	{
		pr_err("Failed to add cdev\n");
		unregister_chrdev_region(dev_num, 1);
		return retval;
	}

	led_class = class_create(THIS_MODULE, "led_class");
	if(IS_ERR(led_class))
	{
		retval = PTR_ERR(led_device);
		goto err_del_cdev;
	}
	led_class->devnode = led_devnode;

	led_device = device_create(led_class, NULL, dev_num, NULL, "LED");
	if(IS_ERR(led_device))
	{
		retval = PTR_ERR(led_device);
		goto err_destroy_class;
	}

	pr_info("LED Module & /dev/LED setup successfully completed! Major: %d\n", MAJOR(dev_num));

	gpio_request(GPIO_LED, "LED");

    return 0;

err_destroy_class:
	class_destroy(led_class);
err_del_cdev:
	cdev_del(&led_cdev);

	return retval;
}

/* 모듈이 커널에서 제거될 때 호출되는 함수 */
static void __exit led_exit(void)
{
	pr_info("LED Module is exiting...\n");

	device_destroy(led_class, dev_num);
	class_destroy(led_class);
	cdev_del(&led_cdev);
	unregister_chrdev_region(dev_num, MY_DEV_COUNT);
	gpio_free(GPIO_LED);
    pr_info("Unregistered device number\n");
}

module_init(led_init);  
module_exit(led_exit);

커널 프로그래밍을 해 보신 분들은 조금 익숙하실 듯 합니다. 조금 뜯어볼까요?

#define GPIO_LED (108)

아까 얻은 gpio led의 핀 번호를 넣어줍니다.

// open
static int led_open(struct inode *inode, struct file *file)
{
	pr_info("LED Device opened!\n");

	gpio_direction_output(GPIO_LED, 0);
	return 0;
}

// release
static int led_release(struct inode *inode, struct file *file)
{
	pr_info("LED Device closed!\n");
	return 0;
}

이 부분들은 open() 혹은 close()를 어플리케이션에서 요청을 하면 실행되는 부분입니다. 즉, 이 함수를 통해 어플리케이션에서 해당 디바이스를 오픈하면 gpio_direction_output() 이 실행되는 방식입니다. 이 부분을 통해 해당 gpio 핀을 output으로 설정해줄 수 있는 것입니다. close()를 하면 아무 작업도 하지 않고 dmesg를 통해 로그만 찍어줍니다.

 

다음은 write()를 했을 때 호출되는 함수입니다.

// write
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	char kernel_buffer[BUFFER_SIZE];
	int retval;

	if(count > BUFFER_SIZE - 1)
	{
		count = BUFFER_SIZE - 1;
	}

	retval = copy_from_user(kernel_buffer, buf, count);
	if(retval != 0)
	{
		pr_err("Failed to receive data from user\n");
		return -EFAULT;
	}

	kernel_buffer[count] = '\0';
	pr_info("Received from user: %s\n", kernel_buffer);

	(!strcmp(kernel_buffer, "0")) ? gpio_set_value(GPIO_LED, 0) : gpio_set_value(GPIO_LED, 1);

	return count;
}

어렵게 생각할 거 없습니다. 중요한건 맨 밑에 strcmp() 한 줄입니다.

커널 버퍼에 0이 입력되면 gpio를 0으로 세팅하고, 그렇지 않으면 1로 세팅합니다.

 

다음으로는 모듈이 커널에 적재될 때 호출되는 함수인 init을 볼까요?

/* 모듈이 커널에 로드될 때 호출되는 함수 */
static int __init led_init(void)
{
	int retval;

	pr_info("LED Module is up...\n");

	retval = alloc_chrdev_region(&dev_num, 0, 1, "LED_Driver");

	if(retval < 0)
	{
		pr_err("Failed to allocate device number!\n");
		return retval;
	}
	cdev_init(&led_cdev, &led_fops);
	led_cdev.owner = THIS_MODULE;

	retval = cdev_add(&led_cdev, dev_num, 1);
	if(retval < 0)
	{
		pr_err("Failed to add cdev\n");
		unregister_chrdev_region(dev_num, 1);
		return retval;
	}

	led_class = class_create(THIS_MODULE, "led_class");
	if(IS_ERR(led_class))
	{
		retval = PTR_ERR(led_device);
		goto err_del_cdev;
	}
	led_class->devnode = led_devnode;

	led_device = device_create(led_class, NULL, dev_num, NULL, "LED");
	if(IS_ERR(led_device))
	{
		retval = PTR_ERR(led_device);
		goto err_destroy_class;
	}

	pr_info("LED Module & /dev/LED setup successfully completed! Major: %d\n", MAJOR(dev_num));

	gpio_request(GPIO_LED, "LED");

    return 0;

err_destroy_class:
	class_destroy(led_class);
err_del_cdev:
	cdev_del(&led_cdev);

	return retval;
}

이게 좀 기네요. 하지만 아아아아주 간단하게 분석을 해볼까요? 디바이스 드라이버를 올리기 위해서는 메이저/마이너 번호를 할당받아야 합니다. 그 부분이 바로 alloc_chrdev_region() 입니다. 이 함수를 통해 값을 할당받으면, 다음으로는 아까 만들어준 함수들과 디바이스를 연결해주는 작업을 cdev_init()을 통해 진행해줍니다.

그리고 디바이스를 cdev_add()를 통해 추가해 준 뒤, 클래스를 만들어 줍니다.

이렇게 하면 디바이스를 모두 추가했습니다. 이제 device_create()를 통해 /dev/ 에 디바이스를 추가해주고, gpio_request()로 위에서 본 함수들(gpio_direction_out() 등) 을 사용할 준비를 해 주면 됩니다.

 

exit()은 init()의 역순이므로 설명하지 않겠습니다.

 

 

이제 해당 파일을 빌드할 makefile을 만들어줍시다.

obj-m := led.o
KDIR := /lib/modules/$(shell uname -r)/build

default:
        $(MAKE) ARCH=arm64 -C$(KDIR) M=$(shell pwd) modules

clean:
        $(MAKE) -C$(KDIR) M=$(shell pwd) clean

 

이제 make 명령어 하나만 치면 빌드가 됩니다. 매우 간단하죠?

 

led_app.c는 매우매우 간단합니다. 단순히 켜고 끄는 것을 보는 정도로만 할 예정이니까요.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>

#include "sk_ioctl.h"

#define BUFFER_SIZE 100

int main()
{
	int retn;
	int fd;
	char buffer[BUFFER_SIZE];

	int flag = 0;

	fd = open("/dev/LED", O_RDWR);
	printf("fd = %d\n", fd);
	if(fd < 0)
	{
		perror("/dev/LED error");
		exit(-1);
	}
	else
	{
		printf("LED has been detected...\n");
	}

	int choice;

	printf("1. ON\t2. OFF: ");
	scanf("%d", &choice);

	if(choice == 1)
	{
		write(fd, "1", 1);
	}
	else
	{
		write(fd, "0", 1);
	}

	close(fd);
}

간단하죠? 1이면 키고 그렇지 않으면 LED를 끄는 정도로 제어할 생각입니다.

 

실제로 실행을 해 볼까요?

LED를 켠 상태(좌), 끈 상태(우)

 

로그는 다음과 같습니다.

보시다시피 로그도 잘 작동하는 것을 확인할 수 있습니다.

 

다음 번에는 카메라를 구할 수 있으면 카메라도 제어해보도록 하겠습니다.

 

가격이 일단 너무 괜찮은 디바이스라고 생각합니다. GPIO 핀 번호도 큰 무리없이 다루기 좋구요.

항상 디바이스마트에서 사는 제품은 믿고 살 수 있으리라 생각이 드네요ㅎㅎ

AI 특화 보드다 보니 다음번에는 CNN이나 YOLO 같은걸 좀 적용해볼 수 있으면 좋겠다는 생각이 듭니다. 한 번 공부해볼 기회가 있으면 좋겠습니다.

'기타' 카테고리의 다른 글

라즈베리파이와 STM32간 I2C 통신하기  (0) 2026.07.07
[RDK 보드] config 화면 띄우기  (0) 2026.06.23

관련글 더보기

댓글 영역