ADC 드라이버 설계: 블로킹 vs 논블로킹 폴링 기법 비교
이전 글에서 다룬 것: 잘 구조화된 인터페이스로 하위 레벨 드라이버 세부사항과 애플리케이션 로직 분리, 하드웨어 플랫폼 간 확장성·이식성 향상.
이 글에서는 실제 ADC 드라이버 구현에 초점, 폴링 접근법 다룹니다. 블로킹과 논블로킹 설계 대조, 각 시스템 성능 영향 분석.
블로킹 vs 논블로킹 드라이버
마이크로컨트롤러 드라이버 생성 시 개발자는 API가 작업 완료까지 실행을 블록할지 결정해야 함. 블로킹 드라이버는 프로그램 플로우 정지, 후속 코드가 태스크 완료 후만 실행되도록 보장. 전형적 예: UART 백드 printf.
printf("Hello World!");
블로킹 UART 드라이버면 이 호출 다음 줄이 12바이트 문자열(96비트) 완전 전송될 때까지 실행 안 됨. 실제 지연은 보드레이트 의존: 1Mbps면 약 96µs, 9.6kbps면 약 10ms.
반면 논블로킹 드라이버는 전송 개시 후 즉시 애플리케이션에 제어 반환. UART가 인터럽트, DMA, 전송 버퍼 통해 데이터 스트리밍하면서 계속 처리 가능. 이 접근이 지연 민감 컨텍스트서 효율성 향상하지만 드라이버와 애플리케이션 복잡도 증가.
적정 모델 선택은 애플리케이션 타이밍 요구와 하드웨어 구성 의존. 고속 UART(예: 1Mbps)면 논블로킹 설계 오버헤드 이점 상회 가능. 저속 링크(예: 9.6kbps)면 관리 가능한 추가 복잡도로 상당한 성능 이득.
ADC 드라이버 아키텍처
견고한 ADC 드라이버 통상 3계층 구성:
- 로우레벨 드라이버: 초기화 시 구성 구조체 받아 하드웨어 레지스터 설정.
- 애플리케이션 코드: 제네릭 HAL 함수 호출해 변환 시작, 결과 조회.
- 구성 모듈: 기기별 파라미터 추상화.
로우레벨 드라이버가 간결·재사용 가능 API 가진 하드웨어 추상화 계층(HAL) 제공. 실전 프로젝트 예시:
AdcError_t Adc_Init(const AdcConfig_t * Config);
AdcError_t Adc_StartConversion(void);
bool Adc_ConversionComplete(void);
void Adc_RegisterWrite(uint32_t const Address, uint32_t const Value);
uint32_t Adc_RegisterRead(uint32_t Address);
void Adc_CallbackRegister(AdcCallback_t const Function, TYPE (*CallbackFunction)(type));
이 API들로 직관적 초기화, 변환 제어, 상태 폴링 가능. 레지스터 접근 헬퍼로 개발자가 단일 채널 변환 등 커스텀 기능 위해 퍼블릭 API 비대화 없이 HAL 확장 가능.
단순 블로킹 ADC 드라이버 구현
최소 블로킹 드라이버는 단일 Adc_Sample 루틴 노출: 하드웨어 시작, 완료 대기, 결과를 호출자 제공 버퍼에 복사. 핵심 아이디어 아래 코드 시연:
블로킹 ADC 드라이버 구현 예시
while 루프가 Adc_ConversionComplete true 반환까지 블록, 이후 드라이버 변환 레지스터 읽어 샘플 저장.
논블로킹 ADC 드라이버로 전환
논블로킹 모델로 전환엔 작은 API 변경 필요: 호출자가 상태 플래그 받고 완료 폴링해야 함. 업데이트된 함수 형태:
논블로킹 ADC 드라이버 구현 예시
애플리케이션에서 전형적 시퀀스:
Adc_Sample();
while (!Adc_ConversionComplete()) {
// 다른 작업 수행
}
// 결과 읽기
이 접근으로 CPU가 ADC 하드웨어 변환 완료 동안 무관 코드 실행 가능.
시사점
블로킹과 논블로킹 ADC 드라이버 각각 뚜렷한 장점:
- 블로킹 드라이버: 단순, 추가 상태 관리 불필요하지만 중요 경로 스톨 가능.
- 논블로킹 드라이버: 동시 실행 허용해 전체 처리량 향상, 하지만 신중한 상태 체크와 잠재적 더 복잡한 드라이버 로직 필요.
다음 글에서 인터럽트 기반 ADC 드라이버 시연으로 센서 수집과 애플리케이션 로직 추가 디커플링 논의 이어갑니다.