[nRF52840, TWI를 사용한 MLX90614 제어]

https://rs29.tistory.com/16

 

nRF52 - TWI 이용 MLX90614 적외선 비접촉 온도 측정 센서

사용 보드 : nRF52840-PDK 사용 IDE : SEGGER Embedded Studio for ARM (노르딕 제품 사용하는 경우 무료 라이센스 이용 가능) 작동 전압 : 2.6~3.6V (MLX90614Bxx, MLX90614Dxx 모델 기준, MLX90614Axx은 4.5~5...

rs29.tistory.com

[개발 환경]

- 개발 보드 : nRF52840-PDK

- IDE : SEGGER Embedded Studio for ARM

- 센서 : MLX90614BCC, 통신 TWI (I2C) 사용

- 무선 통신 : BLE (UART 사용)

- SDK 예제 중 twi_master_using_nrf_twi_mngr(TWI 통신), ble_app_uart(BLE+UART 송수신), ble_app_hrs_freertos(FreeRTOS) 를 기반으로 작성

- 대부분의 코드(BLE, UART 등의 설정)는 예제 그대로 사용

- BLE 통신을 통해 명령어를 수신 및 실행하거나 온도, 방사율 값을 전송함

- 참고한 사이트 : https://www.freertos.org/a00106.html

 

[작동]

- 상시 작동할 태스크는 3개 (보드 0번 LED 토글, MLX90614 온도 측정, 측정된 온도 BLE 통신 전송)

- 각 태스크 후미에 사용한 vTaskDelay()에 입력한 시간 간격마다 LED 토글 및 MLX90614 Object 1 Temperature 레지스터 Read 함수 호출

- MLX90614로부터 온도 데이터를 수신한 직후 호출되는 콜백 함수에서 측정한 온도값을 전송할 BLE 전송 태스크에 알림 및 읽어 온 온도 데이터 전달 (BLE 전송 태스크는 if문을 사용해 알림을 수신하기 전까지는 BLE 전송이 실행되지 않음)

- BLE 전송 태스크에서 알림 및 데이터를 획득한 뒤, 온도 데이터를 섭씨 온도로 변환 및 BLE 통해 전송

 

- BLE 통신을 통해 MLX90614를 제어하는데 사용할 각종 명령어 및 명령어 데이터 수신

- 방사율을 전송할 경우, 그에 필요한 태스크들을 생성한 뒤 삭제 (지속적으로 갱신할 필요가 없는 데이터이기 때문)

 

[코드]

<FreeRTOS 태스크 핸들 생성>

static TaskHandle_t m_led_toggle_thread;		//nRF52840-DK LED0 토글
static TaskHandle_t m_mlx_rd_temp_thread;	//MLX90614 온도 Read
static TaskHandle_t m_mlx_rd_emi_thread;		//MLX90614 방사율 Read
static TaskHandle_t m_ble_mlx_temp_thread;	//읽어 온 온도를 BLE 통신을 사용해 전송
static TaskHandle_t m_ble_mlx_emi_thread;	//읽어 온 방사율을 BLE 통신을 사용해 전송

#if NRF_LOG_ENABLED
static TaskHandle_t m_logger_thread;	//Log 확인
#endif

- 태스크 핸들 생성 (태스크 생성, 중단, 삭제 등 API 호출 시 태스크를 참조하는 데 사용)

- sdk_config.h 에서 NRF_LOG_ENABLED 1 로 설정했을 경우, nrf_log module configuration 내에 활성화한 각종 드라이버 및 라이브러리들의 로그가 출력됨 (J-Link RTT Viewer 사용해 확인 가능)

 

<태스크 생성 함수>

static void task_create_func(void)
{
    if (pdPASS != xTaskCreate(mlx_rd_temp_thread, "MLX90614_RD_TEMP", configMINIMAL_STACK_SIZE+30, NULL, 2, &m_mlx_rd_temp_thread))
    {
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }

    if (pdPASS!=xTaskCreate(led_toggle_thread, "LED0", configMINIMAL_STACK_SIZE + 10, NULL, 2, &m_led_toggle_thread))
    {
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }

    if (pdPASS != xTaskCreate(ble_mlx_temp_thread, "BLE_MLX_TEMP", configMINIMAL_STACK_SIZE+30, NULL, 2, &m_ble_mlx_temp_thread))
    {
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }

//sdkconfig.h - NRF_LOG_ENABLED 1 로 활성화했을 때, Log 출력 태스크 생성
#if NRF_LOG_ENABLED
    if (pdPASS!=xTaskCreate(logger_thread, "LOGGER", 256, NULL, 1, &m_logger_thread))
    {
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
#endif
}

태스크 생성 예)

- mlx_rd_temp_thread : 태스크를 실행할 함수명

- MLX90614_RD_TEMP : 태스크 설명 이름 (디버깅용)

- configMINIMAL_STACK_SIZE+30 : 태스크에 할당할 스택 사이즈

  (configMINIMAL_STACK_SIZE=60)

  (byte가 아닌 word 기준 (32bit MCU의 경우, 1 word=32bit), 스택 사이즈 90=360 bytes)

- NULL : 생성한 태스크에 전달 될 매개변수

- 2 : 태스크 우선 순위

- &m_mlx_rd_temp_thread : 태스크 핸들

 

<MLX90614 온도 Read 태스크>

static void mlx_rd_temp_thread(void *arg)
{
    mlx90614_reg_data_t obj_1_temp_struct={0,0,MLX90614_REG_OBJECT_1_TEMP};
    
    while(1)
    {
        mlx90614_reg_read(&mlx90614_instance,&obj_1_temp_struct,mlx90614_read_temp_cb);
        vTaskDelay(1000);
    }
}

- 센서로부터 온도 데이터를 읽어 오는데 사용할 구조체 변수 선언 및 Object 1 Temperature 레지스터 주소 설정

- MLX90614 레지스터 Read 함수 호출

- vTaskDelay(1000) : 1000ms 마다 태스크 실행

 

<MLX90614 온도 콜백 함수>

//MLX90614 - Object 1 레지스터 데이터 수신 완료 직후에 호출되는 함수
void mlx90614_read_temp_cb(ret_code_t result, mlx90614_reg_data_t * p_raw_data)
{
    uint8_t crc_value;
    
    if (result != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("Read Temperature Callback Error : %d", (int)result);
        return;
    }

    if(p_raw_data->pec!=mlx90614_crc_cal(p_raw_data,MLX90614_Read))
    {
        NRF_LOG_WARNING("MLX90614 Read temperature CRC doesn't match");
        NRF_LOG_INFO("PEC : %X  CRC : %X",p_raw_data->pec,mlx90614_crc_cal(p_raw_data,MLX90614_Read));
    }
    else
    {
    	/*
    	TWI 통신을 통해 성공적으로 Object 1 레지스터에 저장된 온도 데이터를 수신 완료하고
        CRC 계산을 통과하면 BLE 통신을 통해 온도값을 전송하는 태스크에 알림 및 읽어 온 온도 데이터 전송
        m_ble_mlx_temp_thread : 알림을 수신할 태스크 생성에 사용된 태스크 핸들
        p_raw_data->reg_data : 전달할 데이터 (최대 32bit까지만 가능)
        eSetValueWithOverwrite : 전달될 데이터의 행동 설정 (무조건적으로 p_raw_data->reg_data 사용 설정)
        */
        xTaskNotify(m_ble_mlx_temp_thread, p_raw_data->reg_data, eSetValueWithOverwrite);
    }
}

- MLX90614의 온도 레지스터로부터 데이터를 읽어 온 직후 호출되는 함수

- 데이터 수신에 성공 및 CRC 계산값이 일치한다면 BLE 통신을 통해 온도값을 전송하는 태스크에 알림을 전송

- xTaskNotify의 경우, 최대 32bit의 데이터를 알림과 함께 전달 가능하므로 온도 데이터값을 가지고 있는 p_raw_data->reg_data의 값을 알림과 함께 전달

 

<BLE - 온도 전송 태스크>

/*
- xTaskNotify()를 통해 알림이 전달되었을 경우에만 전달된 데이터를 온도값으로 변환한 뒤
  BLE 통신을 통해 온도값을 전송하는 태스크
*/
static void ble_mlx_temp_thread(void *arg)
{
    ret_code_t err_code;
    char data_array[6]={};	//변환된 온도 데이터를 BLE 통신을 통해 전송할 때 사용할 배열
    				//정수 세 자리 + '.'+ 소수점 두자리 = 6
    double temperature;		//변환한 온도를 저장할 변수
    uint16_t temp;		//xTaskNotify를 통해 알림과 같이 전달된 온도 데이터(p_raw_data->reg_data)를 저장할 변수
    uint16_t length=sizeof(data_array);	//BLE 통신을 통해 전송할 데이터의 길이

    while(1)
    {
    	/*
    	- xTaskNotify()를 통해 전달된 알림이 있을 경우에만 ulTaskNotifyTake()를 통해 획득한
          데이터가 0이 아닌 값을 갖는다
        - 알림을 수신할 때까지 최대 10ms동안 Blocked 상태로 대기
          (Blocked 상태일 때 CPU 시간 소모 X)
        */
        if((temp=ulTaskNotifyTake(pdTRUE,10))!=0)
        {
            temperature=mlx90614_temp_conversion(temp);
            //UART 통신을 사용하므로 변환된 온도 데이터를 sprintf()함수를 이용해 문자열로 저장 후 전송
            sprintf(data_array,"%3.2f",temperature);
            
            //BLE 통신 연결이 된 상태 & MLX90614로부터 읽어 온 데이터가 유효할 때 온도값 전송
            //(MLX90614가 유효한 데이터를 측정하기 전에 데이터를 읽어 올 경우 온도 데이터=-273.15)
            if((m_conn_handle!=BLE_CONN_HANDLE_INVALID)&&(temperature!= -273.15))
            {
                err_code=ble_nus_data_send(&m_nus, data_array, &length, m_conn_handle);
                if ((err_code != NRF_ERROR_INVALID_STATE) &&
                    (err_code != NRF_ERROR_RESOURCES) &&
                    (err_code != NRF_ERROR_NOT_FOUND))
                {
                    APP_ERROR_CHECK(err_code);
                }
            }
        }
    }
}

 

<MLX90614 방사율 Read 태스크>

static void mlx90614_rd_emi_thread(void *arg)
{
    mlx90614_reg_data_t emissivity_struct={0,0,MLX90614_REG_EMISSIVITY_1};
    while(1)
    { 
        mlx90614_reg_read(&mlx90614_instance, &emissivity_struct, mlx90614_read_emissivity_cb);
        vTaskDelete(m_mlx_rd_emi_thread);
    }
}

- 방사율을 읽어 오는데 사용할 구조체 변수 선언 및 Emissivity 1 레지스터 주소 설정

- MLX90614 레지스터 Read 함수를 호출하고 태스크 삭제

- 방사율의 경우, 주기적으로 읽어 올 필요가 없기 때문에 필요한 경우에만 태스크를 생성 및 삭제

 

<MLX90614 방사율 콜백 함수>

void mlx90614_read_emissivity_cb(ret_code_t result, mlx90614_reg_data_t * p_raw_data)
{
    uint8_t crc_value;

    if (result != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("Read Emissivity Callback Error : %d", (int)result);
        return;
    }    

    if(p_raw_data->pec!=mlx90614_crc_cal(p_raw_data,MLX90614_Read))
    {
        NRF_LOG_WARNING("MLX90614 Read emissivity CRC doesn't match");
        NRF_LOG_INFO("PEC : %X  CRC : %X",p_raw_data->pec,mlx90614_crc_cal(p_raw_data,MLX90614_Read));
    }
    else
    {   
        xTaskNotify(m_ble_mlx_emi_thread, p_raw_data->reg_data, eSetValueWithOverwrite);
    }
}

- 온도 콜백 함수와 작동 방식 동일

 

<BLE - 방사율 전송 태스크>

static void ble_mlx90614_emi_thread(void *arg)
{
    ret_code_t err_code;
    static char data_array[4]={NULL};	//정수 한자리 + '.' + 소수점 두자리 = 4
    static float emissivity;
    static uint16_t temp;
    static uint16_t length=sizeof(data_array);

    while(1)
    {
        if((temp=ulTaskNotifyTake(pdTRUE,(TickType_t)10))!=0)
        {
            emissivity=mlx90614_emissivity_conversion(temp);
            sprintf(data_array,"%.2f",emissivity);

            if((m_conn_handle!=BLE_CONN_HANDLE_INVALID))
            {
                err_code=ble_nus_data_send(&m_nus, data_array, &length, m_conn_handle);
                if ((err_code != NRF_ERROR_INVALID_STATE) &&
                    (err_code != NRF_ERROR_RESOURCES) &&
                    (err_code != NRF_ERROR_NOT_FOUND))
                {
                    APP_ERROR_CHECK(err_code);
                }
            }
            vTaskDelete(m_ble_mlx_emi_thread);
        }
    }
}

- 마지막에 vTaskDelete() 함수로 전송 완료 후 방사율 전송 태스크 삭제

- 나머지 부분은 온도 전송 태스크와 동일

 

<BLE를 통한 UART 송수신에 사용되는 함수>

- SDK 예제 중 ble_app_uart의 코드 활용

static void nus_data_handler(ble_nus_evt_t * p_evt)
{
    char str_cmd[]={"000"};	//수신한 명령어 저장
    char str_data[]={"000"};	//수신한 데이터 저장 (명령어+명령어에 필요한 데이터 조합)

    //BLE UART를 통해 데이터를 수신했을 때
    if (p_evt->type == BLE_NUS_EVT_RX_DATA)
    {
        uint32_t err_code;

        NRF_LOG_DEBUG("Received data from BLE NUS. Writing data on UART.");
        NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);

        for (uint32_t i = 0; i < p_evt->params.rx_data.length; i++)
        {
            do
            {
                err_code = app_uart_put(p_evt->params.rx_data.p_data[i]);
                if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY))
                {
                    NRF_LOG_ERROR("Failed receiving NUS message. Error 0x%x. ", err_code);
                    APP_ERROR_CHECK(err_code);
                }
            } while (err_code == NRF_ERROR_BUSY);
        }
        if (p_evt->params.rx_data.p_data[p_evt->params.rx_data.length - 1] == '\r')
        {
            while (app_uart_put('\n') == NRF_ERROR_BUSY);
        }
        //수신한 데이터의 1~3번째 바이트를 명령어 배열(str_cmd)에 저장
        memcpy(str_cmd,p_evt->params.rx_data.p_data,3);
        //수신한 데이터의 4~6번째 바이트를 데이터 배열(str_data)에 저장
        if(3<p_evt->params.rx_data.length)
        {
            memcpy(str_data,&p_evt->params.rx_data.p_data[3],3);
        }
    }
    
    /*
    - 수신한 명령어가 있을 때, MLX90614 명령어 실행 함수 호출
    BLE 데이터 송신으로 핸들러 함수가 호출되었을 때는 str_cmd 배열의 값이 "000"을 유지하므로
    MLX90614에 명령어를 실행하는 함수가 실행되지 않는다
     */
    if(strcmp(str_cmd,"000")!=0)
    {
        mlx90614_cmd_func(str_cmd, str_data);
    }
}

