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

[ZSC31014] 아두이노 I2C로 센서 데이터 읽기 및 EEPROM 설정 코드 구현

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

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

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)

// 타임아웃 (ms)
#define I2C_TIMEOUT 100

// 함수 프로토타입
bool zsc31014_start_cm(void);
bool zsc31014_start_nom(void);
bool zsc31014_read_sensor_data(uint16_t *bridge_data, uint16_t *temp_data, uint8_t temp_res_11bit);
bool zsc31014_trigger_measurement(void);
bool zsc31014_read_eeprom(uint8_t word_addr, uint16_t *data);
bool zsc31014_write_eeprom(uint8_t word_addr, uint16_t data);
bool zsc31014_read_all_eeprom(uint16_t *data_array);
bool zsc31014_write_all_eeprom(uint16_t *data_array);

/**
 * @brief Command Mode 진입
 * @retval true 성공, false 에러
 */
bool zsc31014_start_cm(void) {
  uint8_t tx_data[3] = {CMD_START_CM, 0x00, 0x00}; // Start_CM 명령 + 데이터 0x0000

  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  Wire.write(tx_data, 3);
  if (Wire.endTransmission() != 0) {
    Serial.println("Start_CM 실패");
    return false;
  }

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

/**
 * @brief Normal Operation Mode 복귀 및 EEPROM 서명 업데이트
 * @retval true 성공, false 에러
 */
bool zsc31014_start_nom(void) {
  uint8_t tx_data[3] = {CMD_START_NOM, 0x00, 0x00}; // Start_NOM 명령 + 데이터 0x0000

  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  Wire.write(tx_data, 3);
  if (Wire.endTransmission() != 0) {
    Serial.println("Start_NOM 실패");
    return false;
  }

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

/**
 * @brief 센서(브리지) 데이터와 온도 데이터 읽기
 * @param bridge_data 브리지 데이터(14비트) 저장 포인터
 * @param temp_data 온도 데이터(8비트 또는 11비트) 저장 포인터
 * @param temp_res_11bit 온도 해상도 (0: 8비트, 1: 11비트)
 * @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; // Read_DF4 (11비트) 또는 Read_DF3 (8비트)

  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();
  }

  // 상태 비트 확인 (S[1:0], 데이터시트 페이지 24, Table 2.9)
  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비트) 조합
  *bridge_data = ((rx_data[0] & 0x3F) << 8) | rx_data[1];

  // 온도 데이터 조합
  if (byte_count >= 3) {
    if (temp_res_11bit) {
      // 11비트 온도 데이터
      *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)
 * @retval true 성공, false 에러
 */
bool zsc31014_trigger_measurement(void) {
  Wire.beginTransmission(ZSC31014_I2C_ADDR);
  if (Wire.endTransmission() != 0) {
    Serial.println("Write_MR 실패");
    return false;
  }

  // 측정 완료 대기 (1MHz 클럭: 1.5ms, 데이터시트 페이지 31, Table 3.4)
  delay(2);
  return true;
}

/**
 * @brief 특정 EEPROM 워드 읽기
 * @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;
  }

  // Command Mode 진입
  if (!zsc31014_start_cm()) {
    Serial.println("Command Mode 진입 실패");
    return false;
  }

  uint8_t tx_data[3] = {CMD_EEPROM_READ_BASE + word_addr, 0x00, 0x00}; // 읽기 명령
  uint8_t rx_data[3];

  // 읽기 명령 전송
  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;
  }

  // 데이터 수신 (5AHEX + 16비트 데이터)
  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인지 확인
  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];

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

  return true;
}

/**
 * @brief 특정 EEPROM 워드 쓰기
 * @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;
  }

  // Command Mode 진입
  if (!zsc31014_start_cm()) {
    Serial.println("Command Mode 진입 실패");
    return false;
  }

  // T_Config(0x10), Osc_Trim(0x11)은 공장 설정 유지 경고
  if (word_addr == 0x10 || word_addr == 0x11) {
    Serial.print("경고: 워드 0x");
    Serial.print(word_addr, HEX);
    Serial.println("은 공장 설정 유지 권장");
  }

  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;
  }

  // 쓰기 완료 대기 (데이터시트: 10ms, 페이지 37)
  delay(10);

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

  return true;
}

/**
 * @brief 전체 EEPROM 읽기 (0x00 ~ 0x13)
 * @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])) {
      Serial.print("EEPROM 읽기 실패 (워드 0x");
      Serial.print(word_addr, HEX);
      Serial.println(")");
      return false;
    }
    Serial.print("워드 0x");
    Serial.print(word_addr, HEX);
    Serial.print(": 0x");
    Serial.println(data_array[word_addr], HEX);
  }

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

  return true;
}

/**
 * @brief 전체 EEPROM 쓰기 (0x00 ~ 0x13)
 * @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])) {
      Serial.print("EEPROM 쓰기 실패 (워드 0x");
      Serial.print(word_addr, HEX);
      Serial.println(")");
      return false;
    }
    Serial.print("워드 0x");
    Serial.print(word_addr, HEX);
    Serial.print("에 0x");
    Serial.print(data_array[word_addr], HEX);
    Serial.println(" 쓰기 완료");
  }

  // Normal Operation Mode 복귀 및 서명 업데이트
  if (!zsc31014_start_nom()) {
    Serial.println("Normal Operation Mode 복귀 실패");
    return false;
  }

  return true;
}

/**
 * @brief Arduino setup 함수
 */
void setup() {
  Serial.begin(9600);
  Wire.begin(); // I2C 초기화
  while (!Serial); // 시리얼 모니터 대기
  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 전체 읽기 완료");
  } else {
    Serial.println("EEPROM 전체 읽기 실패");
  }

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

  // 특정 워드 쓰기 (예: 워드 0x01에 0x0000 쓰기)
  Serial.println("워드 0x01에 0x0000 쓰기 시작");
  if (zsc31014_write_eeprom(0x01, 0x0000)) {
    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
  sample_data[0x02] = 0x0280; // 슬레이브 주소 0x28
  sample_data[0x0F] = 0x0A80; // PreAmp_Gain=6, A2D_Offset=-1/2 ~ 1/2
  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 환경에서 구현하며, 데이터시트에 명시된 동작을 최대한 유지했습니다.

 

 

반응형