본문 바로가기
아날로그회로(Analog Circuit)/ADC관련

ZSC31014 Sensor Signal Conditioner IC 아두이노 I2C로 센서 데이터 읽기 및 EEPROM 설정 코드 구현

by linuxgo 2025. 8. 12.
Arduino 환경에서 ZSC31014 센서를 제어하기 위해 위의 STM32 코드를 Arduino 코드로 변환하겠습니다. Arduino는 일반적으로 Wire 라이브러리를 사용하여 I2C 통신을 처리하며, STM32의 HAL 함수 대신 Wire 라이브러리 함수를 사용합니다. 또한, Arduino는 일반적으로 printf 대신 Serial.print를 사용하므로 이에 맞게 수정합니다. 아래는 변환된 코드입니다
ZSC31014

상세 내용은 아래 링크를 참조하세요.

https://linuxgo.tistory.com/18

 

[ZSC31014]STM32L432KC에서 I2C통신으로 센서 데이터 읽기 구현

아래는 STM32L432KC에서 ZSC31014의 Normal Operation Mode(Update Mode 또는 Sleep Mode)에서 **센서 데이터(브리지 데이터, 14비트)**와 **온도 데이터(8비트 또는 11비트)**를 I²C 인터페이스를 통해 읽는 C 코드를 제

linuxgo.tistory.com

 

#include <Wire.h>

// ZSC31014 I2C 슬레이브 주소: 7비트 주소 0x28 (데이터시트 페이지 20)
#define ZSC31014_I2C_ADDR 0x28

// 명령 코드: 데이터시트 페이지 37, Table 3.6
#define CMD_START_CM  0xA0 // Command Mode 진입
#define CMD_START_NOM 0x80 // Normal Operation Mode 복귀
#define CMD_EEPROM_READ_BASE  0x00 // EEPROM 읽기 명령 기본값 (0x00 ~ 0x13)
#define CMD_EEPROM_WRITE_BASE 0x40 // EEPROM 쓰기 명령 기본값 (0x40 ~ 0x53)

// 타임아웃: 100ms (I2C 통신 안정성 보장)
#define I2C_TIMEOUT 100

// VDD 제어 핀: 가정 - D2 핀 사용, HIGH=ON, LOW=OFF
#define ZSC_VDD_PIN 2

// 함수 프로토타입
bool zsc31014_power_cycle(void);  // 전원 사이클링
bool zsc31014_start_cm(void);     // Command Mode 진입
bool zsc31014_start_nom(void);    // Normal Operation Mode 복귀
bool zsc31014_read_sensor_data(uint16_t *bridge_data, uint16_t *temp_data, uint8_t temp_res_11bit);  // 센서 데이터 읽기
bool zsc31014_trigger_measurement(void);  // Sleep Mode에서 측정 트리거
bool zsc31014_read_eeprom(uint8_t word_addr, uint16_t *data);  // 단일 EEPROM 워드 읽기
bool zsc31014_write_eeprom(uint8_t word_addr, uint16_t data);  // 단일 EEPROM 워드 쓰기
bool zsc31014_read_all_eeprom(uint16_t *data_array);  // 전체 EEPROM 읽기
bool zsc31014_write_all_eeprom(uint16_t *data_array); // 전체 EEPROM 쓰기

/**
 * @brief ZSC31014 VDD 전원 OFF/ON (리셋 용도)
 *        데이터시트 페이지 23 (2.3.6 POR): IC 락업 방지를 위해 전원 사이클링
 * @retval true 성공
 */
bool zsc31014_power_cycle(void) {
  // VDD OFF: 디지털 핀 LOW로 설정
  digitalWrite(ZSC_VDD_PIN, LOW);
  delay(10);  // 최소 10ms OFF 유지 (POR 안정화, 데이터시트 페이지 7 Table 1.3)

  // VDD ON: 디지털 핀 HIGH로 설정
  digitalWrite(ZSC_VDD_PIN, HIGH);
  delay(10);  // ON 후 안정화 대기 (데이터시트 페이지 28 Figure 3.2: Power-Up ~3ms)

  Serial.println("ZSC31014 전원 사이클링 완료");
  return true;
}

/**
 * @brief Command Mode 진입 (전원 사이클링 후)
 *        데이터시트 페이지 37: Start_CM (0xA0) + don't care 데이터 2바이트
 * @retval true 성공, false 에러
 */
