Devin.KR

원시값·단위와 ADC 변환

110분 안팎

학습 목표

12비트 ADC와 부호 있는 16비트 센서 값을 정수 단위로 변환하고 경계를 검사합니다.

개념

원시값·단위와 ADC 변환

센서 값을 읽은 뒤 그럴듯한 숫자가 나온다고 단위 변환이 맞는 것은 아닙니다. 이번 레슨은 바이트·부호·스케일·버림을 각각 처리해 음수와 경계를 검증합니다. 정수 밀리도와 밀리볼트를 사용하면 UART payload 폭을 고정하고 소수점 문자열에 의존하지 않을 수 있습니다. 원시값과 변환 근거를 남겨야 스케일이나 기준전압이 바뀌어도 원인을 추적할 수 있습니다.

브라우저 입력은 명령 한 개입니다. TEMP 뒤에 상위 바이트와 하위 바이트를 십진 정수로 입력하거나 ADC 뒤에 원시값 하나를 입력합니다. TEMP 255 128은 0xFF80입니다. 바이트 범위는 0~255, ADC 범위는 0~4095입니다. 잘못된 명령·토큰 개수·숫자·범위는 ERROR 한 줄을 출력합니다. 성공하면 단위 표식과 정수 하나만 출력하여 형식을 재현 가능하게 만듭니다.

big-endian 조합은 상위 바이트 곱하기 256 더하기 하위 바이트입니다. 이 단계에서는 결과를 0~65535의 부호 없는 숫자로 둡니다. TEMP 255 128은 65408이며 아직 음수가 아닙니다. 두 바이트 순서를 바꾸면 전혀 다른 값입니다. 버스 이름 자체가 응용 데이터의 endian을 결정하지는 않습니다. 이번 레슨은 SENSOR-48이 정한 형식에 맞춥니다.

부호 복원은 조합 값이 32768 이상이면 65536을 빼는 방식입니다. 0x8000은 -32768, 0x7FFF는 32767, 0xFFFF는 -1입니다. 첫 바이트가 255라고 오류로 판단하지 않습니다. 부호 비트가 1인 정상 음수일 수 있습니다. C에서는 넓은 int32_t에서 뺄셈을 수행하면 범위 밖 unsigned를 signed 16비트로 바로 캐스팅하는 구현 의존 변환에 기대지 않고 의미를 설명할 수 있습니다.

밀리도는 부호 복원한 정수에 1000을 곱하고 256으로 나눕니다. -128은 -500밀리도이므로 -0.5°C입니다. 최대 양수는 127996밀리도, 최소 음수는 -128000밀리도입니다. 디지털 표현 범위이고 실제 센서의 정확도나 동작 온도 범위는 아닙니다. 해상도·정확도·측정 범위를 같은 말로 쓰지 않습니다. 변환 검사는 전기적 교정을 대신하지 않습니다.

나눗셈 정책은 0 방향 버림입니다. C 정수 나눗셈은 이 정책과 맞지만 Python //는 음수에서 더 작은 정수로 내립니다. -1000을 256으로 나누면 C는 -3, Python //는 -4입니다. 브라우저에서는 절댓값을 나눈 뒤 원래 부호를 붙입니다. float로 바꿔 int를 씌우기보다 정수만으로 정책을 명시하면 다른 단위로 확장할 때 오차를 설명하기 쉽습니다.

곱셈 중간 범위도 확인합니다. 원시값 절댓값은 최대 32768이며 1000을 곱한 결과는 int32_t에 들어갑니다. 좁은 16비트 변수에 중간 결과를 담으면 나눗셈 전에 값이 손상됩니다. 반대로 먼저 256으로 나누면 1도 미만 정보가 사라집니다. 타입을 넓힌 다음 곱하고 마지막에 나눕니다. 출력 타입만 넓혀도 앞선 오버플로가 복구되지는 않습니다.

ADC는 부호 없는 12비트 입력입니다. 식은 raw 곱하기 3300 나누기 4095이고 양수 정수 나눗셈으로 내립니다. 0은 0mV, 4095는 3300mV, 2048은 1650mV입니다. 끝점 보정이라는 가상 모델의 선택을 적습니다. 실제 변환기의 이상적 LSB 설명에는 4096이 등장할 수 있으므로 부품 데이터시트의 전달 함수·보정법을 다시 확인합니다. 이 실습에서는 주어진 계약대로 4095를 사용합니다.

4096을 4095로 잘라 성공 처리하면 입력 오류를 숨깁니다. 입력 생성기나 비트 폭이 어긋났다는 단서를 잃습니다. 범위 밖이면 ERROR를 출력하며 변환 결과를 만들지 않습니다. 음수 ADC도 거부합니다. 온도에는 음수가 가능하지만 ADC raw에는 허용되지 않는 차이를 테스트로 드러냅니다. 숫자가 음수라는 이유로 모든 입력을 한 정책으로 처리하지 않습니다.

입력 검사는 파싱과 의미 검사를 나눕니다. int 변환 실패는 숫자 형식 문제이고 유효 정수 256이 바이트 자리에 들어오면 범위 문제입니다. 둘 다 ERROR지만 리뷰에서는 원인을 구별합니다. 빈 입력과 TEMP 0처럼 토큰이 부족한 입력도 처리합니다. IndexError가 발생하면 길이 검사보다 배열 접근을 앞에 둔 부분을 찾습니다. 정상 입력만 올 것이라는 가정으로 경계를 생략하지 않습니다.

