사용한 MLX90614ESF-BCC 모델 탑재 모듈

  • 사용 보드 : nRF52840-PDK
  • 사용 IDE : SEGGER Embedded Studio for ARM (노르딕 제품 사용하는 경우 무료 라이센스 이용 가능)
  • 작동 전압 : 2.6~3.6V (MLX90614Bxx, MLX90614Dxx 모델 기준, MLX90614Axx은 4.5~5.5V)
  • (사용 모듈의 경우, Low Dropout Voltage 레귤레이터 탑재로 3~5V에서 작동)
  • 로직 레벨 : Input High (Min : (VDD-0.1)V), Input Low (Max : 0.6V) (MLX90614Bxx, Dxx 기준)
  • 통신 방식 : PWM, SMBus Compatible 2-Wire Interface (10~100KHz)
  • Slave Address : 0x5A
  • SDK 내 예제 프로젝트를 기반으로 작성 (\examples\peripheral\twi_master_using_nrf_twi_mngr)
  • (SDK 다운로드 : https://developer.nordicsemi.com/)

[센서]

  • Power On Reset 이후, 0.25초 지난 후부터 유효 출력 (RAM내 데이터)
  • 주변 온도 -40°C~+125°C, 물체 온도 -70°C~+380°C 까지 측정 가능
  • 측정 정확도 : 0.5°C
  • 측정 해상도 : 0.02°C
  • PWM 출력의 경우, 0.14°C 해상도로 -20~120°C까지 온도 측정 가능
  • Factory calibrated 된 출력을 갖는다
  • 측정값은 FOV 내 모든 물체의 평균 온도값
  • 센서 장착 주변부에 센서를 뜨겁게 or 차갑게 만드는 요소가 있어선 안되고 뜨겁거나 차가운 측정 물체에 너무 가깝게 근접 시켜서도 안된다
  • xCx(ex:MLX90614BCC) 버전의 경우 내부적으로 측정된 열경사도와 측정된 온도를 보상에 사용한다
  • 이 경우 xCx버전은 열경사도에 덜 민감해지지만 영향이 완전히 없어진 상태는 아니다
  • 따라서 열경사도의 원인을 가능한 피하거나 원인으로부터 센서를 보호해주는 것이 중요하다
  • 물체 방사율 1에 대해 교정(Calibration)되어 있지만 재교정없이 0.1~1.0 사이의 방사율로 조정할 수 있다

[작동 방식]

 

STM32 - I2C 통신 MLX90614 적외선 비접촉 온도 측정 센서

작동 전압 : 2.6~3.6V 통신 방식 : PWM, I2C(10~100KHz) 주변 온도 -40°C~+125°C, 물체 온도 -70°C~+380°C 까지 측정 가능 측정 정확도 : 0.5°C 측정 해상도 : 0.02°C PWM 출력의 경우,..

rs29.tistory.com

 

[사전 설정]

- TWI 통신에 사용할 TWI Sensor module 라이브러리를 이용하기 위해 필요

- nrf_twi_sensor.c 추가 (SDK 폴더 내 \components\libraries\twi_sensor 에 위치)

- 프로젝트에 twi_sensor 라이브러리 폴더 추가

 

- sdk_config.h 에 TWI Sensor 활성화 부분 추가 및 1로 설정 (nRF_Drivers와 nRF_Libraries 사이에 추가)

(<h>nRF_Drivers_External ~ </h> 까지)

// <e> UART1_ENABLED - Enable UART1 instance
//==========================================================
#ifndef UART1_ENABLED
#define UART1_ENABLED 0
#endif
// </e>

// </e>

// </h> 
//==========================================================

// <h> nRF_Drivers_External 

//==========================================================
// <q> NRF_TWI_SENSOR_ENABLED  - nrf_twi_sensor - nRF TWI Sensor module
 

#ifndef NRF_TWI_SENSOR_ENABLED
#define NRF_TWI_SENSOR_ENABLED 1	//0 비활성화
#endif

// </h> 
//==========================================================

// <h> nRF_Libraries 

//==========================================================
// <e> APP_SCHEDULER_ENABLED - app_scheduler - Events scheduler
//==========================================================
#ifndef APP_SCHEDULER_ENABLED
#define APP_SCHEDULER_ENABLED 1
#endif

 

[코드]

- SDK 폴더 내 components\drivers_ext 에 위치한 CCS811, LPS22HB 드라이버 등을 바탕으로 작성됨

- mlx90614.c, mlx90614.h, mlx90614_internal.h 하나의 소스 파일, 두 개의 헤더 파일로 구성

 

- 매크로 선언

/*
main.c
*/

//MLX90614와의 통신에 사용할 TWI 인스턴스 ID
#define TWI_INSTANCE_ID_1 1

//TWI trasaction manager 인스턴스 Queue 최대 크기 (대기 전송 갯수)
#define MAX_PENDING_TRANSACTIONS 255

/*
- TWI transaction manager 인스턴스 정의 매크로
생성할 TWI transaction manager 인스턴스 이름, 트랜잭션 Queue 크기, 사용할 TWI 인스턴스
*/
NRF_TWI_MNGR_DEF(m_nrf_twi_mngr_1, MAX_PENDING_TRANSACTIONS, TWI_INSTANCE_ID_1);

/*
- 공통 TWI 센서 인스턴스 생성 매크로
공통 센서 인스턴스 이름, 사용할 TWI transaction manager 인스턴스, 버퍼 크기
(버퍼 크기의 경우 TWI manager Queue 크기보다 작거나 같아야 한다)
*/
NRF_TWI_SENSOR_DEF(sensor_instance_1, &m_nrf_twi_mngr_1, MAX_PENDING_TRANSACTIONS);

/*
- 센서 인스턴스 생성 매크로
센서 인스턴스 이름, 사용할 공통 TWI 센서 인스턴스, 센서 주소
*/
MLX90614_INSTANCE_DEF(mlx90614_instance, &sensor_instance_1, MLX90614_ADDR);
/*
mlx90614.h
*/

//MLX90614xCx 모델 사용하는 경우 주석 해제
//#define MLX90614xCx

//CRC-8 계산에 이용
#define MLX90614_Write  0
#define MLX90614_Read   1

//센서 주소
#define MLX90614_ADDR 0x5A

//통신에 사용할 TWI manager instance 에 요구하는 최소 Queue 크기
#define MLX90614_MIN_QUEUE_SIZE 4

//MLX90614 통신에 사용할 SDA, SCL PIN
#define MLX90614_SDA_PIN 28
#define MLX90614_SCL_PIN 29

//데이터 콜백 프로토타입
typedef void (* mlx90614_data_callback_t)(ret_code_t result, mlx90614_reg_data_t * p_raw_data);

//MLX90614 인스턴스 정의 매크로
#define MLX90614_INSTANCE_DEF(_mlx90614_inst_name, _p_twi_sensor, _sensor_address)                \
    MLX90614_INTERNAL_INSTANCE_DEF(_mlx90614_inst_name, _p_twi_sensor, _sensor_address)
/*
mlx90614_internal.h
*/

//전송할 데이터 바이트 크기 (송수신 동일. LSB+MSB+PEC)
#define MLX90614_REGISTER_BYTES 3

//MLX90614 RAM 및 EEPROM Register 주소
#define MLX90614_REG_AMBIENT_TEMP   0x06	//주변 온도
#define MLX90614_REG_OBJECT_1_TEMP  0x07	//물체 온도 1
#define MLX90614_REG_OBJECT_2_TEMP  0x08	//물체 온도 2
#define MLX90614_REG_EMISSIVITY_1 0x24	//방사율 주소
#define MLX90614_REG_EMISSIVITY_2 0x2F	//MLX90614xCx 모델을 사용할 경우에만 이용할 추가 방사율 주소
#define MLX90614_REG_SLEEP  0xFF	//슬립 모드 진입에 사용

/*
MLX90614 인스턴스 구조체
p_sensor_data : NRF_TWI_SENSOR_DEF을 통해 생성한 common twi sensor instance
sensor_addr : MLX90614_INSTANCE_DEF를 통해 MLX90614 인스턴스를 생성할 때 입력한 센서 주소
*/
typedef struct
{
    nrf_twi_sensor_t * const p_sensor_data;
    uint8_t const            sensor_addr;
} mlx90614_instance_t;


/*
센서 인스턴스 생성 매크로
*/
#define MLX90614_INTERNAL_INSTANCE_DEF(_mlx90614_inst_name, _p_twi_sensor, _sensor_address)\
    static mlx90614_instance_t _mlx90614_inst_name =                                     \
    {                                                                                    \
        .p_sensor_data = _p_twi_sensor,                                                  \
        .sensor_addr   = _sensor_address,                                                \
    }

- 구조체

/*
mlx90614.h
*/

/*
reg_data : MLX90614로부터 읽어 온 데이터를 저장하거나 MLX90614 EEPROM 레지스터에 입력할 데이터를 저장
	RAM, EEPROM 의 각 레지스터는 16bit 크기를 갖는다
pec : MLX90614로부터 읽어 온 데이터가 제대로 전송이 됐는지 확인하기 위한 Packet Error Code 
	CRC-8 계산 필요
reg_addr : 해당 변수 구조체가 접근할 MLX90614의 레지스터 주소를 저장하는 변수
	PEC 계산에도 이용된다 (단순 레지스터 주소 저장용일뿐. 데이터 전송에는 이용되지 않음)
구조체 변수를 통해 레지스터 데이터를 읽거나 쓸 때, reg_data의 LSB -> MSB -> pec 순으로 송수신 된다
MLX90614의 데이터 송수신은 LSB부터 시작되므로 reg_data 의 MSB, LSB 스왑은 필요없다
*/
typedef struct
{
    uint16_t reg_data;
    uint8_t pec;
    uint8_t reg_addr;
} mlx90614_reg_data_t;

- TWI 설정 및 초기화 (main.c)

//MLX90614와의 통신에 사용할 TWI 통신 설정
static void twi_config(void)
{
    uint32_t err_code;
    
    //통신에 사용할 핀, 속도, 인터럽트 우선 순위 설정
    nrf_drv_twi_config_t const config={
      .scl=29,
      .sda=28,
      .frequency=NRF_DRV_TWI_FREQ_100K,
      .interrupt_priority=APP_IRQ_PRIORITY_MID,
      };
    
    //앞서 생성한 설정을 바탕으로 TWI transaction manager instance 초기화
    err_code=nrf_twi_mngr_init(&m_nrf_twi_mngr_1,&config);
    APP_ERROR_CHECK(err_code);
}

- Register Data Read 함수

/*
mlx90614_instance_t : MLX90614_INSTANCE_DEF을 통해 생성한 MLX90614 센서 인스턴스
mlx90614_reg_data_t : 읽어 온 데이터를 저장할 센서 데이터 구조체
mlx90614_data_callback_t : 데이터 수신 완료 후 호출될 콜백 함수
*/
ret_code_t mlx90614_reg_read(mlx90614_instance_t const * p_instance,
                             mlx90614_reg_data_t *       p_reg_data,
                             mlx90614_data_callback_t    user_cb
                            )
{
    ASSERT(p_instance != NULL);
    ASSERT(p_reg_data != NULL);

    /*
    p_instance->p_sensor_data : NRF_TWI_SENSOR_DEF을 통해 설정된 공통 센서 인스턴스
    p_instance->sensor_addr : MLX90614 센서 인스턴스에 설정된 MLX90614 센서 주소
    p_reg_data->reg_addr : 전달받은 구조체에 저장된 레지스터 주소
    			해당 레지스터 주소에 접근해 데이터를 읽어온다
    nrf_twi_sensor_reg_cb_t : 데이터 수신 완료 후 호출 할 콜백 함수
    MLX90614_REGISTER_BYTES : 읽어 올 데이터의 바이트 크기 (uint8_t 기준)
    			(Data 16bit(LSB(8)+MSB(8)) + PEC(8bit)=24bit)                
    */
    return nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                                   p_instance->sensor_addr,
                                   p_reg_data->reg_addr,
                                   (nrf_twi_sensor_reg_cb_t) user_cb,
                                   (uint8_t *) p_reg_data,
                                   MLX90614_REGISTER_BYTES);
}
  • MLX90614 RAM, EEPROM의 레지스터 주소에 액세스해 레지스터에 저장된 16bit 데이터를 읽어오는 함수
  • nrf_twi_sensor_reg_read 함수는 nrf_twi_mngr_schedule 함수를 이용하므로 수신이 완료될 때까지 대기하는게 아니라 TWI transaction manager instance의 transaction queue에 데이터 수신을 대기시킨다
  • transaction queue에 대기해있던 데이터 수신이 완료된 이후에 콜백 함수가 호출된다
  • (콜백 함수 호출이 필요없다면 NULL 값을 입력)

