기술 가이드

MCP3008 SPI ADC 읽기: 채널 명령·10비트 마스크·전압 배율 검증

MCP3008의 단일 종단 채널 명령과 10비트 응답을 해독하고, Python으로 294,912개 합성 프레임과 전압 배율을 검증합니다.

목차

MCP3008을 SPI에 연결했는데 채널을 바꿔도 값이 같거나 10비트 범위를 벗어난다면, 전압 계산 전에 송수신 바이트부터 확인해야 합니다. 이 글은 단일 종단 CH0~CH7의 명령 생성과 수신값 해독을 다룹니다. PC에서 실행하는 예제로 비트 배치를 검증하며, 실제 보드의 아날로그 정확도 시험은 별도로 진행합니다.

1. 부품과 통신 형식 확인

MCP3008 공식 제품 안내에 따르면 MCP3008은 SPI 인터페이스의 8채널 10비트 ADC입니다. MCP3004나 MCP3208을 같은 설정으로 취급하지 마세요. 부품명과 보드 회로를 먼저 대조합니다.

Microchip 데이터시트 DS20001295E의 §5 Table 5-2와 §6.1 Figure 6-1이 아래 형식의 근거입니다. SPI mode 0, MSB first로 3바이트를 교환하는 구성을 사용합니다.

구간 MOSI 송신 MISO 수신에서 사용하는 부분
첫 바이트 01: 선행 0 일곱 개와 start 비트 데이터로 사용하지 않음
둘째 바이트 (0x08 \| channel) << 4 하위 두 비트 B9·B8
셋째 바이트 00: 클록 생성 B7~B0 전체

CH0 명령은 01 80 00, CH7은 01 F0 00입니다. 단일 종단 선택 비트와 채널 비트를 함께 구성합니다. 수신 둘째 바이트에는 미정의 비트와 null 비트가 있으므로 & 0x03으로 데이터 두 비트만 추출합니다. 채널 번호는 응답에서 되돌아오지 않으므로 요청 채널과 결과를 소프트웨어에서 함께 보관합니다.

2. CS가 유지되는 한 번의 교환

CS는 세 바이트 동안 LOW로 유지하고 교환이 끝나면 HIGH로 돌립니다. MCU에서는 3바이트 full-duplex 전송 API를 사용하되, 하드웨어 NSS가 바이트 사이에 펄스를 만들지 확인합니다. TX 버퍼와 RX 버퍼를 구분하고 전송 완료 뒤에 해독합니다.

임베디드 Linux에서는 커널 spidev 문서의 SPI_IOC_MESSAGE로 TX·RX 버퍼를 가진 길이 3의 전송을 구성할 수 있습니다. 별도의 write()와 read()는 그 사이 CS가 해제되므로 이 교환을 그대로 대신하지 못합니다. 버스 번호와 chip-select는 실제 Device Tree 설정을 확인합니다. 이 글의 PC 예제는 spidev 장치를 열지 않습니다.

3. 보드 없이 실행하는 완전한 예제

아래 전체 코드를 mcp3008_check.py로 저장하고 Python 3에서 python -B mcp3008_check.py로 실행합니다. 외부 패키지는 필요 없습니다. 채널 명령의 고정 기대값과 0·512·1023의 수신 벡터를 먼저 확인하고, 전체 10비트 값에 대해 첫 수신 바이트 및 미정의 비트가 계산에 섞이지 않는지 검사합니다.

from fractions import Fraction


def tx(channel):
    if type(channel) is not int or not 0 <= channel <= 7:
        raise ValueError("channel must be an integer in 0..7")
    return bytes((0x01, (0x08 | channel) << 4, 0x00))


def decode(rx):
    if (not isinstance(rx, (bytes, bytearray, list, tuple))
            or len(rx) != 3
            or any(type(x) is not int or not 0 <= x <= 255 for x in rx)):
        raise ValueError("three unsigned bytes required")
    return ((rx[1] & 0x03) << 8) | rx[2]


def millivolts(raw, vref_mv):
    if type(raw) is not int or not 0 <= raw <= 1023:
        raise ValueError("raw must be an integer in 0..1023")
    if type(vref_mv) is not int or vref_mv <= 0:
        raise ValueError("positive integer VREF in mV required")
    return Fraction(raw * vref_mv, 1024)


