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

[ZSSC3230] 센서 신호 컨디셔너(SSC) STM32을 사용한 I2C 코드 구현

by linuxgo 2025. 8. 15.
반응형

 

이 내용는 ZSSC3230 센서 신호 컨디셔너의 비휘발성 메모리(NVM) 설정, 센서 데이터 측정을 STM32L432KC 마이크로컨트롤러를 I2C 인터페이스를 사용한 구현 방법을 상세히 설명합니다. 데이터시트를 기반으로 작성되었으며, 원시 센서 측정(A2HEX, A3HEX)과 전체 측정(AAHEX)을 위한 설정 및 코드를 포함합니다.

1. ZSSC3230 개요

ZSSC3230은 저전력, 고정밀 커패시턴스-디지털 컨버터로, 센서 신호 컨디셔닝(SSC)을 통해 커패시턴스 센서 데이터를 처리합니다. 최대 18비트 ADC 해상도와 24비트 출력 해상도를 지원하며, I2C 인터페이스를 통해 설정 및 데이터 전송을 수행합니다. 주요 응용 분야는 HVAC, 의료 기기, 웨어러블 장치 등입니다.

주요 특징: 0.5pF~16pF 커패시턴스 범위, 차동/단일 끝 센서 지원, 온도 보정, 저전력 모드(슬립 모드), 최대 10,000회 NVM 쓰기.

2. I2C 인터페이스

ZSSC3230은 I2C 인터페이스를 통해 마이크로컨트롤러와 통신합니다(데이터시트 5.1절, 21~24페이지). I2C 설정과 동작 방식은 다음과 같습니다.

2.1 I2C 프로토콜 사양

  • 주소: 7비트 주소 0x48 (쓰기: 0x90, 읽기: 0x91).
  • 클럭 속도: 최대 400kHz (표준/고속 모드).
  • 프레임 구조:
    • 쓰기: [START] [ADDR+W] [COMMAND] [DATA1] [DATA2] [STOP]
    • 읽기: [START] [ADDR+W] [COMMAND] [RESTART] [ADDR+R] [DATA...] [STOP]
  • 타이밍:
    • 명령어 간 최소 100μs 대기.
    • NVM 쓰기 후 5ms 대기.
    • 최대 응답 시간: ~21ms (최소 업데이트 속도).
  • 상태 바이트: 모든 명령 후 반환, 0x00은 성공, 기타 값은 에러(데이터시트 표 11).

2.2 I2C 명령어 처리

ZSSC3230은 명령어 기반으로 동작하며, 명령어는 1~3바이트로 구성됩니다. 예를 들어:

  • A9HEX: 커맨드 모드 진입, 1바이트, 응답: 상태 바이트.
  • A2HEX: 원시 센서 측정, 3바이트(A2 00 00), 응답: 상태 + 24비트 데이터.
  • 20HEX~3CHEX: NVM 쓰기, 3바이트(CMD DATA_H DATA_L), 응답: 상태.

주의: I2C 통신 시 SCL/SDA 풀업 저항(4.7kΩ 권장)과 전원 안정화 확인 필요.

3. 명령어 리스트

ZSSC3230은 다양한 명령어를 지원하며, 각 명령어는 특정 작업(측정, 설정, 진단)을 수행합니다(데이터시트 표 10, 25~27페이지). 아래는 전체 명령어와 상세 설명입니다.

명령어 코드 설명 입력 출력
NVM 읽기 00HEX~1FHEX NVM 레지스터(주소 0~31) 읽기 1바이트 상태(1) + 데이터(2)
NVM 쓰기 20HEX~3CHEX NVM 레지스터 쓰기 3바이트 (CMD, DATA_H, DATA_L) 상태(1)
체크섬 계산 90HEX NVM 체크섬 계산 및 저장 1바이트 상태(1)
원시 센서 측정 A2HEX SSC 보정 없는 센서 데이터 3바이트 (A2 00 00) 상태(1) + 데이터(3)
사용자 설정 원시 측정 A3HEX 사용자 정의 설정으로 원시 측정 3바이트 (CMD, CONFIG_H, CONFIG_L) 상태(1) + 데이터(3)
원시 온도 측정 A6HEX SSC 보정 없는 온도 데이터 3바이트 (A6 00 00) 상태(1) + 데이터(3)
슬립 모드 A8HEX 저전력 모드 진입 1바이트 상태(1)
커맨드 모드 A9HEX 커맨드 모드 진입 1바이트 상태(1)
전체 측정 AAHEX SSC 보정된 센서+온도 데이터 1바이트 상태(1) + 센서(3) + 온도(3)
주기적 측정 ABHEX 주기적 SSC 측정 시작 1바이트 상태(1) + 센서(3) + 온도(3)
오버샘플링 측정 ACHEX~AFHEX 2x~32x 오버샘플링 측정 1바이트 상태(1) + 센서(3) + 온도(3)
브로큰 칩 테스트 B0HEX 칩 진단 테스트 1바이트 상태(1) + 결과(2)
PDM 정지 B4HEX 주기적 측정 모드 비활성화 1바이트 없음
소프트 리셋 F0HEX 칩 소프트 리셋 2바이트 (F0 00) 없음

