수업 정리/임베디드시스템소프트웨어

[embedded system software/11주차] 이론

gyuun365 2026. 5. 14. 15:36

해당 글은 건국대학교 진현욱 교수님의 임베디드 시스템 소프트웨어 수업 내용을 정리한 글입니다. 

 

시리얼 통신 프로토콜

우리는 I2CSPI 라는 두 가지 대표적인 시리얼 통신 프로토콜에 대해 설명을 하겠다.

 

Inter-Integrated Circuit

단거리, 2-wire serial communication protocol로서 low-speed peripheral(주변장치)를 연결하기 위해 사용되는 시리얼 컴퓨터 버스다. 

2개의 물리적인 라인은 SCL과 SDA를 말하며 각각 아래와 같다.

 

  • SCL (Serial Clock): 데이터 전송을 동기화하는 클록 신호가 흐른다.
  • SDA (Serial Data): 실제 데이터가 전송되는 라인이다

 

모든 컴포넌트 사이에  2가지 관계가 존재하는데

 

  • Master: SCL라인을 구동하며, 일반적으로 버스에 하나만 존재하여 신호를 주도한다.
  • Slave: 마스터의 신호에 응답하며, 스스로 전송을 시작할 수 없다. 무조건 Master가 주도함

하드웨어 연결

 

라즈베리 파이에서 SCL, SDA를 위한 핀이 정해져 있다. 

 

I2C 통신 프로토콜

이 버스에 연결된 모든 장치는 SCL과 SDA 라인의 상태를 상시 모니터링하여 아래 조건들을 감시한다.

 

Start는 SCL이 High일 때, SDA가 High에서 Low로 떨어지면 통신 시작을 알리는 것(1->0)

이 신호가 오면 통신을 시작하겠구나 하고 준비를 한다.

Start 조건을 만든 직후에는 반드시 SCL을 Low로 떨어뜨려 주어야 한다.

 

Stop는 SCL이 High일 때, SDA가 Low에서 High로 떨어지면 통신 시작을 알리는 것(0->1)

이 신호가 나오면 현재 진행 중이던 트랜잭션이 완전히 종료된다.

더보기
#include <stdio.h>

// 가상의 하드웨어 레지스터/핀 제어를 시뮬레이션하기 위한 변수
int SCL = 1;
int SDA = 1;

// 신호 안정화를 위한 미세 지연 함수
void i2c_dly(void) {
    // 실제 임베디드 환경에서는 nop 명령어 방식이나 마이크로초(us) 단위의 delay를 사용합니다.
    for (volatile int i = 0; i < 100; i++); 
}

// [Start Sequence] 통신 시작 신호 생성
void i2c_start(void) {
    SCL = 1;        // SCL을 High로 설정 [cite: 122]
    i2c_dly();      // [cite: 124]
    SDA = 0;        // SCL이 High인 상태에서 SDA를 Low로 떨어뜨림 (Start 조건 발생) [cite: 126]
    i2c_dly();      // 인식 지연 [cite: 127]
    SCL = 0;        // 다음 데이터 비트 송신을 위해 SCL을 Low로 당김 
    i2c_dly();      // [cite: 135]
}

// [Stop Sequence] 통신 종료 신호 생성
void i2c_stop(void) {
    SDA = 0;        // Stop 조건을 만들기 위해 먼저 SDA를 Low로 설정 [cite: 128]
    i2c_dly();      // [cite: 129]
    SCL = 1;        // SCL을 High로 설정 [cite: 131]
    i2c_dly();      // [cite: 138]
    SDA = 1;        // SCL이 High인 상태에서 SDA를 High로 올림 (Stop 조건 발생) [cite: 139]
    i2c_dly();      // [cite: 140]
}

int main(void) {
    printf("I2C Start & Stop Sequence Simulation\n");
    
    i2c_start();
    printf("Start Condition Generated -> SCL: %d, SDA: %d\n", SCL, SDA);
    
    i2c_stop();
    printf("Stop Condition Generated  -> SCL: %d, SDA: %d\n", SCL, SDA);
    
    return 0;
}

data transfer

#include <stdio.h>

extern int SCL;
extern int SDA;
void i2c_dly(void);

