[목적]

- TCS34725 센서를 통해 측정된 RGB값을 명도(Perceived Lightness)로 변환해 원두의 로스팅 정도를 파악

(변환 공식 : https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color - Myndex)

- 실제 원두용 색도계는 근적외선 파장(NIR)을 측정

 

[실험 조건]

- 홀빈 상태와 분쇄 원두, 두 경우를 나누어 측정

- 홀빈, 분쇄 원두 표면과 센서와의 거리는 약 10mm

- A4 용지를 흰색 기준점으로 삼아 RGB 255,255,255 값을 갖는 최소한의 Timing , Gain 값으로 센서 레지스터 설정

- 아그트론 측정값을 갖는 원두를 테스트용으로 사용

- 홀빈 상태의 원두는 측정값 편차로 10회 측정 후 평균값 이용

- 분쇄 원두는 에스프레소 분쇄도, 측정 편차가 발생하지 않으므로 1회만 측정

 

[테스트 과정 중 문제점]

- 홀빈 상태의 경우, 일관된 RGB 값을 측정하는 것은 불가했음

(타원형인 원두의 모양으로 인해 센서와 측정되는 부분의 거리를 일정하게 유지할 수 없는 점과 원두 표면과 센터컷 사이의 색상 편차 때문으로 추정)

- 따라서, 측정 때마다 케이스에 담은 원두를 재정렬 및 평탄화 과정을 거친 뒤 10회 측정후 평균값을 구함

(아그트론 넘버를 사용하는 몇몇 원두용 색도계에서도 홀빈 상태에서의 측정값 편차가 발생한다고 함)

 

[테스트 대상]

- 측정 원두 : 아그트론 넘버 홀빈(59.64), 분쇄(61.02) (아그트론 넘버 낮을 수록 어두운 색상)

 

[실험 과정]

<A4 용지 색상 측정>

Timing 값 설정

- A4 용지를 기준으로 RGB 255, 255, 255 값을 갖는 최소한의 Timing, Gain 값을 찾아서 설정

 

<홀빈 색상 측정>

케이스에 최대한 평평하게 채운 뒤 측정

1회 2회 3회 4 5회 6회 7회 8회 9회 10회
37.46 37.73 37.55 37.55 37.95 37.64 37.63 37.64 37.84 37.84

(측정된 명도값은 소수점 세번째 자리에서 반올림)

- 평균 : 37.68

 

<분쇄 원두 색상 측정>

- 1회 측정값 : 37.77

 

[결론]

- 홀빈 색상 측정할 때, 센서와 측정되는 홀빈의 거리를 최대한 일정하게 유지해 측정값의 편차를 최소화하는게 중요

(최대한 홀빈을 평평하게 정렬)

- 홀빈 측정 결과들간의 오차가 있기 때문에 다회차 측정은 피할 수 없는 상황

- 홀빈 10회 측정 평균과 분쇄 원두 측정값의 차이는 0.09

- 너무 작은 차이로 인해 실제 홀빈 및 분쇄 원두 색상 측정용으로 사용하기에는 좀 어려워보이는데 확실한 결론을 위해선 더 다양한 데이터를 누적 시킬 필요가 보임

- 만약, 누적된 데이터를 통해 실제 사용이 유효하다 판단된다면

- 로스팅 단계에 따른 색상 기준점이 없기 때문에 아그트론 값을 가진 원두의 명도 측정값과 비교해 기준점을 만든다거나 누적된 데이터(로스팅 시간과 수분 증발율에 따른 명도값)를 기반으로 나름의 기준점 작성

- 홀빈과 분쇄 원두 사이의 명도 차이를 통해 언더 디벨롭 여부 파악 가능할듯

/** 20.06.09 수정

 * 1. tcs_read_all_reg_thread() 함수 수정 및 본문에 추가 (static 구조체 변수 -> pvPortMalloc 메모리 할당으로 변경)

 * 2. tcs34725_cmd_func() 함수 수정 (static 구조체 변수 -> pvPortMalloc 메모리 할당으로 변경 및 자잘한 수정)

 * 3. tcs34725_read_threshold() 함수 수정 (단순 LOW, HIGH 선택 방법에서 구조체 포인터 변수 활용 방식으로 변경)

 * 4. tcs34725_read_reg_cb(), tcs34725_read_thr_cb() 함수 수정 (vPortFree() 추가)

 */

 

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

https://rs29.tistory.com/18

 

nRF52 - TWI 이용 TCS34725 RGB 색상 검출 센서

- 사용 보드 : nRF52840-PDK - 사용 IDE : SEGGER Embedded Studio for ARM (노르딕 제품 사용하는 경우 무료 라이센스 이용 가능) - 작동 전압 : 2.7~3.6V (사용 모듈의 경우, 3.3~5.0V) - 검출 색상 : Red, Gre..

rs29.tistory.com

 

[개발 환경]

- 개발 보드 : nRF52840-PDK

- IDE : SEGGER Embedded Studio for ARM

- 센서 통신 : 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_app_uart 예제 그대로 사용

- BLE 통신을 통해 명령어를 수신 및 실행하거나 RGBC 측정값과 레지스터 정보를 전송

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


<기본 작동>

[RGBC 측정값 전송]

- RGBC 레지스터에 저장된 측정값을 읽어 오는 태스크가 vTaskDelay() 간격으로 상시 실행

- RGBC 측정값 수신 완료 후, 읽어 온 RGBC 값을 다룰 콜백 함수가 호출됨

- 콜백 함수에서 RGBC 값을 가공한 뒤에 큐에 등록하고 RGBC 값을 BLE를 통해 전송하는 태스크에 xTaskNotify() 함수를 사용해 알림 전달

- RGBC 데이터 BLE 전송 태스크에서 알림 수신 및 큐에 등록된 RGBC 데이터값을 받아와 BLE 통신을 통해 RGBC값을 전송

 

[명령어 실행]

- BLE 통신을 통해 데이터를 수신하면 명령어 부분과 데이터 부분으로 나눈 뒤에 큐에 등록 및 명령어를 처리하는 태스크에 알림 전달

- 명령어 처리 태스크는 알림 및 큐에 등록된 명령어 데이터를 받아와 명령어와 일치하는 TCS34725 레지스터에 액세스해 명령어 함께 수신된 데이터를 입력하고 다시 해당 레지스터에 액세스해 저장된 데이터를 읽어 옴 (데이터 입력 확인)

- 레지스터로부터 데이터를 읽어 온 직후, 콜백 함수가 호출되고 이 콜백 함수에서 데이터를 가공해 큐에 등록 및 BLE 통신을 통해 레지스터 데이터를 전송하는 태스크에 알림 전달

- 레지스터 데이터를 전송하는 태스크에서 알림 수신 및 큐에 등록된 데이터를 받아와 BLE 통신을 통해 전송


[코드]

 <TCS34725 RGBC Read 태스크>

static void tcs_read_rgbc_thread(void *arg)
{
    //스택 사이즈 확인용. 할당된 스택 크기에서 사용되고 남은 크기를 알려준다
    #ifdef STACK_SIZE_CHK
    configSTACK_DEPTH_TYPE uxHighWaterMark2;
    uxHighWaterMark2=uxTaskGetStackHighWaterMark(NULL);
    uint8_t stack_left=255;
    #endif

	//TCS34725로부터 읽어 온 RGBC 데이터를 저장할 구조체 변수
    tcs34725_rgbc_data_t tcs_rgbc_thread;
    
    while(1)
    {
    	//TCS34725 RGBC 레지스터에 접근해 저장된 측정값을 읽어 오는 함수 호출
        //측정값 수신 완료 후, 콜백 함수 호출
        tcs34725_read_rgbc(&tcs34725_instance,&tcs_rgbc_thread,tcs34725_rgbc_cb);
        
        vTaskDelay(200);

        //잔여 스택 크기를 지속적으로 갱신
        #ifdef STACK_SIZE_CHK
        uxHighWaterMark2=uxTaskGetStackHighWaterMark(NULL);
        if(uxHighWaterMark2<stack_left)
        {
            stack_left=uxHighWaterMark2;
            printf("Available stack size of RGBC read thread : %d\r\n",uxHighWaterMark2);
        }
        #endif
    }
}

<TCS34725 RGBC 콜백 함수>

void tcs34725_rgbc_cb(ret_code_t result, tcs34725_rgbc_data_t * p_raw_data)
{
    if(result!=NRF_SUCCESS)
    {
        NRF_LOG_INFO("tcs rgbc callback failed");
        return;
    }
    
    //가공한 RGB 데이터를 저장할 구조체 변수
    tcs34725_rgbc_data_t tcs_rgbc_cb_str;
    
    //읽어 온 RGB 데이터 가공 후, 선언한 구조체 변수에 저장
    tcs_rgbc_cb_str.clear=p_raw_data->clear;
    tcs_rgbc_cb_str.red=(int)((double)p_raw_data->red/p_raw_data->clear*255);
    tcs_rgbc_cb_str.green=(int)((double)p_raw_data->green/p_raw_data->clear*255);
    tcs_rgbc_cb_str.blue=(int)((double)p_raw_data->blue/p_raw_data->clear*255);

    //가공한 RGBC 데이터를 저장한 구조체 변수를 큐에 전송
    //큐에 남은 자리가 있다면 RGBC 구조체 변수 큐에 등록 후 BLE 전송 태스크에 알림 전달
    if(uxQueueSpacesAvailable(m_tcs_rgb_data_queue)!=0)
    {
        if(pdTRUE!=xQueueSend(m_tcs_rgb_data_queue, &tcs_rgbc_cb_str, 10))
        {
            NRF_LOG_INFO("TCS34725 RGBC CB : Queue send fail\r\n");
        }
        xTaskNotifyGive(m_ble_tcs_rgbc_send_thread);
    }
    //큐가 Full 이라면 큐에 RGBC 구조체 변수를 덮어쓰기 하고 알림 전달 X
    else
    {
        if(pdTRUE!=xQueueOverwrite(m_tcs_rgb_data_queue, &tcs_rgbc_cb_str))
        {
            NRF_LOG_INFO("TCS34725 RGBC CB : Queue overwrite fail\r\n");
        }
    }
    
    //BLE 통신을 통해 RGBC 데이터를 전송하는 태스크에 알림 전달
    xTaskNotifyGive(m_ble_tcs_rgbc_send_thread);
}

<TCS34725 RGBC BLE 전송 태스크>