- BLE를 통해 UART 방식의 통신을 전송, 수신했을 때 호출되는 핸들러 함수

- MLX90614를 제어하는데 사용할 각종 명령어 및 데이터를 수신 및 배열에 저장한 뒤 제어 함수 호출

 

<MLX90614 명령어 실행 함수>

void mlx90614_cmd_func(char *str_cmd, char *str_data)
{
    if(strcmp(str_cmd,"REM")==0)   //Read Emissivity
    {
        if (pdPASS != xTaskCreate(mlx90614_rd_emi_thread, "MLX90614_RD_EMI", configMINIMAL_STACK_SIZE+10, NULL, 2, &m_mlx_rd_emi_thread))
        {
            APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
        }
        if (pdPASS != xTaskCreate(ble_mlx90614_emi_thread, "MLX90614_BLE_RD_EMI", configMINIMAL_STACK_SIZE+30, NULL, 2, &m_ble_mlx_emi_thread))
        {
            APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
        }
    }
    else
    {
        
    }
}

- "REM" 문자열을 수신한 경우, MLX90614로부터 방사율을 읽어오는 태스크와 읽어 온 방사율 데이터를 BLE 통신을 통해 전송하는 태스크를 생성

 

<Led 토글 태스크>

static void led_toggle_thread(void * pvParameter)
{
    UNUSED_PARAMETER(pvParameter);
    while (true)
    {
        bsp_board_led_invert(BSP_BOARD_LED_0);
        vTaskDelay(1000);
    }
}