4. NVM 설정

ZSSC3230의 NVM은 센서 설정, 보정 계수, 출력 설정을 저장합니다. 원시 센서 측정과 전체 측정은 서로 다른 레지스터를 사용합니다.

4.1 원시 센서 측정 NVM 설정

원시 센서 측정(A2HEX, A3HEX)은 12HEX19HEX를 참조합니다.

NVM 주소 비트 필드 이름 설명 권장 설정
12HEX 5:0 shift_cap 제로-시프트 커패시턴스: 0.0pF~15.75pF 0.25pF (00 0001)
7:6 adc_bits ADC 해상도: 00(12비트), 01(14비트), 10(16비트), 11(18비트) 14비트 (01)
8 noise_mode 0(저전류), 1(저노이즈) 저노이즈 (1)
13:9 cap_range 최대 커패시턴스: 0.5pF~16.0pF 2.0pF (00010)
14 sensor_leakage 누설 전류 보상: 0(비활성화), 1(활성화) 0
15 sensecap_type 센서 유형: 0(차동), 1(단일 끝) 차동 (0)
19HEX 1:0 CC_pin_selection 측정 핀: 00(없음), 01(CC), 10(CC’), 11(CC+CC’) CC (01)
2 Dither 디지털 디더링: 0(비활성화), 1(활성화) 0
3 En_sh2 감산 모드: 0(비활성화), 1(활성화) 0
4 En_shlddrv 액티브 쉴드: 0(비활성화), 1(활성화) 0
5 Dyn_imp 고전류 구동: 0(비활성화), 1(활성화) 0

설정 절차

  1. 센서 특성 확인: 2pF, 차동, CC 핀.
  2. 커맨드 모드 진입: A9HEX.
  3. NVM 설정: 12HEX=0x4102, 19HEX=0x0001.
  4. 체크섬 계산: 90HEX.
  5. 검증: NVM 읽기 및 CRC 확인.

4.2 전체 측정 NVM 설정

전체 측정(AAHEX)은 SSC 보정을 적용하며, 추가 레지스터를 설정합니다.

NVM 주소 비트 필드 이름 설명 권장 설정
00HEX 3:0 Out_res 출력 해상도: 0000(12비트)~1111(24비트) 24비트 (1111)
5:4 Out_mode 출력 모드: 00(센서+온도), 01(센서), 10(온도) 센서+온도 (00)
9:6 Update_rate 업데이트 속도: 0000(최대)~1111(최소) 최대 (0000)
10 SSC_off SSC 보정: 0(활성화), 1(비활성화) 활성화 (0)
01HEX 15:0 Output_min 출력 최소값 -32768 (0x8000)
02HEX 15:0 Output_max 출력 최대값 32767 (0x7FFF)
03HEX 15:0 Offset_S[15:0] 센서 오프셋(24비트 하위) 캘리브레이션, 예: 0x0000
04HEX 15:0 Gain_S[15:0] 센서 게인(24비트 하위) 캘리브레이션, 예: 0x4000
05HEX 15:0 Tcg[15:0] 온도 게인 보정 캘리브레이션, 예: 0x0000
06HEX 15:0 Tco[15:0] 온도 오프셋 보정 캘리브레이션, 예: 0x0000
0DHEX 15:0 Offset_S[23:16], Gain_S[23:16] 상위 비트 캘리브레이션, 예: 0x0000
0EHEX 15:0 Tcg[23:16], Tco[23:16] 상위 비트 캘리브레이션, 예: 0x0000
12HEX 15:0 위와 동일 원시 센서 설정 0x4102
19HEX 15:0 위와 동일 원시 센서 설정 0x0001
1AHEX 2:0 SSC_freq SSC 주파수: 000(최대)~111(최소) 최대 (000)

설정 절차

  1. 센서 및 출력 특성 확인: 2pF, 차동, CC 핀, 24비트 출력.
  2. 커맨드 모드 진입: A9HEX.
  3. PDM 비활성화: B4HEX.
  4. NVM 설정: 00HEX=0x0F00, 01HEX=0x8000, 02HEX=0x7FFF, 03HEX~06HEX, 0DHEX, 0EHEX, 12HEX=0x4102, 19HEX=0x0001, 1AHEX=0x0000.
  5. 체크섬 계산: 90HEX.
  6. 검증: NVM 읽기 및 CRC 확인.

