/** 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 제어]
[개발 환경]
- 개발 보드 : 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 횟수는 초기화된다
[기타]
- 센서와 측정 물체간의 거리가 멀어질 수록 측정을 위해 사용하는 광원의 세기가 강해져야한다. 그렇지 않을경우, 부정확한(원래의 색상보다 어두운) 값이 측정된다
'nRF52' 카테고리의 다른 글
TCS34725 이용 원두 색상 측정 테스트 (0) | 2020.05.12 |
---|---|
nRF52 - TWI 이용 TCS34725 RGB 색상 검출 센서 제어 (0) | 2020.03.15 |
nRF52 - FreeRTOS, BLE 사용 MLX90614 제어 (0) | 2020.02.26 |
nRF52 - TWI 이용 MLX90614 적외선 비접촉 온도 측정 센서 (0) | 2020.02.03 |
nRF52 - TWI(I2C) 이용 SSD1306 OLED 제어 (1) | 2020.01.13 |