- SDK-blinky_freertos 예제에 있는 태스크

- vTaskDelay에 입력한 시간(ms) 간격으로 nRF52840-DK 보드의 0번 LED를 토글

 

<스택 크기 측정>

-예) MLX90614 Object 1 Register Read 태스크

static void mlx_rd_temp_thread(void *arg)
{
    mlx90614_reg_data_t obj_1_temp_struct={0,0,MLX90614_REG_OBJECT_1_TEMP};

    UBaseType_t uxHighWaterMark;
    uxHighWaterMark=uxTaskGetStackHighWaterMark(NULL);
    
    while(1)
    {
        mlx90614_reg_read(&mlx90614_instance,&obj_1_temp_struct,mlx90614_read_temp_cb);
        vTaskDelay(1000);
        uxHighWaterMark=uxTaskGetStackHighWaterMark(NULL);
        printf("mlx_rd_temp_thread remaining stack space : %d\r\n",uxHighWaterMark);
    }
}

- FreeRTOSConfig.h 에서 INCLUDE_uxTaskGetStackHighWaterMark 1 로 설정 필요

- 태스크를 생성하며 할당한 스택 크기 중 태스크가 실행된 이후 측정된 사용 가능 스택 잔량의 최소 크기를 반환

- 반환값이 0에 근접할 수록 스택 오버 플로우에 가까워짐

 

<스마트폰을 통한 온도 및 방사율값 수신>

- nRF Toolbox 애플리케이션 사용

- nRF52840-DK와 연결된 직후부터 BLE 통신을 통해 온도값이 수신되기 시작

- 온도 측정 중 "REM"(방사율 측정 및 수신) 명령어를 전송해 MLX90614에 현재 설정된 방사율을 BLE 통신을 통해 수신

- MLX90614로부터 방사율을 읽어오는 TWI 통신은 TWI transaction manager 라이브러리의 nrf_twi_mngr_schedule() 함수를 사용해 TWI 전송 큐에 등록되므로 온도값을 읽어 오는 중간에 사용되어도 온도값 수신에 영향을 미치지 않는다

- MLX90614로부터 방사율을 읽어오는데 사용되는 mlx90614_rd_emi_thread() 태스크는 mlx90614_reg_read() 함수를 호출한 이후에 삭제되므로 MLX90614로부터 저장된 방사율 데이터를 읽어오는 TWI 통신에 영향을 미치지 않는다

