그라운드 임베디드 유튜브 구독

노트 / 자료구조와 알고리즘

링 버퍼: 인터럽트와 메인 루프 사이의 다리

UART로 들어오는 바이트를 잃지 않고 쌓아 두는 원형 큐. 인터럽트가 쓰고 메인 루프가 읽는 구조입니다.

GROUND EMBEDDED · 정리 노트자료구조 02링 버퍼 1#define N 64// 2의 거듭제… 2static volatile uint8_t buf[N]; 3static volatile uint16_t head, tail; 4void rb_put(uint8_t b) {// ISR에서 …

UART 수신 인터럽트는 바이트가 올 때마다 짧게 실행되어야 합니다. 받은 바이트를 처리까지 하면 다음 바이트를 놓칩니다. 그래서 인터럽트는 버퍼에 넣기만 하고, 해석은 메인 루프가 합니다. 이 사이를 잇는 것이 링 버퍼입니다.

구조

배열 하나와 인덱스 두 개(head, tail)로 만듭니다. 끝에 도달하면 처음으로 돌아갑니다.

#define N 64                      // 2의 거듭제곱이면 나머지 연산 대신 & 사용
static volatile uint8_t buf[N];
static volatile uint16_t head, tail;

void rb_put(uint8_t b) {          // ISR에서 호출
    uint16_t next = (head + 1) & (N - 1);
    if (next != tail) { buf[head] = b; head = next; }  // 가득 차면 버림
}
int rb_get(uint8_t *b) {          // 메인 루프에서 호출
    if (tail == head) return 0;   // 비었음
    *b = buf[tail]; tail = (tail + 1) & (N - 1);
    return 1;
}

잠금 없이 안전한 이유

쓰는 쪽은 head만, 읽는 쪽은 tail만 바꿉니다. 생산자와 소비자가 하나씩이고 인덱스 읽기가 원자적이면 인터럽트를 끄지 않아도 됩니다. 칸 하나를 비워 두는 이유는 "가득 참"과 "비었음"을 구분하기 위해서입니다.

크기 정하기

보레이트와 메인 루프 최악 주기로 계산합니다. 115200bps면 초당 약 11520바이트, 메인 루프가 5ms 걸리면 그 사이 약 58바이트가 들어오므로 64바이트로는 빠듯하고 128바이트가 안전합니다.