static void ble_tcs_rgbc_send_thread(void *arg)
{
    ret_code_t err_code;
    
    //큐에 등록된 RGBC 구조체 변수 데이터를 받아 올 RGBC 구조체 변수 선언
    tcs34725_rgbc_data_t ble_tcs_send_rgb;
    
    //BLE 통신을 통해 전송될 문자열을 저장할 캐릭터 배열
    char data_array[18]={};
    //BLE 통신을 통해 전송될 문자열의 길이를 저장할 변수
    uint16_t length=sizeof(data_array);

    while(1)
    {
    	/*
    	전달 받은 알림이 있는지 확인 (알림이 없다면 0, 있다면 !=0) 
        && 큐에서 BLE 통신을 통해 전송할 RGBC 구조체 변수 읽어 옴
        */
        if((ulTaskNotifyTake(pdTRUE,10)!=0)&&
          (pdPASS==xQueueReceive(m_tcs_rgb_data_queue, &ble_tcs_send_rgb, 10)))
        {	
            //sprintf 함수를 통해 BLE 통신을 통해 전송될 문자열 생성
            //RGB((3)명령어)+Clear(5)+Red(3)+Green(3)+Blue(3)=17+"\n"
            sprintf(data_array,"RGB%5d%3d%3d%3d",ble_tcs_send_rgb.clear,ble_tcs_send_rgb.red,ble_tcs_send_rgb.green,
                    ble_tcs_send_rgb.blue);
            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);
                }
            }
        }
    }
}

<BLE 통신을 통해 수신한 UART 데이터 처리 함수>

/*
- BLE 통신을 통해 수신된 문자열 (명령어(3)+레지스터 데이터(3) or 쓰레스홀드 데이터(5))을
  저장하기 위한 구조체
*/
typedef struct{
    char cmd[4],data[6];
}tcs34725_cmd_t;

static void nus_data_handler(ble_nus_evt_t * p_evt)
{
    if (p_evt->type == BLE_NUS_EVT_RX_DATA)
    {
        uint32_t err_code;
        
        //수신한 문자열을 명령어, 데이터 부분으로 나누어 저장할 구조체 변수 선언
        tcs34725_cmd_t nus_cmd_str={0};

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

        //수신한 문자열 UART 출력
        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번째 문자를 구조체 변수의 명령어 부분에 저장
        memcpy(nus_cmd_str.cmd, p_evt->params.rx_data.p_data, 3);

        /*
        일반 레지스터 설정은 최대 255까지 입력 가능하므로 세 자리의 문자 길이,
        쓰레스홀드는 최대 65535까지 입력 가능하므로 다섯 자리의 문자 길이를 갖는다
        */
        
        /*
        - 일반 레지스터 설정
        수신한 문자열의 4~6번째 문자를 구조체 변수의 데이터 부분에 저장
        */
        if((4<p_evt->params.rx_data.length)&&(p_evt->params.rx_data.length<8))
        {
            memcpy(nus_cmd_str.data, &p_evt->params.rx_data.p_data[3], 3);
        }
        /*
        - 쓰레스홀드 설정
        수신한 문자열의 4~8번째 문자를 구조체 변수의 데이터 부분에 저장
        */
        else if(7<p_evt->params.rx_data.length)
        {
            memcpy(nus_cmd_str.data,&p_evt->params.rx_data.p_data[3],5);
        }

        //큐 잔여량을 확인해 잔여 상황에 따라 큐 전송을 다르게 처리함
        if(uxQueueSpacesAvailable(m_tcs_cmd_queue)!=0)
        {
            //큐에 남은 공간이 있을 경우, 구조체 변수를 큐에 등록 후 명령어 처리 태스크에 알림 전달
            if(pdPASS!=xQueueSend(m_tcs_cmd_queue,&nus_cmd_str,10))
            {
                NRF_LOG_INFO("NUS DATA HANLDER : Queue send fail");
            }
            xTaskNotifyGive(m_tcs_wr_reg_thread);
        }
        else
        {
            //큐에 남은 공간이 없는 경우, 덮어쓰기로 큐에 구조체 변수를 등록하고 알림은 전달하지 않음        
            if(pdPASS!=xQueueOverwrite(m_tcs_cmd_queue,&nus_cmd_str));
            {
                NRF_LOG_INFO("NUS DATA HANLDER : Queue overwrite fail");
            }
        }
    }
}

- SDK - BLE_APP_UART 예제에 나와있는 함수를 사용

- 큐에 남은 공간이 없을 때 구조체 변수를 덮어쓰기하면서 알림을 전달하지 않는 이유는 기존에 등록된 큐에 새로운 데이터를 덮어쓰는 행동은 처리해야 할 큐의 양을 증가 시키는 것이 아니기 때문

 

<수신 명령어 처리 태스크 및 함수>

static void tcs_wr_reg_thread(void *arg)
{
    ret_code_t err_code;
    tcs34725_cmd_t wr_cmd_str;

    while(1)
    {
    	/*
        전달된 알림이 있고 큐에 등록된 데이터를 성공적으로 읽어 왔다면 
        TCS34725 명령어 처리 함수에 읽어 온 명령어 구조체 변수 전달
        */
        if((ulTaskNotifyTake(pdTRUE,10)!=0)&&(pdPASS==xQueueReceive(m_tcs_cmd_queue,&wr_cmd_str,10)))
        {
            tcs34725_cmd_func(&wr_cmd_str);
        }
        vTaskDelay(100);
    }
}

/*
전달된 명령어에 따라 해당 레지스터에 액세스해 명령어와 함께 수신된 데이터를 입력하고
다시 해당 레지스터에 액세스해 설정된 데이터를 읽어 온 뒤 BLE 통신을 통해 전송
*/
void tcs34725_cmd_func(tcs34725_cmd_t *cmd_func_str)
{
	ret_code_t err_code;
	
    //수신한 명령어가 쓰레스홀드 명령어일 경우
    if((strcmp(cmd_func_str->cmd,"THL")==0)||(strcmp(cmd_func_str->cmd,"THH")==0))
    {
        //TCS34725로부터 쓰레스홀드 설정값을 수신할 쓰레스홀드 구조체 포인터 변수 메모리 할당
        tcs34725_threshold_data_t *tcs_cmd_thr=(tcs34725_threshold_data_t*)pvPortMalloc(sizeof(tcs34725_threshold_data_t));

        //쓰레스홀드 하한 설정
        if(strcmp(cmd_func_str->cmd,"THL")==0)
        {
            NRF_LOG_INFO("Set Threshold Low");
            err_code=tcs34725_set_threshold(&tcs34725_instance,TCS34725_THRESHOLD_LOW,chartoint(cmd_func_str->data,5));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set Threshold Low fail");
                return;
            }
            tcs_cmd_thr->reg_addr=TCS34725_REG_THRESHOLD_LOW_L;
            err_code=tcs34725_read_threshold(&tcs34725_instance, tcs_cmd_thr, tcs34725_read_thr_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read Threshold Low fail");
                return;
            }
        }
        //쓰레스홀드 상한 설정
        else if(strcmp(cmd_func_str->cmd,"THH")==0)
        {
            NRF_LOG_INFO("Set Threshold High");
            err_code=tcs34725_set_threshold(&tcs34725_instance,TCS34725_THRESHOLD_HIGH,chartoint(cmd_func_str->data,5));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set Threshold High fail");
                return;
            }
            tcs_cmd_thr->reg_addr=TCS34725_REG_THRESHOLD_HIGH_L;
            err_code=tcs34725_read_threshold(&tcs34725_instance, tcs_cmd_thr, tcs34725_read_thr_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read Threshold High fail");
                return;
            }
        }
    }
    //쓰레스홀드 외에 명령어일 때
    else
    {
        //TCS34725 레지스터값을 수신할 구조체 포이터 변수 메모리 할당
        tcs34725_reg_data_t *tcs_cmd_str=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
        
        //TCS34725 각 레지스터에 설정된 데이터를 읽어 온 후 BLE 통신을 통해 전송하는 명령어
        if(strcmp(cmd_func_str->cmd,"RAR")==0)
        {
            /*    
            TCS34725의 각종 레지스터에 설정된 데이터를 읽어오는 태스크 생성 (ex:인터럽트 활성화 여부, 쓰레스홀드값 등등)
            읽어 온 데이터는 콜백 함수를 통해 가공된 후 BLE 통신을 통해 전송
            */
            if(pdPASS!=xTaskCreate(tcs_read_all_reg_thread, "TCS_READ_ALL_REG", configMINIMAL_STACK_SIZE+30,
                           NULL, 3, &m_tcs_reg_all_send_thread))
            {
                APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
            }
        }
        //타이밍값 설정
        else if(strcmp(cmd_func_str->cmd,"TIM")==0)
        {
            NRF_LOG_INFO("Set Timming");
            err_code=tcs34725_set_timing(&tcs34725_instance,chartoint(cmd_func_str->data,3));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set timing fail");
                return;
            }
            tcs_cmd_str->reg_addr=TCS34725_REG_TIMING;
            err_code=tcs34725_read_reg(&tcs34725_instance,tcs_cmd_str,tcs34725_read_reg_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read timing fail");
                return;
            }
        }
        //대기 시간 설정
        else if(strcmp(cmd_func_str->cmd,"WAT")==0)
        {
            NRF_LOG_INFO("Set Wait Time");
            err_code=tcs34725_set_wait_time(&tcs34725_instance,chartoint(cmd_func_str->data,3));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set wait time fail");
                return;
            }
            tcs_cmd_str->reg_addr=TCS34725_REG_WAIT_TIME;
            err_code=tcs34725_read_reg(&tcs34725_instance,tcs_cmd_str,tcs34725_read_reg_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read wait time fail");
                return;
            }
        }
        //게인값 설정
        else if(strcmp(cmd_func_str->cmd,"GIN")==0)
        {
            NRF_LOG_INFO("Set gain");
            err_code=tcs34725_set_gain(&tcs34725_instance,chartoint(cmd_func_str->data,3));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set gain fail");
                return;
            }
            tcs_cmd_str->reg_addr=TCS34725_REG_CONTROL;
            err_code=tcs34725_read_reg(&tcs34725_instance,tcs_cmd_str,tcs34725_read_reg_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read gain fail");
                return;
            }
        }
        //인터럽트 활성화 설정
        else if(strcmp(cmd_func_str->cmd,"ENA")==0)
        {
            NRF_LOG_INFO("Set interrupt");
            err_code=tcs34725_set_interrupt(&tcs34725_instance,chartoint(cmd_func_str->data,3));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set interrupt fail");
                return;
            }
            tcs_cmd_str->reg_addr=TCS34725_REG_ENABLE;
            err_code=tcs34725_read_reg(&tcs34725_instance,tcs_cmd_str,tcs34725_read_reg_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read interrupt fail");
                return;
            }
        }
        //Wait Long 활성화 설정 (활성화시 대기 시간이 설정된 대기 시간 x12가 된다)
        else if(strcmp(cmd_func_str->cmd,"WLO")==0)
        {
            NRF_LOG_INFO("Set wait long");
            err_code=tcs34725_set_wait_long(&tcs34725_instance,chartoint(cmd_func_str->data,3));
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set wait long fail");
                return;
            }
            tcs_cmd_str->reg_addr=TCS34725_REG_CONFIG;
            err_code=tcs34725_read_reg(&tcs34725_instance,tcs_cmd_str,tcs34725_read_reg_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read wait long fail");
                return;
            }
        }
        /*
        - 인터럽트가 발생하기까지 필요한 쓰레스홀드값을 벗어나는 Clear 값의 연속 측정 횟수 설정
        Persistence 값은 다른 레지스터 데이터와는 다르게 10진수값과 1:1 매칭이 되지 않는다
        따라서 설정값에 해당하는 2진수 데이터로 변환하는 과정이 필요하다
        */
        else if(strcmp(cmd_func_str->cmd,"PER")==0)
        {
            //2진수 데이터로 변환된 Persistence 값을 저장할 변수
            uint8_t persistence_val;
            
            NRF_LOG_INFO("Set Persistence");
            
            //BLE 통신을 통해 수신된 Persistence 데이터는 문자열이므로 상수로 변환
            persistence_val=chartoint(cmd_func_str->data,3);
            
            //설정값에 해당하는 2진수값으로 변환 (ex: 5 = 0100, 60 = 1111)
            persistence_val=tcs34725_per_dectobin(persistence_val);
            
            err_code=tcs34725_set_persistence(&tcs34725_instance,persistence_val);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Set Persistence fail");
                return;
            }
            tcs_cmd_str->reg_addr=TCS34725_REG_PERSISTENCE;
            err_code=tcs34725_read_reg(&tcs34725_instance,tcs_cmd_str,tcs34725_read_reg_cb);
            if(err_code!=NRF_SUCCESS)
            {
                NRF_LOG_INFO("Read Persistence fail");
                return;
            }
        }
        else
        {
            return;
        }
    }
}

