최근 Nextchip의 APACHE5 플랫폼을 활용한 실습을 진행하면서, Embedded Linux와 FPGA가 결합된 환경을 실제로 경험하게 되었다.
결국 실제로 동작하는 하드웨어 로직은 대부분 OS와 함께 구동되는 구조라는 것을 느끼게 되었고, RTL 엔지니어는 자신이 설계한 블록이 리눅스 시스템 내에서 어떻게 인식되고, 어떻게 제어되는지를 이해할 필요가 있다고 느껴 Embedded Linux에 대한 공부를 시작하고자 한다.
임베디드 리눅스 자동차 분야 전문가 로드맵
1주차: 리눅스 시스템 구조와 부트 과정 이해
학습 개념:
임베디드 리눅스 시스템은 전원을 켠 후 부트 ROM → 1차 부트로더 → 2차 부트로더 → 커널 → 루트파일시스템 초기화의 다단계 부트 과정을 거칩니다. 일반적인 임베디드 하드웨어에서는 칩 내부의 부트 ROM 코드가 CPU 리셋 벡터에서 부트로더(예: U-Boot)를 찾아 RAM으로 로드하고 실행합니다beyondlogic.org. Raspberry Pi의 경우 별도의 내장 플래시가 없기 때문에 Broadcom VideoCore GPU가 내장 부트로더를 실행하여 SD 카드의 1차 부트로더(bootcode.bin)와 2차 부트로더(start.elf) 펌웨어를 순차적으로 로드합니다beyondlogic.org. 이 2차 부트로더가 config.txt 등의 설정을 읽은 뒤 커널 이미지를 메모리에 적재하고, ARM 코어를 리셋 해제하여 리눅스 커널이 실행을 시작합니다beyondlogic.org. 이러한 부트 과정을 통해 최종적으로 커널이 루트파일시스템을 마운트하고 init 프로세스를 실행하면서 리눅스 시스템이 구동됩니다.
실습 과제:
- Raspberry Pi OS 등의 이미지를 사용해 Raspberry Pi를 부팅하고, 부팅 로그(dmesg)를 살펴보세요. 로그에서 부트로더(U-Boot 사용 시)와 커널이 초기화하는 과정을 확인합니다.
- Raspberry Pi의 부트 파티션에 있는 config.txt, cmdline.txt 파일을 열어 내용을 확인해보세요. 부트로더 단계에서 어떻게 커널에 인자를 전달하고 장치 트리를 지정하는지 이해합니다.
- (선택) Raspberry Pi 시리얼 콘솔(UART)을 PC에 연결해 부팅 메시지를 모니터링해보세요. 부트 과정 각 단계에서 어떤 출력이 나오는지 관찰합니다.
관련 자료:
- Beyond Logic: Raspberry Pi의 부트 과정 설명
- 임베디드 리눅스 부팅 시퀀스 (BeagleBone 예시)
- Raspberry Pi 부트 시퀀스 (nayab.xyz 블로그)
2주차: U-Boot 부트로더 이해 및 빌드
학습 개념:
임베디드 리눅스에서는 U-Boot가 널리 사용되는 부트로더입니다. U-Boot는 하드웨어를 초기화하고 커널을 로드하는 역할을 하며, 개발자가 명령행에서 메모리 내용 확인, 부트 설정 변경, 네트워크 부팅 등 다양한 기능을 활용할 수 있는 환경을 제공합니다beyondlogic.org. 이번 주는 U-Boot의 구조와 빌드 방법을 배우고 Raspberry Pi에 **체인로딩(chain-loading)**하는 실습을 합니다. Raspberry Pi는 자체 GPU 부트로더로도 커널을 로드할 수 있지만, U-Boot를 사용하면 TFTP를 통한 커널 다운로드 등 개발과 디버깅에 유용한 기능들을 활용할 수 있습니다beyondlogic.org. Raspberry Pi에서 U-Boot를 사용하려면 SD카드의 config.txt에 U-Boot를 커널처럼 지정해 2차 부트로더가 U-Boot를 실행하도록 설정합니다 (예: kernel=u-boot.bin 설정)beyondlogic.org.
실습 과제:
- 크로스 컴파일러 환경을 구축합니다 (Ubuntu PC에서 gcc-arm-linux-gnueabi 또는 gcc-arm-linux-gnueabihf 설치).
- U-Boot 소스를 다운로드하여 Raspberry Pi 타겟으로 빌드하세요. (예: make rpi_4_defconfig로 기본 설정 후 make로 빌드).
- 생성된 u-boot.bin을 SD 카드 부트 파티션에 복사하고, **config.txt**에 kernel=u-boot.bin 항목을 추가합니다beyondlogic.org. 그런 다음 Raspberry Pi를 부팅하면 곧바로 U-Boot 프롬프트가 나타납니다.
- U-Boot 콘솔에서 help를 입력해 사용 가능한 명령을 조회하고, printenv로 환경변수를 확인해보세요. 또, bootefi나 bootm 명령을 이용해 (현재는 커널 없음) 부팅을 시도해보고 동작을 이해합니다.
- (선택) U-Boot에서 TFTP를 이용해 커널을 메모리에 로드하는 실험을 해보세요. 이를 위해서는 Raspberry Pi를 이더넷으로 연결하고 호스트 PC에 TFTP 서버를 설정해야 합니다.
관련 자료:
- Beyond Logic: Raspberry Pi용 U-Boot 컴파일 가이드
- Denx U-Boot 공식 문서 (U-Boot 사용법)
- 임베디드 U-Boot 부트로더 소개 (AKM)
3주차: 리눅스 커널 빌드 및 부트 이해
학습 개념:
이 주차에는 리눅스 커널 자체를 빌드하고 부팅하는 방법을 다룹니다. 리눅스 커널은 모놀리식 구조로 동작하며 부트로더에 의해 로드된 후 하드웨어를 초기화하고 사용자 공간을 시작합니다. Raspberry Pi와 같은 보드에서는 커널과 함께 디바이스 트리(Device Tree) 바이너리(.dtb)가 사용됩니다. Device Tree는 보드의 하드웨어 정보를 커널에 전달하는 데이터 구조로, 부트로더가 커널에 이 파일을 넘겨줍니다labs.dese.iisc.ac.in. 이를 통해 하나의 커널 이미지로도 서로 다른 보드의 하드웨어를 지원할 수 있습니다. Device Tree에는 CPU, 메모리, 주변장치(GPIO, I2C 등)의 연결 정보가 기술되며, 커널은 이를 파싱해 해당 하드웨어에 맞는 드라이버를 올바르게 초기화합니다. 이번 주에는 커널 구성(make menuconfig 등)과 빌드, Device Tree 개념을 이해하고, 자신만의 커널 이미지를 Raspberry Pi에서 부팅해보는 것이 목표입니다.
실습 과제:
- Raspberry Pi용 리눅스 커널 소스 코드를 다운로드합니다 (공식 Raspberry Pi 리포지토리나 커널 메인라인 소스와 Pi용 패치).
- 교차 컴파일러를 사용해 커널 빌드를 수행합니다. Raspberry Pi 4 기준으로 make bcm2711_defconfig 등 기본 설정을 불러온 뒤 make -j4 zImage modules dtbs로 커널과 모듈, Device Tree 블롭(dtbs)을 빌드하세요.
- 빌드된 결과물 중 커널 이미지(zImage 또는 Image)와 해당 보드의 .dtb 파일을 SD 카드 부트 파티션에 복사합니다 (예: kernel8.img 이름으로 복사하고, 기존 파일은 백업). cmdline.txt에서 이전 커널 이름을 새로운 이름으로 바꾸거나, U-Boot 사용 시 U-Boot에서 수동으로 새로운 커널과 DTB를 지정해 부팅합니다.
- Raspberry Pi를 부팅하여 사용자 커널이 올라오는지 확인합니다. uname -a 명령으로 커널 버전이나 빌드 정보를 확인해 본인이 빌드한 커널인지 검증하세요.
- (선택) 커널 설정을 변경하여 기능을 추가/제거해보고 부팅에 성공하는지 실험합니다. 예를 들어, Preempt-RT 패치를 적용하거나, 불필요한 드라이버를 제외하여 커널 크기를 줄이는 등의 시도를 해보세요.
관련 자료:
- Raspberry Pi 공식 문서: 리눅스 커널 컴파일
- Device Tree 활용 및 구조 (한글 블로그)
- Bootlin: Device Tree 개요 자료(pdf) (슬라이드 319~)
4주차: Buildroot/Yocto를 통한 RootFS 구성
학습 개념:
임베디드 리눅스 시스템에서는 **루트 파일시스템(root filesystem)**을 직접 구성해야 합니다. 이를 자동화해주는 도구로 Buildroot와 **Yocto Project(OpenEmbedded)**가 널리 쓰입니다. Buildroot는 비교적 단순한 메이크파일 기반 빌드 시스템으로, 최소한의 리눅스 배포 이미지를 빠르게 만들 수 있도록 설계되었습니다incredibuild.com. Yocto는 여러 레이어와 복잡한 도구를 포함한 포괄적인 프레임워크로, 임베디드 리눅스 배포판 자체를 생성하는 데 사용됩니다incredibuild.com. Buildroot는 쉬운 구성과 짧은 빌드 시간을 장점으로 하며 주로 빠르게 임베디드 이미지를 제작하거나 소규모 프로젝트에 쓰이고, Yocto는 유연성과 확장성 덕분에 자동차 산업 등 상용 제품 개발에 주로 사용됩니다. 이번 주에는 Buildroot를 사용해 Raspberry Pi용 커스텀 RootFS 이미지를 만들어보고, Yocto 프로젝트의 개념과 활용 방법도 개략적으로 알아봅니다.
실습 과제:
- Buildroot를 다운로드하여 Raspberry Pi 보드 구성을 선택하고(make raspberrypi4_defconfig 등) RootFS 이미지를 빌드하세요. 이 때 Buildroot가 툴체인, 커널, BusyBox 기반의 최소 rootfs 등을 자동으로 빌드하는 과정을 관찰합니다.
- 완성된 SD 카드 이미지를 Raspberry Pi에 구워 부팅해봅니다. BusyBox 셸 프롬프트가 나타나면, 기본 명령어(ls, dmesg 등)가 작동하는지 확인합니다. 크기가 매우 작은 커스텀 리눅스 시스템이 부팅된 것을 확인하세요.
- Yocto Project의 개념을 학습합니다. Poky reference 배포판과 레이어, 레시피(.bb 파일), BitBake 빌드 시스템의 개념을 익히세요. (시간이 충분하다면, Yocto로 Raspberry Pi용 간단한 이미지(core-image-minimal 등)도 빌드해보지만, 빌드에 오래 걸릴 수 있으므로 결과만 분석해도 좋습니다.)
- Buildroot로 빌드한 이미지에 특정 패키지를 추가해보고 싶다면, menuconfig에서 패키지 선택을 한 뒤 이미지를 재빌드해보세요. 예를 들어 vim 에디터나 python 인터프리터를 포함시켜 기능을 확장해볼 수 있습니다.
- (선택) Yocto의 레이어 구조를 실습해보세요. 예를 들어, 새로운 레이어를 추가하고 간단한 커스텀 레시피를 작성하여 이미지에 반영하는 과정을 따라가봅니다.
관련 자료:
- Buildroot 공식 사이트 및 매뉴얼 (첫 장의 Quick Start 사용)
- Yocto Project 개요 (한글 번역)
- Yocto vs Buildroot 비교 블로그 (Incredibuild)incredibuild.comincredibuild.com
5주차: 센서 제어 및 인터페이스 (GPIO, I2C, SPI)
학습 개념:
임베디드 시스템에서 센서 제어를 위해서는 GPIO, I2C, SPI와 같은 인터페이스를 다룰 수 있어야 합니다. 리눅스에서는 sysfs 등의 커널 인터페이스를 통해 이러한 하드웨어 자원을 사용자 공간에서 제어할 수 있습니다. Sysfs는 커널의 하드웨어 정보를 가상 파일시스템 형태로 노출하여, 사용자가 파일 읽기/쓰기로 장치를 제어하도록 해줍니다ics.com. (참고: 최신 커널에서는 /dev/gpiochip* 디바이스를 통한 새로운 GPIO Char 드라이버 인터페이스가 도입되었지만, 본 실습에서는 이해를 돕기 위해 전통적인 sysfs 사용을 다룹니다.) I2C 버스는 필립스사가 개발한 두 선(double wire) 직렬 통신 규격으로, 센서와 같은 여러 저속 장치들을 두 신호선(SDA, SCL)만으로 연결해 제어하기에 적합합니다instructables.com. SPI 버스는 4선(클럭, MOSI, MISO, CS) 방식의 고속 동기식 직렬 통신으로 ADC, 디스플레이 등 다양한 주변장치를 접속하는 데 쓰입니다. 이번 주는 Raspberry Pi의 GPIO 핀으로 LED를 제어하고, I2C/SPI 기반 센서를 읽어보면서 사용자 공간에서 하드웨어를 다루는 방법을 실습합니다.
실습 과제:
- Raspberry Pi의 GPIO 핀에 LED와 저항을 연결하고, 해당 GPIO를 출력으로 설정하여 **LED 점멸(blink)**을 구현합니다. 방법: sysfs 인터페이스(/sys/class/gpio/)에서 LED 연결 핀 번호를 export하고 방향(direction)을 "out"으로 설정한 뒤, value에 1/0를 에코(echo)하여 켜고 끕니다. 쉘 스크립트로 주기적으로 토글하여 LED가 주기적으로 깜빡이는지 확인하세요.
- Raspberry Pi에 I2C 연결되는 온도/조도 센서 등을 연결하고 읽어봅니다. 우선 Raspberry Pi 설정(raspi-config 또는 config.txt의 dtparam=i2c_arm=on)으로 I2C 인터페이스를 활성화하세요. I2C 툴(sudo apt install i2c-tools)의 i2cdetect -y 1 명령으로 버스 상에 연결된 I2C 기기 주소를 확인합니다. 그 다음 i2cget/i2cset 등을 사용하거나 C 코드에서 /dev/i2c-1 디바이스 파일을 열어 I2C 통신을 수행해 센서의 값을 읽습니다. 센서 데이터시트의 레지스터 구조를 참고하여 올바른 위치에서 값을 가져오는지 검증하세요.
- (선택) SPI 센서/장치 제어: Raspberry Pi의 SPI 인터페이스 (dtparam=spi=on 활성화)를 사용해 SPI 장치를 제어합니다. 예를 들어 SPI 기반 ADC MCP3008이나 OLED 디스플레이를 연결했다면, /dev/spidev0.0 디바이스를 통해 데이터 송수신을 해보세요. C 언어로 open("/dev/spidev0.0", ...) 후 ioctl로 SPI 모드/속도를 설정하고 read/write로 통신하는 코드를 작성하거나, Python의 spidev 라이브러리를 활용해도 좋습니다.
- (선택) C로 간단한 프로그램을 작성하여 GPIO 입력을 **폴링(polling)**하거나 인터럽트 기반으로 처리해보세요. Raspberry Pi의 버튼 입력(GPIO in)에 대해 변경 시 sysfs의 value를 읽어 출력하거나, poll() 시스템콜로 변화 감지를 구현합니다.
관련 자료:
- Raspberry Pi GPIO 제어 (sysfs) 튜토리얼
- Raspberry Pi I2C 센서 사용 방법 (Instructables)
- Linux SPI dev 인터페이스 소개 (스택익스체인지 Q&A)
6주차: V4L2 기반 카메라 입력 처리
학습 개념:
**Video4Linux2 (V4L2)**는 리눅스에서 카메라, 웹캠 등의 영상 장치에 대한 표준 API를 제공하는 프레임워크입니다medium.com. V4L2 드라이버를 통해 /dev/video0와 같은 디바이스 파일이 생성되고, 사용자 공간 프로그램은 이를 열어 영상 프레임을 캡처하거나 스트리밍할 수 있습니다. V4L2의 동작은 ioctl 호출을 통해 장치를 제어하는 형태로 이루어지며, 프레임버퍼를 **메모리 매핑(mmap)**하여 커널-유저 간 복사 없이 영상을 주고받기도 합니다. 이번 주에는 V4L2를 이용해 카메라로부터 영상을 캡처하는 방법을 학습합니다. Raspberry Pi에서는 CSI 카메라 모듈이나 USB 웹캠을 사용할 수 있는데, 최신 Raspberry Pi OS에서는 libcamera 기반 스택을 사용하지만 여기서는 V4L2 호환 드라이버 활용 방법을 다룹니다. (라즈베리파이 카메라 모듈의 경우 bcm2835-v4l2 커널 모듈을 로드하면 /dev/video0 인터페이스를 통해 V4L2 API로 접근 가능합니다.)
실습 과제:
- Raspberry Pi에 카메라 모듈을 연결했다면, sudo modprobe bcm2835-v4l2로 V4L2 드라이버를 로드하여 /dev/video0를 생성합니다. USB 웹캠을 쓰는 경우 그냥 연결하면 일반적으로 UVC 드라이버에 의해 /dev/video0가 잡힙니다.
- v4l2-ctl (v4l-utils 패키지)을 활용하여 카메라 장치의 정보를 확인합니다. 예를 들어 v4l2-ctl --list-formats-ext -d /dev/video0 명령으로 지원하는 영상 포맷과 해상도를 열람하고medium.com, v4l2-ctl --stream-mmap --stream-count=1 --stream-to=frame.jpg -d /dev/video0 명령으로 한 프레임을 캡처해 JPEG 파일로 저장해봅니다. 이 파일을 열어 실제 이미지가 찍혔는지 확인하세요.
- C 언어로 V4L2 API를 직접 사용해보기: V4L2 예제 코드를 참고하여 /dev/video0를 열고 (open), VIDIOC_QUERYCAP ioctl로 장치 능력을 확인한 뒤, VIDIOC_S_FMT로 영상 포맷 설정, VIDIOC_REQBUFS와 VIDIOC_QUERYBUF로 버퍼를 확보 및 mmap, VIDIOC_STREAMON으로 스트리밍 시작, VIDIOC_QBUF/VIDIOC_DQBUF로 프레임을 수신하는 순서를 구현합니다medium.com. 한 프레임을 메모리에 받아온 후 파일로 저장하는 코드를 작성해 보세요. (이 단계는 난이도가 있으므로, Linux V4L2 예제 코드나 공식 V4L2 문서를 참고하여 진행합니다.)
- (선택) OpenCV 라이브러리를 사용하여 카메라에서 영상을 획득하고 처리해보세요. OpenCV의 VideoCapture 클래스를 이용하면 내부적으로 V4L2를 사용하여 프레임을 받아올 수 있습니다. 받아온 영상을 그레이스케일로 변환하거나 화면에 디스플레이하여 실시간 영상 처리의 기초를 체험합니다.
관련 자료:
- V4L2 API 프로그래밍 예제 (Medium 블로그)medium.com
- Gateworks: V4L2 개요 및 v4l2-ctl 사용법
- Raspberry Pi Camera V4L2 드라이버 사용 (공식 문서)
7주차: DRM/KMS 기반 디스플레이 출력
학습 개념:
**Direct Rendering Manager (DRM)**은 현대 리눅스 커널의 그래픽 서브시스템으로, GPU와 디스플레이에 대한 공통 인터페이스를 제공합니다en.wikipedia.org. DRM의 한 부분인 **Kernel Mode Setting (KMS)**은 디스플레이의 해상도, 주파수 등의 화면 모드 설정을 커널에서 제어하는 기능으로, 과거의 fbdev를 대체하여 더욱 안전하고 효율적인 화면 관리가 가능합니다jyos-sw.medium.com. DRM을 통해 사용자 공간 프로그램은 libdrm을 사용하거나 ioctl 호출로 프레임버퍼(화면 메모리)를 할당하고, 디스플레이 컨트롤러에 프레임버퍼를 연결하여 화면에 그래픽을 출력할 수 있습니다. 이번 주에는 간단한 DRM/KMS 사용 예제를 통해 프레임버퍼에 도형이나 이미지 데이터를 출력하는 실습을 합니다. (주의: DRM 프로그래밍은 비교적 고급 주제이므로, 기본적인 흐름을 체험하는 수준으로 다룹니다.)
실습 과제:
- Raspberry Pi의 HDMI 등의 디스플레이 출력을 준비합니다 (모니터 연결). 커널 부팅 시 DRM 드라이버가 /dev/dri/card0 등을 생성하며 프레임버퍼 콘솔이 표출될 것입니다. 우선 fbdev 호환 인터페이스(/dev/fb0)가 있다면 cat /dev/urandom > /dev/fb0처럼 임의 데이터를 써보고 화면에 노이즈 패턴이 나타나는지 확인해보세요. (최근 Raspberry Pi OS에서는 기본값으로 fbdev가 없을 수 있습니다.)
- modetest (libdrm 툴)을 사용하여 현재 DRM 출력 모드를 조회하고 테스트해봅니다. modetest -M vc4 (Raspberry Pi 4의 DRM 드라이버 이름은 vc4)로 연결된 커넥터, 지원 모드 목록, CRTC, plane 정보를 확인합니다. modetest 명령으로 컬러바 등 테스트 화면을 표시할 수도 있습니다.
- C 언어로 DRM/KMS 간단한 출력 프로그램 작성: DRM 장치를 open("/dev/dri/card0", O_RDWR)로 열고, drmModeGetResources로 커넥터/CRTC를 조회한 뒤, drmModeSetCrtc로 모드 설정을 수행합니다. drmModeAddFB를 통해 dumb buffer (GPU 메모리상 프레임버퍼)를 생성하고, mmap으로 사용자 공간에 매핑하여 픽셀 데이터를 직접 써보세요. 예를 들어 프레임버퍼 메모리를 모두 0으로 채우면 검은 화면, 특정 패턴으로 채우면 색색의 줄무늬 등을 출력할 수 있습니다. 작성한 후에는 drmModeSetCrtc로 해당 버퍼를 스캔아웃하도록 하고 화면에 변화가 생기는지 확인합니다.
- (선택) 조금 더 발전시켜, 간단한 그래픽 (예: 화면 중앙에 색상 사각형 그리기)을 출력해보세요. 또는 kmscube와 같은 데모 프로그램을 빌드하여 실행해보세요. 이는 DRM을 통해 GPU로 3D 큐브를 렌더링하는 예제인데, 실행하여 GPU가 제대로 동작하고 화면에 렌더링되는지 관찰합니다.
관련 자료:
- DRM/KMS 사용자 공간 입문 예제 (GitHub)
- Xilinx Wiki: DRM/KMS 개요
- 디스플레이 모드설정 (Kernel DRM Documentation)
8주차: RTOS 기초 및 FreeRTOS 이해
학습 개념:
**실시간 운영체제(RTOS)**는 정해진 시간 내에 작업을 수행하는 것을 보장해야 하는 시스템에서 사용됩니다. FreeRTOS는 마이크로컨트롤러용으로 설계된 오픈 소스 경량 RTOS로, 여러 개의 태스크를 우선순위 기반으로 스케줄링하여 실행합니다medium.com. 일반적인 OS인 리눅스와 달리 RTOS는 결정론적 스케줄링을 제공하며, 수 마이크로초 단위의 컨텍스트 스위칭과 높은 응답성을 특징으로 합니다. FreeRTOS의 핵심은 태스크 생성, 우선순위 할당, 그리고 타이머 인터럽트 기반의 스케줄러입니다. 이번 주에는 FreeRTOS의 구조와 동작 원리를 학습하고, STM32 MCU 보드에 간단한 FreeRTOS 예제를 구동시켜 봅니다. 이를 통해 **임베디드 하드웨어 제어의 또 다른 방식(RTOS)**을 이해하고, 리눅스와의 차이점을 파악합니다.
실습 환경 준비:
STM32 계열 개발 보드(예: STM32 Nucleo 또는 Discovery 보드)를 준비하고, PC에 STM32CubeIDE 혹은 ARM GCC 툴체인+OpenOCD를 설치합니다. CubeIDE를 사용하면 프로젝트 생성 시 FreeRTOS를 손쉽게 설정할 수 있습니다medium.com.
실습 과제:
- STM32CubeIDE를 이용하여 새로운 STM32 프로젝트를 생성하고 FreeRTOS 활성화 설정을 합니다 (CubeMX 설정에서 FreeRTOS 미들웨어 추가). 기본적으로 두 개의 태스크(Task) 예제가 생성되도록 설정할 수 있습니다.
- 하나의 태스크를 만들어 0.5초마다 보드의 LED를 토글하도록 구현하고, 또 하나의 태스크는 1초마다 UART로 메시지를 송신하도록 만듭니다. 두 태스크의 우선순위를 다르게 주어 (예: LED 태스크 우선순위 높음, UART 태스크 낮음) 동작을 관찰합니다. FreeRTOS가 우선순위에 따라 태스크를 스케줄링하여 실행함을 확인하세요. (LED 깜빡임 주기가 일정하고, UART 송신도 가능하지만 LED 토글에 지연을 주지 않는지 확인)
- FreeRTOS 핵심 개념인 틱 타이머(Tick Timer) 주기를 변경해 봅니다 (기본 1kHz틱 -> 100Hz 등). CubeMX 설정이나 FreeRTOSConfig.h에서 configTICK_RATE_HZ 값을 조정한 후 다시 빌드/실행하고, 태스크 주기 변화에 영향이 있는지 확인합니다.
- (선택) 시스템 부하와 우선순위 실험: LED 토글 태스크에 인위적인 연산 지연을 넣어보고, 우선순위가 높은 태스크가 지속적으로 실행되면 낮은 태스크가 얼마나 지연되는지 관찰합니다. 이를 통해 RTOS의 스케줄링 동작과 우선순위 역전 현상을 경험해볼 수 있습니다.
- (선택) STM32CubeIDE 없이 GCC + FreeRTOS로 개발: STM32용 FreeRTOS 예제 프로젝트를 GNU Make로 구성해 빌드하고, J-Link나 OpenOCD로 보드에 플래시하여 실행해봅니다. 이 과정에서 FreeRTOS의 포팅 레이어(vPort) 초기화나 링커 스크립트 설정 등을 확인합니다.
관련 자료:
- FreeRTOS 공식 사이트 (커널 설명 및 다운로드)
- FreeRTOS 아키텍처 (AOSA 챕터)aosabook.org
- STM32 + FreeRTOS 실습 튜토리얼 (STM32CubeIDE 사용법)
9주차: FreeRTOS 고급 – 태스크 통신과 실제 활용
학습 개념:
이번 주에는 FreeRTOS의 태스크 간 통신 및 동기화 기법을 다룹니다. RTOS에서는 여러 태스크가 동시에 동작하므로, 안전하게 데이터를 주고받거나 실행 순서를 조정하기 위해 큐(Queue), 세마포어(Semaphore), 뮤텍스(Mutex) 등을 제공합니다. FreeRTOS의 큐는 한 태스크에서 다른 태스크로 데이터를 전달할 수 있는 FIFO 버퍼로서, 센서 데이터 수집 등에서 자주 사용됩니다medium.com. 세마포어는 임계구역 보호나 이벤트 신호로 활용되고, 뮤텍스는 우선순위 역전 문제를 해결하는 특성을 가집니다. 또한 하드웨어 인터럽트와 RTOS 간 협조를 위해 **ISR 안전 함수(xQueueSendFromISR 등)**가 제공됩니다. 이번 주는 이러한 기능을 활용하여 좀 더 복잡한 임베디드 로직을 구현해보고, 실시간 시스템 설계의 기초를 연습합니다.
실습 과제:
- 태스크 간 큐 통신: 두 개의 태스크를 만들어 하나는 주기적으로 센서 값을 생산(예: 가상으로 ADC 값을 생성하거나 보드의 내장 온도센서/포텐셔미터 값을 읽음)하고, 다른 하나는 큐를 통해 그 값을 받아 UART로 출력하거나 특정 로직을 수행하도록 합니다. xQueueCreate로 큐를 생성하고, 생산 태스크는 xQueueSend로 데이터 구조체/값을 보내고 소비 태스크는 xQueueReceive로 블로킹 대기하여 데이터를 수신합니다. 큐가 데이터의 흐름을 일렬로 보장하는지 확인하세요.
- 세마포어를 이용한 동기화: 한 태스크는 LED를 빠르게 깜빡이고 있고, 다른 태스크는 버튼 인터럽트를 대기하고 있다고 가정합니다. GPIO 외부 인터럽트 콜백 (혹은 polling 태스크)에서 바이너리 세마포어를 xSemaphoreGiveFromISR로 전달하고, 대기 중이던 태스크가 xSemaphoreTake으로 이를 받으면 LED 깜빡임 태스크에 영향을 주는 동작(예: 깜빡임 중지 or 주기 변경)을 수행하도록 합니다. 이를 통해 인터럽트 -> 태스크 신호 전달을 연습합니다.
- 타이머와 이벤트 그룹 (선택): FreeRTOS의 소프트웨어 타이머를 사용해 일정 주기마다 이벤트를 발생시키고 여러 태스크가 이를 공유하는 이벤트 그룹(Event Group) 비트를 기다리는 시나리오를 구현합니다. 예를 들어 1초 타이머로 이벤트 비트를 세트(set)하고, 두 개의 태스크가 xEventGroupWaitBits으로 해당 비트를 기다렸다가 동시에 깨어나 각자 작업을 수행하도록 만들 수 있습니다. 이때 여러 태스크가 동시에 깨어났을 때의 스케줄링을 관찰합니다.
- (응용) 간단한 RTOS 응용 프로젝트: FreeRTOS 상에서 센서 제어 및 액츄에이터 제어를 통합하는 응용을 설계해보세요. 예를 들어 라인 팔로워 자동차를 생각해봅시다. 하나의 태스크는 광센서 값을 주기적으로 읽고, 다른 태스크는 모터를 제어합니다. 센서 태스크는 큐로 센서 데이터를 보내고, 모터 제어 태스크는 이를 받아 라인 검출 여부에 따라 모터 속도를 제어하는 식입니다. 실제 하드웨어가 없다면 센서값은 시뮬레이션하고, UART 출력으로 모터 명령을 보는 것으로 대체합니다. 이러한 설계를 통해 RTOS 기반 시스템의 태스크 구조와 통신 방식을 고민해볼 수 있습니다.
관련 자료:
- FreeRTOS 큐/세마포어 공식 문서
- FreeRTOS 실습 예제 모음 (GitHub) (C 언어 예제 참고)
- STM32 FreeRTOS Examples (예제 코드 설명)
10주차: 리눅스 커널 모듈 개발 입문
학습 개념:
이제 임베디드 리눅스의 하드웨어 제어를 커널 공간에서 수행하는 방법인 커널 모듈(Module) 개발을 배웁니다. 커널 모듈은 컴파일된 후 커널에 동적으로 적재되어(kernel space에서 동작하는) 하드웨어를 제어하거나 커널 기능을 확장하는 코드입니다. 디바이스 드라이버 역시 커널 모듈의 한 형태로 구현됩니다linux-kernel-labs.github.io. 커널 모듈은 일반 애플리케이션과 달리 OS의 특권 모드에서 동작하므로, 잘못 작성 시 시스템 패닉 등 심각한 오류를 일으킬 수 있지만 하드웨어 자원에 직접 접근이 가능합니다spiceworks.com. 이번 주는 간단한 커널 모듈을 작성/빌드/적재해보면서 커널 로그를 확인하고, 모듈이 커널과 상호작용하는 방법을 익히는 것이 목표입니다.
실습 과제:
- 개발 환경 설정: 호스트 PC에 현재 Raspberry Pi 커널 버전에 맞는 커널 소스 코드와 헤더를 설치합니다. (Raspberry Pi OS라면 sudo apt install raspberrypi-kernel-headers로 준비). 교차 컴파일 환경에서 모듈 컴파일을 할 것이므로, Makefile에 KERNELDIR을 커널 소스 디렉토리로 설정합니다.
- 가장 간단한 Hello World 커널 모듈을 작성합니다. <linux/module.h>, <linux/kernel.h>를 포함하고 init_module(또는 모듈 매크로 module_init)과 cleanup_module(또는 module_exit) 함수를 정의하여, 모듈 로드 시 printk(KERN_INFO "...")로 메시지를 로그에 남기고 언로드 시도 동일하게 메시지를 남기도록 합니다. 이 코드를 make로 빌드하면 .ko 모듈 파일이 생성됩니다.
- Raspberry Pi 보드로 모듈 파일을 복사한 뒤, **insmod**로 모듈을 커널에 적재합니다. dmesg 명령으로 커널 로그를 확인하여 "Hello" 메시지가 출력되었는지 확인합니다. lsmod로 모듈이 올라간 것을 보고, **rmmod**로 모듈을 제거한 후 "Goodbye" 메시지가 출력되는지도 확인하세요. 이를 통해 커널 모듈의 로드/언로드 과정을 이해합니다.
- 모듈 파라미터를 추가해봅니다. 모듈 소스에 module_param(test, int, 0);와 같은 매크로로 전역변수를 모듈 파라미터로 노출하고, insmod example.ko test=5처럼 모듈을 올려봅니다. printk로 해당 파라미터 값을 출력하여 전달이 잘 되는지 확인하세요.
- (선택) 간단한 커널 내 작업: 모듈 로드 시 커널 스레드를 하나 생성하여 1초 주기로 작업을 수행하게 하거나 (kthread_run 사용), 타이머를 설정해주고 (init_timer 또는 timer_setup) 일정 시간 후 콜백을 실행시키는 등의 동작을 구현해보세요. 이런 방식을 통해 커널 내부에서 주기적 작업을 수행하는 방법을 실험합니다.
관련 자료:
- Linux 커널 모듈 프로그래밍 가이드
- 모듈 개발 예제 (Linux Device Drivers 3판) - Chapter 2 참고
- 커널 모듈 예제 코드 모음 (GitHub)
11주차: 리눅스 디바이스 드라이버 개발 (Char Device 예제)
학습 개념:
이 주차에는 실제 하드웨어를 제어하는 리눅스 디바이스 드라이버를 개발해봅니다. 디바이스 드라이버는 커널 모듈 형태로 작성되며, 특정 하드웨어 장치와 OS 사이의 인터페이스를 담당합니다linux-kernel-labs.github.io. 디바이스 드라이버는 문자 디바이스(char device), 블록 디바이스, 네트워크 디바이스 등으로 나뉘는데, 우리는 가장 단순한 문자 디바이스 드라이버를 구현해볼 것입니다. 문자 디바이스 드라이버는 /dev/mydevice와 같은 장치 파일을 통해 read/write 시스템콜 인터페이스를 제공하며, 이를 통해 사용자 프로그램이 하드웨어와 상호작용할 수 있게 됩니다. 이번 실습을 통해 이전 주차에 사용자 공간에서 하던 GPIO 제어를 커널 드라이버로 옮겨서 수행해보고, 드라이버 구조(파일 연산자, major/minor 번호, udev 연동 등)를 학습합니다.
실습 과제:
- 간단한 문자 디바이스 드라이버를 작성합니다. 예제로 Raspberry Pi의 특정 GPIO에 연결된 LED를 제어하는 드라이버를 만들어보겠습니다. 커널 모듈 코드 내에서 alloc_chrdev_region으로 장치 번호를 할당하고, cdev_init과 cdev_add로 캐릭터 디바이스를 등록합니다. file_operations 구조체를 정의하여 open, release, write 등의 콜백을 구현하는데, write 함수에서는 사용자가 보낸 버퍼의 내용을 해석하여 해당 GPIO를 켜거나 끄는 동작을 수행하면 됩니다. (예: 사용자로부터 "1"을 쓰면 LED ON, "0"을 쓰면 OFF).
- Raspberry Pi의 실제 GPIO 번호를 코드에 하드코딩하거나, Device Tree를 통해 받아오도록 할 수 있습니다. 우선 간단히 GPIO 번호를 코드에 정의하고 (gpio_request_one로 요청), gpio_direction_output으로 설정합니다. 이후 gpio_set_value를 통해 LED 제어를 수행합니다.
- 모듈을 빌드/적재하면 /dev에 장치 노드가 생성되어야 합니다. (alloc_chrdev_region으로 얻은 major 번호를 토대로 mknod 명령으로 수동 생성하거나, moderne 시스템에서는 udev 규칙에 의해 자동 생성되기도 함). ls -l /dev/myled로 존재를 확인합니다.
- 이제 사용자 공간 테스트를 합니다. echo 1 > /dev/myled 명령으로 LED가 켜지는지, echo 0 > /dev/myled로 꺼지는지 확인합니다. 제대로 동작한다면, 이 장치파일에 대한 읽기/쓰기 동작이 커널 드라이버로 전달되어 하드웨어 제어가 이루어진 것입니다.
- (확장) 입력 장치 드라이버: 만약 버튼을 연결했다면, 유사한 방식으로 GPIO를 입력으로 설정하고 file_operations의 read를 구현하여 버튼 상태를 반환하는 드라이버를 만들어볼 수 있습니다. 또는 인터럽트 기반으로 버튼 누름을 감지하여 커널에서 이벤트를 기록하고, read 호출 시 이벤트를 소비하는 형태로 구현해도 좋습니다. 이처럼 입력/출력 디바이스 제어를 커널에서 수행하면, 사용자 공간에 불필요한 busy-wait 없이 효율적으로 하드웨어와 인터페이스할 수 있습니다.
- (고급) Device Tree 연동: 작성한 드라이버를 Device Tree와 연동하면 커널 부팅 시 자동 로드되도록 구성할 수도 있습니다. Raspberry Pi의 Device Tree 소스에 새로운 노드를 추가하고, 모듈에 OF match 테이블을 등록하여 플랫폼 드라이버 형태로 동작하게 개선해보는 것은 더 심화된 주제이므로 필요에 따라 도전합니다.
관련 자료:
- Linux Character Device Drivers - 기본 원리linux-kernel-labs.github.io
- Raspberry Pi GPIO Driver 예제 (영문 블로그)
- 장치 드라이버 개발 핵심 개념 정리
12주차: 종합 프로젝트 및 심화 학습 제안
학습 개념:
마지막 주차에서는 지금까지 배운 내용을 통합하여 종합 미니 프로젝트를 수행하고, 향후 심화하면 좋을 주제를 제안합니다. 자동차 임베디드 시스템에서는 리눅스 기반 메인 컨트롤러와 RTOS 기반 마이크로컨트롤러가 함께 사용되는 경우가 많습니다. 예를 들어 자율주행 차량을 생각해보면, 메인 프로세서는 리눅스를 실행하며 카메라 영상 처리나 UI 표시를 담당하고, 보조 MCU들은 FreeRTOS 같은 RTOS를 실행하며 센서 데이터 수집이나 모터 제어처럼 시간-critical한 작업을 맡습니다. 이러한 구성에서는 서로 간 통신이 필수적입니다. 금주의 프로젝트에서는 Raspberry Pi(임베디드 리눅스)와 STM32(FreeRTOS)가 **직렬 통신(UART)**으로 센서 데이터를 주고받으며 협업하는 시스템을 만들어봅니다. 또한 추가로 공부할만한 주제들 – 예를 들면 CAN 통신, 리눅스 실시간 패치(RT-PREEMPT), 자동차 규격(ASPICE, ISO 26262) 등을 간략히 소개합니다.
프로젝트 – 이기종 시스템 통신 구현:
- Raspberry Pi와 STM32 보드를 UART로 연결합니다 (Pi의 TXD/RXD 핀 <-> STM32 UART 핀 교차 연결, 공통 GND). 통신 속도는 115200bps 등으로 맞춥니다.
- STM32(FreeRTOS): 이전 주차의 큐/세마포어 실습을 확장하여, 주기적으로 센서 값을 읽는 대신 임의의 데이터를 생성한 후 UART로 송신하는 태스크를 만듭니다. 예를 들어 100ms마다 현재 보드의 온도 또는 가상 센서값을 계산하여 printf(UART)로 출력합니다. UART 송신은 ST HAL의 HAL_UART_Transmit 또는 FreeRTOS+UART 예제를 활용하세요.
- Raspberry Pi(Linux): 리눅스 사용자 공간에서 C나 파이썬으로 간단한 UART 수신 프로그램을 작성합니다. /dev/serial0 (GPIO UART) 장치를 열고, read()로 들어오는 바이트 스트림을 읽어와 콘솔에 출력하거나 파일에 로깅합니다. 이 프로그램을 백그라운드에서 실행하면 Raspberry Pi에서 STM32가 보내는 센서 데이터 스트림을 실시간으로 받아볼 수 있습니다.
- 양쪽 시스템이 정상 통신한다면, 응용을 조금 바꾸어서 양방향 통신도 시도해보세요. 예를 들어 Raspberry Pi에서 특정 명령(문자열)을 UART로 보내면 STM32가 이를 받아 LED를 켜거나 특정 동작을 수행하도록 구현합니다. STM32에서는 UART 수신용 인터럽트 또는 저우선순위 태스크에서 HAL_UART_Receive 대기, Raspberry Pi에서는 키 입력을 받아 UART로 송신하는 간단한 코드가 필요합니다.
- 이러한 임베디드-RTOS 간 통신을 통해 분산 제어 시스템의 기초를 체험할 수 있습니다. (자동차에서는 UART 대신 CAN이나 이더넷 등이 쓰이지만, 원리는 유사합니다.)
심화 학습 제안:
- CAN Bus 통신: 자동차 분야 필수 통신인 CAN 프로토콜을 학습하고, 여건이 된다면 Raspberry Pi와 STM32에 CAN 트랜시버 모듈을 연결하여 센서 데이터를 주고받는 연습을 하세요. Linux에서는 SocketCAN 인터페이스(can0 네트워크 인터페이스로 노출)를 통해 CAN 버스를 다루고, FreeRTOS(STM32)에서는 HAL 라이브러리로 CAN 송수신을 구현할 수 있습니다.
- 실시간 리눅스 (PREEMPT_RT): 자동차처럼 하드 실시간 요구가 있는 경우 리눅스 커널에 RT 패치를 적용하여 실시간 커널을 사용하기도 합니다. PREEMPT_RT가 적용된 커널 빌드 과정을 학습하고, 로드가 걸린 상황에서의 인터럽트 레이턴시나 스케줄링 지터를 측정해보는 것도 좋습니다.
- 자동차 표준 및 안전: AUTOSAR 등 자동차 임베디드 표준 구조나 ISO 26262 (기능 안전) 개념도 장기적으로 학습하세요. 임베디드 리눅스 차량용 시스템은 이러한 표준 하에서 개발되므로, 아키텍처 설계나 안전 시스템 구축에 대한 이해가 중요합니다.
- 디바이스 드라이버 심화: 다양한 종류의 드라이버 (I2C 센서 드라이버, SPI 디스플레이 드라이버, USB 드라이버 등)를 직접 만들어 보거나 기존 커널 소스의 드라이버 코드를 분석합니다. 특히 Platform Driver와 Device Tree binding, 그리고 커널의 전원 관리(PM)와 중단 처리(IRQ) 모델을 공부하면 드라이버 개발 역량을 한층 높일 수 있습니다.
관련 자료:
- 임베디드 리눅스에서 UART/직렬통신 다루기
- CAN Bus 프로그래밍 (SocketCAN 예제)
- PREEMPT_RT 리얼타임 커널 설명 (OSADL)
이번 커리큘럼을 모두 완료하면, 리눅스 시스템 부팅부터 응용, RTOS 실습, 그리고 커널 드라이버 개발까지 임베디드 리눅스 전문가로서 갖춰야 할 핵심 역량을 차례대로 습득하게 됩니다. 각 주차의 실습을 통해 얻은 경험을 바탕으로, 자동차 임베디드 시스템에서 센서 처리, 영상 출력, 실시간 제어, 디바이스 드라이버 개발을 자신있게 수행할 수 있을 것입니다. 앞으로는 이를 응용하여 실제 자동차 프로젝트(예: 자율주행 플랫폼 프로토타입 등)에 도전해보세요!
'Linux > Embedded Linux' 카테고리의 다른 글
리눅스 시스템 구조와 부트 과정 이해(1) - dmesg, 부트로더 (0) | 2025.04.20 |
---|