- BLE 방사율 전송 태스크 또한 알림을 획득하고 BLE 전송을 완료한 이후에만 태스크 삭제가 실행되므로 BLE 통신을 통한 방사율값 전송에 영향을 미치지 않는다

 

https://github.com/lee35210/nRF52840_BLE_FREERTOS_MLX90614

사용한 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

MLX90614ESF-BCC 모델을 탑재한 모듈

  • 작동 전압 : 2.6~3.6V
  • 통신 방식 : PWM, I2C(10~100KHz)
  • 주변 온도 -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 사이의 방사율로 조정할 수 있다

[작동]

  • 측정된 온도값은 RAM내 지정된 주소에 16비트 16진수 데이터로 저장된다
  • 명령어를 통해 RAM, EEPROM에 저장된 각종 데이터들을 읽거나 필요에 따라 변경할 수 있다
  • 작동 시작 후 첫번째 데이터는 0.25초 뒤에 얻을 수 있다
  • 각 주소는 16bit의 데이터를 갖는다
  • 읽기/쓰기 둘 다 LSByte(16비트 데이터의 하위 8비트), MSByte(16비트 데이터의 상위 8비트) 순으로 데이터 전송이 진행된다

- EEPROM

EEPROM 주소표

  • EEPROM 내에 제한된 주소들만이 변경 가능하지만 전체 주소를 읽는 것은 가능하다
  • 원하는 주소에 값을 쓰기 전에 반드시 해당 주소에 0을 입력해 기존 데이터를 지워준 뒤 원하는 값을 입력해야 한다
  • 0x04 방사율의 경우, 0.1~1.0 까지 조정 가능하고 변경에 따른 재교정은 하지 않아도 된다
  • Factory calibration 을 유지하기 위해 특별한 도구 없이는 다음 비트와 레지스터를 변경해서는 안된다
  • Emissivity [15...0]; Config Register1 [14...11;7;3]; addresses 0x0F and 0x19

- RAM

RAM 주소표

  • 쓰기는 불가능하고 제한된 램 레지스터만 읽는 것이 가능하다
  • 0x06 - 주변 온도, 0x07 - 물체 온도1, 0x08 - 물체 온도2 가 저장된다

- Command

명령어표

  • RAM, EEPROM의 접근을 원하는 경우, x xxxx 부분에 Read/Write를 원하는 주소를 입력한다
  • (ex : RAM Obj1 온도 : 0x07, EEPROM Emissivity(방사율) : 0x24)

- Read

데이터 읽기 예) 램 0x07 주소에 저장된 Object1 온도 측정값을 읽어올 때

  • 센서 주소 : 0x5A (MSB부터 주소가 채워져야 하므로 MSB로 1비트 시프트 한 값이 0xB4)
  • 사용자가 수신할 데이터는 LSByte, MSByte, PEC 총 3 바이트
  • SDA 라인에서 첫번째 바이트는 0xB4(0x5A<<1+W(0))
  • 두번째 바이트는 사용자가 입력한 명령어
  • Repeated Start Condition 이후 세번째 바이트는 센서 주소+Read 비트=0xB5 (0x5A<<1+R(1))
  • 네번째 해당 주소의 LSByte, 다섯번째 해당 주소의 MSByte (LSB가 먼저 오기 때문에 이후에 순서를 바꿔줘야 한다)
  • 마지막 여섯번째 바이트는 0xB4(첫번째 바이트)~MSByte까지 총 5바이트를 CRC-8 계산을 통해 얻은 CRC값

-Write

데이터 쓰기 예) EEPROM 0x02 주소(PWMCTRL)에 데이터를 입력할 때

  • EEPROM에만 데이터 쓰기가 가능하다
  • 원하는 주소에 데이터를 쓰려면 반드시 해당 주소에 0x0000(LSByte(0x00)+MSByte(0x00))을 먼저 입력해 기존 16비트 데이터를 지운 뒤에 원하는 데이터를 입력해야 한다
  • 0x0000을 입력하고 최소 5ms(권장 10ms) 기다린 뒤 원하는 16비트 데이터를 입력하고 다시 5ms(권장 10ms) 기다린 후에 제대로 값이 입력됐는지 Read 명령어 통해 확인
  • 사용자가 전송할 데이터는 Command, LSByte, MSByte, PEC 총 4바이트
  • 첫번째 바이트는 센서 주소 0x5A<<1 + W(0) = 0xB4
  • 두번째 바이트는 명령어 (위의 경우 EEPROM 액세스 001x xxxx + PWMCTRL에 데이터를 쓸 것이므로 0x02 -> 0010 0000 + 0000 0010 = 0010 0010 = 0x22 가 된다)
  • 세번째 바이트는 입력할 16비트 데이터의 하위 8비트
  • 네번째 바이트는 입력할 16비트 데이터의 상위 8비트
  • 다섯번째 바이트는 첫번째 0xB4 바이트부터 네번째 MSByte까지 총 4바이트를 CRC-8 계산을 통해 얻은 CRC 값

[CUBE MX 설정]

  • 센서와의 통신에 사용할 I2C 설정 및 데이터 검증에 CRC-8 사용하므로 CRC 사용 설정

 Standard Mode(100KHz) 사용
인터럽트 활성화
CRC 설정 (8-bit, X2+X1+X0, Init Value=0)

[코드]

- 사용에 필요한 함수들 정의

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <math.h>	//pow 함수 사용
/* USER CODE END Includes */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define mlx90614xCx	//MLX90614xCx 타입을 사용하지 않는 경우 마스크 처리 필요
#define MLX90614_addr 0x5A	// MLX90614 Slave Address
#define mlx_cmd_pointer 1	// 수신, 전송 버퍼내 명령어(CMD)를 입력할 배열 위치
#define mlx_cmd_amb 0x06	// Ta(Ambient Temperature) Address (RAM)
#define mlx_cmd_obj_1 0x07	// Tobj1(Object1 Temperature) Address (RAM)
#define mlx_cmd_obj_2 0x08	// Tobj2(Object1 Temperature) Address (RAM)
#define mlx_cmd_emissivity 0x24	// Emissivity Address (EEPROM)
#define mlx_cmd_emissivity_2 0x2F	// MLX90614xCx 버전에 한해 방사율을 변경했을 때 함께 조정해줘야 하는 레지스터 (EEPROM)
/* USER CODE END PD */

- 전역 변수 선언

