노트 / 자료구조와 알고리즘
링 버퍼: 인터럽트와 메인 루프 사이의 다리
UART로 들어오는 바이트를 잃지 않고 쌓아 두는 원형 큐. 인터럽트가 쓰고 메인 루프가 읽는 구조입니다.
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바이트가 안전합니다.