// [Data Transmission] 1바이트 전송 및 ACK 확인 함수
void i2c_tx(unsigned char d) {
    char x;
    
    // 8비트 동안 반복 실행 (MSB인 D7부터 D0까지) [cite: 150, 151, 163]
    for (x = 8; x > 0; x--) {
        // 최상위 비트(0x80 = 1000 0000)가 1인지 0인지 검사하여 SDA에 대입 [cite: 164]
        if (d & 0x80) {
            SDA = 1; // [cite: 164]
        } else {
            SDA = 0; // [cite: 165]
        }
        
        i2c_dly();      // SDA 비트 안착을 위한 딜레이 추가 [cite: 172]
        SCL = 1;        // SCL을 High로 올려 슬레이브가 읽어가도록 함 
        i2c_dly();      // High 유지 딜레이 [cite: 172]
        
        d <<= 1;        // 데이터를 왼쪽으로 1비트 시프트하여 다음 비트 준비 [cite: 168]
        SCL = 0;        // SCL을 Low로 내려 다음 SDA 변동을 허용함 
    }
    
    // 9번째 클록: ACK 비트 처리 구간 
    SDA = 1;            // 마스터가 SDA 라인을 릴리즈(High)하여 슬레이브가 ACK를 줄 수 있게 함 
    i2c_dly();          
    SCL = 1;            // 슬레이브의 ACK 상태를 읽기 위해 SCL을 High로 올림 
    i2c_dly();          // 수신 확인을 위한 비트 딜레이 [cite: 175]
    
    // 실제 하드웨어라면 여기서 SDA 핀의 상태(0이 되었는지)를 읽어 ACK 여부를 리턴합니다.
    // if (SDA == 0) printf("ACK Received!\n");
    
    SCL = 0;            // 통신을 이어서 하거나 종료할 수 있도록 SCL을 다시 Low로 정리 
    i2c_dly();
}

 

SCL=1;  에서 SCL = 0 로  되었을때 1Bit를 보냈다고 생각하면 된다.

 

  1. 마스터는 SCL이 Low일 때 SDA 핀에 1 또는 0의 값을 출력한다.
  2. 그런 다음 SCL을 High로 올리면, 슬레이브가 이 High 구간 동안 SDA의 값을 읽어간다.
  3. 마스터는 다시 SCL을 Low로 내리고 다음 비트를 준비한다. 이 과정을 8번 반복한다.

 


i2c-dev 커널 모듈: 리눅스에서 사용자가 사용자 공간(User Space)에서 I2C 버스에 직접 접근할 수 있게 해주는 드라이버이다.

 open(), read(), write(), ioctl()로 다룰 수 있다.

 

하지만 이러한 커널 시스템 콜을 직접 사용하는 것은 난이도가 있다. 그래서 예전에 말한 WiringPi를 이용할 수 가 있는데

 

 

  • 초기화: int wiringPiI2CSetup(int devId) 함수를 사용하여 통신할 장치를 초기화하고 파일 디스크립터(fd)를 얻는다.
  • 단순 읽기/쓰기: wiringPiI2CRead(fd)wiringPiI2CWrite(fd, data)를 통해 1바이트 단위로 데이터를 주고받을 수 있다.
  • 레지스터 기반 제어 (추상화): 장치 내부의 특정 메모리 주소(Register)를 지정하여 데이터를 쓰거나 읽는 기능을 제공한다.
    • wiringPiI2CWriteReg8 / Reg16: 특정 레지스터에 8비트 혹은 16비트 값을 기록한다.
    • wiringPiI2CReadReg8 / Reg16: 특정 레지스터로부터 8비트 혹은 16비트 값을 읽어온다.

GPIO 확장: 라즈베리 파이의 자체 GPIO 핀이 부족할 때, 이 칩을 연결하면 16개의 추가적인 입출력 핀(GPA0~7, GPB0~7)을 사용할 수 있다

 

SPI

Serial Peripheral Interface의 약자로서 모토로라에서 제안한 동기식 시리얼 통신 규격으로, 주로 아주 짧은 거리의 장치 간 통신에 사용된다.