/* USER CODE BEGIN PV */
uint8_t mlx_read_buffer[6]={0xB4,0x00,0xB5,0};	//ADDR((0x5A<<1)+W(0)), CMD, ADDR((0x5A<<1)+R(1)), LSByte, MSByte, PEC
uint8_t mlx_write_buffer[5]={0xB4,0};	//ADDR((0x5A<<1)+W(0)), CMD, LSByte, MSByte, PEC
uint8_t mlx_write_zero[5]={0xB4,0};	//ADDR((0x5A<<1)+W(0)), CMD, LSByte, MSByte, PEC
/* USER CODE END PV */
  • Read/Write에 사용할 전역 변수 배열 선언
  • 용이한 PEC 계산을 위해 Read/Write에서 PEC에 사용되는 데이터의 갯수만큼 배열 선언
  • Read의 경우 첫번째, 세번째 배열은 0xB4, 0xB5의 값을 사용하므로 그에 맞춰 배열 선언 및 초기화
  • Write의 경우 첫번째 바이트만 0xB4로 고정되므로 그에 맞춰 배열 선언 및 초기화

- 측정된 온도를 읽어오는 함수

void mlx_read_temperature(void)
{
	double temp_obj_1, temp_obj_2, temp_amb; //물체1, 물체2, 주변 온도를 저장할 변수
	uint8_t crc_obj_1,crc_obj_2,crc_amb;	 //물체1, 물체2, 주변 온도 CRC값 저장할 변수

	/*
	Ambient temperature(센서 주변 온도), RAM-0x06
	*/
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_amb;
	//데이터 버퍼의 CMD 부분에 데이터 수신에 사용할 명령어 입력 (단순 PEC계산용)
    
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_amb,I2C_MEMADD_SIZE_8BIT,
			(uint8_t *)&mlx_read_buffer[3],3);
	/*
	인터럽트 사용해 데이터 수신, 슬레이브의 특정 메모리 주소로부터 데이터를 읽어오는 함수
	사용할 I2C, 슬레이브 주소(MSB로 1비트 시프트 필요), 접근할 내부 메모리 주소(CMD), 내부 메모리 주소 크기, 
	수신할 데이터 저장할 포인터 주소(mlx_read_buffer[3]), 수신할 데이터 갯수(mlx_read_buffer[3]~[5])
	*/
    
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	//수신 완료(I2C Ready 상태)까지 대기
    
	temp_amb=(mlx_read_buffer[4]<<8|mlx_read_buffer[3]);
	/*
	16비트로 구성된 센서 데이터가 하위 8비트->상위 8비트 순으로 수신되므로 mlx_read_buffer[4]에
	저장된 MSByte를 MSB로 8비트 시프트 한 뒤 LSByte(mlx_read_buffer[3])와 합친다
	*/
    
	temp_amb=(temp_amb*0.02)-273.15;
	//10진수로 변환한 온도 측정값을 섭씨 온도로 변환하는 과정이 필요하다
	//Ta[°C] = (Tareg*0.02)-273.15	//(Ta register*0.02)=켈빈온도
    
	crc_amb=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);
	//mlx_read_buffer에 저장된 0xB5, 0x07(CMD), 0xB4, LSByte, MSByte를 CUBE MX를 통해 설정한
	//값들을 기준으로 CRC값을 계산한다
    
	if(crc_amb==mlx_read_buffer[5])
	{
		printf("Ambient Temperature : %.2lf\n",temp_amb);
        //센서로부터 수신한 PEC(Packet Error Code, mlx_read_buffer[5])와 계산한 CRC값이 일치하는 경우 출력
	}
	else
	{
    		//PEC값과 CRC계산값이 일치하지 않는 경우
	}
	
	/*
	Object 1 temperature, RAM-0x07
	*/
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_obj_1;
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_obj_1,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	temp_obj_1=(mlx_read_buffer[4]<<8|mlx_read_buffer[3]);
	temp_obj_1=(temp_obj_1*0.02)-273.15;
	crc_obj_1=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);

	if(crc_obj_1==mlx_read_buffer[5])
	{
		printf("Obj 1 Temperature : %.2lf\n",temp_obj_1);
	}
	else
	{
    		//PEC값과 CRC계산값이 일치하지 않는 경우
	}

	/*
	Object 2 temperature, RAM-0x07
	*/
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_obj_2;
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_obj_2,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	temp_obj_2=(mlx_read_buffer[4]<<8|mlx_read_buffer[3]);
	temp_obj_2=(temp_obj_2*0.02)-273.15;
	crc_obj_2=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);

	if(crc_obj_2==mlx_read_buffer[5])
	{
		printf("Obj 2 Temperature : %.2lf\n",temp_obj_2);
	}
	else
	{
    		//PEC값과 CRC계산값이 일치하지 않는 경우
	}
}

 

  • 센서로부터 온도값을 읽어오는 함수
  • 값을 읽어오는데 사용할 데이터 버퍼의 형태는
  • {0xB4, 0x00, 0xB5, 0x00, 0x00, 0x00} //(센서주소<<1+W(0), CMD, 센서주소<<1+R(1), LSByte, MSByte, PEC)
  • 용이한 CRC 계산을 위해 첫번째 0xB4, 세번째 0xB5는 값을 고정한 상태로 두고 두번째 0x00(CMD)는 사용할 명령어에 따라 변경. 그리고 나머지 배열 세칸에 센서로부터 수신한 데이터를 저장한 뒤 [0]~[4]까지 CRC 계산
  • RAM에 저장된 Ambient Temperature, Object1 Temperature, Object2 Temperature 데이터의 수신 및 처리 방식은 명령어(CMD)를 제외하고는 전부 동일

- 방사율(Emissivity) 변경 함수