5. STM32L432KC 구현

STM32L432KC를 사용해 ZSSC3230의 I2C 통신, NVM 설정, 모든 명령어를 구현했습니다. STM32Cube HAL 라이브러리를 사용하며, I2C 주소는 0x48(7비트)입니다.

5.1 구현 개요

  • 환경: STM32CubeMX, I2C1(400kHz, 표준 모드), UART(디버깅).
  • 기능:
    • 모든 명령어 지원: 00HEX~FXHEX.
    • NVM 읽기/쓰기, 24비트 계수 처리, CRC 검증.
    • 원시 센서 및 전체 측정 전용 NVM 설정 함수.
    • 에러 처리 및 상태 바이트 확인.
  • 설정 예시: 차동 센서, 2pF, 14비트 ADC, 24비트 출력, CC 핀, 저노이즈, 최대 업데이트 속도.

5.2 코드

ZSSC3230_Full_Implementation.c
/* STM32L432KC I2C를 사용한 ZSSC3230 전체 명령어 및 NVM 설정
 * - STM32Cube HAL 라이브러리 사용
 * - ZSSC3230 I2C 주소: 0x48 (7비트)
 * - 설정: 차동 센서, 2pF, 14비트, CC 핀, 24비트 출력, 빠른 업데이트
 * - 기능: 모든 명령어(00HEX~FXHEX), 24비트 계수, CRC 검증, PDM 비활성화
 */

#include "stm32l4xx_hal.h"
#include 
#include 

/* I2C 핸들 */
extern I2C_HandleTypeDef hi2c1;

/* ZSSC3230 정의 */
#define ZSSC3230_I2C_ADDR    (0x48 << 1)
#define NVM_START_ADDR       0x00
#define NVM_END_ADDR         0x1C
#define CRC_ADDR             0x1C
#define DEFAULT_TIMEOUT_MS   100

/* 24비트 계수 정의 */
typedef struct {
    uint8_t lsb_addr;
    uint8_t msb_addr;
    uint8_t msb_shift;
} CoefficientMap;

static const CoefficientMap coeff_map[] = {
    {0x03, 0x0D, 8}, {0x04, 0x0D, 0}, {0x05, 0x0E, 8}, {0x06, 0x0E, 0},
    {0x07, 0x0F, 8}, {0x08, 0x0F, 0}, {0x09, 0x10, 8}, {0x0A, 0x10, 0},
    {0x0B, 0x11, 8}, {0x0C, 0x11, 0}, {0x16, 0x18, 0}, {0x17, 0x18, 8},
    {0x13, 0x15, 0}, {0x14, 0x15, 8}
};

/* 상태 바이트 확인 */
HAL_StatusTypeDef check_status_byte(uint8_t status) {
    if (status == 0x00) return HAL_OK;
    printf("Error: Status byte 0x%02X\n", status);
    return HAL_ERROR;
}

/* 커맨드 모드 진입 (A9HEX) */
HAL_StatusTypeDef zssc3230_start_command_mode(uint32_t timeout_ms) {
    uint8_t cmd = 0xA9;
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to enter command mode: HAL error %d\n", ret);
        return ret;
    }
    uint8_t status;
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, &status, 1, timeout_ms);
    return (ret == HAL_OK) ? check_status_byte(status) : ret;
}

/* 슬립 모드 진입 (A8HEX) */
HAL_StatusTypeDef zssc3230_start_sleep(uint32_t timeout_ms) {
    uint8_t cmd = 0xA8;
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to enter sleep mode: HAL error %d\n", ret);
        return ret;
    }
    uint8_t status;
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, &status, 1, timeout_ms);
    return (ret == HAL_OK) ? check_status_byte(status) : ret;
}

/* 주기적 측정 모드 (ABHEX) */
HAL_StatusTypeDef zssc3230_start_cyclic_mode(uint32_t *sensor_data, uint32_t *temp_data, uint32_t timeout_ms) {
    uint8_t cmd = 0xAB;
    uint8_t rx_buffer[7];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to start cyclic mode: HAL error %d\n", ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 7, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after ABHEX: HAL error %d\n", ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *sensor_data = (rx_buffer[1] << 16) | (rx_buffer[2] << 8) | rx_buffer[3];
    *temp_data = (rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
    return HAL_OK;
}

/* PDM 비활성화 (B4HEX) */
HAL_StatusTypeDef zssc3230_stop_pdm(uint32_t timeout_ms) {
    uint8_t cmd = 0xB4;
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to stop PDM: HAL error %d\n", ret);
    }
    return ret;
}

