[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

+ Recent posts