bool zsc31014_start_cm(void) {
  // 전원 사이클링: IC 상태 클리어 (데이터시트 페이지 26 3.1)
  if (!zsc31014_power_cycle()) {
    Serial.println("전원 사이클링 실패");
    return false;
  }

  // Start_CM 명령 + don't care 데이터 (0x0000)
  uint8_t tx_data[3] = {CMD_START_CM, 0x00, 0x00};
  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  Wire.write(tx_data, 3);
  if (Wire.endTransmission() != 0) {
    Serial.println("Start_CM 실패");
    return false;
  }

  delay(1);  // 명령 완료 대기: 데이터시트 페이지 37 (10us)
  return true;
}

/**
 * @brief Normal Operation Mode 복귀 및 EEPROM 서명 업데이트
 *        데이터시트 페이지 37: Start_NOM (0x80) + don't care 데이터 2바이트
 * @retval true 성공, false 에러
 */
bool zsc31014_start_nom(void) {
  uint8_t tx_data[3] = {CMD_START_NOM, 0x00, 0x00};
  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  Wire.write(tx_data, 3);
  if (Wire.endTransmission() != 0) {
    Serial.println("Start_NOM 실패");
    return false;
  }

  delay(15);  // 서명 업데이트 포함 최대 15ms 대기 (데이터시트 페이지 37)
  return true;
}

/**
 * @brief 센서(브리지) 데이터와 온도 데이터 읽기
 *        데이터시트 페이지 33 Figure 3.6: Read_DF3 (8비트 온도) 또는 Read_DF4 (11비트 온도)
 * @param bridge_data 브리지 데이터(14비트) 저장 포인터
 * @param temp_data 온도 데이터(8비트 또는 11비트) 저장 포인터
 * @param temp_res_11bit 온도 해상도 (0: 8비트, 1: 11비트, 데이터시트 페이지 38 Config_Reg[3])
 * @retval true 성공, false 에러
 */
bool zsc31014_read_sensor_data(uint16_t *bridge_data, uint16_t *temp_data, uint8_t temp_res_11bit) {
  uint8_t rx_data[4];
  uint8_t byte_count = temp_res_11bit ? 4 : 3;  // 11비트: 4바이트, 8비트: 3바이트

  Wire.requestFrom(ZSC31014_I2C_ADDR, byte_count);
  if (Wire.available() < byte_count) {
    Serial.println("센서 데이터 수신 실패");
    return false;
  }

  for (uint8_t i = 0; i < byte_count; i++) {
    rx_data[i] = Wire.read();
  }

  // 상태 비트 확인: 데이터시트 페이지 24 Table 2.9 (첫 바이트 상위 2비트 S[1:0])
  uint8_t status_bits = (rx_data[0] >> 6) & 0x03;
  if (status_bits == 0x03) {
    Serial.println("진단 오류: EEPROM 무결성 또는 센서 오류");
    return false;
  } else if (status_bits == 0x02) {
    Serial.println("경고: Stale 데이터");
  }

  // 브리지 데이터 (14비트) 조합: 첫 바이트 하위 6비트 + 두 번째 바이트
  *bridge_data = ((rx_data[0] & 0x3F) << 8) | rx_data[1];

  // 온도 데이터 조합
  if (byte_count >= 3) {
    if (temp_res_11bit) {
      // 11비트 온도: 세 번째 바이트 << 3 + 네 번째 바이트 상위 3비트
      *temp_data = (rx_data[2] << 3) | ((rx_data[3] >> 5) & 0x07);
    } else {
      // 8비트 온도: 세 번째 바이트
      *temp_data = rx_data[2];
    }
  } else {
    *temp_data = 0;
  }

  Serial.print("브리지 데이터: 0x");
  Serial.print(*bridge_data, HEX);
  Serial.print(", 온도 데이터: 0x");
  Serial.println(*temp_data, HEX);
  return true;
}

/**
 * @brief Sleep Mode에서 측정 요청 (Write_MR)
 *        데이터시트 페이지 36 Figure 3.9: 빈 쓰기로 깨우기
 * @retval true 성공, false 에러
 */