void mlx_change_emissivity(float new_emissivity)
{
	/*
	방사율(Emissivity) 변경은 EEPROM 내 0x04 번지의 16비트 데이터를 변경하는 것으로 이뤄질 수 있다
	(xCx 모델의 경우 0x0F 번지의 데이터 변경도 필요하다)
	*/
    
	uint8_t crc_emissivity, crc_emissivity_2;	
	/*
	MLX90614xCx 센서의 경우, 방사율을 변경할 때 EEPROM - 0x04(Emissivity)에 더해
	0x0F 번지에 저장된 데이터값도 변경해야 되기 때문에 0x04, 0x0F CRC 값을 저장하는 변수를 두개 생성
	*/
    
	uint16_t new_04, new_0f;//변경할 방사율에 따른 계산값을 0x04, 0x0F 번지에 저장하는데 사용할 변수
	double old_04,old_0f;	//기존에 저장된 0x04, 0x0F 주소의 데이터를 저장할 변수 (xCx 타입에만 필요한 부분)

	printf("New E : %.2lf\r\n",new_emissivity);	//사용자가 입력한 새 방사율값 출력
	new_04=round(65536*(new_emissivity)-1.0);	
	//데이터 시트에 나와있는 공식을 사용해 EEPROM-0x04 번지에 저장할 새 방사율값 계산
	printf("New E (Calculated) : %d, %X\r\n",new_04,new_04);	//계산된 새 방사율값 출력

/*
MLX90614xCx 타입을 사용하는 경우 0x0F 번지의 데이터 변경도 필요하므로 아래의 과정들이 필요하다
xAx, xBx 타입을 사용하는 경우 사용할 필요가 없는 부분
*/
#ifdef mlx90614xCx
	//EEPROM - 0x04 번지에 저장된 현재 방사율값을 읽어온다 (새 방사율값 계산에 필요, xCx 모델만 필요한 부분)
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_emissivity;
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_emissivity,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	crc_emissivity=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);
	old_04=((mlx_read_buffer[4]<<8)|mlx_read_buffer[3]);
	if(mlx_read_buffer[5]==crc_emissivity)	//CRC 계산값과 수신한 PEC값 비교
	{
		printf("Old 0x04 : %d, %X\r\n",(int)old_04,(int)old_04);	
		//계산된 CRC값과 수신한 PEC값이 일치, 현재 0x04 번지에 저장된 방사율 출력
	}
	else	
	{
    		//계산된 CRC값과 수신한 PEC값이 일치하지 않는 경우
	}

	//EEPROM - 0x0F 번지에 저장된 새로운 방사율값을 계산하는데 필요한 데이터를 읽어온다 (xCx 모델만 필요한 부분)
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_emissivity_2;
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_emissivity_2,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	crc_emissivity_2=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);
	old_0f=((mlx_read_buffer[4]<<8)|mlx_read_buffer[3]);
	if(mlx_read_buffer[5]==crc_emissivity_2)	//CRC 계산값과 수신한 PEC값 비교
	{
		printf("Old 0x0F : %d, %X\r\n",(int)old_0f,(int)old_0f);
		//계산된 CRC값과 수신한 PEC값이 일치, 현재 0x0F 번지에 저장된 데이터 출력
	}
	else	
	{
    		//계산된 CRC값과 수신한 PEC값이 일치하지 않는 경우
	}
	
	new_0f=round((old_04/new_04)*old_0f);	//xCx 타입만 해당 (xAx, xBx는 이 변수 자체가 필요 없음)
	//제조사에서 제공하는 데이터 시트에 나와있는 식을 기반으로 EEPROM-0x0F 번지에 저장할 값 계산
    printf("New 0x0F : %d, %X\r\n",new_0f,new_0f);
    
#endif

	/*
	EEPROM에 데이터를 쓰기 전에 우선적으로 데이터를 쓸 주소에 0x000 값을 입력해
	저장된 16비트 데이터를 지워야 할 필요가 있다
	mlx_write_zero 배열의 첫번째 바이트만 0xB4 (센서 주소0x5A<<1+W(0), PEC 계산에 사용)로 고정해 두고
	mlx_write_zero[1] - CMD(필요에 따라 변경, PEC 계산뿐만이 아니라 실제 전송에서 CMD로 사용된다)
	mlx_write_zero[2], [3] - LSByte, MSByte (0x0000을 입력해야되므로 둘 다 0x00의 값을 가진다)
	mlx_write_zero[4] - PEC (0xB4, CMD를 CRC 계산한 결과)
	mlx_write_zero[1]~mlx_write_zero[4]까지의 배열이 센서로 전송된다
	*/
	mlx_write_zero[1]=mlx_cmd_emissivity;	//Write CMD, 0x24(0010 0000 + 0x04)
	mlx_write_zero[4]=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_write_zero,4);  //[0]~[3] CRC 계산 결과
    
	/*
	-센서에 전송될 데이터 버퍼 설정, mlx_write_buffer[0] - 0xB4로 고정 (단순 PEC 계산용)
	mlx_write_buffer[1] - CMD 입력 (실제 전송에서 CMD로 사용 및 PEC 계산에 사용됨) 
	mlx_write_buffer[2] - 새로 계산한 방사율의 LSByte 저장
	mlx_write_buffer[3] - 새로 계산한 방사율의 MSByte 저장
	mlx_write_buffer[4] - mlx_write_buffer[0]~[3]까지의 CRC 계산값 저장
	mlx_write_buffer[1]~mlx_write_buffer[4]까지의 배열이 센서로 전송된다
	*/
	mlx_write_buffer[1]=mlx_cmd_emissivity;	//0x24(0010 0000 + 0x04) (EEPROM - 0x04 (Emissivity)
	mlx_write_buffer[2]=new_04&0xff;	//LSByte
	mlx_write_buffer[3]=(new_04>>8)&0xff;	//MSByte
	mlx_write_buffer[4]=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_write_buffer,4);

	printf("New_04 : %d, %X, %X, %X	crc : %X\r\n",new_04,new_04,mlx_write_buffer[2],mlx_write_buffer[3],mlx_write_buffer[4]);
    
	HAL_I2C_Master_Transmit_IT(&hi2c1,MLX90614_addr<<1,(uint8_t *)&mlx_write_zero[1],4);
	//쓰기를 원하는 주소에 먼저 0x0000 16비트 데이터를 써서 현재 저장된 값을 지운다
	//값을 지울 때도 PEC를 같이 전송해야 한다 (CMD, 0x00, 0x00, PEC)
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	//I2C Ready 상태까지 대기
	HAL_Delay(10);
	//딜레이 사용해 쓰기 완료 후 10ms 대기 (최소 5ms, 권장 10ms)
    
	HAL_I2C_Master_Transmit_IT(&hi2c1,MLX90614_addr<<1,(uint8_t *)&mlx_write_buffer[1],4);
	//CMD, 계산된 방사율을 상/하 8비트씩 나눈 값과 계산된 PEC값을 전송
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	HAL_Delay(10);
	//딜레이 사용해 쓰기 완료 후 10ms 대기 (최소 5ms, 권장 10ms)

	/*
	EEPROM - 0x0F 주소에 데이터 전송
	xCx 타입만 해당되는 사항으로 new_0f=round((old_04/new_04)*old_0f); 를 통해 얻은 16비트 데이터를
	0x0F 주소에 전송한다. 전송 순서 및 방법은 0x04에 데이터 쓸 때와 동일
	*/
    
