12주차 기록을 작성하다보니 내용이 길어져 부득이하게 나누었습니다.
12주차 -1 을 먼저 읽고 오면 내용 이해가 더 잘 될 수 있으니 링크를 걸어 놓도록 하겠습니다.
그러면 다시 12주차 기록 슈~~웃 토!
대부분의 임베디드 SoC에는 USB나 PCI처럼 스스로 “나 여기 있어요!”라고 외칠 수 있는 장치(discoverable device)가 거의 없습니다.
대신 GPIO, UART, I²C 컨트롤러, PWM 등은 CPU 내부에 하드 와이어드(hard-wired) 되어 있어서 부팅할 때 누가 어디에 있는지 미리 알려줘야 합니다.
그래서 리눅스 커널은 이런 장치들을 위한 가상 버스를 만들었는데, 그게 바로 platform bus입니다.
platform_device ←── 이름으로 매칭 ───→ platform_driver
(하드웨어 정보) (제어 코드)
↑ ↑
└────────────── platform bus (중개) ───────────┘

binding
디바이스 트리에 기술된 하드웨어 정보와 커널에 작성된 드라이버 코드를 하나로 묶어주는 과정
Matching
compatible = "vendor,model"; 이라는 식별자를 가짐struct of_device_id 테이블에 자신이 지원하는 compatible 리스트 보유바인딩은 커널이 부팅되거나 모듈이 로드될 때 플랫폼 버스(Platform Bus) 위에서 일어난다.
등록 (Registration)
커널이 디바이스 트리를 읽어 platform_device를 생성하고, 드라이버가 로드 → platform_driver가 등록됩니다.
비교 (Matching)
플랫폼 버스가 양쪽의 compatible 문자열 비교
1. discoverable devices
plug -play
usb, pci , - adapter interrupt! -> enumeration ! <- device descriptor
2. non- discoverable devices
4가지 방법 존재
결합 (Binding)
이름이 일치하면 커널은 드라이버의 probe() 함수를 호출
전달 (Resource Passing)
이때 디바이스 트리에 적힌 reg(주소), interrupts(번호) 등의 자원 정보가 드라이버에 전달됩니다.
과거에는 보드마다 소스코드에 하드 코딩하거나 별도의 헤더파일을 만들어서 빌드했습니다.
→ 매번 커널을 다시 컴파일해야 했고, 유지보수가 힘들었다.
→ 이를 해결
바이너리 형태(.dtb)로 메모리에 올려두고,
커널이 부팅하면서 그 정보를 읽어서 장치를 만듭니다.
Bootloader (U-Boot, Raspberry Pi firmware 등)
↓
kernel8.img + .dtb 파일을 메모리에 올림
↓
커널 시작 → early boot 단계에서 .dtb를 읽음
↓
내부적으로 트리 구조로 펼쳐서 메모리에 저장
(런타임 FDT)
platform_driver의 .of_match_table과 매칭되면서 probe() 함수가 호출되는 시작점⭐️⭐️
compatible = "raspberrypi,bcm2835-gpio";
dtoverlay의 동작 방식
dtoverlay my-overlay명령 실행/boot/overlays/my-overlay.dtbo파일 읽음- 이미 메모리에 펼쳐져 있는 현재 FDT (Flattened Device Tree)를 대상으로 오버레이에 적힌 내용대로 기존 노드 수정 & 새 노드 추가
example)
&i2c1 { status = "okay"; ... }→ 기존 i2c1 노드 찾아서 status 변경my-sensor@48 { ... }→ 새로운 자식 노드 추가
- 변경된 내용을 커널의
of_overlay프레임워크가 적용- 변경된 부분에 해당하는 platform_device들이 새로 생성/갱신됨
- → 해당 compatible을 가진 드라이버의
probe()함수가 새로 호출됨==>💡동적 로딩 !시스템이 켜져있는 상태에서도 dtoverlay 명령어를 통해서 하드웨어 설정을 즉시 반영할 수 있다
⚠️작성 규칙 : 반드시
/dts-v1/;과/plugin/;선언이 포함되어야함
# dtc -@ -I dts -O dtb -o 01_dev.dtbo 01_dev.dts
01_dev.dts:16.30-24.16: Warning (unit_address_vs_reg):
/fragment@0/__overlay__/rpihat_device@0: node has a unit name, but no reg property
#====> dts 파일을 커널이 읽을 수 있는 dtbo로 변경
static int my_probe(struct platform_device *pdev)
{
struct my_private_data *priv;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
// 가장 많이 쓰는 devm_ 패밀리
priv->led = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
priv->key = devm_gpiod_get(&pdev->dev, "key", GPIOD_IN);
priv->irq = gpiod_to_irq(priv->key);
devm_request_irq(&pdev->dev, priv->irq, my_isr,
IRQF_TRIGGER_FALLING, "my-key", priv);
platform_set_drvdata(pdev, priv);
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "mycompany,super-hat" },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "super-hat",
.of_match_table = my_of_match,
}
};
module_platform_driver(my_driver);
devm_ 함수를 적극 활용 → remove에서 해제 안 해도 됨dtoverlay 먼저 → insmod 순서 지키기dev_info() 적극 활용character 디바이스 드라이버 중 하나
major 번호로 장치의 종류를 구분하는 문자 장치의 구분에서 10으로 지정 된다 → 부번호만 설정하면 자동으로 장치 파일을 만들어 준다 .
. miscdevice의 장점class_create, device_create를 호출하지 않아도 /dev/ 아래에 파일이 자동으로 생성cdev_init, cdev_add 같은 복잡한 초기화 과정이 필요 없습니다.주요 입력 장치 : 키보드 , 마우스 , 조이스틱 , 터치 스크린 등등
특징: 간헐적 데이터 발생
바이트 단위 데이터
인터럽트 기반의 동작
-> irq line
USB
usb keyboard
인터럽트 (x) -> irq 라인이 없음 [ 하드웨어 선 x ]
컨트롤 , 이소 , 벌크 , 인터럽트 (흉내) 전송 모드 (O)
event oriented
→ 표준화를 통해 input 디바이스 드라이버 탄생
하드웨어 ──→ 입력 장치 드라이버 (input_dev 등록)
↓
input core (이벤트 큐 관리)
↓
┌───────────┬───────────┬───────────┐
│ │ │ │
evdev keyboard mouse joydev ... ← 이벤트 핸들러들
│ │ │ │
↓ ↓ ↓ ↓
/dev/input/eventX /dev/input/mice 콘솔 키보드 등
이벤트 핸들러는 입력 장치 드라이버(input device driver)가 생성한 이벤트를 받아서 사용자 공간(user-space)으로 전달하는 중간 역할을 하는 모듈
입력 장치(키보드, 버튼, 조이스틱 등)를 만들 때마다
/dev/input/event0,/dev/input/event1,/dev/input/event2… 이런 식으로
번호가 계속 바뀌는 문제
→ 이를 어떻게 해결할 것인가 ??
udev : 동적 장치 관리자 → user system에서 동작
커널의 sysfs 과 연동하여 장치 파일을 동적으로 생성하고 관리
→ 드라이버가 인식한 장치를 사용자 친화적인 환경으로 만들어 주는 사용자 공간의 관리자
root@rpi:/etc/udev/rules.d# cat 99-rpihat.rules
SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="rpihat",
SYMLINK+="input/rpihat", MODE="0666"
# 파일 생성
숫자가 클 수록 나중에 적용된다 → 99 설정
# 1. 규칙 새로고침
sudo udevadm control --reload-rules
root@rpi:~/exercise_A05.251102/08# systemctl restart udev
# 2. 이미 꽂혀있는 장치들에게 규칙 강제 적용 (또는 드라이버를 rmmod -> insmod 해도 됨)
sudo udevadm trigger
root@rpi:~# ls -l /dev/input/rpihat
lrwxrwxrwx 1 root root 6 1월 8 11:37 /dev/input/rpihat -> event2
=> 확인 가능
I²C(Inter-Integrated Circuit)는 2선만으로 여러 장치를 연결할 수 있는 직렬 통신 프로토콜입니다.
마스터(Raspberry Pi)가 클럭을 만들고, 슬레이브(센서, OLED 등)가 대답합니다.