def main():
    expected = ["018000", "019000", "01a000", "01b000",
                "01c000", "01d000", "01e000", "01f000"]
    assert [tx(ch).hex() for ch in range(8)] == expected
    assert decode(bytes.fromhex("000000")) == 0
    assert decode(bytes.fromhex("000200")) == 512
    assert decode(bytes.fromhex("0003ff")) == 1023
    frames = 0
    for raw in range(1024):
        for first in range(256):
            assert decode(bytes((first, raw >> 8, raw & 255))) == raw
            frames += 1
        # Five unknown bits vary; the null bit (bit 2) stays zero.
        for unknown in range(32):
            second = (unknown << 3) | (raw >> 8)
            assert decode(bytes((0, second, raw & 255))) == raw
            frames += 1
        assert millivolts(raw, 3300) == Fraction(raw * 825, 256)
    assert millivolts(512, 3300) == 1650
    assert millivolts(1023, 3300) == Fraction(843975, 256)
    rejected = 0
    calls = [(tx, (x,)) for x in (-1, 8, True, 1.0, "0")]
    calls += [(decode, (x,)) for x in
              (b"", b"12", b"1234", [0, -1, 0],
               [0, 256, 0], [False, 0, 0], "abc")]
    calls += [(millivolts, args) for args in
              ((-1, 3300), (1024, 3300), (True, 3300),
               (0, 0), (0, -1), (0, 3.3))]
    for fn, args in calls:
        try:
            fn(*args)
        except ValueError:
            rejected += 1
        else:
            raise AssertionError("invalid input was accepted")
    print(f"PASS: 8 channels, {frames} frames, 1024 scales, "
          f"{rejected} rejected inputs")
    print("CH0 TX:", tx(0).hex(" "))
    print("512 ->", float(millivolts(512, 3300)), "mV")
    print("1023 ->", float(millivolts(1023, 3300)), "mV")


if __name__ == "__main__":
    main()

실행 결과:

PASS: 8 channels, 294912 frames, 1024 scales, 18 rejected inputs
CH0 TX: 01 80 00
512 -> 1650.0 mV
1023 -> 3296.77734375 mV

294,912개는 소프트웨어에 넣은 합성 프레임 수입니다. 실제 ADC와 통신한 횟수가 아닙니다. 미정의 상위 5비트만 변화시키는 검사에서는 null 비트를 0으로 유지합니다. 마스크만으로 단선이나 비트 손상을 검출할 수는 없습니다.

4. 전압 배율과 양자화

데이터시트 §4의 LSB 크기 VREF / 1024를 기준으로 코드 환산값을 구합니다. 기준 전압 3300mV에서 코드 512는 1650mV, 1023은 3296.77734375mV입니다. 예제는 정수와 Fraction으로 중간 반올림을 피합니다. 이 환산값은 양자화된 코드의 스케일 값이며 실제 입력 전압을 오차 없이 복원한 값이 아닙니다.

5. 실제 보드 점검 순서

  1. MCU·ADC·센서의 공통 접지와 VREF 실측값을 확인합니다. 전원·로직 전압과 아날로그 입력 범위는 데이터시트 전기 특성에 맞춥니다.
  2. CH0를 GND에 연결하고 다음에는 허용 범위의 알려진 전압에 연결해 원시 코드를 비교합니다. 부동 입력을 기준 신호로 쓰지 않습니다.
  3. 로직 애널라이저에서 01 80 00과 세 바이트 동안의 CS LOW를 확인합니다. 채널을 바꾸면 둘째 송신 바이트도 바뀌어야 합니다.
  4. 모든 값이 0 또는 1023이어도 통신 성공으로 판정하지 않습니다. MISO 단선·접지·클록·CS를 먼저 확인합니다.
  5. 타이밍이 맞아도 값이 흔들리면 VREF, 입력 임피던스, 접지와 배선을 확인합니다. 최종 샘플링 속도는 SPI 클록만으로 결정하지 않습니다.

비트 처리 검증을 마친 뒤 동일한 명령·마스크를 펌웨어로 옮기고, 측정 조건과 원시값을 함께 기록하면 전송 문제와 아날로그 오차를 나누어 조사하기 쉽습니다.

공식 참고 문서

문서 확인 및 PC 예제 실행: 2026-10-20. 하드웨어 측정은 수행하지 않았습니다.

관련 서비스

관련 글

댓글

첫 댓글을 남겨 보세요.

관리자 확인 후 게시됩니다.

처리 중입니다...

잠시만 기다려주세요.