//MLX90614xCx 타입을 사용할 경우, 0x0F 번지에 위에서 계산한 새로운 데이터를 입력
//과정은 위의 0x04 번지에 데이터를 쓸 때와 CMD가 다른 것 빼고는 동일
#ifdef mlx90614xCx
	mlx_write_zero[1]=mlx_cmd_emissivity_2;	//0x2F (001x xxxx + 0x0F)
	mlx_write_zero[4]=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_write_zero,4);
	mlx_write_buffer[1]=mlx_cmd_emissivity_2;	//0x2F (001x xxxx + 0x0F)
	mlx_write_buffer[2]=new_0f&0xff;
	mlx_write_buffer[3]=(new_0f>>8)&0xff;
	mlx_write_buffer[4]=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_write_buffer,4);
    
	HAL_I2C_Master_Transmit_IT(&hi2c1,MLX90614_addr<<1,(uint8_t *)&mlx_write_zero[1],4);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	HAL_Delay(10);

	HAL_I2C_Master_Transmit_IT(&hi2c1,MLX90614_addr<<1,(uint8_t *)&mlx_write_buffer[1],4);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	HAL_Delay(10);
#endif

	//EEPROM-0x04 번지에 값이 변경됐는지 확인
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_emissivity;
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_emissivity,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	crc_emissivity=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);
	if((mlx_read_buffer[3]==(new_04&0xff))&&(mlx_read_buffer[4]==((new_04>>8)&0xff)))
	{
		printf("0x04 : Successfully Changed\r\n");	
		//0x04번지에 입력한 새 방사율 데이터와 읽어 온 0x04번지의 방사율 데이터가 동일할 때
	}
	else
	{	
		//0x04번지에 저장된 방사율이 사용자가 입력한 방사율과 다를 때
	}

#ifdef mlx90614xCx
	//EEPROM-0x0F 번지에 값이 변경됐는지 확인
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_emissivity_2;
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_emissivity_2,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	crc_emissivity=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);
	if((mlx_read_buffer[3]==(new_0f&0xff))&&(mlx_read_buffer[4]==((new_0f>>8)&0xff)))
	{
		printf("0x0F : Successfully Changed\r\n");
		//0x0F번지에 입력한 새 방사율 데이터와 읽어 온 0x0F번지의 방사율 데이터가 동일할 때
	}
	else
	{
		//0x0F 번지에 저장된 데이터가 사용자가 입력한 데이터와 다른 경우
	}
#endif
}
  • Emissivity(방사율) 변경에 사용할 함수
  • 0.1~1.0까지 변경 가능하고 방사율 변경에 따른 온도 측정값 교정은 필요하지 않다
  • EEPROM에 데이터를 쓰기 전에 반드시 해당 주소에 0x0000을 전송해 데이터를 지운 뒤 새롭게 입력할 데이터를 전송해야 한다
  • MLX90614xAx, xBx 타입은 EEPROM - 0x04(Emissivity) 번지의 값만을 주어진 계산식을 통해 얻은 새 방사율값으로 변경하면 되지만 xCx 타입은 0x04 및 0x0F 번지에 현재 저장되어있는 데이터를 기반으로 하는 공식(0x04와는 다른)을 활용해 값을 산출한 뒤 이를 0x0F 번지에 추가로 전송해줘야 한다
  • xCx 타입을 사용할 경우, 혹시 모를 상황을 대비해 EEPROM-0x0F 번지에 저장되어있는 데이터를 따로 기록해 두는 것을 권장 (0x0F 번지에 저장되는 값은 0x04, 0x0F 번지의 데이터를 기반으로 계산된 값이라 한번 잘못 저장하면 돌이킬 수 없다)
  • EEPROM에 데이터를 쓴 직후와 그 다음 쓰기 or 읽기 사이에 적어도 5ms(10ms-안전)의 대기 시간이 필요하다
  • Read 함수와는 달리 센서 내 특정 메모리에 접근해서 데이터를 쓰는 방식이 아니기 때문에 CMD도 전송 데이터 버퍼 안에 포함되어 있어야 한다. (CMD, LSByte, MSByte, PEC 순으로 총 4개의 배열을 전송)
  • 센서로부터 데이터를 읽어 올 때와 동일하게 LSByte->MSByte 순으로 전송이 진행된다
  • 쉬운 CRC 계산을 위해 첫번째 배열([0],0x5A<<1)을 고정값으로 둔 채 두번째 배열([1])부터 마지막 배열까지만을 센서에 데이터를 쓰는데 사용
  • 방사율을 변경한 이후, 전원을 껏다 켜서 변경한 방사율값이 유지되는지 확인한다
  • (방사율을 변경하고 전원을 재인가 하지 않았을 때 온도 측정이 부정확해지는 현상 겪음, 재인가 후 온도 측정 정상 및 변경된 방사율 유지 확인)

- 현재 센서에 설정된 방사율을 읽어오는 함수

void mlx_read_emissivity()
{
	uint8_t crc_emissivity;		//CRC 계산값 저장에 사용
	uint16_t read_emissivity;	//EEPROM-0x04 번지로부터 읽어 방사율 데이터를 저장할 변수
	float cur_emissivity;		//읽어 온 방사율 데이터를 실제 방사율로 변환한 값을 저장할 변수
    
	mlx_read_buffer[mlx_cmd_pointer]=mlx_cmd_emissivity;	//EEPROM-0x04
	HAL_I2C_Mem_Read_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,mlx_cmd_emissivity,I2C_MEMADD_SIZE_8BIT,
    	(uint8_t *)&mlx_read_buffer[3],3);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
	crc_emissivity=HAL_CRC_Calculate(&hcrc,(uint32_t *)mlx_read_buffer,5);	//CRC 계산
	
	if(crc_emissivity==mlx_read_buffer[5])	//계산한 CRC값과 읽어 온 PEC 값이 일치할 때
	{
		read_emissivity=(mlx_read_buffer[4]<<8|mlx_read_buffer[3]);	
		//8비트씩 나누어진 방사율 데이터를 비트 시프트와 OR을 사용해 하나로 합침
        
		cur_emissivity=(read_emissivity+1)/65536.0;	
		//식을 통해 실제 방사율 값으로 변환
        
		printf("Current Emissivity : %.2f\r\n",cur_emissivity);	
	}
	else
	{
    		//읽어 온 PEC값과 계산한 CRC값이 일치하지 않을 때
	}
}