/* NVM 읽기 (00HEX ~ 1FHEX) */
HAL_StatusTypeDef zssc3230_nvm_read(uint8_t addr, uint16_t *data, uint32_t timeout_ms) {
    uint8_t cmd = addr;
    uint8_t rx_buffer[3];
    HAL_StatusTypeDef ret;

    if (addr > NVM_END_ADDR) {
        printf("Invalid NVM address: 0x%02X\n", addr);
        return HAL_ERROR;
    }

    ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send read command 0x%02X: HAL error %d\n", cmd, ret);
        return ret;
    }

    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 3, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data for address 0x%02X: HAL error %d\n", addr, ret);
        return ret;
    }

    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;

    *data = (rx_buffer[1] << 8) | rx_buffer[2];
    return HAL_OK;
}

/* NVM 쓰기 (20HEX ~ 3CHEX) */
HAL_StatusTypeDef zssc3230_nvm_write(uint8_t addr, uint16_t data, uint32_t timeout_ms) {
    uint8_t tx_buffer[3] = {0x20 + addr, (data >> 8) & 0xFF, data & 0xFF};
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, tx_buffer, 3, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to write to address 0x%02X: HAL error %d\n", addr, ret);
        return ret;
    }
    uint8_t status;
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, &status, 1, timeout_ms);
    return (ret == HAL_OK) ? check_status_byte(status) : ret;
}

/* NVM 체크섬 계산 (90HEX) */
HAL_StatusTypeDef zssc3230_calculate_nvm_checksum(uint32_t timeout_ms) {
    uint8_t cmd = 0x90;
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to calculate checksum: HAL error %d\n", ret);
        return ret;
    }
    uint8_t status;
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, &status, 1, timeout_ms);
    return (ret == HAL_OK) ? check_status_byte(status) : ret;
}

/* 원시 센서 측정 (A2HEX) */
HAL_StatusTypeDef zssc3230_raw_sensor_measure(uint32_t *data, uint32_t timeout_ms) {
    uint8_t tx_buffer[3] = {0xA2, 0x00, 0x00};
    uint8_t rx_buffer[4];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, tx_buffer, 3, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send A2HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 4, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after A2HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *data = (rx_buffer[1] << 16) | (rx_buffer[2] << 8) | rx_buffer[3];
    return HAL_OK;
}

/* 사용자 설정 원시 센서 측정 (A3HEX) */
HAL_StatusTypeDef zssc3230_raw_sensor_measure_custom(uint16_t config, uint32_t *data, uint32_t timeout_ms) {
    uint8_t tx_buffer[3] = {0xA3, (config >> 8) & 0xFF, config & 0xFF};
    uint8_t rx_buffer[4];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, tx_buffer, 3, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send A3HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 4, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after A3HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *data = (rx_buffer[1] << 16) | (rx_buffer[2] << 8) | rx_buffer[3];
    return HAL_OK;
}

/* 원시 온도 측정 (A6HEX) */
HAL_StatusTypeDef zssc3230_raw_temp_measure(uint32_t *data, uint32_t timeout_ms) {
    uint8_t tx_buffer[3] = {0xA6, 0x00, 0x00};
    uint8_t rx_buffer[4];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, tx_buffer, 3, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send A6HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 4, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after A6HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *data = (rx_buffer[1] << 16) | (rx_buffer[2] << 8) | rx_buffer[3];
    return HAL_OK;
}

