티스토리 뷰
오늘 nRF 칩 기반에서 색온도와 밝기를 측정할 수 있는 센서의 드라이버를 nRF 칩에서 구동할 수 있도록 하는 방법에 대해서 설명하도록 하겠습니다.
우선 컬러/밝기 센서는 어떨 때 주로 사용하는지 알아보면, 우리 생활속에서 색 환경이라는 것이 존재합니다. 쉽게 말해 화장실에서는 주황색을 띄는 램프를, 카페에서는 따뜻한 빛을 주는 노란색 계열의 램프를, 집 거실에서는 각각 인테리어에 따라 다르지만 흰색 빛이나 조금은 누런색을 띄는 빛을 볼 수 있고, 1단계 2단계 3단계 처럼 빛의 밝기도 조절할 수 있습니다. 이처럼 자신이 어떤환경의 빛에 노출되어 있느냐에 따라 뇌에 영향을 주는 것이 다른데요. 카페에서는 휴식의 느낌을 얻지만 사무실에가면 집중의 느낌을 받는 것이 대표적인 입니다. 이렇듯 주변환경을 감지해서 무엇을 해내기 위해선 적절한 센서의 선택과 동작 구현이 필요합니다. 오늘은 그 중에서 아두이노나, 라즈베리와 함께 쉽게 붙여서 쓸 수 있도록 라이브러리화 되어 있는 센서중의 하나인 TCS34725 의 센서 드라이버를 제작하면서 테스트해보도록 하겠습니다.
TCS34725 Datasheet
https://cdn-shop.adafruit.com/datasheets/TCS34725.pdf
기본적으로 센서를 다루기 위해서는 데이터시트를 볼 줄 아는 능력이 필요합니다. 센서의 통신 방식, 통신 방식에 따른 제어를 해야 센서가 초기화 되고 센서가 감지하는 값을 제대로 읽을 수 있기 때문이며 상세한 스펙도 나와 있습니다. 물론 이 센서의 경우 많은 분들이 이를 가지고 예제를 만들었기 때문에 회로만 잘 연결한다면 쉽게 붙여서 써볼수 있겠죠. 다만 우리는 여기서 단순히 라이브러리를 이용해서 센서값을 읽는 것이 아니라, 실제 내가 사용하는 nRF 칩에서 I2C 로우레벨 드라이버를 이용해서 직접 레지스터를 제어하여 값을 읽어내는 일을 할 것입니다.
데브보드를 먼저 구매합니다. 디바이스 마트등에서 쉽게 구할 수 있는데요, 소스를 구현하는 동안 미리 주문해서 받을 수 있도록 하면 됩니다.
https://www.devicemart.co.kr/goods/view?no=1271893
간단한 스펙사항은 구매하는 곳 제일 밑에 나와있습니다.
- Weight : 3.23g
- Dimmentions : 20.44mm / 0.8" x 20.28mm / 0.79"
- This board/chip uses I2C 7-bit address 0x29
이 센서를 상용화 한다면 이러한 데브보드 형태로는 사용하지 않을 것인지라 크기나 무게는 신경 쓰지 않아도 되고, 통신방식이 I2C 라는 점에 착안하며 접근하는 어드레스는 0x29 만 기억해 둡니다. 이 전 글에서 i2C 로우레벨 드라이버를 제작해놨으니 그 드라이버 기반으로 코드를 제작하도록 하겠습니다. 못보셨다면 아레 링크를 통해서 한번 복습하고 오시면 좋겠습니다.
https://hero-space.tistory.com/96
컬러센서의 경우 컬러를 읽어들이는 방법이 각각 다른데, TCS34725의 경우 RGBC 채널로 동작합니다. 컬러를 센싱하는 것은 가장 초기의 XYZ 형태의 것에서 출발해서 RGB로 변환하게 되는데 이 센서의 경우 RGB 형태로 바로 나오기 때문에 코드상에서는 조금 편하지만 정확도에 대해서는 어떨런지 확인이 필요합니다. XY 색도표는 아래와 같은 형태입니다.
x 축은 수치가 커질수록 '붉은색'의 비율이 증가하고 수치가 작아질 수록 '푸른색'의 비율이 증가합니다. 색도 좌표의 y축은 수치가 커질수록 초록빛 비율이 증가하고 수치가 작아질 수록 푸른빛 비율이 증가합니다. x=0.33 인 것은 붉은 기미의 혼색량이 33%인 것을 나타냈고, y=0.33 인 것은 초록 기미의 혼색량이 33%인 것을 나태납니다. 이 위치의 근처는, 나머지 z도 약 0.33(33%) 이 되어 RGB의 혼색의 결과는 백색이 되지만 여기는 백색점이라고 불러 가장 저채도(무채색)가 되는 부분입니다. 가운데 부분을 보시면 그 지점이 어떤 색인지 이해가 되실것인데요.색 관련되서는 여기까지 하고 다시 돌아가도록 하겠습니다.
센서를 제어하는 다양한 옵션과 방법이 있지만 먼저 센서의 ID 값을 읽히는지부터 확인이 필요합니다. 레지스터에 보면 아래와 같이 ID Register는 0x12이고 내가 지금 사용하는 센서의 ID값이 정상적으로 리턴된 후, 세부적인 레지스터 세팅이 필요합니다.
아무래도 TCS3472X 시리즈가 파생이 여러개다보니 센서의 상세한 정보를 먼저확인하는 것이 필요하며 TCS34725의 경우 0x44 리턴되면 정상일 것입니다. 이외에 중요한 레지스터들은 아래와 같습니다.
다만 이 센서의 경우 command register를 wrtie하면서 다른 레지스터를 제어해야하는데 command register의 경우 7번째 bit로 1로 해서 사용해주어야 합니다.
코드로 작성하면 아래와 같이 ID Register를 읽을 수 있습니다.
#define TCS34725_COMMAND_BIT 0x80
#define TCS34725_REG_ID 0x12
#define TCS34725_I2C_ADDRESS (0x29 << 1)
#define TCS34725_I2C_PART_NUMBER 0x44 //TCS34725
#define ctr_read(a) i2c2_read_byte(TCS34725_I2C_ADDRESS, a)
#define ctr_send(a, b) i2c2_send_byte(TCS34725_I2C_ADDRESS, a, b)
#define ctr_enable() i2c2_enable(true)
#define ctr_disable() i2c2_enable(false)
void sensor_init(void)
{
uint8_t data;
ctr_enable();
/* Read Chip ID */
data = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_ID);
nrf_delay_ms(10);
log_warn("TCS34725 Chip ID: 0x%02X\n", data);
if (data == TCS34725_I2C_PART_NUMBER) {
}
ctr_disable();
}
센서가 nRF 칩과 회로적으로 잘 연결이 되어 있다면 ID 값이 정상적으로 리턴될 것이며 이후 자신히 원하는 상세한 초기 세팅을 진행한 뒤 센서값을 주기적으로 읽도록 설계해주면 되는데 우선 가장 기본적으로 세팅이 필요한 것들을 TCS34725 칩 ID 값이 정상적으로 넘어 온다음 세팅을 합니다.
ctr_send(TCS34725_COMMAND_BIT | TCS34725_REG_ATIME, TCS34725_INTEGRATIONTIME_614MS);
ctr_send(TCS34725_COMMAND_BIT | TCS34725_REG_CONTROL, CTR_GAIN_1X);
ctr_send(TCS34725_COMMAND_BIT | TCS34725_REG_ENABLE, TCS34725_REG_ENABLE_PON);
nrf_delay_ms(3);
ctr_send(TCS34725_COMMAND_BIT | TCS34725_REG_ENABLE, TCS34725_REG_ENABLE_PON | TCS34725_REG_ENABLE_AEN);
nrf_delay_ms((256 - TCS34725_INTEGRATIONTIME_614MS) * 12 / 5 + 1);
ATIME의 경우 RGBC 타임에 관련 된 것으로 가장 max인 614ms 로 세팅하였는데 이는 1초에 한번저옫만 디텍되면 충분한 스펙이라 이렇게 세팅하였으며 Gain 값은 기본은 1x 으로 하고 RGBC의 PON과 AEN enable 시켜서 자동으로 새로운 RGBC 를 일곧록 설정한다. 레지스터 중간에 waiting이 필요한 시간은 스펙에 나와있는대로 진행하였습니다.
이제 cct를 계산 하는데 기본적으로 아래와 같고 해당 공식은 ams 사이트의 아래 문서의 5~6페이지에 기술되어있습니다.
color_sensor_data_t* periph_color_sensor_cct_read(void)
{
uint8_t data[8];
uint16_t r, g, b, c;
ctr_enable();
data[0] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_RDATAL);
data[1] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_RDATAH);
data[2] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_GDATAL);
data[3] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_GDATAH);
data[4] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_BDATAL);
data[5] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_BDATAH);
data[6] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_CDATAL);
data[7] = ctr_read(TCS34725_COMMAND_BIT | TCS34725_REG_CDATAH);
ctr_disable();
nrf_delay_ms((256 - TCS34725_INTEGRATIONTIME_614MS) * 12 / 5 + 1);
r = (((uint16_t) data[1] << 8) + data[0]);
g = (((uint16_t) data[3] << 8) + data[2]);
b = (((uint16_t) data[5] << 8) + data[4]);
c = (((uint16_t) data[7] << 8) + data[6]);
g_cct_data.cct = _calculate_cct_dn40(r, g, b, c);
g_cct_data.lux = _calculate_lux(r, g, b);
log_warn("cct[%ld]lux[%ld]\n", g_cct_data.cct, g_cct_data.lux);
return &g_cct_data;
}
uint16_t _calculate_cct(uint16_t r, uint16_t g, uint16_t b)
{
float X, Y, Z, xc, yc, n, cct;
if (r == 0 && g == 0 && b == 0) {
return 0;
}
X = (-0.14282F * r) + (1.54924F * g) + (-0.95641F * b);
Y = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b);
Z = (-0.68202F * r) + (0.77073F * g) + ( 0.56332F * b);
xc = (X) / (X + Y + Z);
yc = (Y) / (X + Y + Z);
n = (xc - 0.3320F) / (0.1858F - yc);
cct = (449.0F * powf(n, 3)) + (3525.0F * powf(n, 2)) + (6823.3F * n) + 5520.33F;
return (uint16_t)cct;
}
https://ams.com/documents/20143/80162/TCS34xx_AN000517_1-00.pdf/1efe49f7-4f92-ba88-ca7c-5121691daff7
- X = (-0.14282)(R) + (1.54924)(G) + (-0.95641)(B)
- Y = (-0.32466)(R) + (1.57837)(G) + (-0.73191)(B)
- Z = (-0.68202)(R) + (0.77073)(G) + (0.56332)(B)
- x = X / ( X +. Y. + Z )
- y = Y /.( X +. Y + Z )
- n = (x − 0.3320) / (0.1858 − y)
- CCT = 449n3 + 3525n2 + 6823.3n + 5520.33
다만 이렇게 할시 실제 색온도계도 측정할때와 비슷한 CCT가 나오긴 하나, 500Kelvin 정도 오차가 있어 dn40이라는 알고리즘을 다시 적용해보도록 하겠습니다. 이 알고리즘은 r, g, b 값 외에도 c 채널 값 까지 이용해서 계산을 하게됩니다.
uint16_t _calculate_cct_dn40(uint16_t r, uint16_t g, uint16_t b, uint16_t c)
{
uint16_t r2, b2;
uint16_t ir, cct;
if (c == 0) {
return 0;
}
ir = (r + g + b > c) ? (r + g + b - c) / 2 : 0;
r2 = r - ir;
b2 = b - ir;
if( r2 == 0) {
return 0;
}
cct = (3810 * (uint32_t)b2) / (uint32_t)r2 + 1391;
return cct;
}
dn40에 대해서는 아래 문서를 참고하여 작성하였습니다.
Lux로 표현되는 밝기의 경우 아래와 같이 계산을 진행하였습니다.
uint16_t _calculate_lux(uint16_t r, uint16_t g, uint16_t b)
{
return (uint16_t)((-0.32466F * r) + (1.57837F * g) + (-0.73191F * b));
}
1초에 한번씩 출력을 해서 확인하니, CCT와 Lux가 실시간으로 개선된 상태로 실제 색온도계에서 내어주는 값어 유사하게 측정이 되었습니다. 물론 센서의 오픈 구조나 렌즈등의 영향을 받을 수 있기에 좀더 주의가 필요하나, 기본적으로 동작 I2C Low Lever 드라이버를 활용하여 TCS34725 드라이버를 제작할 수 있었습니다.
'Technology > Bluetooth' 카테고리의 다른 글
RTOS 기반 Multitasking 환경 구축 (0) | 2022.11.09 |
---|---|
OTA 로 nRF 칩 소프트웨어 업데이트하기 1 (0) | 2022.08.04 |
nRF 칩에서 PWM Frequency 조절 하기 (0) | 2022.07.31 |
nRF 칩에서 Low Lever I2C driver 만들어 사용하기 (0) | 2022.06.26 |
nRF칩에서 I2C 연동하기 twi (0) | 2022.05.29 |