bool zsc31014_trigger_measurement(void) {
  uint16_t config_reg;

  // Config_Reg 읽기: 클럭 설정 확인 (워드 0x00, 비트 [10], 데이터시트 페이지 38)
  if (!zsc31014_read_eeprom(0x00, &config_reg)) {
    Serial.println("Config_Reg 읽기 실패");
    return false;
  }
  uint8_t clk_4mhz = (config_reg & (1 << 10)) ? 1 : 0;  // 1=4MHz, 0=1MHz

  // Write_MR: 빈 데이터 전송
  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  if (Wire.endTransmission() != 0) {
    Serial.println("Write_MR 실패");
    return false;
  }

  // 측정 완료 대기: 데이터시트 페이지 31 Table 3.4-3.5 (4MHz: ~1.5ms, 1MHz: ~6ms)
  delay(clk_4mhz ? 2 : 6);
  return true;
}

/**
 * @brief 특정 EEPROM 워드 읽기
 *        데이터시트 페이지 37: 읽기 명령 (0x00 + word_addr) + 데이터 수신 (0x5A + 16비트)
 * @param word_addr 워드 주소 (0x00 ~ 0x13)
 * @param data 읽은 16비트 데이터 저장 포인터
 * @retval true 성공, false 에러
 */
bool zsc31014_read_eeprom(uint8_t word_addr, uint16_t *data) {
  if (word_addr > 0x13) {
    Serial.print("잘못된 워드 주소: 0x");
    Serial.println(word_addr, HEX);
    return false;
  }

  // 읽기 명령 전송
  uint8_t tx_data[3] = {CMD_EEPROM_READ_BASE + word_addr, 0x00, 0x00};
  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  Wire.write(tx_data, 3);
  if (Wire.endTransmission() != 0) {
    Serial.print("EEPROM 읽기 명령 전송 실패 (워드 0x");
    Serial.print(word_addr, HEX);
    Serial.println(")");
    return false;
  }

  // 데이터 수신 (0x5A + 16비트 데이터)
  uint8_t rx_data[3];
  Wire.requestFrom(ZSC31014_I2C_ADDR, 3);
  if (Wire.available() < 3) {
    Serial.print("EEPROM 데이터 수신 실패 (워드 0x");
    Serial.print(word_addr, HEX);
    Serial.println(")");
    return false;
  }

  for (uint8_t i = 0; i < 3; i++) {
    rx_data[i] = Wire.read();
  }

  // 첫 바이트 확인: 0x5A (데이터시트 페이지 37)
  if (rx_data[0] != 0x5A) {
    Serial.print("EEPROM 읽기 응답 오류 (워드 0x");
    Serial.print(word_addr, HEX);
    Serial.print("): 0x");
    Serial.println(rx_data[0], HEX);
    return false;
  }

  // 16비트 데이터 조합
  *data = (rx_data[1] << 8) | rx_data[2];
  Serial.print("워드 0x");
  Serial.print(word_addr, HEX);
  Serial.print(": 0x");
  Serial.println(*data, HEX);
  return true;
}

/**
 * @brief 특정 EEPROM 워드 쓰기
 *        데이터시트 페이지 37: 쓰기 명령 (0x40 + word_addr) + 16비트 데이터
 * @param word_addr 워드 주소 (0x00 ~ 0x13)
 * @param data 쓸 16비트 데이터
 * @retval true 성공, false 에러
 */
bool zsc31014_write_eeprom(uint8_t word_addr, uint16_t data) {
  if (word_addr > 0x13) {
    Serial.print("잘못된 워드 주소: 0x");
    Serial.println(word_addr, HEX);
    return false;
  }

  // T_Config(0x10), Osc_Trim(0x11) 쓰기 금지: 데이터시트 페이지 38
  if (word_addr == 0x10 || word_addr == 0x11) {
    Serial.print("오류: 워드 0x");
    Serial.print(word_addr, HEX);
    Serial.println("은 공장 설정으로 쓰기 금지");
    return false;
  }

  // 쓰기 명령 + 데이터
  uint8_t tx_data[3] = {
    CMD_EEPROM_WRITE_BASE + word_addr,
    (data >> 8) & 0xFF,
    data & 0xFF
  };

  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  Wire.write(tx_data, 3);
  if (Wire.endTransmission() != 0) {
    Serial.print("EEPROM 쓰기 실패 (워드 0x");
    Serial.print(word_addr, HEX);
    Serial.println(")");
    return false;
  }

  delay(10);  // 쓰기 완료 대기 (데이터시트 페이지 37)
  Serial.print("워드 0x");
  Serial.print(word_addr, HEX);
  Serial.print("에 0x");
  Serial.print(data, HEX);
  Serial.println(" 쓰기 완료");
  return true;
}

