헤더와 HAL 구현 교체
130분 안팎
학습 목표
hal.h 뒤에 RAM 레지스터 구현을 붙이고 기록기에서 직접 주소 접근을 제거합니다.
개념
기록 정책을 그대로 두고 입출력을 교체합니다
앞 모듈의 검증된 Buffer를 새 구조체로 다시 만들지 않습니다. 길이·복사·용량 16·실패 보존 계약은 그대로 유지합니다. 이번 미션 starter는 m02 solution의 C 소스·테스트·관찰 문서를 포함하고 새 HAL 파일을 추가한 상태입니다. 기존 recorder와 record-buffer는 이전 입력 계약을 검증하는 실행 파일로 남고 새 recorder_capture가 HAL을 사용하는 경로가 됩니다. 기존 회귀가 통과한 뒤 새 HAL 검사가 실패해야 확장해야 할 위치를 알 수 있습니다.
장치 의존 부분을 감춘다는 말은 레지스터 변수를 다른 파일에 놓기만 하는 것이 아닙니다. 애플리케이션이 그 변수의 주소나 bit 배치를 알아야 하면 구현 교체 때 함께 바뀝니다. hal.h는 입력 논리 레벨, 출력 요청, 현재 밀리초라는 계약을 노출하고 가짜 RAM 객체의 이름은 노출하지 않습니다. recorder.c가 hal_sim.h를 포함하지 않도록 확인합니다. 시뮬레이터를 제어할 권한과 애플리케이션이 장치를 사용하는 권한을 분리합니다.
세 인터페이스의 상태와 데이터를 읽습니다
hal_gpio_read(unsigned pin, int *out)은 이번 프로젝트에서 입력 핀 0만 허용합니다. 성공하면 1을 반환하며 *out은 0 또는 1입니다. 핀이 잘못되거나 out이 NULL이면 0을 반환하고 기존 out을 유지합니다. 레지스터가 0x80이어도 bit0은 0이므로 버튼 레벨은 0입니다. input_reg != 0을 쓰면 다른 비트를 버튼으로 오인합니다. bit0 마스크로 선택한 뒤 0과 비교해 논리 값으로 정규화합니다.
hal_gpio_write(unsigned pin, int level)은 출력 핀 1과 레벨 0·1만 허용합니다. 성공 상태는 1이고 실패 상태는 0입니다. level 2를 참으로 취급해서 켜지 않습니다. 입력 핀 0에 쓰려는 요청도 거부합니다. 핀을 검사한 다음 마스크를 만들므로 pin 32 같은 잘못된 값에서 폭 이상의 시프트가 발생하지 않습니다. 실패는 output_reg를 보존합니다. 함수 인자 검사가 반환 이후의 안전한 비트 계산을 보장하는 순서입니다.
hal_now_ms는 uint32_t 밀리초 값을 반환합니다. 별도 성공 상태가 없으며 0도 유효한 시각입니다. 현재 모형은 시간이 없는 오류를 정의하지 않았으므로 0을 실패로 판정하지 않습니다. UINT32_MAX도 반환 가능한 값으로 검사합니다. 차이 계산·순환 감지·시간 초과 정책은 이번 API의 구현 목표가 아닙니다. clock_gettime이나 sleep을 추가해 결정적 시험을 실제 시간 의존 시험으로 바꾸지 않습니다.
출력 초기값 0xA0에서 bit1을 켜면 0xA2가 되고 끄면 0xA0로 돌아옵니다. bit0 설정을 보여 준 이전 레슨과 LED bit1은 선택 위치가 다릅니다. 출력 쓰기에 전체 mask를 대입하면 0xA0가 사라집니다. level이 1이면 output_reg | mask, 0이면 output_reg & ~mask를 저장합니다. 같은 요청을 두 번 보내도 같은 상태여야 합니다. 지금의 출력은 일반 RW 모형이며 W1C 확인 함수로 바꾸지 않습니다.
환경 제어 API는 테스트 쪽에 둡니다
hal_sim.h는 reset, 입력 주입, 시각 주입, 출력 관찰을 제공합니다. reset은 각 테스트의 출발 상태를 명시적으로 만듭니다. 이전 시험의 값이 다음 시험에 남아 순서에 따라 성공하는 일을 줄입니다. 이 함수들은 PC fixture 제어이며 보드용 hal.h의 일부가 아닙니다. demo가 250ms를 주입할 때 recorder_capture는 그 사실을 모르고 hal_now_ms로 시각을 읽습니다. 기록기 코드를 고치지 않고 환경만 바꾸는 지점이 HAL 경계입니다.
RAM 변수 input_reg, output_reg, now_ms는 hal_sim.c 안에서 static으로 정의합니다. 헤더에 저장 객체를 정의하지 않으므로 여러 소스가 포함해도 중복 정의를 만들지 않습니다. static은 여기에서 외부 파일의 이름 접근을 제한합니다. 스냅샷이거나 동기화된 저장이라는 의미는 아닙니다. 단일 시뮬레이터 상태 하나를 공유하는 설계이며 여러 보드를 동시에 모델링하는 컨텍스트 인자는 아직 추가하지 않습니다.
헤더 가드는 같은 헤더가 두 번 포함돼도 선언 구성이 반복되는 문제를 줄입니다. 함수 선언의 매개변수와 반환형은 구현과 일치시킵니다. prototype과 구현이 다르면 conflicting types 진단에서 해당 함수와 헤더를 함께 확인합니다. 헤더에 함수 몸체나 전역 객체 정의를 옮겨 중복 심벌을 만들지 않습니다. 함수 선언을 읽는 컴파일과 함수 정의를 연결하는 링크는 별개의 단계입니다.
누적 기록 경로의 실패 정책
recorder_capture는 Buffer 주소와 raw 범위 및 남은 용량을 먼저 확인합니다. 유효하면 버튼을 읽고 LED를 그 레벨로 요청한 다음 현재 시각과 raw를 Sample로 복사합니다. 상태 1은 저장, 0은 잘못된 raw 또는 가득 참, -1은 HAL 실패입니다. 버튼 0이어도 저장합니다. 버튼으로 기록 시작·정지를 전환하는 정책은 다음 모듈에서 도입하므로 지금 미션에 토글이나 디바운스를 섞지 않습니다.
가득 찼다면 입력 읽기·LED 쓰기 전에 돌아오므로 마지막 출력 상태도 그대로입니다. raw가 유효하지 않아도 길이를 늘리지 않습니다. HAL read나 write가 실패하면 새 Sample을 저장하지 않습니다. 모든 장치 부작용을 되돌리는 트랜잭션을 제공한다는 뜻은 아닙니다. 실제 HAL이 요청 중 일부 하드웨어 동작을 수행했다면 추가 복구 정책이 필요합니다. 이번 실패 시험의 증거는 새로운 버퍼 기록이 없다는 범위입니다.
원래 record-buffer는 각 입력 토큰마다 tick을 100씩 증가시킵니다. 새 할 경로의 demo는 250과 300을 외부에서 주입합니다. 이전 CLI 시간을 억지로 바꾸지 않고 두 경로를 비교해 확장 전후의 계약을 보존합니다. hal-demo에서는 42와 0이 각각 해당 시각으로 저장되고 출력은 A2와 A0입니다. 출력 시각을 0부터 100 간격으로 예상하면 fixture 계약을 놓친 것입니다. 코드를 읽을 때 시각을 만드는 쪽과 조회하는 쪽을 구분합니다.
다른 구현을 링크해 경계를 검증합니다
Makefile은 test-hal에 hal_sim.c를 연결하고 test-alternate에는 hal_fixture.c를 연결합니다. 두 구현은 같은 hal.h 선언을 따릅니다. 대체 구현은 시각 777을 반환하며 recorder.c를 바꾸지 않아도 샘플에 777이 저장되어야 합니다. 한 실행 파일에 두 구현을 동시에 연결하면 중복 정의가 나므로 타깃마다 하나를 고릅니다. 헤더만 감추고 기록기 내부가 시뮬레이터 이름에 의존했다면 대체 링크 검사를 통과하기 어렵습니다.
Undefined symbols 또는 undefined reference가 hal_now_ms를 가리키면 헤더 선언의 존재보다 링크 대상의 정의를 확인합니다. hal_sim.c나 대체 구현을 목록에서 빠뜨렸는지 봅니다. duplicate symbol은 두 HAL 정의를 같은 타깃에 넣었는지 확인합니다. 컴파일 옵션을 지워 진단을 감추지 않습니다. make test는 이전 범위 검사·CLI·버퍼·UBSan 회귀와 새 HAL·대체 구현·읽기 및 쓰기 실패·레지스터 계산을 순서대로 검사합니다.
HAL 검사에는 NULL, 출력 핀 읽기 거부, 입력 핀 쓰기 거부, 잘못된 레벨, 최대 시각, 용량 초과를 넣었습니다. 35 checks는 해당 함수 검사 수이며 전체 회귀 숫자와 합쳐 하나의 장치 인증으로 설명하지 않습니다. 이전 DEBUG.md의 LLDB 관찰 미완료 기록도 보존합니다. 그 사실은 이번 정상 논리 검사의 통과와 별개입니다. 이번 미션 명령은 make test이고 기존 check.sh는 앞 모듈의 외부 메모리·디버거 검사로 남습니다.
다음 작성자가 이어받을 문서
README에 핀 번호와 방향, active-high 의미, 레지스터 폭과 초기값, 시각 단위와 주입 방식을 적습니다. 실제 주소·전압·풀업·핀 mux는 아직 정하지 않은 항목으로 표시합니다. 구성도와 test-hal, test-alternate의 연결 대상을 비교해 구현 교체 근거를 남깁니다. 다음 모듈은 이 solution에서 버튼 튐과 기록 상태를 추가합니다. Buffer 필드명과 hal.h 계약을 보존해 후속 작성자가 새 동작을 시험하는 데 집중할 수 있게 합니다.
따라하기
이전 회귀와 새 실패 분리
starter 폴더에서 실행합니다. PASS memory-records 이후 FAIL 이름이 나오는 HAL 검사부터 수정합니다.
make test세 HAL 동작 수정
hal_sim.c의 입력 bit0 선택, 출력 bit1 보존, 주입 시각 반환을 구현한 뒤 관찰 실행 파일을 만듭니다.
make -s hal-demo주입 시각과 출력 보존 확인
solution에서 실행한 결과입니다. raw 0도 정상 저장이며 A0 비트는 두 번 모두 남습니다.
./hal-demo실행 결과
capture=1 time=250 raw=42 output=A2 capture=1 time=300 raw=0 output=A0
HAL 경계만 재검증
대체 HAL과 실패 HAL은 같은 기록기 소스로 별도 실행 파일에 연결됩니다.
make -s hal-test실행 결과
hal: 35 checks, 0 failures alternate HAL: 0 failures HAL fault: 0 failures HAL fault: 0 failures registers: 196608 comparisons, 0 failures
누적 미션 마무리
make test로 기존 회귀까지 함께 확인하고 README의 핀·폭·시간·PC 한계를 자신의 구성도와 대조합니다.
make test확인 문제
실습
m02 미션 solution을 포함한 누적 실습입니다. hal_sim.c의 세 동작을 완성하고 make test로 기존 회귀와 새 HAL 검사를 모두 통과합니다. recorder.c는 hal.h만 사용하도록 유지합니다. README에 핀 0 입력·핀 1 출력, 8비트 RW 보존, ms fixture 계약과 실제 보드에서 추가 확인할 항목을 정리합니다.
실행 명령
make test
기대 결과
기존 PASS pc-workbench·PASS memory-records와 hal: 35 checks, 0 failures, 대체·실패 HAL 0 failures, 레지스터 비교 0 failures
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 특정 레지스터 비트만 바꾸는 코드를 설명해 주시면 됩니다.