기술 가이드

TMP102 온도 변환: 12·13비트 부호·음수 반올림

TMP102의 두 바이트를 부호 있는 온도로 복원하고 12,288개 비트 조합을 검증합니다. 확장 모드 확인, 정수 단위, 음수 반올림과 펌웨어 적용 절차를 설명합니다.

목차

온도 센서에서 두 바이트를 읽었는데 영하에서만 수천 도가 나오거나, 설정을 바꾼 뒤 값이 두 배가 된다면 전송 성공과 숫자 해석을 따로 확인해야 합니다. 이 글은 TMP102의 12비트·13비트 온도 데이터를 C 정수로 복원하는 계산부를 만들고, 가능한 비트 조합 전체를 검사합니다.

대상은 I²C 센서 드라이버를 작성하는 MCU·펌웨어 개발자입니다. 예제는 Windows 호스트 GCC에서 검증했습니다. 실제 TMP102, MCU, I²C 파형을 시험한 결과는 아니며 다른 센서의 레지스터에 그대로 적용하면 안 됩니다.

1. 센서의 형식과 응용 프로그램의 단위를 분리하기

TI 공식 데이터시트의 6.3.1절과 표 6-8·6-9에서 확인할 조건은 다음과 같습니다. 온도 레지스터는 상위 바이트부터 읽으며, 일반 모드는 상위 12비트, 확장 모드는 상위 13비트를 사용합니다. 한 카운트는 0.0625°C이고 음수는 2의 보수입니다. 두 번째 바이트의 D0는 결과 형식을 나타냅니다. TMP102 데이터시트 Rev. I

이 예제의 반환값 q4섭씨의 16배인 정수입니다. 400이면 25°C, -1이면 -0.0625°C입니다. 센서가 제공한 분해능을 보존한 채 저장하고, 표시 직전에만 백분의 일 도로 반올림합니다. 소수점 아래 네 자리를 표현할 수 있다는 사실과 실제 측정 정확도는 별개입니다.

2. 부호를 복원하는 계산 순서

먼저 ((uint32_t)msb << 8) | lsb로 바이트를 결합합니다. 수신 배열의 주소를 int16_t*로 바꾸어 읽으면 CPU 바이트 순서와 정렬 조건까지 고려해야 하므로, 여기서는 각 바이트의 위치를 명시합니다.

일반 모드라면 오른쪽으로 4비트, 확장 모드라면 3비트 이동합니다. 그 결과를 각각 12비트·13비트 숫자로 보고 최상위 데이터 비트가 1이면 2^비트수를 뺍니다. 예를 들어 일반 모드의 FF F0는 추출값 4095에서 4096을 뺀 -1입니다. unsigned 계산으로 비트를 추출한 뒤, 충분히 큰 int32_t 범위 안에서 뺄셈하므로 음수의 오른쪽 시프트 규칙에 기대지 않습니다.

함수는 호출자가 기대하는 EM 설정과 결과 바이트의 D0가 같은지도 확인합니다. 설정과 다르면 실패를 반환하고 출력 인수를 유지합니다. 이 검사는 드라이버의 형식 혼동을 찾기 위한 것이며 통신 CRC를 대신하지 않습니다. 설정 변경과 읽기를 여러 태스크에서 동시에 수행한다면 드라이버 접근을 직렬화해야 합니다.

3. 복사해서 실행하는 전체 C 예제

아래 내용을 tmp102_decode.c로 저장하고 gcc -std=c11 -Wall -Wextra -Werror tmp102_decode.c -o tmp102_decode로 빌드합니다. Windows PowerShell에서는 .\tmp102_decode.exe, Linux에서는 ./tmp102_decode로 실행합니다. 검증용 assert가 동작하도록 -DNDEBUG는 지정하지 마세요.

/* Original example: JDevCompany, 2026. CC0-1.0. */
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

/* Success returns an exact integer in units of 1/16 degree C. */
static bool tmp102_q4(uint8_t msb, uint8_t lsb,
bool expected_em, int32_t *out)
{
if (out == NULL) return false;
bool em = (lsb & 1u) != 0u;
if (em != expected_em) return false;
if ((lsb & (em ? 0x06u : 0x0eu)) != 0u) return false;

uint32_t word = ((uint32_t)msb << 8) | lsb;
unsigned bits = em ? 13u : 12u;
uint32_t raw = word >> (em ? 3u : 4u);
int32_t value = (int32_t)raw;
if ((raw & (UINT32_C(1) << (bits - 1u))) != 0u)
value -= (int32_t)(UINT32_C(1) << bits);
*out = value;
return true;
}

/* Integer hundredths, nearest rounding; ties away from zero. */
static int32_t q4_to_centi(int32_t q4)
{
assert(q4 >= -4096 && q4 <= 4095);
int32_t scaled = q4 * 100;
return scaled >= 0 ? (scaled + 8) / 16
: -((-scaled + 8) / 16);
}

