새 I2C 센서를 붙이는데 값이 계속 이상했다. 코드를 몇 번 고쳤는데 나아지지 않았고 사양서를 보니 코드 문제가 아니었다.
Table of contents
Open Table of contents
변환 시간이 필요했다
읽는 흐름은 이랬다.
i2c_write(ADDR, REG_CTRL, CMD_START); /* 측정 시작 */
i2c_read(ADDR, REG_DATA, buf, 2); /* 값 읽기 */
CMD_START 를 보내고 곧바로 i2c_read 를 건다. 사양서에는 이렇게 적혀 있었다.
Measurement time: typ. 65ms, max. 85ms (14-bit resolution)
측정에 65밀리초가 걸리고 그 전에 읽으면 이전 값이나 쓰레기가 나온다. 그 값은 데이터시트의 타이밍 표에 그대로 적혀 있었다.
i2c_write(ADDR, REG_CTRL, CMD_START);
delay_ms(90); /* 최대치보다 여유 */
i2c_read(ADDR, REG_DATA, buf, 2);
90밀리초를 기다리게 했고 최대치가 85니까 여유를 뒀다. 이 시간은 센서가 정한 것이라 코드로 줄일 수 없다.
준비 여부를 물어보는 방법도 있었다
기다리는 대신 상태를 확인할 수도 있었다.
i2c_write(ADDR, REG_CTRL, CMD_START);
int wait = 0;
uint8_t st;
do {
delay_ms(5);
i2c_read(ADDR, REG_STATUS, &st, 1);
wait += 5;
} while (!(st & STATUS_READY) && wait < 200);
if (!(st & STATUS_READY)) return -1; /* 상한 초과 */
i2c_read(ADDR, REG_DATA, buf, 2);
STATUS_READY 가 서면 바로 읽으니 평균 대기가 짧아진다. 대신 통신이 늘어나고 5밀리초마다 상태를 묻는다.
이 장치는 1초에 한 번 읽으면 되니 고정 대기로 했고 자주 읽어야 하는 자리라면 상태 확인이 낫다.
해상도 설정에 따라 시간이 달라졌다
사양서를 더 읽어 보니 해상도 설정이 있었다.
14-bit 65ms
12-bit 17ms
11-bit 9ms
우리는 0.1도 단위면 충분해서 해상도를 12비트로 낮췄고 대기가 17밀리초로 줄었다.
필요한 정밀도를 먼저 정하면 나머지가 정해진다. 무조건 최고 해상도를 쓸 이유가 없었고 그 설정도 I2C 로 레지스터에 써 넣는 값이다.
변환식과 전기 특성
값을 변환하는 식도 확인했다.
Temperature = -46.85 + 175.72 * (raw / 65536)
내가 쓰던 식은 raw / 256.0 이었는데 다른 센서의 식이었다.
float temp = -46.85f + 175.72f * ((float)raw / 65536.0f);
계수가 센서마다 다르니 비슷한 센서라고 같은 식을 쓰면 안 된다. 계수를 #define 으로 빼 두면 다음에 센서가 바뀌어도 그 줄만 고치면 된다.
값이 가끔 튀는 문제도 남아 있어서 사양서의 전기 특성을 봤다.
Supply voltage: 2.1 ~ 3.6V
I2C pull-up: 10kΩ typ.
회로를 확인하니 풀업 저항이 4.7k였고 범위 안이긴 한데 선이 길어서 신호가 흔들렸다.
I2C 는 SDA 와 SCL 두 신호선을 여럿이 나눠 쓰는 구조라 풀업이 없으면 신호가 높은 쪽으로 안 올라간다. 이것은 코드로 못 고쳐서 하드웨어 담당자에게 넘겼다.
증상이 코드에서 안 잡히면 그 아래를 봐야 한다.
검증 — 확인 순서와 식별 레지스터
새 장치를 붙일 때 볼 순서를 적었다.
1. 사양서에서 확인
- 주소, 레지스터 맵
- 측정 시간, 준비 확인 방법
- 값 변환식
- 동작 범위 (온도, 전압)
- 통신 속도 상한
2. 통신 확인
- 주소로 응답하는가 (스캔)
- 식별 레지스터 값이 맞는가
3. 값 확인
- 알려진 조건에서 값이 맞는가 (상온에서 25도 근처)
- 범위 안인가
- 반복 읽었을 때 흔들림이 얼마인가
2번의 식별 레지스터 확인이 유용했다. 대부분의 센서가 고정 값을 주는 레지스터를 갖고 있다.
uint8_t id;
i2c_read(ADDR, REG_ID, &id, 1);
if (id != EXPECTED_ID) {
log_error("센서 식별 불일치: 0x%02X (기대 0x%02X)", id, EXPECTED_ID);
return -1;
}
REG_ID 가 맞으면 통신은 되는 것이고 값이 이상하면 통신이 아니라 다른 문제다. 통신 자체가 안 되는데 값 처리를 보는 것은 순서가 뒤바뀐 것이다.
값들이 어디서 왔는지 알 수 있게 주석도 달았다.
#define MEAS_TIME_MS 90 /* 사양서 5.2절: 14bit max 85ms */
#define TEMP_MIN (-40.0f) /* 사양서 2.1절 동작 범위 */
#define EXPECTED_ID 0xBE /* 사양서 6.4절 */
숫자만 있으면 왜 그 값인지 모르는데 사양서 절 번호를 적어 두니 확인이 빨라졌다.
정리
- 하드웨어가 정한 것은 코드로 못 바꾸니 사양서를 먼저 본다
- 같은 자리를 계속 고치면 그 자리가 원인이 아닐 수 있다
- 측정 시간이 있으면 그만큼 기다리고 최대치보다 여유를 둔다
- 준비 확인 방식과 고정 대기는 읽는 빈도로 고른다
- 정밀도 설정이 측정 시간을 정한다
- 변환식은 센서마다 다르니 비슷하다고 같은 식을 쓰지 않는다
I2C는 풀업이 없으면 신호가 안 올라간다- 코드에서 안 잡히면 전기 특성과 회로를 본다
- 식별 레지스터로 통신 여부를 먼저 가른다
#define상수 옆에 사양서 절 번호를 적어 둔다