- 온도 데이터, 방사율 데이터 변환 함수

//읽어 온 16bit 방사율 데이터를 데이터 시트에 나와있는 식을 통해 실제 방사율값으로 변환
inline float mlx90614_emissivity_conversion(uint16_t emissivity_val)
{
    return (emissivity_val+1)/65536.0;
}

//읽어 온 16bit 온도 데이터를 데이터 시트에 나와있는 식을 통해 실제 온도값으로 변환 (섭씨 기준)
inline double mlx90614_temp_conversion(uint16_t temperature_val)
{
    return (temperature_val*0.02)-273.15;
}
  • 콜백 함수에서 사용

- CRC-8 테이블 및 계산 함수

/*
속도면에서 필요할 때마다 CRC 값을 계산하는 방식보다 계산된 값이 저장된 테이블을 통해 
CRC 계산값을 찾는 것이 더 유리해 테이블 사용이 권장되므로 테이블 사용
*/
static const uint8_t crc8_table[256]=
{
    0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
    0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
    0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
    0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
    0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
    0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
    0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
    0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
    0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
    0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
    0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
    0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
    0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
    0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
    0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
    0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
};

/*
mlx90614_reg_data_t : 전달받은 구조체의 데이터값을 기반으로 PEC 계산
write_read : 읽어 온 데이터의 PEC 계산을 할 것인지 
		MLX90614에 입력할 PEC값을 계산할 것인지를 결정
*/
int mlx90614_crc_cal(mlx90614_reg_data_t * p_raw_data, uint8_t write_read)
{
    //CRC-8 계산 결과값을 저장할 변수
    uint8_t sum_val=0;

    /*
    읽어 온 데이터에 대한 CRC-8 계산을 순차적으로 실행하면서 sum_val 변수에 저장
    */
    if(write_read==MLX90614_Read)
    {
        uint8_t pec_array[5]={(MLX90614_ADDR<<1), p_raw_data->reg_addr, ((MLX90614_ADDR<<1)|1),
                            LSB_16(p_raw_data->reg_data), MSB_16(p_raw_data->reg_data)};
        for(int i=0; i<5; i++)
        {
            sum_val=crc8_table[(sum_val^pec_array[i])];
        }        
    }
    /*
    레지스터에 입력할 PEC값을 순차적으로 CRC-8 계산하면서 sum_val 변수에 저장
    */
    else if(write_read==MLX90614_Write)
    {
        uint8_t pec_array[4]={(MLX90614_ADDR<<1), p_raw_data->reg_addr, 
                            LSB_16(p_raw_data->reg_data), MSB_16(p_raw_data->reg_data)};
        for(int i=0; i<4; i++)
        {
            sum_val=crc8_table[(sum_val^pec_array[i])];
        }
    }
    //CRC-8 계산 결과 반환
    return sum_val;
}
  • nRF5 SDK는 CRC-16, CRC-32 계산 라이브러리만 제공
  • CRC-8 테이블은 http://www.sunshine2k.de/coding/javascript/crc/crc_js.html 사이트를 참고해 작성
  • MLX90614로부터 읽어 온 PEC는
  • (MLX90614 Address<<1+W(0)) + Register Address(Command) + (MLX90614 Address<<1+R(1)) + Register Data (LSB) + Register Data (MSB) 의 CRC-8 계산
  • MLX90614에 데이터를 쓸 때 함께 입력할 PEC는
  • (MLX90614 Address<<1+W(0)) + Register Address(Command) + Register Data (LSB) + Register Data (MSB) 의 CRC-8 계산

