UART 통신, 단순히 printf만 찍고 계신가요? 하드웨어 의존성을 제거하고 유연한 펌웨어를 만들기 위한 4단계 계층 분리 설계법을 공개합니다.
신입 사원 시절, 저도 `HAL_UART_Transmit` 함수 하나로 모든 걸 해결하려 했던 적이 있습니다. 하지만 기능이 늘어날수록 메인 루프가 버벅거리고, 하드웨어를 바꾸면 코드를 다 엎어야 하는 대참사가 일어났죠. 오늘은 그 '스파게티 지옥'에서 탈출하는 아키텍처 설계법을 배워볼게요! 😊
1. 왜 함수 호출 하나로 통신하면 안 될까요? 🤔
코드 중간에 `HAL_UART_Transmit`을 직접 넣는 방식은 이른바 'Blocking' 방식입니다. 데이터가 전송될 때까지 CPU가 아무것도 못 하고 기다려야 하죠.
💡 핵심 설계 원칙
통신 매체(UART)와 데이터 처리 로직(Protocol)은 철저히 분리되어야 합니다. 그래야 UART를 SPI나 CAN으로 바꿔도 로직은 그대로 유지할 수 있습니다.
통신 매체(UART)와 데이터 처리 로직(Protocol)은 철저히 분리되어야 합니다. 그래야 UART를 SPI나 CAN으로 바꿔도 로직은 그대로 유지할 수 있습니다.
2. 펌웨어 통신의 4단계 계층 구조 📊
전문가들은 통신을 아래와 같은 계층으로 나누어 관리합니다. 각 단계가 독립적일수록 유지보수가 쉬워집니다.
| 계층 | 역할 | 비고 |
|---|---|---|
| Low Level (HAL) | UART ISR/DMA를 통한 데이터 수신 | 하드웨어 종속 |
| Buffer Layer | Ring Buffer에 데이터 임시 저장 | 충격 완화 장치 |
| Parser Layer | 패킷 시작/끝 확인 및 유효성 검사 | 프로토콜 정의 |
| Application | 해석된 명령에 따른 기능 수행 | 비즈니스 로직 |
⚠️ 주의하세요!
인터럽트(ISR) 안에서 복잡한 파싱 로직을 수행하지 마세요. ISR은 오직 데이터를 버퍼로 옮기는 일만 해야 합니다.
인터럽트(ISR) 안에서 복잡한 파싱 로직을 수행하지 마세요. ISR은 오직 데이터를 버퍼로 옮기는 일만 해야 합니다.
3. 실전 코드 적용: 링 버퍼 인터페이스 👩💻
📝 핵심 아키텍처 코드
// 1. 하드웨어 추상화를 위한 구조체 정의
typedef struct {
void (*Transmit)(uint8_t *pData, uint16_t Size);
uint8_t (*ReceiveByte)(void);
bool (*IsDataAvailable)(void);
} CommInterface;
// 2. Ring Buffer를 통한 비동기 처리
void UART_ISR_Callback() {
uint8_t data = HAL_UART_Receive_IT(&huart1);
RingBuffer_Put(&rxBuffer, data); // 버퍼에 던져두고 즉시 탈출!
}
이렇게 구성하면 PC 시뮬레이터 환경에서도 `CommInterface`의 함수 포인터만 바꿔 끼워 통신 로직을 완벽하게 테스트할 수 있습니다.
🔢 통신 지연시간 계산기
보레이트 (Baudrate):
데이터 크기 (Bytes):
마무리: 핵심 내용 요약 📝
오늘 배운 내용은 펌웨어 개발자로서 '레벨 업'하기 위한 필수 관문입니다.
UART 모듈화 3계명
✨ 탈종속화: HAL 함수를 직접 호출하지 마세요. 인터페이스 레이어를 두어 하드웨어 독립성을 확보해야 합니다.
📊 비동기화: Ring Buffer는 필수입니다. 수신은 인터럽트로, 처리는 메인 루프에서 분리하세요.
👩💻 테스트 가능성: 로직이 분리되면 하드웨어 없이도 유닛 테스트가 가능해집니다.

