UART 프레임과 SPI 기록 저장
140분 안팎
학습 목표
UART 길이·검사합 프레임과 SPI 모드·CS 순서를 구현해 링 버퍼 데이터를 전송·저장합니다.
개념
UART 프레임과 SPI 기록 저장
변환이 끝났다면 다른 장치가 같은 기록을 해석하도록 바이트 규격을 정합니다. UART 프레임을 검증하고 SPI 가상 저장기에 같은 바이트를 덧붙입니다. C 구조체를 memcpy해 보내면 패딩·CPU endian·타입 폭에 의존할 수 있습니다. 필드마다 폭과 순서를 정해 직렬화합니다. 수신기는 전체 길이와 검사합 뒤에만 출력 기록을 확정합니다. 그럴듯한 온도가 나오는 것만으로 규격 일치를 판단하지 않습니다.
프레임은 A5, 0A, timestamp_ms 4바이트, temp_mc 4바이트, adc_mv 2바이트, XOR 1바이트로 총 13바이트입니다. 0A는 payload 열 바이트만 뜻합니다. timestamp는 uint32, 온도는 2의 보수 int32, 전압은 uint16이며 모두 big-endian입니다. 8N1은 UART 비트 구성으로 payload 길이와 다릅니다. 송수신 baud가 일치한다는 전제를 별도로 적습니다.
검사합은 길이부터 payload 마지막까지 XOR합니다. 시작 A5는 제외합니다. 범위가 다르면 정상 프레임도 거부하므로 인덱스 1~11을 명시합니다. XOR는 일부 오류를 놓치고 인증 기능도 없습니다. 두 바이트의 같은 비트가 함께 바뀌면 결과가 같을 수 있습니다. 단일 검사합 불일치를 구별하는 기능을 구현하며 강한 무결성 보장으로 설명하지 않습니다.
uart_encode는 capacity가 13 미만이면 0이고 배열을 쓰지 않습니다. 공간이 있으면 시프트로 바이트를 채웁니다. 음수를 uint32_t로 바꾸면 하위 32비트 표현을 얻습니다. 수신 시에는 int64_t에서 2의 32승을 빼 부호를 복원합니다. 범위 밖 값을 int32_t로 바로 바꾸는 구현 의존 동작에 기대지 않습니다. 형식 계약과 언어 변환 규칙을 나누어 읽습니다.
uart_decode는 null 인자, 총 길이, 시작·payload 길이, 검사합 순으로 확인합니다. 길이 오류는 BUS_LENGTH, XOR 불일치는 BUS_CHECKSUM입니다. 잘린 배열에서 checksum부터 읽으면 경계 밖 접근입니다. 검사가 모두 성공한 뒤 계산값을 BusRecord에 확정합니다. 실패하면 out를 보존합니다. 이 함수는 완전한 배열을 받으며 스트림 분할 수신·재동기화까지 구현하지 않습니다.
SpiStore는 bytes 256개, length, mode, selected와 선택·해제 계수입니다. mode0에서만 선택이 성공하고 선택 중 다시 선택하면 BUS_CS입니다. 선택 없이 쓰기도 BUS_CS입니다. 전체 공간을 검사하고 부족하면 BUS_FULL입니다. 일부만 쓴 뒤 full을 반환하지 않습니다. 실제 플래시의 명령·주소·busy는 따로 필요하므로 이 RAM 모델을 플래시 드라이버로 설명하지 않습니다.
선택은 CS가 낮아지는 사건, 해제는 높아지는 사건입니다. select→write→release를 유지하고 실패한 write 뒤에도 release합니다. 선택 상태를 남기면 다음 접근과 겹칠 수 있습니다. 테스트는 full 실패 뒤 selected가 0인지 확인합니다. 모델은 호출 순서만 검사하며 실제 setup/hold·클럭 속도·파형 품질은 보드에서 별도 확인합니다. 계수만 같아도 순서가 맞는지 조건식을 읽습니다.
앞 모듈 Buffer는 길이 16 append 배열이고 링 버퍼가 아닙니다. SPI도 append이며 full을 거부합니다. 계획의 링 버퍼 데이터 전송은 순환 재사용까지 구현되었다는 의미로 해석하지 않습니다. 지금은 이벤트 큐만 head를 순환합니다. 기록을 순환으로 바꾸면 버릴 데이터 정책과 회귀가 함께 바뀌므로 용량 계약을 유지합니다. 이후 큐 모듈에서 재사용을 확장합니다.
bus_consume은 이벤트 하나를 꺼내 recording을 확인합니다. 정지면 통신하지 않습니다. 기록 중이면 I2C·ADC·용량 확인 뒤 지역 프레임을 만듭니다. SPI 성공 후 기존 Buffer에 ADC raw를 추가하고 UART 배열을 공개합니다. raw 필드를 밀리볼트로 몰래 바꾸지 않습니다. 프레임의 adc_mv는 변환값이므로 두 경로의 이름과 단위를 구별합니다. 한 이벤트에 기존 저장 경로를 중복 호출하지 않습니다.
실패 이벤트는 소비하며 재시도하지 않습니다. 오래된 센서값을 새 프레임으로 만들지 않아 Buffer·SPI 길이·uart_n을 보존합니다. 다음 정상 이벤트에서 다시 읽습니다. 다음 상태 머신의 출발점입니다. 무한 재시도를 소비 함수에 넣으면 버튼과 타이머가 막힐 수 있습니다. 재시도 정책·상한은 별도 상태에서 정합니다. 오류 후 다음 샘플이 무엇을 의미하는지 명시합니다.
timestamp는 예정이 아니라 hal_now_ms의 소비 시각입니다. 예정 1200을 1225에 처리하면 payload는 1225입니다. 지연된 이벤트로 과거 온도를 복원하지 않습니다. 여러 이벤트를 같은 now에서 처리하면 시각이 같을 수 있습니다. 지연 분석이 필요하면 due를 별도 로그로 남깁니다. 이 payload만으로 마감 준수를 판정하지 않습니다. 예정선과 실제 측정의 의미를 분리합니다.
UART 공개는 RAM 복사이며 실제 직렬 전송 완료가 아닙니다. SPI도 RAM 복사라 두 외부 장치에 원자적으로 기록되었다는 보장이 없습니다. 실물에는 SPI 성공 후 UART 실패 같은 부분 완료가 있어 식별자·재전송을 검토합니다. 이번 근거는 실패 전 용량 검사, 쓰기 실패 시 CS 해제, 성공 후 배열 일치입니다. 모델 보장을 물리 장치로 확대하지 않습니다.
starter의 uart_decode는 XOR를 계산하지만 비교가 없습니다. BUS_CHECKSUM 식이 실패하면 범위·비교 위치를 확인합니다. encode만 고치면 수신기가 손상을 받아들이는 문제는 남습니다. 검사 뒤 out를 확정합니다. 왕복만 시험하면 양쪽이 같은 오류를 공유할 수 있어 알려진 바이트와 기대 checksum 0x74를 별도로 대조합니다. 테스트 입력을 구현에서 복사해 만들지 않습니다.
미션은 m05 solution의 소스·회귀를 포함해 센서 경로를 추가합니다. 반복 순서는 timed_button, timer_poll, bus_consume입니다. 같은 이벤트를 timed_consume이나 controller_step으로 다시 저장하지 않습니다. 테스트는 100~1000ms 열 번 수집, 분리 후 보존, 1225ms 시각, ADC 범위, UART 공간 부족, SPI full을 확인합니다. 시작·정지는 기존 버튼 회귀로 보존합니다.
제출 문서에는 연결 표·바이트 규격·make test 로그·손상 거부·full 뒤 CS 해제 근거를 적습니다. 사람이 읽는 온도뿐 아니라 원본 바이트도 남깁니다. 다음 작성자가 BUS-SPEC.md와 tests를 읽어 복구를 확장할 수 있어야 합니다. 서재에서는 UART·SPI 신호와 장치별 프로토콜을 보충하고 여기서는 기록기 데이터 계약과 실패 흐름을 완성합니다. 검증하지 않은 배선은 완료로 표시하지 않습니다.
독립 검토값은 timestamp 100, temp_mc -500, adc_mv 3300입니다. timestamp 바이트는 00 00 00 64, 온도는 FF FF FE 0C, 전압은 0C E4입니다. 길이 0A를 포함한 XOR는 74입니다. A5를 포함하면 다른 값이므로 검사 범위를 다시 봅니다. 예를 송신기와 수신기에 각각 적용하면 왕복만으로 안 보이던 폭·부호 오류를 찾습니다. 용량 256에는 13바이트 프레임 19개까지 들어가지만 기존 Buffer 용량 16이 먼저 제한할 수 있습니다.
따라하기
독립 프레임 계산
명세로 계산한 기대값을 C 구현과 비교합니다.
from functools import reduce
from operator import xor
p = bytes([10]) + (100).to_bytes(4, "big") + (-500).to_bytes(4, "big", signed=True) + (3300).to_bytes(2, "big")
f = bytes([0xA5]) + p + bytes([reduce(xor, p)])
print(f.hex(" ").upper())
print("bytes", len(f))실행 결과
A5 0A 00 00 00 64 FF FF FE 0C 0C E4 74 bytes 13
수신 검사와 CS
uart_decode의 XOR 비교 TODO를 고칩니다. full 쓰기 뒤 selected=0과 길이 보존을 확인합니다.
make -s buses-test실행 결과
sensor-buses: 79 cases, 0 failures
버튼부터 통신까지 이어 실행
bus_demo.c는 timed_button → timer_poll → bus_consume 순서로 실행합니다. 시작 입력을 20ms에 확정하여 열 번 기록하고 정지 확정 후 저장이 늘지 않는지 검사합니다. solution에서 실행한 결과와 비교합니다.
make -s bus-demo-test실행 결과
bus-demo: samples=10 spi=130 temp_mc=-500 adc_mv=3300 stopped=1
누적 미션 완성
embedded-sensor-buses의 두 TODO를 완성합니다. BUS-SPEC.md와 실제 로그·실물 후속 항목을 제출합니다. 통신은 ISR 밖에서 수행합니다.
make test확인 문제
실습
buses.c의 UART XOR 불일치 TODO를 구현합니다. BUS-SPEC.md를 읽고 tests 및 이전 events.c를 보존합니다. make test로 기존 회귀도 통과시킵니다. 오류별 입력과 출력 보존 근거를 제출합니다.
실행 명령
make test
기대 결과
기존 회귀 및 sensor-buses: 79 cases, 0 failures; 종료 코드 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 센서가 응답하지 않을 때 점검할 순서를 설명해 주시면 됩니다.