I2C와 달리 single master를 가지고 있으며 총 4개의 물리적인 선을 사용하는 버스 구조이다

  • SCLK: 시리얼 클록 (마스터가 생성).
  • MOSI: 마스터 출력, 슬레이브 입력 (데이터 송신용). master -> slave
  • MISO: 마스터 입력, 슬레이브 출력 (데이터 수신용). slave -> master
    • 그래서 full-duplex 구조로 사용이 가능하다!!
  • SS (Slave Select): 통신할 슬레이브를 선택하는 신호.

그림과 같이 최대 2개를 가질 수 있으며 통신할 슬레이브를 선택하는 신호라고 보면 된다.

 

위에서 말했듯이 Full-deplex 통신 방식이 사용되는 것을 볼 수가 있는데,

master -> slave 시에는 무조건 MOSI 선을 사용하고 반대에는 MISO선을 사용하는 것을 볼 수 있다.

 

  1. 마스터가 SS 라인을 활성화(보통 Low)한다.
  2. 마스터가 클록(SCLK)을 발생시킨다.
  3. 클록에 맞춰 MOSI로 명령을 보내고, 동시에 MISO로 응답을 받는다.
  4. 전송이 끝나면 SS 라인을 다시 High로 올려 연결을 해제한다.

 

SPI Driver in Linux

spidev: 리눅스에서 SPI 장치에 접근하기 위한 드라이버.

  • /dev/spidev0.0 (첫 번째 버스, 첫 번째 슬레이브) 같은 경로로 접근한다.

read() or write() 는 Half-duplex access 밖에 가능하지 않고

 

ioctl()을 사용해야 full- duplex access가 가능하다.

더보기

 

  • SPI_IOC_WR_MODE: 통신 모드(CPOL, CPHA) 설정.
  • SPI_IOC_WR_MAX_SPEED_HZ: 최대 통신 속도 설정.
  • SPI_IOC_MESSAGE(N): Full-duplex 전송을 수행할 때 사용하며, spi_ioc_transfer 구조체에 버퍼 정보를 담아 보낸다.

 

WiringPi SPI Library

 

  • 초기화: int wiringPiSPISetup(int channel, int speed)
    • 채널은 0 또는 1을 선택하며, 속도는 보통 500,000~32,000,000Hz 범위 내에서 설정한다.
  • 데이터 송수신: int wiringPiSPIDataRW(int channel, unsigned char *data, int len)
    • 매우 중요: 이 함수는 data 버퍼에 있는 내용을 보내는 동시에, 슬레이브로부터 받은 내용을 같은 버퍼에 덮어쓴다. 따라서 실행 후 data를 확인하면 수신된 값이 들어있다

 

비교 항목 Inter-Integrated Circuit (I2C) Serial Peripheral Interface (SPI)


필요한 선 개수

(Wire)


단 2개 (SDA, SCL)만 있으면 되므로 회로가 매우 단순해지고 핀을 아낄 수 있습니다.


최소 4개 (SCLK, MOSI, MISO, SS)가 필요하며, 슬레이브가 늘어날 때마다 SS 선이 추가로 필요합니다.


통신 속도

(Speed)
상대적으로 저속입니다. 표준 규격상 최대 3.4Mbps 정도의 속도를 냅니다.

전이중(Full-Duplex) 통신을 지원하여 데이터를 보내면서 동시에 받을 수 있고, 속도가 Mbps이상으로 매우 빠릅니다.


전송 거리

(Distance)
풀업 저항을 사용하는 버스 구조 덕분에, SPI에 비해 상대적으로 먼 거리까지 신호를 안정적으로 보낼 수 있습니다. 신호 감쇠에 취약하여 매우 짧은 거리에서만 쓸 수 있습니다. 보통 하나의 PCB 보드 내부를 벗어나기 힘듭니다.


소비 전력

(Power)
라인을 High 상태로 유지하기 위해 풀업 저항을 거쳐 지속적으로 전류가 흐르므로, SPI보다 전력 소모가 큽니다. 마스터와 슬레이브가 푸시풀(Push-Pull) 방식으로 라인을 직접 구동하므로, I2C에 비해 전력 소모가 적습니다.