기술 가이드

ADS1115 ADC 전압 변환: 16비트 부호·PGA 배율·단발 완료 확인

ADS1115의 MSB 우선 16비트 코드와 PGA 배율을 해독하고, 524,288개 조합의 Python 예제로 부호·끝점·단발 변환 상태를 검증합니다.

목차

센서 전압을 읽을 때 ADS1115의 두 바이트를 단순히 0~65535로 해석하면 음수가 큰 양수로 바뀝니다. 부호를 고쳐도 PGA 설정을 잘못 연결하면 전압이 두 배 또는 절반으로 보입니다. 이 글에서는 MSB 우선 조립 → 16비트 부호 복원 → 해당 변환의 PGA 배율 적용 순서를 검증합니다.

대상은 ADS1115를 MCU·임베디드 Linux에 연결하는 펌웨어 개발자입니다. Python 예제는 표준 라이브러리만 사용하는 호스트 계산 모델이며, 실제 I²C 통신이나 보드 측정을 수행하지 않습니다.

1. 공식 문서에서 확인할 세 가지

기준 문서는 Texas Instruments의 ADS111x 데이터시트 SBAS444E입니다. 7.5.2절은 레지스터 바이트 전송 순서, 7.5.4절은 출력 형식, 8.1.3절은 Config 레지스터를 설명합니다.

  • Conversion 레지스터 주소는 0x00이며 결과는 16비트 2의 보수입니다. 먼저 들어오는 바이트가 상위 바이트입니다.
  • Config 주소는 0x01이며 PGA[2:0]는 비트 11:9에 있습니다. 같은 원시 코드도 이 설정에 따라 전압이 달라집니다.
  • MODE=1인 단발 변환에서 읽은 OS=0은 변환 중, OS=1은 현재 변환 중이 아님을 뜻합니다. OS=1만 보고 새 측정이 시작됐다고 판단하지 않습니다.

아래 내용은 ADS1115 기준입니다. ADS1015의 12비트 출력 형식에 그대로 적용하면 안 됩니다.

2. 32767이 아니라 32768로 나누기

원시 코드 s와 양의 풀스케일 크기 FS를 사용하면 이상적인 전압은 다음과 같습니다.

word = (MSB << 8) | LSB s = word - 65536 (word의 최상위 비트가 1일 때) V = s × FS / 32768

음수 끝점은 -32768, 양수 끝점은 32767입니다. 따라서 음수는 정확히 -FS, 양수는 FS - 1 LSB까지 표시됩니다. 32767로 나누면 양수 끝점을 억지로 FS에 맞추게 되어 스케일이 달라집니다.

예를 들어 PGA=001, FS=4.096V이면 1 LSB는 125µV입니다.

받은 바이트 부호 복원 코드 계산 전압
00 01 1 125µV
FF FF -1 -125µV
40 00 16384 2.048V
80 00 -32768 -4.096V
7F FF 32767 4.095875V

표의 끝점은 코드 변환 예시입니다. 전원 전압과 실제 입력 조건에 따라 그 코드에 해당하는 전압을 인가할 수 있는지는 별도로 판단합니다.

3. PGA 코드와 배율을 함께 보관하기

데이터시트의 PGA 코드를 계산식에 옮기면 아래와 같습니다. LSB 열은 FS를 32768로 나눈 계산값입니다.

PGA 코드 풀스케일 범위 1 LSB
000 ±6.144V 187.5µV
001 ±4.096V 125µV
010 ±2.048V 62.5µV
011 ±1.024V 31.25µV
100 ±0.512V 15.625µV
101, 110, 111 ±0.256V 7.8125µV

마지막 두 코드도 ±0.256V입니다. 정의되지 않은 값으로 버리거나 배열 범위를 벗어나게 처리하지 않습니다. 다만 드라이버에서 설정값을 만들 때는 의도를 읽기 쉬운 101을 사용할 수 있습니다.

RTOS 태스크에서 채널을 번갈아 읽는다면 {원시 코드, 채널, PGA, 측정 순번}을 한 샘플로 전달하세요. 전역 PGA 변수를 먼저 바꾸고 이전 채널의 코드를 나중에 변환하면 부호와 통신이 모두 정상이어도 스케일 오류가 생깁니다. 설정 변경과 측정 완료를 한 태스크에서 소유하거나 버스 잠금 구간을 명확히 두는 편이 좋습니다.

4. 실행 가능한 전체 Python 예제

아래 내용을 ads1115_check.py로 저장하고 Python 3에서 실행합니다. 반환 단위는 µV이며 Fraction을 사용해 7.8125µV처럼 소수인 LSB도 정확히 유지합니다. 펌웨어로 옮길 때는 필요한 단위와 정수 범위를 정한 후 고정소수점 또는 부동소수점 구현을 선택하세요.