/**
 * @brief 전체 EEPROM 읽기 (0x00 ~ 0x13)
 *        Command Mode에서 모든 워드 읽기, 중복 진입 방지
 * @param data_array 20개 워드(16비트) 저장 배열
 * @retval true 성공, false 에러
 */
bool zsc31014_read_all_eeprom(uint16_t *data_array) {
  // Command Mode 진입: 루프 밖에서 한 번만 호출
  if (!zsc31014_start_cm()) {
    Serial.println("Command Mode 진입 실패");
    return false;
  }

  // 워드 0x00 ~ 0x13 읽기
  for (uint8_t word_addr = 0x00; word_addr <= 0x13; word_addr++) {
    if (!zsc31014_read_eeprom(word_addr, &data_array[word_addr])) {
      zsc31014_start_nom();  // 에러 시 NOM 복귀
      return false;
    }
  }

  // Normal Operation Mode 복귀
  if (!zsc31014_start_nom()) {
    Serial.println("Normal Operation Mode 복귀 실패");
    return false;
  }

  Serial.println("EEPROM 전체 읽기 완료");
  return true;
}

/**
 * @brief 전체 EEPROM 쓰기 (0x00 ~ 0x13)
 *        Command Mode에서 모든 워드 쓰기, 중복 진입 방지
 * @param data_array 쓸 20개 워드(16비트) 데이터 배열
 * @retval true 성공, false 에러
 */
bool zsc31014_write_all_eeprom(uint16_t *data_array) {
  // Command Mode 진입: 루프 밖에서 한 번만 호출
  if (!zsc31014_start_cm()) {
    Serial.println("Command Mode 진입 실패");
    return false;
  }

  // 워드 0x00 ~ 0x13 쓰기
  for (uint8_t word_addr = 0x00; word_addr <= 0x13; word_addr++) {
    if (!zsc31014_write_eeprom(word_addr, data_array[word_addr])) {
      zsc31014_start_nom();  // 에러 시 NOM 복귀
      return false;
    }
  }

  // Normal Operation Mode 복귀
  if (!zsc31014_start_nom()) {
    Serial.println("Normal Operation Mode 복귀 실패");
    return false;
  }

  Serial.println("EEPROM 전체 쓰기 완료");
  return true;
}

/**
 * @brief Arduino setup 함수
 */
void setup() {
  Serial.begin(9600);  // 시리얼 통신 초기화 (디버깅 로그)
  while (!Serial);     // 시리얼 모니터 연결 대기
  Wire.begin();        // I2C 초기화
  Wire.setWireTimeout(I2C_TIMEOUT * 1000, true);  // 타임아웃 100ms 설정 (us 단위)

  // VDD 제어 핀 설정
  pinMode(ZSC_VDD_PIN, OUTPUT);
  digitalWrite(ZSC_VDD_PIN, HIGH);  // 초기 전원 ON

  Serial.println("ZSC31014 테스트 시작");
  zsc31014_example();  // 테스트 함수 호출
}

/**
 * @brief Arduino loop 함수
 */
void loop() {
  // 주기적으로 센서 데이터 읽기
  uint16_t bridge_data, temp_data;
  Serial.println("Update Mode에서 센서/온도 데이터 읽기");
  if (zsc31014_read_sensor_data(&bridge_data, &temp_data, 1)) {
    // 데이터는 함수 내부에서 출력됨
  } else {
    Serial.println("Update Mode 데이터 읽기 실패");
  }
  delay(1000);  // 1초 대기
}

/**
 * @brief 메인 함수 예시 (사용 예시)
 */