int main(void)
{
struct { uint8_t hi, lo; bool em; int32_t q4; } cases[] = {
{0x19, 0x00, false, 400}, /* +25 C */
{0xff, 0xf0, false, -1}, /* -0.0625 C */
{0xe7, 0x00, false, -400}, /* -25 C */
{0x00, 0x00, false, 0},
{0x0c, 0x81, true, 400}, /* +25 C, extended */
{0xff, 0xf9, true, -1},
{0x4b, 0x01, true, 2400} /* +150 C encoding */
};
for (unsigned i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
int32_t out = 99999;
assert(tmp102_q4(cases[i].hi, cases[i].lo, cases[i].em, &out));
assert(out == cases[i].q4);
}

unsigned tested = 0;
for (unsigned em = 0; em < 2; ++em) {
unsigned bits = em ? 13u : 12u;
int32_t half = (int32_t)(UINT32_C(1) << (bits - 1u));
for (int32_t expected = -half; expected < half; ++expected) {
uint32_t raw = (uint32_t)(expected < 0
? expected + 2 * half : expected);
uint32_t wire = (raw << (em ? 3u : 4u)) | em;
int32_t actual = 99999;
assert(tmp102_q4((uint8_t)(wire >> 8), (uint8_t)wire,
em != 0, &actual));
assert(actual == expected);
++tested;
}
}
int32_t unchanged = 777;
assert(!tmp102_q4(0x19, 0x00, true, &unchanged));
assert(!tmp102_q4(0x19, 0x02, false, &unchanged));
assert(!tmp102_q4(0x0c, 0x83, true, &unchanged));
assert(!tmp102_q4(0, 0, false, NULL));
assert(unchanged == 777);
assert(q4_to_centi(-1) == -6);
assert(q4_to_centi(2) == 13);
assert(q4_to_centi(-2) == -13);
assert(q4_to_centi(400) == 2500);
printf("PASS: %u encodings, 7 vectors, invalid inputs, rounding\n", tested);
printf("25 C -> %ld centi-C; -0.0625 C -> %ld centi-C\n",
(long)q4_to_centi(400), (long)q4_to_centi(-1));
return 0;
}

실행 결과:

PASS: 12288 encodings, 7 vectors, invalid inputs, rounding
25 C -> 2500 centi-C; -0.0625 C -> -6 centi-C

4. 이 시험이 확인한 것

직접 지정한 7개 입력 벡터로 양수·영점·음수·모드 차이를 확인합니다. 이어 일반 모드 4,096개와 확장 모드 8,192개, 합계 12,288개 부호 있는 값을 바이트로 구성한 뒤 다시 복원해 원래 값과 비교합니다. 기대 모드 불일치, 예약 비트 이상, NULL 출력 인수도 실패해야 합니다.

모든 비트 조합을 시험하는 것은 숫자 변환의 경계값을 검증하기 위한 것입니다. 비트로 표현할 수 있는 범위 전체가 센서의 보증 측정 범위라는 뜻은 아닙니다. 확장 모드의 150°C 벡터 역시 형식 계산 시험이며 해당 온도에서 정확도를 실측한 결과가 아닙니다.

반올림 정책은 최근접, 정확히 중간이면 0에서 먼 쪽입니다. 따라서 0.125°C는 0.13°C, -0.125°C는 -0.13°C입니다. -0.0625°C를 백분의 일 도 단위로 변환하면 -6입니다. 표시할 때 음수를 정수부·소수부로 나눌 경우 부호를 따로 처리해야 0.-06 같은 문자열을 피할 수 있습니다. 원본 q4를 유지하면 이후 표시 자릿수가 바뀌어도 다시 계산할 수 있습니다.

5. 실제 펌웨어에 연결할 때

I²C 드라이버는 온도 레지스터를 선택하고 성공적으로 읽은 두 바이트만 변환 함수에 넘기도록 구성하세요. NACK·타임아웃·길이 부족이면 이전 버퍼를 새 측정값처럼 사용하지 말고 오류 상태를 전달합니다. 측정 결과와 함께 수신 시각, 유효 여부, 당시 EM 설정을 기록하면 통신 문제와 계산 문제를 구분하기 쉽습니다.

전원 투입 직후 0°C가 읽힐 수 있으므로 첫 변환 완료 전에 읽은 값인지 확인해야 합니다. 레지스터 선택과 읽기 절차는 공식 데이터시트의 6.5절, 변환 모드는 6.4절을 참고하세요. TI 프로그래밍·동작 모드 문서

증상별로는 영하에서만 큰 양수가 나오면 부호 복원을, 일정한 배율 차이면 EM과 시프트 수를, 가끔 큰 값이 튀면 수신 상태·바이트 순서·버퍼 수명을 먼저 확인합니다. 이 순서로 원시 바이트와 변환값을 함께 남기면 센서 교체나 보정값 조정 전에 재현 가능한 오류를 좁힐 수 있습니다.

예제 코드는 JDevCompany가 작성한 CC0-1.0 예제입니다. 공식 문서 확인일과 호스트 실행 검증일은 2026-09-24이며, 정확도·전기적 조건·레지스터의 최종 기준은 TI TMP102 공식 제품 페이지와 연결된 데이터시트입니다.

관련 글

댓글

첫 댓글을 남겨 보세요.

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

처리 중입니다...

잠시만 기다려주세요.