from fractions import Fraction import struct FS_UV = (6144000, 4096000, 2048000, 1024000, 512000, 256000, 256000, 256000) def decode_uv(data, pga): if type(data) not in (bytes, bytearray) or len(data) != 2: raise ValueError("exactly two bytes required") if type(pga) is not int or not 0 <= pga <= 7: raise ValueError("PGA must be an integer from 0 to 7") word = (data[0] << 8) | data[1] signed = word - 0x10000 if word & 0x8000 else word return Fraction(signed * FS_UV[pga], 32768) def single_shot_ready(config): if type(config) is not int or not 0 <= config <= 0xFFFF: raise ValueError("16-bit Config required") if not config & 0x0100: raise ValueError("single-shot mode required") return bool(config & 0x8000) def main(): vectors = ((b"\x00\x00", 1, Fraction(0)), (b"\x00\x01", 1, Fraction(125)), (b"\xff\xff", 1, Fraction(-125)), (b"\x40\x00", 1, Fraction(2048000)), (b"\x80\x00", 1, Fraction(-4096000)), (b"\x7f\xff", 1, Fraction(4095875)), (b"\x00\x01", 5, Fraction(125, 16))) for data, pga, expected in vectors: assert decode_uv(data, pga) == expected checked = 0 for word in range(65536): data = word.to_bytes(2, "big") reference = struct.unpack(">h", data)[0] for pga, fs in enumerate(FS_UV): result = decode_uv(data, pga) assert result == Fraction(reference * fs, 32768) assert -fs <= result < fs checked += 1 assert not single_shot_ready(0x4383) assert single_shot_ready(0xC383) try: single_shot_ready(0xC283) except ValueError: pass else: raise AssertionError("continuous mode was accepted") for data, pga in [(b"", 1), (b"\0", 1), (b"\0\0\0", 1), (b"\0\0", -1), (b"\0\0", 8), (b"\0\0", True)]: try: decode_uv(data, pga) except ValueError: pass else: raise AssertionError("invalid input was accepted") print(f"PASS: {checked} raw/PGA cases; 7 vectors; readiness and invalid inputs") print(f"4000h, PGA=001: {float(decode_uv(b'\x40\x00', 1)):.1f} uV") if __name__ == "__main__": main()

실행 명령과 확인한 출력입니다.

python -B ads1115_check.py PASS: 524288 raw/PGA cases; 7 vectors; readiness and invalid inputs 4000h, PGA=001: 2048000.0 uV

16비트 원시 값 65,536개와 PGA 코드 8개의 524,288개 조합을 검사했습니다. 수동 부호 확장은 독립적인 struct.unpack("&gt;h", ...) 결과와 비교했고, 알려진 경계값·음수 1 LSB·세 코드가 공유하는 최소 범위를 확인했습니다. 길이가 잘못된 수신 데이터와 범위를 벗어난 PGA도 거부합니다.

검사에 통과했다는 것은 디지털 해독과 이상적인 배율 계산이 맞는다는 뜻입니다. 실제 ADC 오프셋·노이즈·보드 배선·기준 측정 정확도까지 확인한 결과는 아닙니다.

5. 단발 변환은 설정 쓰기부터 완료 확인까지 묶기

예시 설정 0xC383은 AIN0 대 GND, PGA=001(±4.096V), 단발 모드, 128SPS, 비교기 비활성화입니다. Config에 보낼 때는 포인터 01, 상위 바이트 C3, 하위 바이트 83 순서입니다. 장치의 7비트 I²C 주소와 레지스터 포인터는 서로 다른 값입니다.

  1. 장치가 유휴 상태인지 확인한 뒤 OS=1인 설정을 쓰고, I²C 쓰기 성공 여부를 검사합니다. 이미 변환 중이면 OS=1 쓰기로 새 변환을 시작할 수 없습니다.
  2. 전원 기동 시간과 변환 시간을 고려해 Config를 읽습니다. 단발 변환의 OS=0이면 대기하고, 드라이버가 시작한 변환이 끝났음을 확인한 뒤 결과를 읽습니다. 기동 직후 유휴 OS 값이나 직전 측정값을 새 결과로 오해하지 않도록 측정 상태를 관리합니다.
  3. 대기에는 전체 시간 제한과 버스 오류 경로를 둡니다. 128SPS의 명목 주기는 약 7.8125ms이지만 통신·기동·발진기 오차도 있으므로 그 값만큼 정확히 잠들면 항상 완료된다고 가정하지 않습니다.
  4. Conversion 포인터 00을 선택하고 두 바이트를 받은 다음, 그 변환에 적용된 PGA로 계산합니다.

예제의 single_shot_ready()는 주어진 Config의 모드와 OS만 검사합니다. 변환 시작 성공이나 측정 순번을 추적하는 I²C 드라이버를 대신하지 않습니다. 연속 변환에서는 설정을 바꾸어도 진행 중인 이전 설정의 변환이 먼저 끝날 수 있으므로 채널 전환 절차를 따로 설계해야 합니다.

6. 입력 범위와 실제 보드 점검

±4.096V 설정은 각 핀에 ±4.096V를 넣어도 된다는 뜻이 아닙니다. 데이터시트 5.3절의 정상 동작 조건에서 각 아날로그 입력은 GND~VDD 범위입니다. 예를 들어 VDD=3.3V이고 AIN0=1.0V, AIN1=2.0V이면 차동 입력은 -1.0V이지만 어느 핀도 음전압이 아닙니다. 절대 최대 정격을 정상 측정 범위로 사용하지 마세요.

증상 먼저 확인할 내용
작은 음수가 매우 큰 양수로 보임 바이트 조립 후 16비트 부호 복원 여부
측정값이 일정하게 두 배·절반 변환에 사용한 PGA와 계산 배율의 일치 여부
값을 바꿨는데 이전 채널 값이 나옴 변환 완료·설정 변경 시점·샘플 소유 태스크
계속 0으로 읽힘 전원 투입 후 첫 변환 완료 여부와 포인터 00 선택
양수 끝점이 기대한 FS보다 한 단계 작음 32767과 32768의 차이, 포화와 실제 입력 범위

처음 보드에 적용할 때는 허용 범위 안의 알려진 직류 입력을 두세 단계로 바꾸고, 원시 두 바이트·Config·기준 계측값을 함께 기록하세요. 그다음 PGA와 채널을 바꾸어 디지털 배율 오류와 아날로그 오차를 분리하면 원인을 좁히기 쉽습니다.

공식 참고 문서

예제는 이 글을 위해 작성했으며 실제 하드웨어 없이 실행한 계산 검증입니다.

관련 글

댓글

첫 댓글을 남겨 보세요.

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

처리 중입니다...

잠시만 기다려주세요.