Start : SCL High + SDA High → LowStop : SCL High + SDA Low → HighStart
│
▼
SCL ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
┌──┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
SDA A6 A5 A4 A3 A2 A1 A0 W (7bit 주소 + W=0) ACK D7~D0 ACK Stop
└───────────────────────┘ └────────┘
Master가 보냄 Slave가 ACK Master 데이터 Slave ACK1
Start → [7bit 주소 + W(0)] → ACK → [레지스터 주소] → ACK → [데이터] → ACK → Stop
→ 9번째 비트마다 ACK(NACK) 확인이 들어갑니다.
| 전체 FLOW | Module to Excute |
|---|---|
![]() | ![]() |
i2c_adapter ← 하드웨어 (BCM2835 I²C 컨트롤러) → /dev/i2c-1
↑
i2c_algorithm ← 전송 방법 (master_xfer 함수 포인터): 소프트웨어 측면
↑
i2c_client ← 우리가 만드는 드라이버 (BMP280, SSD1306 등)
Soc에는 대개 I2C 컨트롤러가 내장되어 있음 → 저수준 인터페이스
→ 이게 /dev/i2c-{num} → 이렇게 장치 파일로 표현됨
해당 디바이스 파일을 열고 write , read, ioctl 함수를 호출하면 각각 i2c adapter의 함수로 연결
❔ioctl : I2C 버스는 여러 장치가 공유하므로, "지금 누구랑 말할 것인가"를 결정
→ 사용자 공간에서는 /dev/i2c-1 파일을 열고 ioctl(I2C_SLAVE)로 “지금 누구랑 얘기할지” 정합니다.
i2cdetect -F <busaddress. ie, 1>
1. I2C
standard
2. SMBus ( is a subset of I2C )
variation (-> PC , notebook)
smbus_ * => 비교적 안전하게 쓸 수 있음
#장치가 버스에 붙었는지 확인
i2cdetect -y 1→ 0x76 (BMP280), 0x3C (SSD1306) 등이 보이면 성공
#칩 ID 읽어보기 (가장 확실한 생존 확인)
i2cget -y 1 0x76 0xD0 b # BMP280 → 0x58 나와야 정상
#레지스터 직접 쓰고 읽기 (기본 통신 확인)
i2cset -y 1 0x76 0xE0 0xB6 # 소프트 리셋
i2cget -y 1 0x76 0xD0 b
BMP280 칩 ID 읽기 예제 : 0xD0 값 읽어 오기
src code
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#define I2C_BUS_PATH "/dev/i2c-1" // RPi의 기본 I2C 버스
#define BMP280_I2C_ADDR 0x76 // 센서의 슬레이브 주소
#define BMP280_ID_REG 0xD0 // 칩 ID가 저장된 레지스터 주소
int main() {
int fd;
unsigned char buf[1];
// 1. I2C 버스 파일 열기
fd = open(I2C_BUS_PATH, O_RDWR);
// 2. 통신할 대상(Slave) 주소 설정
if (ioctl(fd, I2C_SLAVE, BMP280_I2C_ADDR) < 0) {
.
}
// 3. 읽고 싶은 레지스터 번호를 먼저 전송 (Write)
// "지금부터 0xD0번지에 있는 값을 알고 싶어"라고 명령하는 단계
buf[0] = BMP280_ID_REG;
write(fd, buf, 1);
// 4. 레지스터 값 읽기 (Read)
// 센서가 보낸 1바이트 데이터를 수신
read(fd, buf, 1) ;
// 5. 결과 출력 (BMP280의 경우 정상 시 0x58 출력)
printf("BMP280 Chip ID: 0x%02X\n", buf[0]);
close(fd);
}
static int bmp280_probe(struct i2c_client *client) {
// 1. 장치가 맞는지 ID 확인
u8 id;
i2c_smbus_read_byte_data(client, 0xD0); // 0x58 확인
// 2. private data 할당
struct bmp280_data *data = devm_kzalloc(...);
// 3. i2c device 등록
i2c_set_clientdata(client, data);
// 4. sysfs나 character device 생성 (선택)
return 0;
}
static const struct i2c_device_id bmp280_id[] = {
{ "bmp280", 0 },
{ }
};
static struct i2c_driver bmp280_driver = {
.driver = { .name = "bmp280" },
.probe = bmp280_probe,
.id_table = bmp280_id,
};
module_i2c_driver(bmp280_driver);
리눅스는 원래 PC 친화적.
SMBus(Smart Battery Bus, I²C의 서브셋)를 기본적으로 더 잘 지원합니다.
모든 리눅스 I²C 어댑터가 SMBus 명령을 보장
→ 그래서 실습에 사용한 BMP280 이외에도 대부분의 센서는 i2c_smbus_read_byte_data() 같은 함수를 씁니다.
BMP280을 활용한 클라이언트 드라이버의 구현은 BMP280 Driver 에 구현 내용을 따로 정리하도록 하겠습니다!
산업용 센서 및 I/O장치를 위한 표준화 프레임 워크
- 통합된 API
- 고성능 데이터 처리
버퍼링
- 재사용 가능한 프레임 워크
IIO CoreIIO channelIIO BufferIIO Triggeriio_device_enable_buffer() 함수를 통해 트리거와 버퍼를 연결 후 다양한 트리거 소스 사용[물리 세계]
↓ (물리량 → 전기 신호)
센서 (BMP280, DHT11, MPU6050 등)
↓ (아날로그 → 디지털, 레지스터 읽기)
드라이버 (IIO 드라이버 코드)
↓ (측정값 → 채널별 raw/processed 값)
IIO 채널 (in_temp_raw, in_pressure_input 등)
↓ (트리거 발생 시점에 데이터 캡처)
트리거 (timer / external GPIO / software)
↓ (캡처된 데이터 임시 저장)
IIO 버퍼 (FIFO처럼 쌓아두기)
↓ (IIO core가 관리)
IIO Core (sysfs + /dev/iio:deviceX 인터페이스 제공)
↓ (두 가지 주요 경로)
├─── A. sysfs 경로 ──→ 설정/단일 값 읽기
│ (/sys/bus/iio/devices/iio:deviceX/in_*)
└─── B. 문자 장치 파일 ──→ 고속/연속 데이터 스트리밍
(/dev/iio:deviceX read())
↓
사용자 공간 애플리케이션
(libiio, 직접 read(), cat, iio_info 등)
IIO VS sysfs VS Character device
장점 : 간편한 key -value 쌍으로 설정 및 접근 ⇒ 구현이 간단
단점
- read : 한번에 한개의 샘플 데이터만 읽음 → 데이터 스트리밍에 부적합
- 타이밍이 보장되지 않음
- 버퍼링 및 큐잉 기능 없음
open , read , write , ioctl을 통한 접근
장점 : 자유도가 높다 . ioctl로 복잡한 제어 명령 설정 가능
단점
- 장치마다 제각각인 명령 형식 → 호환성 x
- read, open ,write 등 모든 인터페이스를 직접 구현해야하는 부담
- 표준화된 인터페이스 제공
- sysfs를 통해 장치 설정 및 정보 조회
- 통합된 버퍼링 방식 제공
- dev/iio:deviceX 형태의 문자 장치 파일을 사용할 수 있음 .
- read() 함수를 통해 버퍼링된 데이터 묶음을 효율적으로 읽을 수 있다
→ 비동기적 read
- trigger 지원
- 다채널 지원 → 온도 압력 습도 등을 하나의 버퍼에 동시에 받는다
DHT-11를 활용하여 라즈베리 파이에서 IIO 실습을 진행했습니다.
#IIO 코어 모듈 로드
root@rpi:~/# modprobe industrialio
#dht-11 module 확인
root@rpi:~/# modprobe -D dht11
insmod /lib/modules/6.1.21-v8+/kernel/drivers/iio/industrialio.ko.xz
insmod /lib/modules/6.1.21-v8+/kernel/drivers/iio/humidity/dht11.ko.xz
# 의존성을 가진다
root@rpi:~/# lsmod | grep 03_dev
03_dev 16384 0
industrialio 90112 2 iio_hwmon,03_dev
-> industrialio 먼저 올려야함
root@rpi:~/# modinfo 03_dev.ko
filename: /root/../03_dev.ko
license: GPL v2
description: DHT11 humidity/temperature sensor driver
srcversion: C1D68BD9D145141E3DCFD8E
alias: of:N*T*CrpihatC*
alias: of:N*T*Crpihat
depends: industrialio
name: 03_dev
vermagic: 6.1.21-v8+ SMP preempt mod_unload modversions aarch64
=========================================================
#모듈 로드 이후
root@rpi:/sys/bus/iio/devices/iio:device0# xxd in_temp_input
00000000: 3235 3032 0a 2502.
root@rpi:/sys/bus/iio/devices/iio:device0# xxd in_humidityrelative_input
00000000: 3138 3030 0a 1800.
root@rpi:/sys/bus/iio/devices/iio:device0
=> 해당 경로에 인터페이스 생성
==> APP client에서도 여기서 값을 읽어온다.
static const struct iio_chan_spec dht11_chan_spec[] = {
{
.type = IIO_TEMP ,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
}
};iio = devm_iio_device_alloc(dev, sizeof(*dht11));
dht11 = iio_priv(iio); // iio->priv → dht11 구조체 연결
iio->name = pdev->name;
iio->info = &dht11_iio_info; // read_raw() 콜백 연결
iio->modes = INDIO_DIRECT_MODE; // 버퍼링 모드 지정
iio->channels = dht11_chan_spec; // 온도/습도 2개 채널
iio->num_channels = ARRAY_SIZE(dht11_chan_spec);
devm_iio_device_register(dev, iio); // IIO 디바이스 등록 (/sys/bus/iio/ 아래 생성) IIO 등록 → in_temp_input, in_humidityrelative_input 자동 생성 root@rpi:~/# ./app
Temperature: 2507
Humidity is: 1700
================= > 클라이언트에서 값을 확인 후 출력 | 항목 | Misc Device | IIO 버전 | 차이점 |
|---|---|---|---|
| 인터페이스 | /dev/rpihat (misc device) + cat으로 문자열 출력 | /sys/bus/iio/devices/iio:deviceX/ (sysfs) + /dev/iio:deviceX (버퍼링) | 표준화 (IIO) vs 커스텀 (misc) |
| 데이터 접근 방식 | read() → 단일 값 (습도 온도 문자열) | read_raw() → 온도/습도 각각 독립 채널로 정수 값 제공 | 채널별 분리 + 고속 버퍼링 가능 |
| 사용자 접근 방법 | cat /dev/rpihat → "550 2500" | cat /sys/bus/iio/.../in_temp_input → 온도 cat/sys/bus/iio/.../in_humidityrelative_input → 습도 | IIO 표준 경로 사용 (다른 IIO 센서와 동일) |
| 버퍼링/스트리밍 | 없음 (1회 읽기) | /dev/iio:deviceX read로 버퍼링된 샘플 묶음 읽기 가능 | 고속/연속 측정에 최적화 |
| 트리거/비동기 지원 | 없음 | IIO trigger (timer/external) 사용 가능 → 주기적 자동 샘플링 | 실시간 스트리밍 가능 |
| 호환성/확장성 | 낮음 (커스텀 인터페이스) | 높음 (IIO는 libiio, ROS, Yocto 등에서 표준 지원) | 현대 리눅스 센서 표준 |
해당 내용을 마지막으로 12주차의 내용이 끝났습니다 . 다양한 디바이스별로 여러 드라이버 구현 방법을 빠르게 배우면서 코드 구현 보다는 차이점 파악 및 다뤄보기 위주로 교육이 흘러갔는데요 . . 앞으로 혼자서 끄적여 보면서 실제로 디바이스 드라이버를 만드는 임베디드 초고수가 되기 위해서 달려 나가 보겠습니닷
월요일에 점심 근처 카페에서 커피를 샀더니 꽃까지 받아 부러씁니다.
괜시리 꽃을 보고 기분이 좋아지는 것은 1살을 더 먹었기 때문일까요 .. 헛헛 😭

Welcome to my World .
Just Enjoy your life
And Good Luck .