void zsc31014_example(void) {
  uint16_t eeprom_data[20];  // 0x00 ~ 0x13 워드 저장 배열
  uint16_t bridge_data, temp_data;

  // 전체 EEPROM 읽기
  Serial.println("EEPROM 전체 읽기 시작");
  if (!zsc31014_read_all_eeprom(eeprom_data)) {
    Serial.println("EEPROM 전체 읽기 실패");
    return;
  }

  // 특정 워드 읽기 (예: 워드 0x01)
  uint16_t single_data;
  Serial.println("워드 0x01 읽기 시작");
  if (zsc31014_start_cm() && zsc31014_read_eeprom(0x01, &single_data) && zsc31014_start_nom()) {
    Serial.print("워드 0x01: 0x");
    Serial.println(single_data, HEX);
  } else {
    Serial.println("워드 0x01 읽기 실패");
  }

  // 특정 워드 쓰기 (예: 워드 0x01에 0x0000 쓰기)
  Serial.println("워드 0x01에 0x0000 쓰기 시작");
  if (zsc31014_start_cm() && zsc31014_write_eeprom(0x01, 0x0000) && zsc31014_start_nom()) {
    Serial.println("워드 0x01 쓰기 완료");
  } else {
    Serial.println("워드 0x01 쓰기 실패");
  }

  // 전체 EEPROM 쓰기 (샘플 데이터)
  uint16_t sample_data[20];
  memset(sample_data, 0, sizeof(sample_data));  // 0으로 초기화
  sample_data[0x01] = 0x0004;  // I2C, 1MHz, 11비트 온도, Update_Rate=25ms (페이지 38 Table 3.7)
  sample_data[0x02] = 0x0280;  // 슬레이브 주소 0x28
  sample_data[0x0F] = 0x0A80;  // PreAmp_Gain=6, A2D_Offset=-1/2 ~ 1/2 (페이지 16 Table 2.4)
  Serial.println("EEPROM 전체 쓰기 시작");
  if (zsc31014_write_all_eeprom(sample_data)) {
    Serial.println("EEPROM 전체 쓰기 완료");
  } else {
    Serial.println("EEPROM 전체 쓰기 실패");
  }

  // Sleep Mode에서 센서 데이터와 온도 데이터 읽기
  Serial.println("Sleep Mode에서 센서/온도 데이터 읽기 시작");
  if (zsc31014_trigger_measurement()) {
    if (!zsc31014_read_sensor_data(&bridge_data, &temp_data, 1)) {
      Serial.println("Sleep Mode 데이터 읽기 실패");
    }
  } else {
    Serial.println("Write_MR 실패");
  }
}
사용 방법
  1.   하드웨어 연결:
    •   ZSC31014의 I2C 핀(SDA, SCL)을 Arduino의 I2C 핀에 연결합니다 (예: Arduino Uno의 경우 SDA는 A4, SCL은 A5).
    •   적절한 풀업 저항(4.7kΩ 권장)을 SDA와 SCL 라인에 연결합니다.
  2.   Arduino 설정:
    •   Arduino IDE에서 코드를 업로드하기 전에 Wire 라이브러리가 포함되어 있는지 확인합니다(기본적으로 포함됨).
    •   시리얼 모니터를 9600 보드로 설정하여 출력을 확인합니다.
  3.   주의사항:
    •   ZSC31014의 I2C 통신은 데이터시트에 따라 정확한 타이밍과 명령 형식이 필요합니다. 코드의 delay 값은 데이터시트에 명시된 최소 시간을 준수합니다.
    •   EEPROM 쓰기 시 워드 주소 0x10(T_Config)과 0x11(Osc_Trim)은 공장 설정을 유지하는 것이 권장되므로 경고 메시지를 유지했습니다.
    •   Arduino의 I2C 버스 속도는 기본적으로 100kHz이며, 필요 시 Wire.setClock(400000)setup()에 추가하여 400kHz로 설정할 수 있습니다.
테스트
  •   setup()에서 zsc31014_example()이 호출되어 EEPROM 읽기/쓰기, 센서 데이터 읽기 등을 테스트합니다.
  •   loop()에서는 1초마다 Update Mode에서 센서 데이터를 읽어 출력합니다.
  •   시리얼 모니터를 통해 출력된 데이터(브리지 데이터, 온도 데이터, EEPROM 값 등)를 확인할 수 있습니다.
추가 고려사항.
  •   타이밍 최적화: 데이터시트의 최소 대기 시간(예: 10us, 1.5ms)을 준수했지만, 실제 환경에서 타이밍 문제가 발생하면 delay 값을 조정해야 할 수 있습니다.
  •   에러 처리: Wire 라이브러리의 에러 처리는 제한적이므로, I2C 통신 실패 시 추가 디버깅(예: I2C 스캐너 사용)을 권장합니다.
이 코드는 ZSC31014의 기능을 Arduino 환경에서 구현하며, 데이터시트에 명시된 동작을 최대한 유지했습니다.