BMP280에서 읽은 숫자를 섭씨로 쓰려면 센서별 보정 계수를 적용해야 합니다. UART 로그에 원시값과 계수를 함께 남기면 센서가 연결되지 않은 PC에서도 변환 오류를 재현할 수 있습니다. 이 글은 기존 보드의 펌웨어 점검을 위한 호스트 계산 실습입니다. 실물 센서 통신·측정 정확도 시험은 포함하지 않습니다.
레지스터와 자료형을 먼저 맞추기
Bosch BMP280 공식 데이터시트 Rev.1.26의 §3.11과 §4.3.7을 기준으로 합니다.
| 읽기 시작 주소 | 바이트 수 | 해석 |
|---|---|---|
| 0x88 | 6 | dig_T1: unsigned 16비트, dig_T2·dig_T3: signed 16비트. 각 계수는 LSB 먼저 |
| 0xFA | 3 | 온도 ADC: MSB 먼저 이어 붙인 뒤 하위 4비트를 제외한 unsigned 20비트 |
두 블록의 바이트 순서가 다릅니다. 보정 계수에 쓰는 little-endian 설정을 ADC 전체에 적용하면 안 됩니다. §3.12 예시 표에는 UT·UP와 주소 표기가 서로 뒤바뀐 부분이 있으므로 온도 주소는 §4.3.7의 0xFA~0xFC를 따릅니다.
Python으로 계산하고 재현하기
아래 전체 코드를 bmp280_temperature.py로 저장하고 Python 3에서 실행합니다. 외부 패키지는 필요 없습니다. 검증문을 유지하도록 -O 옵션은 쓰지 않습니다.
python -B bmp280_temperature.py
"""Host-side BMP280 temperature decoding exercise; no hardware access."""
def calibration(data):
if len(data) != 6:
raise ValueError("read exactly 6 bytes from 0x88")
return (int.from_bytes(data[0:2], "little"),
int.from_bytes(data[2:4], "little", signed=True),
int.from_bytes(data[4:6], "little", signed=True))
def adc_temperature(data):
if len(data) != 3:
raise ValueError("read exactly 3 bytes from 0xFA")
return int.from_bytes(data, "big") // 16
def temperature_centi(adc, coefficients):
if adc not in range(1048576):
raise ValueError("adc must be an unsigned 20-bit value")
t1, t2, t3 = coefficients
linear = ((adc // 8 - 2 * t1) * t2) // 2048
delta = adc // 16 - t1
quadratic = ((delta * delta // 4096) * t3) // 16384
fine = linear + quadratic
return (5 * fine + 128) // 256, fine
if __name__ == "__main__":
trim = calibration(bytes.fromhex("70 6B 43 67 18 FC"))
adc = adc_temperature(bytes.fromhex("7E ED 00"))
assert trim == (27504, 26435, -1000)
assert adc == 519888
assert temperature_centi(adc, trim) == (2508, 128422)
# The six-byte parser must retain unsigned T1 and signed T2/T3.
for word in range(65536):
pair = word.to_bytes(2, "little")
unsigned, signed, _ = calibration(pair + pair + b"\0\0")
assert unsigned == word
assert signed == (word if word in range(32768) else word - 65536)
# Every ADC code round-trips; ignored low nibble cannot alter the result.
for value in range(1048576):
packed = (value * 16).to_bytes(3, "big")
assert adc_temperature(packed) == value
assert adc_temperature(packed[:2] + bytes([packed[2] | 15])) == value
for function, value in [(calibration, b"\0" * 5),
(adc_temperature, b"\0" * 2)]:
try:
function(value)
except ValueError:
pass
else:
raise AssertionError("short read accepted")
for invalid in (-1, 1048576):
try:
temperature_centi(invalid, trim)
except ValueError:
pass
else:
raise AssertionError("out-of-range ADC accepted")
print("25.08 C; t_fine=128422")
print("PASS: 65536 signed words, 1048576 ADC codes, 4 invalid inputs")
공식 §3.12의 계수와 원시값을 넣으면 2508(0.01°C 단위), 즉 25.08°C와 t_fine=128422가 나옵니다. 위 바이트열은 그 수치로 구성한 합성 입력입니다. 실제 장치에는 그 장치에서 읽은 계수를 넣어야 합니다.
코드는 고정소수점 단계마다 음의 무한대 방향 정수 나눗셈을 사용합니다. Python의 //는 음수에서도 이 규칙을 지키므로 산술 오른쪽 시프트를 수식으로 표현할 수 있습니다. MCU C로 옮길 때는 중간 곱셈 범위, 음수 시프트와 나눗셈 규칙을 별도로 확인해야 합니다.
이번 실습에서 확인하는 것
실행 결과는 다음과 같습니다.
25.08 C; t_fine=128422 PASS: 65536 signed words, 1048576 ADC codes, 4 invalid inputs
공식 계산 예시 하나를 기준점으로 확인하고, 16비트 전체 입력에서 signed/unsigned 구분을 검사합니다. 20비트 전체 입력에는 하위 니블을 0과 15로 바꿔 넣어도 ADC가 같아야 합니다. 짧은 읽기 두 건과 범위 밖 ADC 두 건도 거부합니다. 이 검사는 프레임 해독과 계산을 대상으로 하며, 모든 생산 계수 조합의 정확도를 보증하는 검사는 아닙니다.
보드에 적용할 때 확인할 상태
§3.3.2·§4.3.3에 따라 온도 측정 활성화와 측정 완료를 먼저 확인합니다. 초기화 때 im_update를 확인하고, 변환 중인 레지스터를 최신 측정값으로 취급하지 않습니다. 측정 생략 시 출력되는 0x80000은 설정·상태와 함께 해석해야 합니다. 이 순수 계산 함수는 센서의 활성화·완료 상태를 알 수 없습니다.
로그에는 계수 6바이트, ADC 3바이트, 측정 설정과 시각을 같이 남겨 보세요. 고정 계수를 복사했는지, 계수 부호가 바뀌었는지, 읽기 길이가 부족했는지를 비교하기 쉬워집니다. 압력 보정까지 구현한다면 동일 측정에 대응하는 t_fine을 전달해야 합니다.
댓글
첫 댓글을 남겨 보세요.