- 슬립 모드

슬립모드 예시

  • 슬립 모드에 진입하기 위해선 0xFF 명령어와 0xB4+0xFF를 CRC 계산한 결과값인 0xE8만 센서에 전송하면 된다
  • 전류 소비를 2.5μA로 제한하기 위해서 슬립 상태를 유지하는 동안 SCL핀을 Low로 유지하는 것을 권장. SCL 핀에 연결된 내부 통합 제너 다이오를 통한 누설 전류가 있기 때문. SCL 핀을 Push-Pull로 설정하고 SCL 핀에 Pull-Up 레지스터가 연결되어있지 않은 상태여야 한다.
  • 슬립 모드를 종료하기 위해선 SCL이 High인 상태에서 SDA를 최소 33ms 동안 Low로 유지하거나
  • or POR (Power on Reset)
void mlx_sleep()
{
	uint8_t enter_sleep[2]={0xFF,0xE8};	//0xFF-Sleep CMD, 0xE8-PEC (0xB4+0xFF)
	HAL_I2C_Master_Transmit_IT(&hi2c1,(uint16_t)MLX90614_addr<<1,(uint8_t *)&enter_sleep,2);
	while(HAL_I2C_GetState(&hi2c1)!=HAL_I2C_STATE_READY)
	{
	}
}

void mlx_wakeup()
{
	/*
	MLX90614와의 통신에 사용하는 I2C를 해제한 뒤, SDA로 사용하던 GPIO 핀을 새로운 설정으로 초기화
	SDA핀이 Low를 출력하도록 설정한 뒤 딜레이 함수를 이용해 33ms를 대기함으로써 슬립 모드에서 벗어난다
	이후 SDA핀에 대한 GPIO설정을 해제한 뒤 MLX90614를 사용하기 위해 I2C를 다시 초기화한다
	*/
	HAL_I2C_DeInit(&hi2c1);	//MLX90614와의 통신에 사용하는 I2C 해제
    
	/*
	SCL로 사용되던 GPIO 핀을 새롭게 설정
	*/
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);	//통신에 사용하던 I2C SDA 핀과 핀이 속한 포트
	GPIO_InitStruct.Pin = GPIO_PIN_9;	//SDA로 사용하던 핀 번호
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;	//Output Mode
	GPIO_InitStruct.Pull = GPIO_NOPULL;	//No Pull-Up
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);	//GPIO 초기화 시작
	HAL_Delay(33);	//33ms 대기 (SDA핀은 0인 상태)
	HAL_GPIO_DeInit(GPIOB,GPIO_PIN_9);	//GPIO 설정 해제
    
	HAL_I2C_Init(&hi2c1);	//MLX90614 통신에 사용하던 I2C 초기화 시작
	HAL_Delay(250);	
	//센서가 정상 작동을 시작하고 0.25초 후에 유효한 첫 데이터를 얻을 수 있으므로 250ms 동안 대기
}
  • I2C 통신을 통해 슬립 모드를 벗어나는 방법은 찾지 못했기 때문에 I2C 설정을 해제하는 방법으로 진행
  • MLX90614와의 통신에 사용하던 I2C를 해제한 뒤 I2C SDA핀으로 사용되던 GPIO핀을 LOW 출력 설정
  • (SCL핀의 경우 High 상태를 유지하는 것을 확인했으므로 SDA핀만 재설정)
  • 딜레이 함수를 통해 슬립 모드에서 벗어나기 위한 최소 시간인 33ms 동안 대기 (SDA핀 LOW 상태)
  • 이후 GPIO핀 설정을 해제하고 센서와의 통신을 위해 I2C를 다시 초기화한다

mlx_sleep() 함수 실행 이후 바로 wakeup 함수 호출한 상황
wake up 이후 0.25초 대기
대기 후 온도 mlx_read_temperature() 함수 시작

- 예시

void main(void)
{
	...
	/* USER CODE BEGIN 2 */
	HAL_Delay(1000);
	mlx_read_emissivity();	//EEPROM-0x04 번지에 현재 설정되어있는 방사율 읽어옴
	mlx_change_emissivity(1.0);	//방사율 1.0으로 변경
	printf("\n");
  	mlx_read_emissivity();	//현재 설정되어있는 방사율 읽기 (설정 변경 확인용)
	mlx_change_emissivity(0.9);	//방사율 0.9로 변경
	printf("\n");
	mlx_read_emissivity();	//현재 설정되어있는 방사율 읽기 (설정 변경 확인용)
	mlx_sleep();	//슬립 모드 진입
	printf("Sleep\n");
	mlx_wakeup();	//슬립 모드에서 빠져나옴
	printf("Wake up\n");
	mlx_read_emissivity();	//현재 설정되어있는 방사율 읽기 (설정 변경 확인용)
  	/* USER CODE END 2 */

  	/* Infinite loop */
  	/* USER CODE BEGIN WHILE */
  	while (1)
  	{
  	/* USER CODE END WHILE */

  	/* USER CODE BEGIN 3 */

  		mlx_read_temperature();
  		HAL_Delay(1000);
  	}
  	/* USER CODE END 3 */
}
  • 방사율 1.0 설정 후, 0.9로 재변경

출력 결과

- xCx 타입의 경우

  • 딜레이가 없는 경우, 디버그가 실행 직후부터 main() 함수가 재실행되어 mlx_change_emissivity() 함수가 호출 및 실행되다가 디버그 창이 뜨면서 도중에 중단되는 상황이 발생
  • 이로인해, EEPROM 내에 데이터를 쓰던 중에 통신이 중단되면서 내부 데이터가 기존 데이터+새로운 데이터가 혼재된 상태가 되었다
  • EEPROM-0x04 번지의 방사율은 고정된 값과 주어진 방사율을 기반으로 계산된 값이 들어가기 때문에 이에따른 문제가 없었지만 0x0F 번지의 데이터는 기존에 저장된 0x04, 0x0F 번지의 데이터를 기반으로 새로운 값이 만들어지기에 비정상적인 값이 저장되는 현상이 발생했다
  • 때문에 딜레이 함수를 사용해 mlx_change_emissivity() 함수가 실행되는 시기를 늦춤으로써 디버그 준비 직후부터 실행 직전까지의 mlx_change_emissivity() 함수 호출이 안되게끔 했다

 

+ Recent posts