- 방사율 변경 함수

  • EEPROM으로부터 데이터를 읽어오거나 데이터를 쓸 경우엔 데이터 시트의 EEPROM 테이블에 나와있는 레지스터 주소에서 0x20 의 값을 더해 준 값을 사용해야 한다
  • ex) EEPROM 방사율 주소 = 0x04 번지, 실제 사용은 0x04 | 0x20 = 0x24 (CMD) 이용
  • MLX90614xCx 모델의 경우, EEPROM 0x0F 번지의 데이터도 변경해줘야 온도가 제대로 측정되는데 이 데이터는 0x04 번지의 데이터를 계산할 때와는 다르게 기존 0x0F 번지에 저장되어있던 값을 이용해 계산되므로 우선적으로 0x0F에 저장된 값을 따로 기록해 둘 것을 권장 (사용 모듈의 경우, 0x0F = 0x079A 인 상태였음)
  • EEPROM에 새로운 데이터를 입력하기 위해선 우선 해당 레지스터 주소에 0x0000 데이터를 입력해 기존 데이터를 지워줘야 한다
*
mlx90614_instance_t : MLX90614_INSTANCE_DEF을 통해 생성한 MLX90614 센서 인스턴스
new_emissivity : EEPROM에 입력할 새 방사율
*/
ret_code_t mlx90614_emissivity_write(mlx90614_instance_t const * p_instance,
                                    float new_emissivity
                                    )
{
    ASSERT(p_instance != NULL);

    //방사율은 0.1~1.0까지만 설정 가능
    if(new_emissivity<0.10 || 1.0<new_emissivity)
    {
        NRF_LOG_WARNING("Invaild Emissivity Value");
        return NRF_ERROR_INVALID_DATA;
    }

    //TWI transaction manager instance의 Queue 상태를 확인하기 위한 부울 변수
    bool idle_chk=false;

    //에러 코드 저장
    ret_code_t err_code;
    
    //레지스터에 새 방사율 데이터가 제대로 써졌는지 확인용
    uint16_t emissivity_chk_data;
    
    /*
    새 방사율값을 저장할 구조체
    reg_addr : 계산된 새 방사율값을 저장할 EEPROM 레지스터 주소 (0x04 | 0x20 = 0x24)
    reg_data : 데이터 시트에 나와있는 식을 통해 계산된 16비트 데이터
    pec : 데이터에 이어서 전송될 PEC 데이터
    	만약 제대로 계산된 PEC값이 전송되지 않는다면 EEPROM은 기존 데이터를 유지한다
    */
    mlx90614_reg_data_t new_emissivity_1;
    new_emissivity_1.reg_addr=MLX90614_REG_EMISSIVITY_1;
    new_emissivity_1.reg_data=(int)(round(65536*(new_emissivity)-1.0));
    new_emissivity_1.pec=mlx90614_crc_cal(&new_emissivity_1,MLX90614_Write);

    //0x04 번지에 새 방사율 데이터가 제대로 저장이 됐는지 확인하기 위해 계산된 새 방사율 데이터 저장
    emissivity_chk_data=new_emissivity_1.reg_data;

    /*
    EEPROM에 데이터를 쓰기 위해선 우선적으로 해당 레지스터에 0x0000 값을 갖는 16비트 데이터를 입력해야 한다
    0x0000 의 값을 전송할 구조체 선언 후 레지스터 주소 입력, PEC 계산값 저장
    */
    mlx90614_reg_data_t emissivity_zero;
    emissivity_zero.reg_addr=MLX90614_REG_EMISSIVITY_1;
    emissivity_zero.pec=mlx90614_crc_cal(&emissivity_zero,MLX90614_Write);

    /*
    - MLX90614xCx 모델을 사용하는 경우에만 실행되는 부분
    현재 EEPROM 0x04 번지에 입력되어있는 방사율 데이터가 필요하다
    */
    #ifdef MLX90614xCx
    
    //현재 EEPROM에 저장되어있는 0x04 번지의 방사율 데이터를 저장할 구조체 변수 선언 및 레지스터 주소 입력
    mlx90614_reg_data_t old_emissivity_1;
    old_emissivity_1.reg_addr=MLX90614_REG_EMISSIVITY_1;

    //old_emissivity_1 구조체에 EEPROM 0x04 (CMD 0x24) 번지에 저장된 현재 방사율값 읽어와 저장
    err_code=nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                           p_instance->sensor_addr,
                           MLX90614_REG_EMISSIVITY_1,
                           (nrf_twi_sensor_reg_cb_t) NULL,
                           (uint8_t *) &old_emissivity_1,
                           MLX90614_REGISTER_BYTES);

    //nrf_twi_sensor_reg_read 함수 실행 에러 발생시
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614xCx : Old emissivity 1 read failed.");
        return err_code;
    }

    /*
    데이터 송수신에 사용하는 TWI Sensor module 라이브러리는 
    TWI trasaction manager 라이브러리의 nrf_twi_mngr_schedule 함수를 이용해 데이터를 전송한다
    때문에 old_emissivity_1 구조체에 레지스터로부터 읽어 온 데이터가 저장되기까지 대기를 한 후
    (=TWI transaction manager queue==idle 상태)
    CRC 값을 계산 및 읽어 온 PEC값과 비교한다
    */
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    
    //수신이 제대로 됐는지 확인을 위해 읽어 온 PEC 값과 CRC-8 계산 결과를 비교
    if(old_emissivity_1.pec!=mlx90614_crc_cal(&old_emissivity_1,MLX90614_Read))
    {
        NRF_LOG_WARNING("MLX90614xCx : Old emissivity 1 CRC doesn't match");
        return NRF_ERROR_INVALID_DATA;
    }
    #endif

	//모델 상관없이 공통적으로 진행 시작
    
    //0x0000의 데이터값을 먼저 전송해 방사율 레지스터 주소(0x04)의 데이터를 지운다
    err_code=nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                               p_instance->sensor_addr,
                               MLX90614_REG_EMISSIVITY_1,
                               (uint8_t *)&emissivity_zero,
                               sizeof(emissivity_zero)/sizeof(uint8_t)
                               );
    //nrf_twi_sensor_reg_write 함수 실행 에러 발생시
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614  : Emissivity 1 erase failed");
        return err_code;
    }

    /*
    0x0000 데이터 입력 후, 새 방사율 데이터를 전송하기 위해선 최소 5ms, 권장 10ms의
    여유 시간이 필요하므로 0x0000 데이터 전송 완료까지 기다린 뒤에 딜레이 함수를 이용해 10ms 대기
    */
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    nrf_delay_ms(10);

    //데이터 시트에 나와있는 식을 통해 계산된 새 방사율값 EEPROM 0x04 번지에 전송
    err_code=nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                           p_instance->sensor_addr,
                           MLX90614_REG_EMISSIVITY_1,
                           (uint8_t*)&new_emissivity_1,
                           sizeof(new_emissivity_1)/sizeof(uint8_t)
                           );
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614 : New emissivity 1 write failed");
        return err_code;
    }
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    
    //새 방사율 데이터를 레지스터에 쓴 이후에도 똑같이 최소 5ms, 권장 10ms의 대기가 필요하다
    nrf_delay_ms(10);

    /*
    EEPROM 0x04 번지에 저장된 방사율 데이터를 읽어와 앞서 emissivity_chk_data에 저장한 
    새 방사율 데이터와 비교해 EEPROM에 새 방사율 데이터가 제대로 입력됐는지 확인
    */
    err_code=nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                                   p_instance->sensor_addr,
                                   MLX90614_REG_EMISSIVITY_1,
                                   (nrf_twi_sensor_reg_cb_t) NULL,
                                   (uint8_t *) &new_emissivity_1,
                                   MLX90614_REGISTER_BYTES);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614 : New emissivity 1 read failed", err_code);
        return err_code;
    }
    /*
    TWI transaction manager instance의 queue에 데이터 수신이 대기된 상태로 존재하기 때문에
    수신 완료까지(Queue==0) 대기한 후에 데이터 비교를 진행한다
    */
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    
    //EEPROM 0x04 번지에 새 방사율 데이터가 제대로 입력됐는지 확인
    if(new_emissivity_1.reg_data!=emissivity_chk_data)
    {
        NRF_LOG_INFO("New Emissivity 1 data doesn't match");
        return NRF_ERROR_INVALID_DATA;
    }

    /*
    - MLX90614xCx 모델을 사용하는 경우에만 실행되는 부분
    MLX90614xCx 모델의 경우, 방사율 데이터를 변경할 때 EEPROM 0x04 번지의 데이터만 바꾸는 것이 아니라 
    0x0F 번지에 저장되어 있는 값도 데이터 시트에 나와있는 식을 통해 얻은 값으로 변경해야 
    방사율에 따른 옳바른 온도값이 측정된다
    */
    #ifdef MLX90614xCx
    
    /*
    앞서 0x04 번지의 데이터를 지우기 위해 선언 및 사용한 0x0000 데이터를 가진 구조체 변수에 
    0x0F 주소 입력 및 PEC값 재계산해 저장
    */
    emissivity_zero.reg_addr=MLX90614_REG_EMISSIVITY_2;
    emissivity_zero.pec=mlx90614_crc_cal(&emissivity_zero, MLX90614_Write);

    /*
    new_emissivity_2 : EEPROM 0x0F 번지에 쓸 새 방사율 데이터를 저장할 구조체 변수
    old_emissivity_2 : 현재 EEPROM 0x0F 번지에 저장된 방사율 데이터를 읽어와 저장할 구조체 변수
    */
    mlx90614_reg_data_t new_emissivity_2, old_emissivity_2;
    
    //EEPROM 0x0F 번지 주소 입력 (0x0F | 0x20 = 0x2F)
    new_emissivity_2.reg_addr=MLX90614_REG_EMISSIVITY_2;
    old_emissivity_2.reg_addr=MLX90614_REG_EMISSIVITY_2;

    //현재 EEPROM 0x0F 번지에 입력되어있는 방사율 데이터를 읽어와 old_emissivity_2 구조체 변수에 저장
    err_code=nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                           p_instance->sensor_addr,
                           MLX90614_REG_EMISSIVITY_2,
                           (nrf_twi_sensor_reg_cb_t) NULL,
                           (uint8_t *) &old_emissivity_2,
                           MLX90614_REGISTER_BYTES);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614xCx : Old emissivity 2 read failed");
        return err_code;
    }
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    if(old_emissivity_2.pec!=mlx90614_crc_cal(&old_emissivity_2,MLX90614_Read))
    {
        NRF_LOG_WARNING("Old emissivity 2 CRC doesn't match");
        return NRF_ERROR_INVALID_DATA;
    }

    /*
    기존에 저장되어있는 0x04 번지의 방사율 데이터(old_emissivity.reg_data)와 앞서 0x04 번지에 입력한
    새로운 방사율 데이터(new_emissivity_1.reg_data) 그리고 0x0F 번지에 저장되어있던 데이터
    (old_emissivity_2.reg_data)를 이용해 0x0F 번지에 입력될 새로운 데이터 값을 구한다
    */
    //0x0F 번지에 입력할 새로운 데이터값 계산 후 저장
    new_emissivity_2.reg_data=
	(round(((double)old_emissivity_1.reg_data/new_emissivity_1.reg_data)*old_emissivity_2.reg_data));
        
    //0x0F 번지에 전송될 PEC값을 CRC-8 계산 후 저장
    new_emissivity_2.pec=mlx90614_crc_cal(&new_emissivity_2,MLX90614_Write);
    
    //위에서 계산된 0x0F에 입력할 새 방사율 데이터를 데이터 확인용 변수에 저장
    emissivity_chk_data=new_emissivity_2.reg_data;
 
    //0x000 데이터를 먼저 전송해 0x0F 번지 데이터 지움
    err_code=nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                               p_instance->sensor_addr,
                               MLX90614_REG_EMISSIVITY_2,
                               (uint8_t *)&emissivity_zero,
                               sizeof(emissivity_zero)/sizeof(uint8_t)
                               );
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614xCx : Emissivity 2 erase failed", err_code);
        return err_code;
    }
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    nrf_delay_ms(10);

    //0x0F 번지에 새롭게 계산된 방사율 데이터를 전송
    err_code=nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                           p_instance->sensor_addr,
                           MLX90614_REG_EMISSIVITY_2,
                           (uint8_t *)&new_emissivity_2,
                           sizeof(new_emissivity_2)/sizeof(uint8_t)
                           );
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614xCx : New emissivity 2 write failed");
        return err_code;
    }
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
    nrf_delay_ms(10);
	
    //새로운 방사율 데이터가 제대로 써졌는지 확인하기 위해 0x0F 번지에 저장된 데이터를 읽어 옴
    err_code=nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                           p_instance->sensor_addr,
                           MLX90614_REG_EMISSIVITY_2,
                           (nrf_twi_sensor_reg_cb_t) NULL,
                           (uint8_t *) &new_emissivity_2,
                           MLX90614_REGISTER_BYTES);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("MLX90614xCx : New emissivity 2 read failed");
        return err_code;
    }
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);
	
    //0x0F 번지에 새 방사율 데이터가 제대로 써졌는지 확인
    if(new_emissivity_2.reg_data=!emissivity_chk_data)
    {
        NRF_LOG_INFO("New emissivity 2 data doesn't match");
        return NRF_ERROR_INVALID_DATA;
    }
    #endif
    
    return NRF_SUCCESS;
}