/* 전체 측정 (AAHEX) */
HAL_StatusTypeDef zssc3230_measure(uint32_t *sensor_data, uint32_t *temp_data, uint32_t timeout_ms) {
    uint8_t cmd = 0xAA;
    uint8_t rx_buffer[7];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send AAHEX: HAL error %d\n", ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 7, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after AAHEX: HAL error %d\n", ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *sensor_data = (rx_buffer[1] << 16) | (rx_buffer[2] << 8) | rx_buffer[3];
    *temp_data = (rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
    return HAL_OK;
}

/* 오버샘플링 측정 (ACHEX, ADHEX, AEHEX, AFHEX) */
HAL_StatusTypeDef zssc3230_oversample_measure(uint8_t oversample_cmd, uint32_t *sensor_data, uint32_t *temp_data, uint32_t timeout_ms) {
    if (oversample_cmd != 0xAC && oversample_cmd != 0xAD && oversample_cmd != 0xAE && oversample_cmd != 0xAF) {
        printf("Invalid oversample command: 0x%02X\n", oversample_cmd);
        return HAL_ERROR;
    }
    uint8_t cmd = oversample_cmd;
    uint8_t rx_buffer[7];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send 0x%02X: HAL error %d\n", cmd, ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 7, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after 0x%02X: HAL error %d\n", cmd, ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *sensor_data = (rx_buffer[1] << 16) | (rx_buffer[2] << 8) | rx_buffer[3];
    *temp_data = (rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
    return HAL_OK;
}

/* 브로큰 칩 테스트 (B0HEX) */
HAL_StatusTypeDef zssc3230_broken_chip_test(uint16_t *result, uint32_t timeout_ms) {
    uint8_t cmd = 0xB0;
    uint8_t rx_buffer[3];
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, &cmd, 1, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to send B0HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = HAL_I2C_Master_Receive(&hi2c1, ZSSC3230_I2C_ADDR, rx_buffer, 3, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to receive data after B0HEX: HAL error %d\n", ret);
        return ret;
    }
    ret = check_status_byte(rx_buffer[0]);
    if (ret != HAL_OK) return ret;
    *result = (rx_buffer[1] << 8) | rx_buffer[2];
    return HAL_OK;
}

/* 소프트 리셋 (FXHEX) */
HAL_StatusTypeDef zssc3230_soft_reset(uint32_t timeout_ms) {
    uint8_t tx_buffer[2] = {0xF0, 0x00};
    HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, ZSSC3230_I2C_ADDR, tx_buffer, 2, timeout_ms);
    if (ret != HAL_OK) {
        printf("Soft reset failed: HAL error %d\n", ret);
    }
    return ret;
}

/* 24비트 계수 읽기 */
HAL_StatusTypeDef zssc3230_nvm_read_24bit(uint8_t coeff_idx, int32_t *value, uint32_t timeout_ms) {
    if (coeff_idx >= sizeof(coeff_map) / sizeof(coeff_map[0])) {
        printf("Invalid coefficient index: %d\n", coeff_idx);
        return HAL_ERROR;
    }
    uint16_t lsb_data, msb_data;
    HAL_StatusTypeDef ret = zssc3230_nvm_read(coeff_map[coeff_idx].lsb_addr, &lsb_data, timeout_ms);
    if (ret != HAL_OK) return ret;
    ret = zssc3230_nvm_read(coeff_map[coeff_idx].msb_addr, &msb_data, timeout_ms);
    if (ret != HAL_OK) return ret;
    uint32_t raw = ((msb_data >> coeff_map[coeff_idx].msb_shift) & 0xFF) << 16 | lsb_data;
    *value = (raw & 0x800000) ? (int32_t)(raw | 0xFF000000) : (int32_t)raw;
    return HAL_OK;
}

/* 24비트 계수 쓰기 */
HAL_StatusTypeDef zssc3230_nvm_write_24bit(uint8_t coeff_idx, int32_t value, uint32_t timeout_ms) {
    if (coeff_idx >= sizeof(coeff_map) / sizeof(coeff_map[0])) {
        printf("Invalid coefficient index: %d\n", coeff_idx);
        return HAL_ERROR;
    }
    HAL_StatusTypeDef ret;
    uint16_t lsb_data = value & 0xFFFF;
    uint16_t msb_data;
    ret = zssc3230_nvm_read(coeff_map[coeff_idx].msb_addr, &msb_data, timeout_ms);
    if (ret != HAL_OK) return ret;
    uint8_t shift = coeff_map[coeff_idx].msb_shift;
    uint8_t mask = (shift == 0) ? 0xFF00 : 0x00FF;
    msb_data = (msb_data & mask) | (((value >> 16) & 0xFF) << shift);
    ret = zssc3230_nvm_write(coeff_map[coeff_idx].lsb_addr, lsb_data, timeout_ms);
    if (ret != HAL_OK) return ret;
    ret = zssc3230_nvm_write(coeff_map[coeff_idx].msb_addr, msb_data, timeout_ms);
    return ret;
}

/* NVM 전체 읽기 */
HAL_StatusTypeDef zssc3230_nvm_read_all(uint16_t *data_buffer, uint32_t timeout_ms) {
    HAL_StatusTypeDef ret = zssc3230_start_command_mode(timeout_ms);
    if (ret != HAL_OK) return ret;
    for (uint8_t addr = NVM_START_ADDR; addr <= NVM_END_ADDR; addr++) {
        ret = zssc3230_nvm_read(addr, &data_buffer[addr], timeout_ms);
        if (ret != HAL_OK) return ret;
    }
    return HAL_OK;
}

/* NVM 전체 쓰기 */
HAL_StatusTypeDef zssc3230_nvm_write_all(uint16_t *data_buffer, uint32_t timeout_ms) {
    HAL_StatusTypeDef ret = zssc3230_start_command_mode(timeout_ms);
    if (ret != HAL_OK) return ret;
    ret = zssc3230_stop_pdm(timeout_ms);
    if (ret != HAL_OK) return ret;
    for (uint8_t addr = NVM_START_ADDR; addr <= NVM_END_ADDR; addr++) {
        ret = zssc3230_nvm_write(addr, data_buffer[addr], timeout_ms);
        if (ret != HAL_OK) return ret;
    }
    ret = zssc3230_calculate_nvm_checksum(timeout_ms);
    return ret;
}

/* CRC 계산 */
uint16_t zssc3230_calculate_crc(uint16_t *data_buffer) {
    uint32_t crc = 0;
    uint16_t poly = 0x8005;
    for (uint8_t addr = NVM_START_ADDR; addr < NVM_END_ADDR; addr++) {
        uint16_t data = data_buffer[addr];
        for (int i = 15; i >= 0; i--) {
            uint32_t bit = (crc >> 15) ^ ((data >> i) & 1);
            crc = (crc << 1) | bit;
            if (bit) crc ^= poly;
        }
    }
    return (uint16_t)(crc & 0xFFFF);
}

/* CRC 검증 */
HAL_StatusTypeDef zssc3230_verify_crc(uint16_t *data_buffer, uint32_t timeout_ms) {
    uint16_t calc_crc = zssc3230_calculate_crc(data_buffer);
    uint16_t stored_crc;
    HAL_StatusTypeDef ret = zssc3230_nvm_read(CRC_ADDR, &stored_crc, timeout_ms);
    if (ret != HAL_OK) {
        printf("Failed to read stored CRC\n");
        return ret;
    }
    if (calc_crc == stored_crc) {
        printf("CRC verification passed (0x%04X)\n", calc_crc);
        return HAL_OK;
    } else {
        printf("CRC verification failed (Calculated: 0x%04X, Stored: 0x%04X)\n", calc_crc, stored_crc);
        return HAL_ERROR;
    }
}

/* 원시 센서 측정 전 NVM 설정 */
HAL_StatusTypeDef zssc3230_setup_raw_sensor(uint32_t timeout_ms) {
    HAL_StatusTypeDef ret;

    // 1. 커맨드 모드 진입
    ret = zssc3230_start_command_mode(timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("Command mode entered\n");

    // 2. PDM 비활성화
    ret = zssc3230_stop_pdm(timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("PDM stopped\n");

    // 3. 12HEX 설정 (차동, 2pF, 14비트, 저노이즈, shift_cap=0.25pF)
    uint16_t reg_12 = (0 << 15) | // sensecap_type: 차동
                      (0 << 14) | // sensor_leakage: 비활성화
                      (2 << 9)  | // cap_range: 2.0pF
                      (1 << 8)  | // noise_mode: 저노이즈
                      (1 << 6)  | // adc_bits: 14비트
                      (1);        // shift_cap: 0.25pF
    ret = zssc3230_nvm_write(0x12, reg_12, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x12] set to 0x%04X\n", reg_12);

    // 4. 19HEX 설정 (CC 핀)
    uint16_t reg_19 = (1 << 0); // CC_pin_selection: CC
    ret = zssc3230_nvm_write(0x19, reg_19, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x19] set to 0x%04X\n", reg_19);

    // 5. 체크섬 계산
    ret = zssc3230_calculate_nvm_checksum(timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("Checksum calculated\n");

    return HAL_OK;
}

/* 전체 측정 전 NVM 설정 */
HAL_StatusTypeDef zssc3230_setup_full_measurement(uint32_t timeout_ms) {
    HAL_StatusTypeDef ret;

    // 1. 커맨드 모드 진입
    ret = zssc3230_start_command_mode(timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("Command mode entered\n");

    // 2. PDM 비활성화
    ret = zssc3230_stop_pdm(timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("PDM stopped\n");

    // 3. 00HEX 설정 (24비트, 센서+온도, 빠른 업데이트, SSC 활성화)
    uint16_t reg_00 = (0 << 10) | // SSC_off: 0
                      (0 << 6)  | // Update_rate: 0000
                      (0 << 4)  | // Out_mode: 00
                      (15);       // Out_res: 1111 (24비트)
    ret = zssc3230_nvm_write(0x00, reg_00, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x00] set to 0x%04X\n", reg_00);

    // 4. 01HEX 설정 (출력 최소값: -32768)
    ret = zssc3230_nvm_write(0x01, 0x8000, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x01] set to 0x8000\n");

    // 5. 02HEX 설정 (출력 최대값: 32767)
    ret = zssc3230_nvm_write(0x02, 0x7FFF, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x02] set to 0x7FFF\n");

    // 6. 보정 계수 설정 (예: 기본값)
    uint16_t calib_data[] = {0x0000, 0x4000, 0x0000, 0x0000}; // Offset_S, Gain_S, Tcg, Tco
    uint8_t calib_addrs[] = {0x03, 0x04, 0x05, 0x06};
    for (int i = 0; i < 4; i++) {
        ret = zssc3230_nvm_write(calib_addrs[i], calib_data[i], timeout_ms);
        if (ret != HAL_OK) return ret;
        printf("NVM[0x%02X] set to 0x%04X\n", calib_addrs[i], calib_data[i]);
    }
    ret = zssc3230_nvm_write(0x0D, 0x0000, timeout_ms); // Offset_S[23:16], Gain_S[23:16]
    if (ret != HAL_OK) return ret;
    printf("NVM[0x0D] set to 0x0000\n");
    ret = zssc3230_nvm_write(0x0E, 0x0000, timeout_ms); // Tcg[23:16], Tco[23:16]
    if (ret != HAL_OK) return ret;
    printf("NVM[0x0E] set to 0x0000\n");

    // 7. 12HEX 설정 (차동, 2pF, 14비트, 저노이즈, shift_cap=0.25pF)
    uint16_t reg_12 = (0 << 15) | // sensecap_type: 차동
                      (0 << 14) | // sensor_leakage: 비활성화
                      (2 << 9)  | // cap_range: 2.0pF
                      (1 << 8)  | // noise_mode: 저노이즈
                      (1 << 6)  | // adc_bits: 14비트
                      (1);        // shift_cap: 0.25pF
    ret = zssc3230_nvm_write(0x12, reg_12, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x12] set to 0x%04X\n", reg_12);

    // 8. 19HEX 설정 (CC 핀)
    uint16_t reg_19 = (1 << 0); // CC_pin_selection: CC
    ret = zssc3230_nvm_write(0x19, reg_19, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x19] set to 0x%04X\n", reg_19);

    // 9. 1AHEX 설정 (SSC 주파수: 최대)
    ret = zssc3230_nvm_write(0x1A, 0x0000, timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("NVM[0x1A] set to 0x0000\n");

    // 10. 체크섬 계산
    ret = zssc3230_calculate_nvm_checksum(timeout_ms);
    if (ret != HAL_OK) return ret;
    printf("Checksum calculated\n");

    return HAL_OK;
}

/* 메인 함수: 테스트 및 디버깅 */
void zssc3230_command_example(void) {
    HAL_StatusTypeDef ret;
    uint16_t nvm_data[29];
    uint32_t sensor_data, temp_data;
    uint16_t result;
    uint32_t timeout_ms = DEFAULT_TIMEOUT_MS;

    // 1. 원시 센서 측정 설정 및 테스트
    ret = zssc3230_setup_raw_sensor(timeout_ms);
    if (ret == HAL_OK) {
        printf("Raw sensor setup completed\n");
        ret = zssc3230_raw_sensor_measure(&sensor_data, timeout_ms);
        if (ret == HAL_OK) printf("Raw sensor data: 0x%06X\n", sensor_data);
        else printf("Raw sensor measurement failed\n");

        uint16_t config = 0x4102; // 12HEX 상위 + 19HEX 하위
        ret = zssc3230_raw_sensor_measure_custom(config, &sensor_data, timeout_ms);
        if (ret == HAL_OK) printf("Custom raw sensor data: 0x%06X\n", sensor_data);
        else printf("Custom raw sensor measurement failed\n");
    } else {
        printf("Raw sensor setup failed\n");
    }

    // 2. 전체 측정 설정 및 테스트
    ret = zssc3230_setup_full_measurement(timeout_ms);
    if (ret == HAL_OK) {
        printf("Full measurement setup completed\n");
        ret = zssc3230_measure(&sensor_data, &temp_data, timeout_ms);
        if (ret == HAL_OK) {
            printf("SSC-corrected sensor: 0x%06X, temp: 0x%06X\n", sensor_data, temp_data);
        } else {
            printf("Full measurement failed\n");
        }
    } else {
        printf("Full measurement setup failed\n");
    }

    // 3. 기타 명령어 테스트
    ret = zssc3230_raw_temp_measure(&temp_data, timeout_ms);
    if (ret == HAL_OK) printf("Raw temp data: 0x%06X\n", temp_data);
    else printf("Raw temp measurement failed\n");

    ret = zssc3230_oversample_measure(0xAC, &sensor_data, &temp_data, timeout_ms);
    if (ret == HAL_OK) printf("Oversample-2 sensor: 0x%06X, temp: 0x%06X\n", sensor_data, temp_data);
    else printf("Oversample-2 measurement failed\n");

    ret = zssc3230_start_cyclic_mode(&sensor_data, &temp_data, timeout_ms);
    if (ret == HAL_OK) printf("Cyclic mode started, sensor: 0x%06X, temp: 0x%06X\n", sensor_data, temp_data);
    else printf("Cyclic mode failed\n");

    ret = zssc3230_stop_pdm(timeout_ms);
    if (ret == HAL_OK) printf("PDM stopped\n");
    else printf("PDM stop failed\n");

    ret = zssc3230_broken_chip_test(&result, timeout_ms);
    if (ret == HAL_OK) printf("Broken chip test: %s (0x%04X)\n", result ? "Fail" : "Pass", result);
    else printf("Broken chip test failed\n");

    ret = zssc3230_start_sleep(timeout_ms);
    if (ret == HAL_OK) printf("Sleep mode entered\n");
    else printf("Sleep mode failed\n");

    ret = zssc3230_soft_reset(timeout_ms);
    if (ret == HAL_OK) printf("Soft reset succeeded\n");
    else printf("Soft reset failed\n");

    // 4. NVM 전체 읽기 및 CRC 검증
    ret = zssc3230_nvm_read_all(nvm_data, timeout_ms);
    if (ret == HAL_OK) {
        printf("NVM read all succeeded:\n");
        for (uint8_t i = 0; i <= NVM_END_ADDR; i++) {
            printf("NVM[0x%02X] = 0x%04X\n", i, nvm_data[i]);
        }
        ret = zssc3230_verify_crc(nvm_data, timeout_ms);
    } else {
        printf("NVM read all failed\n");
    }

    // 5. 24비트 계수 테스트 (Tcg, 인덱스 2)
    ret = zssc3230_nvm_write_24bit(2, 0x123456, timeout_ms);
    if (ret == HAL_OK) printf("Tcg write (24-bit) succeeded\n");
    else printf("Tcg write failed\n");

    int32_t coeff_value;
    ret = zssc3230_nvm_read_24bit(2, &coeff_value, timeout_ms);
    if (ret == HAL_OK) printf("Tcg (24-bit) = 0x%06X\n", (uint32_t)coeff_value);
    else printf("Tcg read failed\n");
}

5.3 구현 상세

코드는 다음과 같은 주요 기능을 제공합니다:

  • 모듈화: 각 명령어(A2HEX, AAHEX 등)를 별도 함수로 구현해 재사용성 향상.
  • NVM 설정 함수:
    • zssc3230_setup_raw_sensor: 원시 측정용 12HEX, 19HEX 설정.
    • zssc3230_setup_full_measurement: 전체 측정용 00HEX~1AHEX 설정.
  • 24비트 계수 처리: zssc3230_nvm_read_24bit, zssc3230_nvm_write_24bit로 보정 계수 관리.
  • CRC 검증: zssc3230_calculate_crc, zssc3230_verify_crc로 데이터 무결성 확인.
  • 에러 처리: HAL 상태와 ZSSC3230 상태 바이트를 확인해 디버깅 용이.
  • 테스트 루틴: zssc3230_command_example로 모든 기능 테스트.

설정은 차동 센서(2pF, CC 핀, 14비트 ADC, 24비트 출력, 저노이즈, 최대 업데이트 속도)를 가정하며, 실제 애플리케이션에서는 캘리브레이션 데이터를 사용해야 합니다.

6. 주의사항

  • 보정 계수: 03HEX~0EHEX는 실제 센서 캘리브레이션 데이터로 설정해야 하며, 예시 값(0x0000, 0x4000)은 테스트용입니다.
  • 커패시턴스 범위: shift_capcap_range 이하로 설정(예: 0.25pF ≤ 2pF).
  • I2C 타이밍: 명령어 간 100μs, NVM 쓰기 후 5ms 대기 준수.
  • NVM 쓰기 제한: 약 10,000회로 제한되므로 빈번한 쓰기 피하기.
  • PDM 모드: 설정 변경 전 B4HEX로 비활성화.
  • 하드웨어: I2C 핀(GPIOB, 4.7kΩ 풀업 저항), VDD 안정화(2.3V~3.6V) 확인.
  • 디버깅: UART 출력을 통해 상태 바이트와 에러 로그 확인.

7. 사용 방법

  1. STM32CubeMX 설정:
    • I2C1 활성화(400kHz, 표준 모드, GPIOB 핀).
    • UART 활성화(디버깅용, 예: USART2).
    • 시스템 클럭 설정(예: 80MHz).
  2. 코드 통합: 위 코드를 main.c 또는 별도 소스 파일에 추가.
  3. 빌드 및 디버깅: STM32CubeIDE로 빌드, zssc3230_command_example 호출.
  4. 캘리브레이션: 실제 센서 데이터로 보정 계수 갱신.
  5. 테스트: UART 터미널로 출력 확인.

8. 결론

ZSSC3230은 고정밀 커패시턴스 센서 애플리케이션에 적합하며, I2C 인터페이스와 NVM 설정을 통해 유연한 데이터 처리가 가능합니다. 이 글의 코드는 STM32L432KC에서 모든 명령어와 설정을 구현하여 안정적인 측정을 보장합니다. 실제 구현 시 캘리브레이션, 타이밍, 하드웨어 설정에 주의해야 하며, 추가 최적화(예: 인터럽트 기반 I2C 처리)를 고려할 수 있습니다.

반응형