- 명령어에 해당하는 레지스터에 데이터를 입력하고 다시 액세스해 저장된 데이터를 읽어 온 뒤 BLE 통신을 통해 전송함으로써 명령어와 데이터가 제대로 입력되었는지 확인

 

<TCS34725 레지스터 Read 콜백 함수>

/*
- BLE 전송 태스크에서 전송될 데이터를 저장하기 위한 구조체
- 명령어(3)+레지스터 데이터(3)
- or 명령어(3)+쓰레스홀드 레지스터 데이터(5)
- +'\n'
*/
typedef struct{
    char send_data[9];
}tcs34725_ble_reg_t;

void tcs34725_read_reg_cb(ret_code_t result, tcs34725_reg_data_t * p_raw_data)
{
    if(result!=NRF_SUCCESS)
    {
        NRF_LOG_INFO("TCS34725 register read fail");
        return;
    }
    
    //명령어 부분을 저장할 캐릭터 배열
    char read_reg_cb_cmd[]="CMD";
    
    //2진수 Persistence 값을 10진수로 변환하는데 사용할 변수
    uint8_t persistence_value;
    
    //Timing, Wait Time 레지스터 값이 0인 경우(설정값=256),
    //tcs34725_reg_data_t 구조체의 레지스터 값을 저장하는 변수 자료형은 uint8_t라 
    //오버플로우 발생하므로 uint16_t 변수를 이용해 따로 저장
    uint16_t reg_value;
    
    /*
    TCS34725 레지스터에 액세스하기 위해서 레지스터 주소=레지스터 주소+커맨드 레지스터(0x80)를 사용하므로
    레지스터 주소 & 0x1F 를 사용해 커맨드 레지스터 부분을 제거해 레지스터 주소만 남긴다
    */
    p_raw_data->reg_addr&=0x1F;
    
    //레지스터 주소에 해당하는 명령어를 앞서 선언한 캐릭터 배열에 저장
    switch(p_raw_data->reg_addr)
    {
    	//Enable 레지스터 (인터럽트 활성화 여부 확인)
        case TCS34725_REG_ENABLE :
            NRF_LOG_INFO("Enable register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"ENA");
            break;
        //타이밍 레지스터
        case TCS34725_REG_TIMING :
            NRF_LOG_INFO("Timing register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"TIM");
            reg_value=256-p_raw_data->reg_data;
            break;
        //Wait Time 레지스터(Wait Time 활성화 여부 확인)
        case TCS34725_REG_WAIT_TIME :
            NRF_LOG_INFO("Wait time register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"WAT");
            reg_value=256-p_raw_data->reg_data;
            break;
        /*
        - Persistence 레지스터
        Persistence 값은 다른 레지스터 데이터와는 다르게 10진수 값이 아닌 2진수값이므로 10진수로 변환
        */
        case TCS34725_REG_PERSISTENCE :
            NRF_LOG_INFO("Persistence register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"PER");
            persistence_value=p_raw_data->reg_data;
            persistence_value=tcs34725_per_bintodec(persistence_value);
            p_raw_data->reg_data=persistence_value;
            break;
        //Configuration 레지스터 (Wait Long 활성화 여부 확인)
        case TCS34725_REG_CONFIG :
            NRF_LOG_INFO("Configuration register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"WLO");
            break;
        //컨트롤 레지스터 (게인 설정)
        case TCS34725_REG_CONTROL :
            NRF_LOG_INFO("Control register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"GIN");
            break;
        // ID 레지스터 (센서 모델명 확인)
        case TCS34725_REG_ID :
            NRF_LOG_INFO("ID register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"IDR");
            break;
        //Status 레지스터 (인터럽트 활성화 여부 및 RGBC 유효한지 확인(integration cycle 완료 여부))
        case TCS34725_REG_STATUS :
            NRF_LOG_INFO("Status register : %X",p_raw_data->reg_data);
            strcpy(read_reg_cb_cmd,"STA");
            break;
        default :
            break;
    }

	//BLE 통신을 통해 레지스터 데이터를 전송하는 태스크에 전달될 구조체 변수 생성
    tcs34725_ble_reg_t tcs_ble_send_str;
    
    //구조체 변수에 명령어+데이터로 구성된 문자열 입력
    if((p_raw_data->reg_addr==TCS34725_REG_TIMING)||(p_raw_data->reg_addr==TCS34725_REG_WAIT_TIME))
    {
        sprintf(tcs_ble_send_str.send_data,"%s%3d",read_reg_cb_cmd,reg_value);
    }
    else
    {
        sprintf(tcs_ble_send_str.send_data,"%s%3d",read_reg_cb_cmd,p_raw_data->reg_data);
    }
  
    //콜백 함수가 호출됐다는 건, TCS34725 레지스터로부터 데이터 수신이 완료됐다는 뜻이므로
    //레지스터 Read에 사용했던 구조체 포인터 변수에 할당된 메모리를 해제
    vPortFree(p_raw_data);
    
  	//큐에 잔여 공간이 남아있다면 구조체 변수를 큐에 등록한 뒤 BLE 전송 태스크에 알림 전달
    if(uxQueueSpacesAvailable(m_tcs_reg_data_queue)!=0)
    {
        if(pdPASS!=xQueueSend(m_tcs_reg_data_queue,&tcs_ble_send_str,10))
        {
            NRF_LOG_INFO("TCS34725 READ REG CB : Queue send fail");
        }
        xTaskNotifyGive(m_ble_tcs_reg_send_thread);
    }
    //큐가 꽉찬 경우, 구조체 변수를 덮어쓰기로 큐에 등록하고 알림 전달은 하지 않음
    else 
    {
        if(pdPASS!=xQueueOverwrite(m_tcs_reg_data_queue,&tcs_ble_send_str))
        {
            NRF_LOG_INFO("TCS34725 READ REG CB : Queue overwrite fail");
        }
    }
}

- p_raw_data->reg_addr는 tsc34725_reg_read() 함수를 통해 전달된 구조체 변수의 레지스터 주소를 가리킨다

- 이 레지스터 주소와 Switch문을 통해 레지스터 주소에 해당하는 명령어를 선택한다

- 명령어+읽어 온 데이터를 큐에 등록한 뒤 BLE 전송 태스크에 알림을 전달

 

<TCS34725 레지스터 데이터 BLE 전송 태스크>

static void ble_tcs_reg_send_thread(void *arg)
{
    ret_code_t err_code;
    
    //큐에 등록된 명령어+레지스터 데이터로 구성된 데이터를 받아올 구조체 변수
    tcs34725_ble_reg_t ble_tcs_send_reg;
    
    //구조체 변수의 길이 (BLE 전송에 필요)
    uint16_t length=sizeof(ble_tcs_send_reg);

    while(1)
    {
    	/*
    	- 전달된 알림이 있는 경우, BLE 전송 시작
        - xTaskNotifyGive()는 해당 태스크에 알림값을 1씩 증가 시킨다
        (xTaskNotify는 32bit내 임의의 값을 전달 가능)
        - ulTaskNotifyTake() 매개변수
        - pdTRUE의 경우, 알림을 획득하면서 알림값을 초기화 시킨다
        - pdFALSE의 경우, 알림을 획득하면서 현재 누적된 알림값을 1만큼 감소 시킨다
        - 큐에 데이터를 등록하면서 xTaskNotifyGive()를 통해 알림값을 1씩 누적 시키므로
          큐에 등록된 데이터 숫자만큼 순차적으로 데이터를 전송한다
        */
        if((ulTaskNotifyTake(pdFALSE,10)!=0)&&
        (pdPASS==xQueueReceive(m_tcs_reg_data_queue, &ble_tcs_send_reg, 10)))
        {
            if(m_conn_handle!=BLE_CONN_HANDLE_INVALID)
            {
                err_code=ble_nus_data_send(&m_nus, ble_tcs_send_reg.send_data, &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);
                }
            }
        }
    }
}

- TCS34725 레지스터 Read 콜백 함수에서 BLE 통신을 통해 전송될 구조체 변수를 큐에 등록하면서 xTaskNotifyGive()를 통해 알림도 함께 전달하는데 이 알림값은 1씩 누적되는 값이다

- 그리고 ulTaskNotifyTake의 매개변수를 pdFALSE로 설정할 경우, 알림을 획득하면서 누적된 알림값을 1씩 감소 시키므로 누적된 알림값만큼 BLE 전송이 실행된다

- (pdTRUE의 경우, 알림 획득하며 알림값을 초기화하기 때문에 큐에 누적된 데이터를 처리하기에 부적합)

- 따라서 큐에 등록된 데이터를 처리하는 속도보다 큐에 등록되는 데이터의 속도가 더 빨라 큐가 Full이 되더라도 순차적으로 먼저 큐에 등록된 데이터부터 처리하며 큐를 비움

 

<쓰레스홀드 콜백 함수>

void tcs34725_read_thr_cb(ret_code_t result, tcs34725_threshold_data_t * p_reg_data)
{
    if(result!=NRF_SUCCESS)
    {
        NRF_LOG_INFO("TCS34725 Threshold register read fail");
        return;
    }

    char read_thr_cb_cmd[]="CMD";
    
    if(p_reg_data->reg_addr==TCS34725_REG_THRESHOLD_LOW_L)
    {
        NRF_LOG_INFO("Threshold Low value : %d",p_reg_data->threshold_data);
        strcpy(read_thr_cb_cmd,"THL");
    }
    else
    {
        NRF_LOG_INFO("Threshold High value : %d",p_reg_data->threshold_data);
        strcpy(read_thr_cb_cmd,"THH");
    }

    tcs34725_ble_reg_t tcs_ble_send_str;
    sprintf(tcs_ble_send_str.send_data,"%s%5d",read_thr_cb_cmd,p_reg_data->threshold_data);
    vPortFree(p_reg_data);

    if(uxQueueSpacesAvailable(m_tcs_reg_data_queue)!=0) //The number of free spaces available in the queue.
    {
        if(pdPASS!=xQueueSend(m_tcs_reg_data_queue,&tcs_ble_send_str,10))
        {
            NRF_LOG_INFO("TCS34725 THRESHOLD CB : Queue send fail");
        }
    }
    else
    {
        if(pdPASS!=xQueueOverwrite(m_tcs_reg_data_queue,&tcs_ble_send_str))
        {
            NRF_LOG_INFO("TCS34725 THRESHOLD CB : Queue overwrite fail");
        }
    }
    xTaskNotifyGive(m_ble_tcs_reg_send_thread);
}

- 쓰레스홀드 레지스터 데이터는 16bit로 구성되어 있으므로 레지스터 Read 콜백 함수에서 처리하는게 아니라 따로 쓰레스홀드 콜백 함수를 만들어 처리

- 데이터 길이가 5인 것을 제외하면 레지스터 Read 콜백 함수와 동일

- BLE 전송은 레지스터 Read 태스크와 동일하게 TCS34725 레지스터 데이터 BLE 전송 태스크 이용

 

<TCS34725 각종 설정값들을 전송하는 태스크 (RAR 명령어 수신했을 때)>

/* pvPortMalloc() 함수를 이용해 TCS34725 레지스터값을 수신할 구조체 포인터 변수들 생성
 * 수신 완료 후 호출되는 콜백 함수에서 vPortFree() 함수를 통해 메모리 할당 해제됨
 */
static void tcs_read_all_reg_thread(void *arg)
{
    tcs34725_reg_data_t *enable=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *timing=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *wait_time=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *persistence=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *config=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *control=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *id=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    tcs34725_reg_data_t *status=(tcs34725_reg_data_t*)pvPortMalloc(sizeof(tcs34725_reg_data_t));
    
    tcs34725_threshold_data_t *threshold_low=(tcs34725_threshold_data_t*)pvPortMalloc(sizeof(tcs34725_threshold_data_t));
    tcs34725_threshold_data_t *threshold_high=(tcs34725_threshold_data_t*)pvPortMalloc(sizeof(tcs34725_threshold_data_t));

    while(1)
    {
        enable->reg_addr=TCS34725_REG_ENABLE;
        tcs34725_read_reg(&tcs34725_instance, enable, tcs34725_read_reg_cb);

        timing->reg_addr=TCS34725_REG_TIMING;
        tcs34725_read_reg(&tcs34725_instance, timing, tcs34725_read_reg_cb);

        wait_time->reg_addr=TCS34725_REG_WAIT_TIME;
        tcs34725_read_reg(&tcs34725_instance, wait_time, tcs34725_read_reg_cb);
                
        persistence->reg_addr=TCS34725_REG_PERSISTENCE;
        tcs34725_read_reg(&tcs34725_instance, persistence, tcs34725_read_reg_cb);
        
        config->reg_addr=TCS34725_REG_CONFIG;
        tcs34725_read_reg(&tcs34725_instance, config, tcs34725_read_reg_cb);
        
        control->reg_addr=TCS34725_REG_CONTROL;
        tcs34725_read_reg(&tcs34725_instance, control, tcs34725_read_reg_cb);

        id->reg_addr=TCS34725_REG_ID;
        tcs34725_read_reg(&tcs34725_instance, id, tcs34725_read_reg_cb);

        status->reg_addr=TCS34725_REG_STATUS;
        tcs34725_read_reg(&tcs34725_instance, status, tcs34725_read_reg_cb);

        threshold_low->reg_addr=TCS34725_REG_THRESHOLD_LOW_L;
        tcs34725_read_threshold(&tcs34725_instance, threshold_low, tcs34725_read_thr_cb);

        threshold_high->reg_addr=TCS34725_REG_THRESHOLD_HIGH_L;
        tcs34725_read_threshold(&tcs34725_instance, threshold_high, tcs34725_read_thr_cb);
    }
}

 

<GPIO 핀 핸들러 함수 (TCS34725 인터럽트 처리)>

void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    if(pin==TCS34725_INT_PIN)
    {
        ret_code_t err_code;
                
        //BLE 통신을 통해 전송될 구조체 변수 생성 및 명령어 부분을 인터럽트 발생 명령어로 설정
        static tcs34725_ble_reg_t tcs_int_alarm={0};
        strcpy(tcs_int_alarm.send_data,"INT");
        
        NRF_LOG_INFO("TCS34725 RGBC Interrupt occured");
        
        //인터럽트 클리어 (인터럽트 클리어 명령어를 사용하지 않을 경우, 더 이상의 인터럽트 발생 X)
        err_code=tcs34725_int_clear(&tcs34725_instance);
        APP_ERROR_CHECK(err_code);
        if(err_code==NRF_SUCCESS)
        {
            NRF_LOG_INFO("TCS34725 Clear channel interrupt clear");
        }
        
        //큐에 빈 자리가 있을 경우, 큐에 구조체 변수 등록 및 알림 전달
        if(uxQueueSpacesAvailable(m_tcs_reg_data_queue)!=0)
        {
            if(pdPASS!=xQueueSend(m_tcs_reg_data_queue,&tcs_int_alarm,10))
            {
                NRF_LOG_INFO("Interrupt queue send fail");
            }
            xTaskNotifyGive(m_ble_tcs_reg_send_thread);
        }
        //큐에 빈 자리가 없을 경우, 구조체 변수를 덮어쓰기로 큐에 등록하고 알림 전달 X
        else
        {
            if(pdPASS!=xQueueOverwrite(m_tcs_reg_data_queue,&tcs_int_alarm))
            {
                NRF_LOG_INFO("Interrupt queue overwrite fail");
            }
        }
    }
}

//GPIO 초기화
static void gpio_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    //GPIO 핀이 HIGH -> LOW 로 전환되는 것을 감지
    nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
    
    //풀업 설정
    in_config.pull = NRF_GPIO_PIN_PULLUP;

    //TCS34725 인터럽트 핀에 연결된 GPIO 핀, 핀 환경설정, 핀 변화 감지시 호출될 핸들러 함수
    err_code = nrf_drv_gpiote_in_init(TCS34725_INT_PIN, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);

    //GPIO Input 핀 감지 활성화
    nrf_drv_gpiote_in_event_enable(TCS34725_INT_PIN, true);
}

- TCS34725 인터럽트 발생시 인터럽트 핀이 HIGH->LOW로 전환되므로 TCS34725 인터럽트 핀에 연결된 GPIO Input 핀이 HIGH->LOW로 전환될 때, 이벤트 핸들러가 호출되도록 설정

- 인터럽트 발생했을 경우, TCS34725 레지스터 데이터 BLE 전송 태스크를 통해 인터럽트 발생 명령어 전송

 

소스 : https://github.com/lee35210/nRF52840_BLE_FREERTOS_TCS34725

 

 

- 연결이 됨과 동시에 nRF52840 보드에 "RAR" 명령어를 보내 Timing, Gain, Wait Time, Wait Long, Interrupt, Persistence, Threshold Low, Threshold High 데이터를 읽어 온다

- 이후, RGBC Read 태스크가 vTaskDelay() (ms) 간격으로 실행되면서 측정한 RGBC 값을 BLE 통신을 통해 스마트폰에 전송

 

[타이밍과 게인 설정]

- 타이밍값과 게인값은 측정되는 RGBC 값에 영향을 준다

- (RGB값의 변화는 측정 색상에 따라 변화율이 다르고 Clear 값은 설정 값에 비례해서 변화한다)

- 대체로 두 설정 값이 낮아질 수록 측정되는 Clear 및 RGB값이 낮아지면서 측정 색상이 어두워지고

- 높은 갚을 가질 수록 측정되는 Clear 및 RGB 값이 높아지면서 측정 색상이 밝아지는 경향이 있다

- (측정 색상에 따라 두 설정값에 무관한 일관된 RGB 값을 갖는 경우도 존재한다 (Clear 값이 세자리수에서 65535까지 변화하는 상황에서도))

- Clear 값이 이미 최대치인 65535의 값을 갖는 상황에서도 타이밍 값이 높아질 수록 측정되는 RGB 값이 증가할 수 있다

- (RGBC 모든 색상은 0~65535의 값을 갖는다)

 

[RGBC 측정]

- RGBC 측정 주기는 {(256-타이밍 레지스터 데이터) + (256-Wait Time 레지스터 데이터)}*2.4ms 이다

- RGBC 값이 측정되는데 걸리는 시간은 2.4ms * (256-Timing 레지스터에 저장된 값) 

- 예) 타이밍 레지스터에 저장된 값이 246(0xF6)일 경우, RGBC 측정에 걸리는 시간은 (256-246) * 2.4ms = 24ms

- 대기 시간(Wait Time)은 RGBC 측정 사이에 센서가 대기 상태로 존재할 시간을 의미한다

- 대기 시간 계산은 타이밍 계산 방법과 동일 (256-Wait Time 레지스터 데이터)*2.4ms

- 따라서 측정되는 RGBC 값이 빠르게 갱신되길 원한다면 대기 시간 값을 낮춰야한다

(타이밍 레지스터 값을 낮추는 것도 한 방법일 수 있으나 타이밍 레지스터 데이터는 측정되는 RGBC값에 영향을 준다)

- Wait Long을 활성화할 경우, 대기 시간 = (256-Wait Time 레지스터 데이터)*2.4ms*12 가 된다

- 코드는 (256-입력 데이터)가 해당 레지스터에 저장되도록 설계됐다

 

[인터럽트]

- 인터럽트 활성화 전제

- Persistence 값이 0일 경우, 쓰레스홀드 값과는 상관없이 RGBC 측정 때마다 인터럽트가 발생한다

- Persistence 값은 인터럽트가 발생하기까지 필요한 쓰레스홀드 범위를 벗어나는 Clear 값의 연속 측정 횟수를 의미

- 따라서 인터럽트가 발생하는데 걸리는 시간은 {(타이밍 값 + 대기 시간 값)*2.4ms}*Persistence 값이 된다

- 예) 타이밍 150, 대기 시간 50, Persistence 10일 경우

- (150*2.4 + 50*2.4)*10 = 4,800ms  (Clear 값이 10 연속으로 쓰레스홀드 범위를 벗어나는 상황을 전제)

- 설정된 Persistence 횟수에 도달하기 전에 Clear 값이 쓰레스홀드 범위 내로 측정된다면 Persistence 횟수는 초기화된다

 

[기타]

- 센서와 측정 물체간의 거리가 멀어질 수록 측정을 위해 사용하는 광원의 세기가 강해져야한다. 그렇지 않을경우, 부정확한(원래의 색상보다 어두운) 값이 측정된다

/** 20/05/10 - <TCS34725 레지스터 Read 콜백 함수> Timing, Wait Time 설명 추가

 *

 ** 20/06/09

 * 1. tcs34725_read_threshold() 함수 수정

 * 2. tcs34725_read_all_config() 함수 삭제

 * 3. timer_handler() 함수내 tcs34725_read_rgbc() 함수에서 사용되는 구조체 변수를 전역 변수에서

 *    malloc() 함수를 사용한 메모리 할당 방식으로 변경

 */

 

사용한 TCS34725 모듈

- 사용 보드 : nRF52840-PDK

- 사용 IDE : SEGGER Embedded Studio for ARM (노르딕 제품 사용하는 경우 무료 라이센스 이용 가능)

- 작동 전압 : 2.7~3.6V (사용 모듈의 경우, 3.3~5.0V)

- 검출 색상 : Red, Green, Blue, Clear (센서에 감지되는 빛의 세기)

- 통신 : TWI (I2C), 최대 속도 400kbps,

          SCL, SDA - Input Low Voltage 최대 0.3 VDD, Input High Voltage 최소 0.7 VDD

- SDK 내 예제 프로젝트를 기반으로 작성

(TWI 통신, APP TIMER : examples\peripheral\twi_master_using_nrf_twi_mngr)

(GPIO 인터럽트 : examples\peripheral\pin_change_int)

(APP BUTTON : examples\ble_peripheral\ble_app_blinky)

(SDK 다운로드 : https://developer.nordicsemi.com/)

- 제조사 제품 페이지 : https://ams.com/ko/tcs34725 (데이터시트 다운 가능)

 

[작동 방식]

<기본 작동>

TCS34725 데이터 시트내 시스템 상태 다이어그램

- 전원 인가시, 내부 power-on-reset이 디바이스를 초기화하고 low-power Sleep 상태로 만듦

- I2C 버스에서 스타트 컨디션이 감지되면 디바이스는 아이들 상태로 전환되어 Enable 레지스터(0x00)의 PON 비트(Power ON)를 체크함

- PON 비트가 비활성화 되어 있다면 전력 감소를 위해 슬립 상태로 전환

- 활성화되어 있다면 RGBC 기능(AEN 비트)이 활성화 되기 전까지 아이들 상태를 유지한다

- RGBC 기능이 활성화되면 디바이스는 대기 및 RGBC 감지를 순서대로 실행한다

- (대기 시간(Wait Time)이 길어질 수록 색상 감지 간격이 길어지므로 빠른 색상 감지를 위해서는 낮은 대기 시간을 설정할 필요가 있다)

- 완료되면 아이들 상태로 돌아오고 PON, AEN 비트가 활성화 되어있는 한 디바이스는 자동으로 Wait-RGBC 주기를 반복한다

 

<RGBC 작동>

- RGBC 엔진은 RGBC 게인 컨트롤(AGAIN)과 RGBC 포토 다이오드를 위한 네 개의 통합 ADC를 갖는다

- RGBC 통합 시간(ATIME)은 RGBC의 해상도 및 감도에 영향을 준다

- 네 개 채널의 통합이 동시에 이뤄지며 변환이 완료되면 결과값은 컬러 데이터 레지스터로 전송된다

- 전송 중 인식 불가능한 데이터가 읽히는 것을 막기 위해 더블 버퍼링 된다

- 전송 이후, 디바이스는 설정된 상태 머신에 따라 다음 상태로 자동으로 이동한다

 

<인터럽트>

- 인터럽트 기능은 사용자가 설정한 범위를 벗어나는 빛의 세기에 대해 센서를 폴링할 필요를 없앰으로써 시스템을 간소화하고 효율을 상승시킨다.

- 인터럽트 기능이 활성화 되어있는 동안 스테이터스 레지스터(0x13)에서 인터럽트 상태를 확인 가능하다

- 인터럽트 아웃풋 상태는 Enable 레지스터(0x00)의 RGBC 인터럽트 활성화(AIEN) 영역을 사용해 활성화 할 수 있다

- 사용자는 두개의 16비트 인터럽트 쓰레스홀드 레지스터를 통해 원하는 빛의 세기의 하한 및 상한 제한을 설정할 수 있다

- 인터럽트는 클리어 데이터(CDATA)가 클리어 인터럽트 로우 쓰레스홀드(AILTx) 보다 낮거나 클리어 인터럽트 하이 쓰레스홀드(AITHx) 보다 클 때 발생된다

- 쓰레스홀드는 로우 쓰레스홀드, 하이 쓰레스홀드순으로 비교되므로 만약, 로우 쓰레스홀드가 하이 쓰레스홀드 보다 높은 쓰레스홀드 값이 설정되있다면 하이 쓰레스홀드는 무시되고 로우 쓰레스홀드만이 비교된다

- 인터럽트 발생 시기를 추가 제어하기 위해 디바이스는 지속성 필터를 제공한다

- 지속성 필터를 통해 사용자는 인터럽트 발생까지의 클리어 쓰레스홀드 범위를 벗어나는 측정값의 연속 발생 횟수를 지정 할 수 있다

- 지속성 필터 레지스터(0x0C)를 통해 사용자는 Clear 지속성 필터(APERS) 값을 지정할 수 있다

- 지속성 필터가 인터럽트를 발생시킨 이후, 스페셜 펑션 인터럽트 클리어 커맨드를 받기 전까지는 인터럽트 핀의 LOW 상태가 유지된다 (인터럽트 핀이 HIGH->LOW 로 전환되며 인터럽트 발생)

 

<시스템 타이밍>

TCS34725 데이터 시트내 다이어그램

- 시스템 상태 머신은 상태 개요 및 시스템의 디바이스 컨트롤을 제공하는 상태로의 전환을 제공한다

- 전원 관리 기능(WEN(Wait time Enable))이 활성화 되어있을 때, 상태 머신은 대기 상태로 전환된다

- 대기 시간은 WTIME과 대기 시간을 12배로 연장하는 WLONG에 의해 결정된다

- RGBC(AEN) 기능이 활성화 되어있을 때, 상태 머신은 RGBC 초기화 및 RGBC ADC 상태를 통해 전환된다

- RGBC 초기화 상태는 2.4ms 가 걸리고 RGBC ADC 시간은 통합 시간(ATIME)에 의존한다

- RGBC 주기의 결과로 인터럽트가 발생하면 RGBC ADC 끝에 실행된다

 

<전원 관리>

데이터시트 내 전류 소비 예시

- 전력 소비는 대기 상태를 통해 관리 가능

- 대기 상태는 일반적으로 64uA 를 소모

- 최악의 전류 소모는 대기 상태를 사용하지 않는 것

 

[TWI 통신]

데이터시트 내 I2C(TWI) 통신 프로토콜 예시

- Write 가능한 모든 레지스터(커맨드 레지스터 제외)는 전원 재인가시 설정값이 초기화된다

- Wait Time 레지스터만 0xFF의 초기값을 갖고 나머지 레지스터는 0x00의 초기값을 갖는다

 

<커맨드 레지스터 (레지스터 주소 X)>

데이터시트 - 커맨드 레지스터

- 읽거나 쓸 타겟 레지스터 주소를 특정화하는데 사용됨

- 타겟 레지스터 주소 + CMD 명령어가 장치 주소를 제외한 첫번째 바이트에 와야 한다

- 7번 비트 CMD는 특정 레지스터 접근이던 인터럽트 클리어를 위한 목적이던 1이여야 한다

- 5, 6번 비트는 레지스터 주소 접근 방식 및 인터럽트 클리어 명령어를 사용하는데 쓰이는 비트

  00일 경우 : 지정한 레지스터만 반복해서 읽거나 쓴다

  (예 : 0x00 번지로부터 3바이트 Read를 실행한 경우, 0x00 번지만 세 번 읽는다)

  01일 경우 : 지정한 레지스터 주소 사용 이후, 자동으로 레지스터 주소값이 1 증가된다

  (예 : 0x00 번지로부터 3바이트 Read를 실행한 경우, 0x00, 0x01, 0x02 레지스터 주소에 저장된 데이터를 읽어 온다)

  11일 경우 : 4~0 번 비트에 00110를 함께 설정 및 전송해 인터럽트 클리어를 실행한다

  (인터럽트 후 인터럽트 핀은 LOW 상태가 되는데 클리어를 실행하면 다시 HIGH로 전환되어 인터럽트 재실행 가능)

 

<Enable 레지스터 0x00>

데이터시트 - 인에이블 레지스터

- 주로 TCS34725 전원 ON, OFF 에 사용되는 레지스터

- BIT 4 (AIEN) : 인터럽트 활성화 비트

- BIT 3 (WEN) : 대기 활성화 비트

- BIT 1 (AEN) : RGBC 활성화 비트. 2채널 ADC를 활성화 한다

- BIT 0 (PON) : 타이머와 ADC채널이 작동되도록 내부 오실레이터를 활성화하는 비트

 

<타이밍 레지스터 0x01>

데이터 시트 - 타이밍 레지스터

- RGBC 클리어와 IR 채널 ADC들의 내부 통합 시간을 2.4ms 단위로 조절

- 최대 RGBC 카운트 = (256-ATIME)*1024 (최대 65535)

- 카운트가 늘어날 수록 RGBC 검출 시간이 증가하지만 해상도 및 감도도 증가해 더 정확한 색상 검출이 가능해진다

- 설정값은 1~256을 갖는데 실제 레지스터에 입력되는 값은 256-설정값이 되어야 한다

 

<대기 시간 레지스터 0x03>

데이터 시트 - Wait Time 레지스터

- 대기 시간을 12배로 늘리는 WLONG 비트가 활성화되지 않는한 2.4ms 단위로 대기 시간을 설정한다

- WTIME은 2의 보수로 프로그램된다

- 설정값은 1~256을 갖는데 실제 레지스터에 입력되는 값은 256-설정값이 되어야 한다

 

<RGBC 인터럽트 쓰레스홀드 레지스터 0x04~0x07>

데이터시트 - 쓰레스홀드 레지스터

- 측정되는 빛의 세기 (Clear)의 상한 및 하한을 설정하는 레지스터

- 하한 및 상한은 각각 2바이트로 별도의 값 변환없이 10진수 입력 (0~65535까지 설정 가능)

- 레지스터 주소는 하위 8비트, 상위 8비트 순으로 입력할 16비트 데이터를 8비트씩 나누어 하위 8비트 먼저 전송

- 하한 보다 더 낮은 값, 상한 보다 더 높은 값이 측정됐을 때만 인터럽트가 실행된다

  (인터럽트 발생 시기는 지속 레지스터(0x0C)에 설정된 값에 영향을 받는다)

- 인터럽트 핀은 기본적으로 HIGH 상태를 유지하다 인터럽트가 발생하며 LOW로 전환된다

- 인터럽트 핀은 LOW상태를 유지하므로 인터럽트 클리어를 실행하기 전까진 더 이상의 인터럽트가 발생하지 않는다

 

<지속 레지스터 0x0C>

 

데이터시트 - 지속 레지스터

- 디바이스의 필터링 인터럽트 기능을 제어하는데 사용되는 레지스터

- 각 통합 주기마다 인터럽트를 발생시킬지 아니면 쓰레스홀드 레지스터에 지정된 값을 벗어나는 결과를 지정한 횟수만큼 측정한 이후에 인터럽트를 발생시킬지를 결정

- 예) 쓰레스홀드 Low=10,000 / High=50,000 이고 APERS=1111일 때,

  60번 연속으로 0~9,999 / 50,001~65535 범위의 Clear 값이 측정된다면 인터럽트가 발생된다

  APERS=0000 이라면 매 RGBC 측정 때마다 인터럽트가 발생한다

 

<환경설정 레지스터 0x0D>

데이터시트 - 환경설정 레지스터

- Wait long 타임 설정 레지스터

- 2번 비트인 WLONG을 활성화할 경우, 대기 시간이 프로그램된 WTIME 시간의 12배가 된다

 

<컨트롤 레지스터 0x0F>

데이터시트 - 컨트롤 레지스터

- RGBC 측정값 증폭에 사용되는 레지스터

- 측정값은 게인값에 정비례하지 않는다 (1x gain*4 != 4x gain)

 

<ID 레지스터 0x12>

데이터시트 - ID 레지스터

- 사용하는 센서가 TCS34725인지 아니면 TCS34727인지를 알려주는 레지스터

- 읽기 전용

 

<스테이터스 레지스터 0x13>

데이터시트 - 스테이터스 레지스터

- 디바이스 내부 상태를 제공해주는 레지스터

- BIT 5 (AINT) : 인터럽트 활성화 여부 (1-인터럽트 활성화)

- BIT 0 (AVALID) : RGBC 활성화 여부

 

<RGBC 채널 데이터 레지스터 0x14~0x1B>

데이터시트 - RGBC 데이터 레지스터

- 측정된 RGBC 데이터를 저장하는 레지스터

- 각 색상은 16비트 데이터로 상하 각 8비트로 나누어 저장된다

- 값을 읽어올 때 추가로 10진수 변환할 필요는 없다

- 연속으로 데이터를 읽어와야 하므로 커맨드 레지스터의 값이 0xA0 (CMD : 1, TYPE : 01 (자동 주소 증가))여야 한다

- 예) 0x14 번지부터 0x1B까지 8바이트를 읽어와야 한다면 슬레이드 주소를 제외한 첫 전송 바이트는 (0x14 | 0xA0)가 되어야 한다

- 하위 데이터 레지스터를 읽을 때, 상위 8비트는 섀도우 레지스터에 저장되어 연속으로 읽어진다

- 따라서 하위 바이트와 상위 바이트를 읽는 사이에 추가적인 ADC 통합 주기가 끝나더라도 옳바른 값을 읽어 온다

 

[코드]

<매크로>

/*
main.c
*/
//TCS34725 센서 주소
#define TCS34725_ADDR 0x29

//TWI PIN
#define TCS34725_SDA_PIN 28
#define TCS34725_SCL_PIN 29

//TSC34725와의 통신에 사용할 TWI 인스턴스 ID
#define TWI_INSTANCE_ID 0

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

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

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

/*
- 센서 인스턴스 생성 매크로
센서 인스턴스 이름, 사용할 공통 TWI 센서 인스턴스, 센서 주소
*/
TCS34725_INSTANCE_DEF(tcs34725_instance, &sensor_instance, TCS34725_ADDR);

 

<TWI 설정>

static void twi_config(void)
{
    uint32_t err_code;
    
    /*
    -TCS34725와의 TWI 통신 설정
    -SDA, SCL핀, 통신속도, 인터럽트 우선순위 설정 뒤 twi 초기화
    */
    nrf_drv_twi_config_t const config={
      .scl=TCS34725_SCL_PIN,
      .sda=TCS34725_SDA_PIN,
      .frequency=NRF_DRV_TWI_FREQ_400K,
      .interrupt_priority=APP_IRQ_PRIORITY_MID,
      };
    
    err_code=nrf_twi_mngr_init(&m_nrf_twi_mngr,&config);
    APP_ERROR_CHECK(err_code);
}

 

<TCS34725 레지스터 구조체 및 Read 함수>

/*
-TCS34725 레지스터로부터 데이터를 읽어오거나 데이터를 쓸 때 사용할 구조체
reg_data : TCS34752 레지스터로부터 읽어 온 데이터 저장 or 레지스터에 쓸 데이터 저장
reg_addr : 액세스 할 레지스터 주소
*/
typedef struct
{
    uint8_t reg_data;
    uint8_t reg_addr;
} tcs34725_reg_data_t;

/*
-TCS34725의 특정 레지스터로부터 데이터를 읽어 오는 함수
-TCS34725_INSTANCE_DEF을 통해 생성한 TCS34725 센서 인스턴스
-p_reg_data : 액세스 할 레지스터 주소와 읽어 온 데이터를 저장하는데 사용할 구조체 변수
-user_cb : TCS34725로부터 데이터를 수신한 직후 호출된 콜백 함수
 (TWI Manager에 큐를 등록한 직후가 아님)
*/
ret_code_t tcs34725_read_reg(tcs34725_instance_t const * p_instance,
                              tcs34725_reg_data_t *      p_reg_data,
                              tcs34725_data_callback_t   user_cb
                             )
{
    /*
    p_instance->p_sensor_data : NRF_TWI_SENSOR_DEF을 통해 설정된 공통 센서 인스턴스
    p_instance->sensor_addr : TCS34725 센서 인스턴스에 설정된 TCS34725 센서 주소
    p_reg_data->reg_addr|0x80 : 전달받은 구조체에 저장된 레지스터 주소+커맨드 레지스터
    				해당 레지스터 주소에 접근해 데이터를 읽어온다
    user_cb : 데이터 수신 완료 후 호출 할 콜백 함수
    p_reg_data : 읽어 온 레지스터 데이터를 저장할 포인터 변수
    TCS34725_REGISTER_SIZE : 읽어 올 데이터의 바이트 크기 (레지스터 하나의 크기는 8비트=1바이트)
    */
    return nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                                   p_instance->sensor_addr,
                                   p_reg_data->reg_addr|0x80,
                                   (nrf_twi_sensor_reg_cb_t) user_cb,
                                   (uint8_t *) p_reg_data,
                                   TCS34725_REGISTER_SIZE);
}