- Sleep 함수

/*
슬립 모드에 진입하기 위해선 Command=0xFF, PEC=0xE8 2바이트를 전송하면 된다
Command는 nrf_twi_sensor_reg_write의 세번째 인자로 쓰인 MLX90614_REG_SLEEP가 사용되므로
실제 전송할 바이트 크기는 PEC 1바이트가 된다
*/
ret_code_t mlx90614_sleep_enter(mlx90614_instance_t const * p_instance)
{
    uint8_t sleep_pec=0xE8;

    return nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                       p_instance->sensor_addr,
                       MLX90614_REG_SLEEP,
                       (uint8_t*)&sleep_pec,
                       sizeof(sleep_pec)/sizeof(uint8_t)
                       );
}

/*
슬립 모드에서 빠져나오기 위해선 SDA 핀을 33ms를 초과하는 시간동안 LOW로 유지해야 한다
이를 위해 MLX90614 와의 통신에 사용하던 TWI를 해제하고 SDA핀을 LOW 출력 설정으로 바꾼 후
딜레이 함수를 통해 34ms 동안 LOW 상태를 유지함으로 슬립 모드에서 탈출
이후에 다시 twi_config() 함수를 실행해 통신에 사용할 TWI를 재초기화해줘야 한다
하지 않을 경우, 센서와의 통신 불가
*/
void mlx90614_sleep_exit(mlx90614_instance_t const * p_instance, uint8_t sda_pin)
{
    bool idle_chk;
    
    //현재 TWI manager instance Queue에 대기해있는 전송이 완료될 때까지 대기
    do
    {
        idle_chk=nrf_twi_mngr_is_idle(p_instance->p_sensor_data->p_twi_mngr);
        nrf_delay_us(10);
    }while(idle_chk!=true);

	//TWI manager instance 해제
    nrf_twi_mngr_uninit(p_instance->p_sensor_data->p_twi_mngr);
    
    //SDA 핀 Output 설정 및 LOW 출력으로 설정
    nrf_gpio_cfg_output(sda_pin);
    nrf_gpio_pin_write(sda_pin,0);
    
    //34ms 동안 SDA=LOW 유지
    nrf_delay_ms(34);
}

- Sleep 실행 예시

1. 방사율, Object 1 온도 읽음

2. 슬립 모드 진입

3. 다섯 번의 Object 1 Read 실행

4. 슬립 모드 탈출

5. twi_config() 실행

6. Object 1 Read 시작

슬립 모드 진입 후 다섯번의 Object 1 Temperature 레지스터 접근 시도가 있었지만 실패
35ms 동안 SDA 핀 LOW 유지해 슬립 모드 탈출

소스 코드 : https://github.com/lee35210/nRF52840-MLX90614

+ Recent posts