성공 출력은 MC -500 또는 MV 3300입니다. MC는 섭씨 밀리도, MV는 밀리볼트라는 표식입니다. -0.5를 출력하면 읽기 쉬워도 채점 계약의 단위가 다릅니다. 정수 0과 문자열 0.0도 형식이 다릅니다. 화면 표시가 필요하면 내부 정수와 외부 표시를 별도 함수로 나눕니다. payload에는 정수를 넣고 화면에서만 자릿수를 정합니다.

경계는 단계별로 고릅니다. TEMP 127 255와 TEMP 128 0은 부호 경계 양쪽, TEMP 255 255는 음수 나눗셈, TEMP 0 1은 작은 양수 분해능입니다. ADC 0과 4095는 끝점, 4096은 거부 경계입니다. 정상 숫자만 반복하면 부호와 범위 오류가 숨어 있습니다. 각 테스트 목적을 한 문장씩 적어 왜 입력이 필요한지 설명합니다.

따라하기는 조합 값, signed_raw, 최종 단위를 순서대로 출력해 손계산과 대조합니다. 어느 단계부터 달라졌는지 찾습니다. 65408을 그대로 온도로 썼다면 부호 복원이 빠졌고 -4밀리도가 나오면 내림 정책을 의심합니다. ADC 끝점이 3299라면 분모나 연산 순서를 점검합니다. 오류 형태를 계산 단계와 연결하면 최종 print만 바꾸는 잘못된 수정을 피합니다.

starter는 유효 TEMP와 ADC 요청에 0만 출력합니다. 0 입력에서 통과해도 다른 값의 변환이 완성된 것은 아닙니다. 모범 답안 전에 원시값 -1과 ADC 최대값을 손으로 계산합니다. ERROR 처리에는 ValueError와 조건 판정을 사용합니다. 광범위한 예외로 구현 오류까지 ERROR로 바꾸면 실패 원인을 숨길 수 있습니다. 입력 거부와 프로그램 버그를 분리합니다.

제출에는 스케일과 버림 정책을 결과 옆에 남깁니다. UART에서 -500을 받은 사람이 이를 -500°C로 읽지 않도록 필드 이름 temp_mc와 adc_mv로 단위를 드러냅니다. 실제 보드에서는 기준전압·보정·센서 정확도를 측정합니다. 이번 성공 기준은 제공한 원시값을 정수 계약으로 바꾸는 것입니다. 물리량 정확도를 실측한 것으로 보고하지 않습니다.

독립 계산에서는 TEMP 255 255를 네 단계로 적습니다. 조합 65535, 부호 -1, 스케일 곱셈 -1000, 최종 버림 -3입니다. 단계별 기록으로 정책이 바뀐 위치를 확인합니다. ADC 1은 0mV로 내림되므로 작은 raw 차이가 정수 출력에 안 드러날 수 있습니다. 이를 입력 손실이나 센서 고장으로 성급히 판단하지 않습니다. 변환 후 정수 단위의 분해능이 원시값보다 거칠 수 있다는 점도 검토합니다.

따라하기

조합·부호·단위 대조

FF80을 세 단계로 계산합니다.

word = 255 * 256 + 128
signed = word - 65536 if word >= 32768 else word
print(word, signed, -(abs(signed * 1000) // 256))

실행 결과

65408 -128 -500

음수 버림 비교

FFFF의 버림 차이를 확인하고 변환 함수를 작성합니다.

print("floor", -1000 // 256)
print("toward-zero", -(1000 // 256))

실행 결과

floor -4
toward-zero -3

ADC 끝점·범위

출력 단위는 정수 mV입니다. 최대값과 범위 밖을 구분합니다.

for raw in (0, 2048, 4095, 4096):
    print(raw, raw * 3300 // 4095 if 0 <= raw <= 4095 else "ERROR")

실행 결과

0 0
2048 1650
4095 3300
4096 ERROR

확인 문제

실습

명령 한 개 TEMP hi lo 또는 ADC raw를 읽습니다. 바이트 0~255, ADC 0~4095를 허용합니다. TEMP는 big-endian int16, 밀리도는 0 방향 버림입니다. ADC는 raw*3300/4095 내림입니다. MC 정수 또는 MV 정수를 출력합니다. 형식·범위 오류와 빈 입력은 ERROR입니다.

모범 답안
import sys
v = sys.stdin.read().split()
try:
    if len(v) == 3 and v[0] == "TEMP":
        hi, lo = map(int, v[1:])
        if not (0 <= hi <= 255 and 0 <= lo <= 255):
            raise ValueError()
        raw = hi * 256 + lo
        signed = raw - 65536 if raw >= 32768 else raw
        mc = abs(signed * 1000) // 256
        if signed < 0:
            mc = -mc
        print("MC", mc)
    elif len(v) == 2 and v[0] == "ADC":
        raw = int(v[1])
        if not 0 <= raw <= 4095:
            raise ValueError()
        print("MV", raw * 3300 // 4095)
    else:
        raise ValueError()
except ValueError:
    print("ERROR")

더 읽기

면접 질문

  • 센서가 응답하지 않을 때 점검할 순서를 설명해 주시면 됩니다.