- 이 함수를 통해 TCS34725 레지스터로부터 데이터를 읽어 온다

- p_reg_data->reg_addr | 0x80 : 레지스터 주소 + 커맨드 레지스터 (CMD : 1, TYPE : 00 (Repeated byte)=0x80)

- 읽어 올 데이터 크기를 1바이트로 설정했으므로 TYPE : 01 (Auto-increment) 로 설정해도 상관없다

 

<TCS34725 Write 함수>

ret_code_t tcs34725_write_reg(tcs34725_instance_t const * p_instance,
                              tcs34725_reg_data_t *       p_reg_data)
{
	//&p_reg_data->reg_data : 레지스터에 입력할 데이터가 저장된 구조체 변수 주소
    return nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                                    p_instance->sensor_addr,
                                    p_reg_data->reg_addr|0x80,
                                    (uint8_t *)&p_reg_data->reg_data,
                                    TCS34725_REGISTER_SIZE);
}

- 아래에 이어질 레지스터 설정 함수들이 이 함수를 통해 TCS34725 레지스터에 설정값을 전송한다

 

<TCS34725 초기화 함수>

/*
-TCS34752 초기화 함수
-인에이블 레지스터(0x00)에 설정 전송
-인터럽트 활성화, 대기 시간 활성화, RGBC 활성화, POWER ON/OFF 설정
*/
ret_code_t tcs34725_init(tcs34725_instance_t const * p_instance)
{
    ret_code_t err_code;
    //레지스터 데이터 구조체 선언 (레지스터 주소 및 인에이블 레지스터에 입력할 데이터 저장)
    tcs34725_reg_data_t enable_reg;
	enable_reg.reg_addr=TCS34725_REG_ENABLE;
    
    //인에이블 설정 저장할 구조체 선언 및 각종 기능 활성화 설정
    tcs34725_config_enable_t init_config;
    init_config.rgbc_interrupt=false;	//인터럽트 활성화
    init_config.wait_enable=true;	//대기 시간 활성화
    init_config.rgbc_enable=true;	//RGBC 측정 활성화
    init_config.power_on=true;		//Power ON/OFF

    //설정값들 해당 위치로 비트 시프트
    enable_reg.reg_data=(init_config.rgbc_interrupt << TCS34725_INT_POS)|
                        (init_config.wait_enable << TCS34725_WAIT_POS)|
                        (init_config.rgbc_enable << TCS34725_RGBC_ENABLE_POS)|
                        (init_config.power_on << TCS34725_POWER_ON_POS);

    //TCS34725 인에이블 레지스터(0x00)로 데이터 전송
    err_code=tcs34725_write_reg(&tcs34725_instance, &enable_reg);
    return err_code;
}

<TCS34725 레지스터 Read 콜백 함수>

/*
- 레지스터 Read 콜백 함수
- 구조체 포인터 변수에 저장된 레지스터 주소를 스위치 문을 통해 확인 후 로그 출력
*/
void tcs34725_read_reg_cb(ret_code_t result, tcs34725_reg_data_t * p_raw_data)
{    
    if(result!=NRF_SUCCESS)
    {
        NRF_LOG_INFO("TCS34725 register read fail");
        return;
    }
    p_raw_data->reg_addr&=0x1F;

    switch(p_raw_data->reg_addr)
    {
        case TCS34725_REG_ENABLE :
            NRF_LOG_INFO("Enable register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_TIMING :
            NRF_LOG_INFO("Timing register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_WAIT_TIME :
            NRF_LOG_INFO("Wait time register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_PERSISTENCE :
            NRF_LOG_INFO("Persistence register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_CONFIG :
            NRF_LOG_INFO("Configuration register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_CONTROL :
            NRF_LOG_INFO("Control register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_ID :
            NRF_LOG_INFO("ID register : %X",p_raw_data->reg_data);
            break;
        case TCS34725_REG_STATUS :
            NRF_LOG_INFO("Status register : %X",p_raw_data->reg_data);
            break;
        default :
            break;
    }
    //레지스터 데이터 수신에 사용된 구조체 포인터 변수 메모리 할당 해제
    free(p_raw_data);
}

- Timing, Wait Time의 경우, 1~256 사이의 설정값을 갖는데 실제 레지스터에 설정값을 입력할 땐 [256-사용자 설정값]이 입력되어야 한다

- 예) Timing값을 1로 설정할 경우, 256-1=255의 값이 실제 레지스터에 입력되는 값이 되어야 한다

- 따라서 콜백 함수에서 Timing, Wait Time 설정값을 확인하려면 [256 - p_raw_data->reg_data = 설정값] 이 되어야 한다

 

<TCS34725 타이밍 설정 함수>

//타이밍 설정 함수 (2.4ms 단위)
//atime : RGBC 통합 주기
ret_code_t tcs34725_set_timing(tcs34725_instance_t const * p_instance,
                                uint16_t atime)
{
    ret_code_t err_code;
    //통합 시간이 최소 1*2.4ms 를 갖는다는 것을 명확히 하기 위해 1~256의 값을 입력해야하도록 설정
    if((atime==0)||(256 < atime))
    {
        err_code=NRF_ERROR_INVALID_DATA;
        return err_code;
    }

    //타이밍 레지스터에 전달될 타이밍 레지스터 데이터 구조체 선언 후 레지스터 주소 및 통합 시간 설정
    tcs34725_reg_data_t timing_str;
    timing_str.reg_addr=TCS34725_REG_TIMING;
    //통합 시간은 2.4ms*(256-ATIME) 으로 결정된다. 즉, 레지스터에 입력되는 실제값이 낮을 수록 통합 시간 ↑
    timing_str.reg_data=(256-atime);
    
    //설정한 레지스터 구조체 변수 타이밍 레지스터로 전송
    err_code=tcs34725_write_reg(p_instance,&timing_str);
    return err_code;
}

- Timing 설정값은 1~256 사이의 값을 갖고 레지스터엔 [256-설정값]이 입력되어야 한다

 

<TCS34725 대기 시간 설정 함수>

//대기 시간 설정 함수 (2.4ms 단위)
//wait_val : 설정할 대기 시간 (wait_val * 2.4ms 가 대기 시간이 된다)
ret_code_t tcs34725_set_wait_time(tcs34725_instance_t const * p_instance,
                                   uint8_t wait_val)
{
    ret_code_t err_code;
    //타이밍 설정 함수와 마찬가지로 최소 대기시간이 1*2.4ms임을 명확히 하기 위해 사용
    if((wait_val==0)||(256 < wait_val))
    {
        err_code=NRF_ERROR_INVALID_DATA;
        return err_code;
    }

	//대기 시간 레지스터에 입력될 구조체 변수 선언 후 대기 시간 레지스터 주소 및 데이터 입력
    tcs34725_reg_data_t wait_time_str;
    wait_time_str.reg_addr=TCS34725_REG_WAIT_TIME
    
    //wait_val*2.4ms가 대기 시간이 된다 (WLONG=1일 경우, *12 추가)
    //실제 레지스터 입력값은 (256 - WAIT TIME)이 되어야한다
    wait_time_str.reg_data=(256-wait_val);
    
    //대기 시간 레지스터(0x03)에 설정한 대기 시간 전송
    err_code=tcs34725_write_reg(p_instance,&wait_time_str);
    return err_code;
}

- Wait Time 설정값은 1~256 사이의 값을 갖고 레지스터엔 [256-설정값]이 입력되어야 한다.

 

<TCS34725 지속 레지스터 설정 함수>

//지속 설정 레지스터 설정 함수
//out_of_range_val : 인터럽트를 발생시키기 위해 필요한 쓰레스홀드 범위를 벗어나는 클리어 값 연속 측정 횟수
ret_code_t tcs34725_set_persistence(tcs34725_instance_t const * p_instance,
                                    tcs34725_persistence_t out_of_range_val)
{
    ret_code_t err_code;
    
    //지속 레지스터에 전송할 구조체 변수 선언 후 지속 레지스터 주소 및 데이터 설정
    tcs34725_reg_data_t persistence;
    persistence.reg_addr=TCS34725_REG_PERSISTENCE;
    persistence.reg_data=out_of_range_val;
    err_code=tcs34725_write_reg(p_instance, &persistence);
    return err_code;
}

<TCS34725 환경설정(Wait long) 레지스터 설정 함수>

//환경 설정 레지스터 설정 함수 (Wait long 외에 설정 가능한 데이터가 없다)
//wait_long_val : WLONG 비트 활성화 여부 결정
ret_code_t tcs34725_set_wait_long(tcs34725_instance_t const * p_instance,
                                   tcs34725_wait_long_t wait_long_val)
{
    ret_code_t err_code;
    tcs34725_reg_data_t wait_long;
    wait_long.reg_addr=TCS34725_REG_CONFIG;
    //WLONG 비트는 1번 비트에 위치하므로 비트 시프트 사용
    wait_long.reg_data=wait_long_val << TCS34725_WAIT_LONG_POS;
    err_code=tcs34725_write_reg(p_instance, &wait_long);
    return err_code;
}

<TCS34725 컨트롤(게인) 레지스터 설정 함수>

//컨트롤 레지스터 설정 함수 (게인값 외에 설정 가능한 데이터 없음)
//gain_val : 설정할 게인값. 0~1번 비트를 사용해 게인값을 1,4,16,60x 설정 가능
ret_code_t tcs34725_set_gain(tcs34725_instance_t const * p_instance,
                                 tcs34725_gain_t gain_val)
{
    ret_code_t err_code;
    tcs34725_reg_data_t gain;
    gain.reg_addr=TCS34725_REG_CONTROL;
    gain.reg_data=gain_val;
    err_code=tcs34725_write_reg(p_instance, &gain);
    return err_code;
}

<TCS34725 쓰레스홀드 설정 함수>

/*
- 쓰레스홀드 데이터 구조체
- threshold_data : 전송 or 수신할 16비트 쓰레스홀드 데이터 (하위 8비트 먼저 전송 or 수신됨)
- reg_addr : 쓰레스홀드 레지스터 주소 (하한 : 0x04~0x05, 상한 0x06~0x07)
*/
typedef struct
{
    uint16_t threshold_data;
    uint8_t reg_addr;
} tcs34725_threshold_data_t;

/*
- 쓰레스홀드 설정 함수
- tcs34725_threshold_lh_t : 설정할 쓰레스홀드값이 쓰레스홀드 하한인지 상한인지 판단하는데 사용
- threshold_val : 설정할 쓰레스홀드 값
*/
ret_code_t tcs34725_set_threshold(tcs34725_instance_t const * p_instance,
                                  tcs34725_threshold_lh_t threshold_low_high,
                                  uint16_t threshold_val)
{
    ret_code_t err_code;
    
    //전송할 데이터를 저장할 쓰레스홀드 구조체 변수 선언 및 설정할 쓰레스홀드값 입력
    tcs34725_threshold_data_t threshold_str;
    threshold_str.threshold_data=threshold_val;

    /*	
    - 설정할 쓰레스홀드값이 상한인지 하한인지에 따라 앞서 선언한 쓰레스홀드 구조체 변수의
    레지스터 주소에 해당 레지스터 주소 입력
    */
    if(threshold_low_high==TCS34725_THRESHOLD_LOW)
    {
        threshold_str.reg_addr=TCS34725_REG_THRESHOLD_LOW_L;	//쓰레스홀드 Low - Low byte
    }
    else if(threshold_low_high==TCS34725_THRESHOLD_HIGH)
    {
        threshold_str.reg_addr=TCS34725_REG_THRESHOLD_HIGH_L;	//쓰레스홀드 High - Low byte
    }
    else
    {
        err_code=NRF_ERROR_INVALID_ADDR;
        return err_code;
    }
    
    /*
    - threshold_str.reg_addr|0xA0 : 각 쓰레스홀드값은 하위 8비트+상위 8비트=16비트이므로
    				커맨드 레지스터 0xA0을 사용해 연속된 레지스터 주소에 데이터를 입력해야 한다
    - TCS34725_THRESHOLD_BYTES : 각 쓰레스홀드 데이터는 2바이트로 구성되므로 2
    */
    err_code=nrf_twi_sensor_reg_write(p_instance->p_sensor_data,
                                      p_instance->sensor_addr,
                                      threshold_str.reg_addr|0xA0,
                                      (uint8_t *)&threshold_str,
                                      TCS34725_THRESHOLD_BYTES);
}

<TCS34725 쓰레스홀드 Read 함수>

/*
- 쓰레스홀드 Read 함수
- thr_data_str : 읽어 온 쓰레스홀드 데이터가 저장될 구조체 포인터 변수
- user_cb : 쓰레스홀드 데이터 수신 완료 후 호출할 콜백 함수
*/
ret_code_t tcs34725_read_threshold(tcs34725_instance_t const * p_instance, 
                                   tcs34725_threshold_data_t * thr_data_str,
                                   tcs34725_threshold_callback_t user_cb)
{
    ret_code_t err_code;

    //thr_data_str.reg_addr|0xA0 : 연이은 레지스터 주소에 접근해 데이터를 읽어야 하므로 
    //레지스터 주소에 0xA0 OR 연산 추가
    err_code=nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                                     p_instance->sensor_addr,
                                     thr_data_str->reg_addr|0xA0,
                                     (nrf_twi_sensor_reg_cb_t) user_cb,
                                     (uint8_t *) thr_data_str,
                                     TCS34725_THRESHOLD_BYTES);
    return err_code;
}

<TCS34725 쓰레스홀드 콜백 함수>

void tcs34725_read_thr_cb(ret_code_t result,
                          tcs34725_threshold_data_t * p_reg_data)
{
    if(result!=NRF_SUCCESS)
    {
        NRF_LOG_INFO("Reading threshold regiseter is failed");
        return;
    }
    //쓰레스홀드 Low 데이터인지 High 데이터인지 구조체 포인터 변수에 저장된 레지스터 주소로 판단 후 출력
    if(p_reg_data->reg_addr==TCS34725_REG_THRESHOLD_LOW_L)
    {
        NRF_LOG_INFO("Threshold Low value : %d",p_reg_data->threshold_data);
    }
    else
    {
        NRF_LOG_INFO("Threshold High value : %d",p_reg_data->threshold_data);
    }
    //쓰레스홀드 레지스터 데이터 수신에 사용된 구조체 포인터 변수 메모리 할당 해제
    free(p_reg_data);
}

<TCS34725 RGBC Read 함수>

/*
- TCS34725 RGBC 측정값을 저장할 구조체
- 각 색상은 하위 8비트 + 상위 8비트 = 총 16비트 (0~65535)
- TWI 통신을 통해 데이터를 읽어 올 때, 각 색상 변수의 하위 8비트->상위 8비트 순으로 데이터가 채워짐
*/
typedef struct
{
    uint16_t clear;
    uint16_t red;
    uint16_t green;
    uint16_t blue;
} tcs34725_color_data_t;

/*
- TCS34725 RGBC Read 함수
- tcs34725_color_data_t : TCS34725 RGBC 구조체
- tcs34725_rgbc_callback_t : TCS34725 RGBC 콜백 함수, RGBC 값 수신 완료 후 호출
*/
ret_code_t tcs34725_read_rgbc(tcs34725_instance_t const * p_instance,
                               tcs34725_color_data_t *     rgbc_str,
                               tcs34725_rgbc_callback_t    user_cb)
{
    ret_code_t err_code;
    
    /*
    - TCS34725_REG_CLEAR | 0xA0 : Clear data low byte(0x14)부터 Blue data high byte(0x1B)까지
    			연속으로 8바이트를 읽어와야 하므로 0xA0 (CMD :1, TYPE : 01 (Auto-increment)) 사용
    - rgbc_str : 읽어온 RGBC 데이터를 저장할 구조체 포인터 변수
    - TCS34725_RGBC_BYTES : 읽어 올 데이터는 총 8바이트이므로 8
    */
    err_code=nrf_twi_sensor_reg_read(p_instance->p_sensor_data,
                           p_instance->sensor_addr,
                           (TCS34725_REG_CLEAR|0xA0),
                           (nrf_twi_sensor_reg_cb_t) user_cb,
                           (uint8_t *) rgbc_str,
                           TCS34725_RGBC_BYTES);
    return err_code;
}

<TCS34725 RGBC Read 콜백 및 RGBC 수신 데이터 출력 함수>

//RGBC 콜백 함수, TCS34725로부터 RGBC 데이터 수신 완료 직후 호출됨
void tcs34725_rgbc_callback(ret_code_t result, tcs34725_color_data_t * p_raw_data)
{
    if(result!=NRF_SUCCESS)
    {
        NRF_LOG_INFO("Reading RGBC registers is failed");
        return;
    }
    //RGBC 출력 함수 호출
    tcs34725_rgbc_print(p_raw_data);
    //RGBC 데이터 수신에 사용된 구조체 포인터 변수 메모리 할당 해제
    free(p_raw_data);
}

/*
- RGBC 측정값 출력 함수
- 구조체 포인터 변수에 저장된 RGB 데이터를 Clear 값으로 나누고 255를 곱한 뒤 출력
  (RGB 값을 0~255 사이로 맞추기 위함)
*/
void tcs34725_rgbc_print(tcs34725_color_data_t * color_str)
{
    uint16_t c_red,c_green,c_blue;

    c_red=(int)((double)color_str->red/color_str->clear*255);
    c_green=(int)((double)color_str->green/color_str->clear*255);
    c_blue=(int)((double)color_str->blue/color_str->clear*255);
    
    NRF_LOG_INFO("Clear : %d",color_str->clear);
    NRF_LOG_INFO("Red   : %d",c_red);
    NRF_LOG_INFO("Green : %d",c_green);
    NRF_LOG_INFO("Blue  : %d",c_blue);
}

- RGB 변환은 https://forums.adafruit.com/viewtopic.php?f=8&t=67795&start=15 참조해서 작성

 

<TCS34725 인터럽트 설정 함수>

/*
- 인터럽트 설정(활성화) 함수
- 먼저 현재 TCS34725 Enable 레지스터에 저장된 데이터를 읽어 온다
  그리고 그 데이터에 int_enable 값에 따라 BIT4 AIEN (인터럽트 인에이블)을 0 or 1로 설정
- 인터럽트 활성화 여부를 추가한 설정 데이터를 TCS34725 인에이블 레지스터에 다시 전송
- int_enable : 인터럽트 활성화 여부
*/
ret_code_t tcs34725_set_interrupt(tcs34725_instance_t const * p_instance,
                                  tcs34725_int_enable_t int_enable)
{
    ret_code_t err_code;
    
    //TCS34725 인에이블 레지스터로부터 읽어 온 데이터를 저장하고 재전송할 구조체 변수 선언
    tcs34725_reg_data_t enable_reg_str;
    enable_reg_str.reg_addr=TCS34725_REG_ENABLE;
    
    //인에이블 레지스터에 저장된 데이터를 읽어 와 구조체 변수에 저장
    tcs34725_read_reg(p_instance,&enable_reg_str,NULL);
    
    //TWI 매니저 큐에 대기된 전송이 없을 때까지(=인에이블 레지스터로부터 데이터 수신이 완료될 때까지)
    //do~while문 반복
    do
    {
        nrf_delay_us(10);
    }while(nrf_twi_mngr_is_idle(&m_nrf_twi_mngr)!=true);

	//읽어 온 인에이블 레지스터 데이터에 int_enable 값에 따라 BIT4 AIEN에 0 or 1 입력
    if(int_enable==TCS34725_INTERRUPT_ENABLE)
    {
        enable_reg_str.reg_data=(enable_reg_str.reg_data|(TCS34725_INT_MASK));
    }
    else if(int_enable==TCS34725_INTERRUPT_DISABLE)
    {
        enable_reg_str.reg_data=(enable_reg_str.reg_data&~(TCS34725_INT_MASK));
    }
    else
    {
        err_code=NRF_ERROR_INVALID_PARAM;
        return err_code;
    }
    //수정한 레지스터 설정값 전송
    tcs34725_write_reg(p_instance, &enable_reg_str);
}

 

<TCS34725 인터럽트 클리어 함수>

/*
- 인터럽트 발생시 호출되는 핸들러
- Clear 쓰레스홀드값을 벗어나는 측정값들로 인해 인터럽트가 발생했음을 알리고
  TCS34725에 인터럽트 클리어 명령을 전송해 인터럽트가 계속해서 발생할 수 있게 함
*/
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    if(pin==TCS34725_INT_PIN)
    {
        ret_code_t err_code;
        NRF_LOG_INFO("TCS34725 RGBC Interrupt occured");
        
        //인터럽트 클리어 명령 전송
        err_code=tcs34725_int_clear(&tcs34725_instance);
        APP_ERROR_CHECK(err_code);
        
        //성공적으로 전송 완료시 출력
        if(err_code==NRF_SUCCESS)
        {
            NRF_LOG_INFO("TCS34725 Clear channel interrupt clear");
        }

    }
}

/*
- 인터럽트 클리어 함수
- 인터럽트 핀이 High->Low로 전환되며 인터럽트가 발생하는데 TCS34725에 인터럽트 클리어 명령어를 
  보내지 않는다면 인터럽트 핀이 Low 상태를 유지하므로 더 이상의 인터럽트가 발생하지 않는다
*/
ret_code_t tcs34725_int_clear(tcs34725_instance_t const * p_instance)
{
    ret_code_t err_code;
    
    /*
    - 커맨드 레지스터 사용
    - 0x66 : TYPE=11 (Special Function), ADDR/SF : 00110 (Clear channel interrupt clear)
    - 0x80 : CMD=1
    */
    uint8_t interrupt_cmd=0x66|0x80;
    err_code=nrf_twi_sensor_write(p_instance->p_sensor_data, p_instance->sensor_addr, &interrupt_cmd, 
                                  TCS34725_REGISTER_SIZE, true);
    return err_code;
}

 

https://github.com/lee35210/nRF52840_TWI_TCS34725

 

lee35210/nRF52840_TWI_TCS34725

nRF52840_TWI_TCS34725. Contribute to lee35210/nRF52840_TWI_TCS34725 development by creating an account on GitHub.

github.com

- nRF52840-DK 1번 버튼 : LED 토글, 2번 버튼 : 쓰레스홀드 인터럽트 활성화, 3번 버튼 : 쓰레스홀드 인터럽트 비활성화

- 핀 설정 : TWI SDA 28, SCL 29 / TCS34725 모듈 LED 제어 30 / TCS34725 인터럽트 감지 31

- APP TIMER에 설정한 시간마다 TCS34725 RGBC 레지스터에 액세스해 측정된 RGBC값 읽어 옴

+ Recent posts