카테고리 없음

VEML3328 color sensor와 평균법

엠칩 2025. 7. 1. 08:55
반응형

 

mikroe Color 10 센서를 구입해 봤습니다.

Color 10 Click

U1  VEML3328 센서는 RGB, Clear, IR 채널 각각에 대해 16비트 해상도로 측정값을 제공합니다.
이 측정값은 센서 내부의 포토다이오드와 증폭기, 아날로그/디지털 회로를 통해 수집된 빛의 강도를 디지털 값으로 변환한 것입니다.

각 채널의 출력값은 다음과 같은 요소에 따라 달라집니다:

  • 빛의 파장: 예를 들어, 적색(R)은 약 610nm, 녹색(G)은 560nm, 청색(B)은 470nm, 적외선(IR)은 825nm에 민감합니다.
  • 조도 강도: 주변 광원의 밝기에 따라 출력값이 비례적으로 증가합니다.
  • 렌즈 투과율온도 보정: 센서는 어두운 렌즈 설계와 -40°C~+85°C의 온도 범위에서도 안정적인 출력을 유지하도록 설계되어 있습니다.

즉, 측정값의 기준은 빛의 스펙트럼 강도이며, 이 값은 **절대적인 광량(lux)**이 아닌 상대적인 디지털 카운트 값입니다.
따라서 실제 조도(lux)나 색온도(K)를 얻으려면 보정 알고리즘이나 캘리브레이션이 필요합니다.
Arduino에서는 따로 라이브러리가 없는 것 같아서 직접 코딩해서 구현했습니다.
AI도움도 받아봤는데 소자의 I2C 주소나 레지스터 주소등 혼선을 일으켜서 방해만 되더군요... ㅡ,.ㅡ;

RGB 3color LED는 WS2812 는 1wire 통신으로 제어되는 LED strip 에 많이 사용되는 소자입니다. 
arduino에서 사용하려면 Adafruit_NeoPixel 라이브러리를 설치하면 쉽게 사용이 가능합니다.

전원표시용 PWR LED가 있는데 녹색이라 측정에 방해가 됩니다. 차라리 백색을 부착해주지...ㅜ

하단 소스에는 DI가 D2, DO가 A0에 연결되어 있는데 서로 바꿔서 꽂았습니다.
I2C SCL SDA는 A5(SDA), A4(SCL)에 연결하면 됩니다.
그리고 5V, 3.3V, GND모두 사용하므로 모두 연결해주면 됩니다.

#include <Wire.h>
#include <Adafruit_NeoPixel.h>

#define COLOR_SENSOR_ADDR 0x10 // VEML3328의 I2C 주소
#define DI_PIN 2              // DI 핀 (D2)
#define LED_PIN A0            // WS2812B LED는 AN핀(A0)에 연결되어 있습니다.
#define NUM_LEDS 1            // 보드에는 1개의 LED가 있습니다.

// VEML3328 레지스터
// 데이터시트 기준 올바른 VEML3328 레지스터 주소
#define REG_COMMAND 0x00 // 명령 레지스터
#define REG_RED     0x05 // Red 데이터
#define REG_GREEN   0x06 // Green 데이터
#define REG_BLUE    0x07 // Blue 데이터
#define REG_IR      0x08 // IR 데이터
#define REG_CLEAR   0x04 // Clear 데이터
#define REG_ID      0x0C // 장치 ID 레지스터

// NeoPixel 라이브러리 객체 생성
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

// --- 스무딩(평균)을 위한 변수 추가 ---
// 스무딩 강도 (0.0 ~ 1.0). 값이 작을수록 스무딩이 강해지고, 클수록 반응성이 높아집니다.
const float SMOOTHING_FACTOR = 0.1;

// 스무딩된 값을 저장할 변수 (부동소수점 사용으로 정밀도 유지)
float smoothedClear = 0;
float smoothedRed = 0;
float smoothedGreen = 0;
float smoothedBlue = 0;
float smoothedIr = 0;

bool firstReading = true; // 첫 번째 측정인지 확인하기 위한 플래그

void setup() {
  Serial.begin(19200);
  // while (!Serial); // Uno 보드에서는 이 라인이 없어도 되며, 때로 문제를 일으킬 수 있습니다.

  Wire.begin(); // I2C 초기화 (Uno: A4=SDA, A5=SCL)

  pinMode(DI_PIN, INPUT);

  strip.begin();           // NeoPixel 라이브러리 초기화
  strip.show();            // 모든 LED를 '꺼짐' 상태로 초기화
  strip.setBrightness(50); // LED 밝기 설정 (0-255), 너무 밝지 않게 50으로 시작

  // I2C 통신 테스트
  Wire.beginTransmission(COLOR_SENSOR_ADDR);
  int error = Wire.endTransmission();
  if (error == 0) {
    Serial.println("I2C device found at address 0x10");
  } else {
    Serial.print("I2C communication failed with error: ");
    Serial.println(error);
    while (1);
  }

  if (!initColorSensor()) {
    Serial.println("Color Sensor initialization failed!");
    // 실패 시 디버깅을 위해 명령 레지스터 값을 다시 읽어옵니다.
    uint16_t status = read16BitRegister(REG_COMMAND);
    Serial.print("Command Register Value: 0x");
    Serial.println(status, HEX);
    while (1);
  }
  Serial.println("Color Sensor initialized successfully");

  Serial.println("\n--- LED Control ---");
  Serial.println("Send 'on' to turn the LED on.");
  Serial.println("Send 'off' to turn the LED off.");
  Serial.println("Send 'rgb <r> <g> <b>' to set a custom color. (e.g. 'rgb 255 0 0' for red)");
  Serial.println("---------------------\n");
}

bool initColorSensor() {
  // 1. 장치 ID를 읽어 통신이 정상적인지 확인합니다. (VEML3328 ID: 0x0028)
  uint16_t deviceId = read16BitRegister(REG_ID);
  Serial.print("Device ID: 0x");
  Serial.println(deviceId, HEX);
  // VEML3328의 ID는 0x28입니다. 상위 비트(MSB)는 예약(reserved) 영역으로, 칩 리비전에 따라 값이 다를 수 있습니다.
  // 따라서 하위 비트(LSB)만 마스킹하여 ID가 일치하는지 확인합니다.
  if ((deviceId & 0x00FF) != 0x0028) {
    Serial.println("Device ID does not match. Check wiring or sensor model.");
    return false;
  }

  // 2. 센서 활성화: SD=0 (활성화) 및 모든 측정 채널(R,G,B,C,IR) 활성화 뭐가 맞는지 모르겠음.
  // VEML3328 데이터시트 기준: R_EN(bit8), G_EN(bit9), B_EN(bit10), IR_EN(bit12), C_EN(bit13)
  // 0x3700 = b0011 0111 0000 0000
  // (C_EN | IR_EN | B_EN | G_EN | R_EN) = (1<<13)|(1<<12)|(1<<10)|(1<<9)|(1<<8) = 0x2000|0x1000|0x0400|0x0200|0x0100 = 0x3700
//   >>>> AI가 알려주는 값하고 실제 데이타가 달라서 애먹었습니다. 
  // writeCommand(REG_COMMAND, 0x3700);
  writeCommand(REG_COMMAND, 0x0000);
  delay(2); // 명령 처리 대기

  // 3. 명령 레지스터를 다시 읽어 설정이 올바르게 적용되었는지 확인합니다.
  uint16_t status = read16BitRegister(REG_COMMAND);
  Serial.print("Command Register Read: 0x");
  Serial.println(status, HEX);
 
  // SD 비트(bit 0)가 0인지 확인하여 센서가 활성화되었는지 판단합니다.
  return (status & 0x0001) == 0x00;
}

// VEML3328은 16비트 명령/데이터를 사용합니다.
void writeCommand(uint8_t reg, uint16_t data) {
  Wire.beginTransmission(COLOR_SENSOR_ADDR);
  Wire.write(reg);
  Wire.write(data & 0xFF);      // 데이터 LSB
  Wire.write(data >> 8);        // 데이터 MSB
  Wire.endTransmission();
}

uint16_t read16BitRegister(uint8_t reg) {
  Wire.beginTransmission(COLOR_SENSOR_ADDR);
  Wire.write(reg);
  // VEML3328 센서에서 데이터를 읽으려면 STOP 신호 없이 REPEATED START를 보내야 합니다.
  // Wire.endTransmission(false)를 사용하여 이를 구현합니다.
  if (Wire.endTransmission(false) != 0) {
    return 0xFFFF; // 통신 오류
  }

  Wire.requestFrom((uint8_t)COLOR_SENSOR_ADDR, (uint8_t)2);
  if (Wire.available() >= 2) {
    uint16_t lsb = Wire.read(); // 데이터시트에 따라 LSB를 먼저 읽습니다.
    uint16_t msb = Wire.read(); // 그 다음 MSB를 읽습니다.
    return (msb << 8) | lsb;
  }
  return 0xFFFF; // 데이터 읽기 실패
}

void readColorData(uint16_t &clear, uint16_t &red, uint16_t &green, uint16_t &blue, uint16_t &ir) {
  red = read16BitRegister(REG_RED);
  green = read16BitRegister(REG_GREEN);
  blue = read16BitRegister(REG_BLUE);
  clear = read16BitRegister(REG_CLEAR);
  ir = read16BitRegister(REG_IR);
}

void loop() {
  // 1. 시리얼 포트로 들어오는 명령을 확인하고 처리합니다.
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    command.trim(); // 앞뒤 공백 및 개행 문자 제거

    if (command.equalsIgnoreCase("on")) {
      strip.setPixelColor(0, strip.Color(255, 255, 255)); // 0번 LED를 흰색(중간 밝기)으로 설정
      strip.show(); // 변경된 색상을 LED에 적용
      Serial.println("LED turned ON (White)");
    } else if (command.equalsIgnoreCase("off")) {
      strip.setPixelColor(0, 0, 0, 0); // 0번 LED를 끔
      strip.show(); // 변경된 상태를 LED에 적용
      Serial.println("LED turned OFF");
    } else if (command.startsWith("rgb ")) {
      // "rgb r g b" 형식의 명령을 파싱합니다.
      int r, g, b;
      // "rgb " 다음부터 숫자 부분을 읽습니다.
      int parsed = sscanf(command.c_str(), "rgb %d %d %d", &r, &g, &b);

      if (parsed == 3) {
        // 파싱 성공: 값 범위를 0-255로 제한하고 LED 색상을 설정합니다.
        r = constrain(r, 0, 255);
        g = constrain(g, 0, 255);
        b = constrain(b, 0, 255);

        strip.setPixelColor(0, strip.Color(r, g, b));
        strip.show();

        Serial.print("LED color set to: RGB[");
        Serial.print(r);
        Serial.print("] G[");
        Serial.print(g);
        Serial.print("] B[");
        Serial.print(b);
        Serial.println("]");
      } else {
        Serial.println("Invalid 'rgb' format. Use: rgb <r> <g> <b>");
      }
    } else {
      Serial.print("Unknown command: ");
      Serial.println(command);
    }
  }

  // 2. 주기적으로 컬러 센서 데이터를 읽고 출력합니다.
  uint16_t clear, red, green, blue, ir;
  readColorData(clear, red, green, blue, ir);

  // --- 지수 이동 평균(Exponential Moving Average) 적용 ---
  if (firstReading) {
    // 첫 측정값으로 스무딩 변수 초기화
    smoothedClear = clear;
    smoothedRed = red;
    smoothedGreen = green;
    smoothedBlue = blue;
    smoothedIr = ir;
    firstReading = false;
  } else {
    // EMA 공식: NewAverage = (alpha * NewValue) + ((1 - alpha) * OldAverage)
    smoothedClear = (SMOOTHING_FACTOR * clear) + (1 - SMOOTHING_FACTOR) * smoothedClear;
    smoothedRed   = (SMOOTHING_FACTOR * red)   + (1 - SMOOTHING_FACTOR) * smoothedRed;
    smoothedGreen = (SMOOTHING_FACTOR * green) + (1 - SMOOTHING_FACTOR) * smoothedGreen;
    smoothedBlue  = (SMOOTHING_FACTOR * blue)  + (1 - SMOOTHING_FACTOR) * smoothedBlue;
    smoothedIr    = (SMOOTHING_FACTOR * ir)    + (1 - SMOOTHING_FACTOR) * smoothedIr;
  }

  // 원본 값과 스무딩된 값을 함께 출력하여 비교
  // Serial.print("Raw(B,C,G,R,IR): ");
  Serial.print(blue); Serial.print(",");
  Serial.print(clear);  Serial.print(",");
  Serial.print(green);  Serial.print(",");
  Serial.print(red);  Serial.print(",");
  Serial.print(ir); Serial.print(",");

  // Serial.print("  |  Smoothed(B,C,G,R,IR): ");
  Serial.print((int)smoothedBlue);  Serial.print(",");
  Serial.print((int)smoothedClear); Serial.print(",");
  Serial.print((int)smoothedGreen); Serial.print(",");
  Serial.print((int)smoothedRed); Serial.print(",");
  Serial.println((int)smoothedIr);

  delay(50); // 너무 빠른 루프 방지를 위해 약간의 딜레이 추가
}

실측값과 지수평균법 비교 데이타

저주파성 노이즈에 대응하려고 적응해봤는데 한계가 있습니다.

200부근이 적색 LED점등시 측정값 - Clear와 적색이 높게 측정
300부근이 녹색 LED점등시 측정값 - Clear와 녹색이 높게 측정
450부근이 청색 LED점등시 측정값 - Clear와 청색이 높게 측정
600부근이 백색 LED점등시 측정값입니다.   Clear는 그래프를 벗어 났고, 청색 녹색 적색 순서로 측정
RGB 255 255 255로 켰는데 각 색상별로 다르게 측정됩니다. 청색 > 녹색 > 적색 순서네요

각 색상별 출력값이 제각각입니다.
센서 자체가 절대값을 읽는게 아니라는 설명이 있어서....
Calibration루틴을 추가했습니다.

calibration start를 입력한다음
검정색 으로 센서를 가려서 최소값을 입력시키고
on을 입력해서 LED를 모두 켠다음 백색 종이로 최대값을 입력시킨다음
calibration stop을 입력해서 캘리브레이션을 종료시킵니다.

이러면 각 RGB 센서마다 최소 / 최대값을 취득한다음 이후 센서에서 입력되는 값을 백분율로 표시해줍니다.
mapped RGB 값이 이 값입니다.

#include <Wire.h>
#include <Adafruit_NeoPixel.h>

#define COLOR_SENSOR_ADDR 0x10 // VEML3328의 I2C 주소
#define DI_PIN 2              // DI 핀 (D2)
#define LED_PIN A0            // WS2812B LED는 AN핀(A0)에 연결되어 있습니다.
#define NUM_LEDS 1            // 보드에는 1개의 LED가 있습니다.

// VEML3328 레지스터
// 데이터시트 기준 올바른 VEML3328 레지스터 주소
#define REG_COMMAND 0x00 // 명령 레지스터
#define REG_RED     0x05 // Red 데이터
#define REG_GREEN   0x06 // Green 데이터
#define REG_BLUE    0x07 // Blue 데이터
#define REG_IR      0x08 // IR 데이터
#define REG_CLEAR   0x04 // Clear 데이터
#define REG_ID      0x0C // 장치 ID 레지스터

// NeoPixel 라이브러리 객체 생성
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

// --- 스무딩(평균)을 위한 변수 추가 ---
// 스무딩 강도 (0.0 ~ 1.0). 값이 작을수록 스무딩이 강해지고, 클수록 반응성이 높아집니다.
const float SMOOTHING_FACTOR = 0.1;
uint8_t mapdata = 0;
// 스무딩된 값을 저장할 변수 (부동소수점 사용으로 정밀도 유지)
float smoothedClear = 0;
float smoothedRed = 0;
float smoothedGreen = 0;
float smoothedBlue = 0;
float smoothedIr = 0;

bool firstReading = true; // 첫 번째 측정인지 확인하기 위한 플래그

// --- 자동 보정(Calibration)을 위한 변수 추가 ---
bool isCalibrating = false; // 보정 모드 활성화 플래그

// 각 채널의 최소/최대 값을 저장할 변수
// 최소값은 매우 큰 값으로, 최대값은 0으로 초기화
uint16_t redMin = 65535, redMax = 0;
uint16_t greenMin = 65535, greenMax = 0;
uint16_t blueMin = 65535, blueMax = 0;
uint16_t clearMin = 65535, clearMax = 0;

void setup() {
  Serial.begin(19200);
  // while (!Serial); // Uno 보드에서는 이 라인이 없어도 되며, 때로 문제를 일으킬 수 있습니다.

  Wire.begin(); // I2C 초기화 (Uno: A4=SDA, A5=SCL)

  pinMode(DI_PIN, INPUT);

  strip.begin();           // NeoPixel 라이브러리 초기화
  strip.show();            // 모든 LED를 '꺼짐' 상태로 초기화
  strip.setBrightness(50); // LED 밝기 설정 (0-255), 너무 밝지 않게 50으로 시작

  // I2C 통신 테스트
  Wire.beginTransmission(COLOR_SENSOR_ADDR);
  int error = Wire.endTransmission();
  if (error == 0) {
    Serial.println("I2C device found at address 0x10");
  } else {
    Serial.print("I2C communication failed with error: ");
    Serial.println(error);
    while (1);
  }

  if (!initColorSensor()) {
    Serial.println("Color Sensor initialization failed!");
    // 실패 시 디버깅을 위해 명령 레지스터 값을 다시 읽어옵니다.
    uint16_t status = read16BitRegister(REG_COMMAND);
    Serial.print("Command Register Value: 0x");
    Serial.println(status, HEX);
    while (1);
  }
  Serial.println("Color Sensor initialized successfully");

  Serial.println("\n--- LED Control ---");
  Serial.println("Send 'on' to turn the LED on.");
  Serial.println("Send 'off' to turn the LED off.");
  Serial.println("Send 'rgb <r> <g> <b>' to set a custom color. (e.g. 'rgb 255 0 0' for red)");
  Serial.println("\n--- Calibration ---");
  Serial.println("Send 'calibrate start' to begin calibration.");
  Serial.println("Send 'calibrate stop' to end calibration.");
  Serial.println("---------------------\n");
  Serial.println("label Blue Clear Green Red IR MappedR MappedG MappedB\n");
}

bool initColorSensor() {
  // 1. 장치 ID를 읽어 통신이 정상적인지 확인합니다. (VEML3328 ID: 0x0028)
  uint16_t deviceId = read16BitRegister(REG_ID);
  Serial.print("Device ID: 0x");
  Serial.println(deviceId, HEX);
  // VEML3328의 ID는 0x28입니다. 상위 비트(MSB)는 예약(reserved) 영역으로, 칩 리비전에 따라 값이 다를 수 있습니다.
  // 따라서 하위 비트(LSB)만 마스킹하여 ID가 일치하는지 확인합니다.
  if ((deviceId & 0x00FF) != 0x0028) {
    Serial.println("Device ID does not match. Check wiring or sensor model.");
    return false;
  }

  // 2. 센서 활성화: SD=0 (활성화) 및 모든 측정 채널(R,G,B,C,IR) 활성화 뭐가 맞는지 모르겠음.
  // VEML3328 데이터시트 기준: R_EN(bit8), G_EN(bit9), B_EN(bit10), IR_EN(bit12), C_EN(bit13)
  // 0x3700 = b0011 0111 0000 0000
  // (C_EN | IR_EN | B_EN | G_EN | R_EN) = (1<<13)|(1<<12)|(1<<10)|(1<<9)|(1<<8) = 0x2000|0x1000|0x0400|0x0200|0x0100 = 0x3700
  // writeCommand(REG_COMMAND, 0x3700);
  writeCommand(REG_COMMAND, 0x0000);
  delay(2); // 명령 처리 대기

  // 3. 명령 레지스터를 다시 읽어 설정이 올바르게 적용되었는지 확인합니다.
  uint16_t status = read16BitRegister(REG_COMMAND);
  Serial.print("Command Register Read: 0x");
  Serial.println(status, HEX);
 
  // SD 비트(bit 0)가 0인지 확인하여 센서가 활성화되었는지 판단합니다.
  return (status & 0x0001) == 0x00;
}

// VEML3328은 16비트 명령/데이터를 사용합니다.
void writeCommand(uint8_t reg, uint16_t data) {
  Wire.beginTransmission(COLOR_SENSOR_ADDR);
  Wire.write(reg);
  Wire.write(data & 0xFF);      // 데이터 LSB
  Wire.write(data >> 8);        // 데이터 MSB
  Wire.endTransmission();
}

uint16_t read16BitRegister(uint8_t reg) {
  Wire.beginTransmission(COLOR_SENSOR_ADDR);
  Wire.write(reg);
  // VEML3328 센서에서 데이터를 읽으려면 STOP 신호 없이 REPEATED START를 보내야 합니다.
  // Wire.endTransmission(false)를 사용하여 이를 구현합니다.
  if (Wire.endTransmission(false) != 0) {
    return 0xFFFF; // 통신 오류
  }

  Wire.requestFrom((uint8_t)COLOR_SENSOR_ADDR, (uint8_t)2);
  if (Wire.available() >= 2) {
    uint16_t lsb = Wire.read(); // 데이터시트에 따라 LSB를 먼저 읽습니다.
    uint16_t msb = Wire.read(); // 그 다음 MSB를 읽습니다.
    return (msb << 8) | lsb;
  }
  return 0xFFFF; // 데이터 읽기 실패
}

void readColorData(uint16_t &clear, uint16_t &red, uint16_t &green, uint16_t &blue, uint16_t &ir) {
  red = read16BitRegister(REG_RED);
  green = read16BitRegister(REG_GREEN);
  blue = read16BitRegister(REG_BLUE);
  clear = read16BitRegister(REG_CLEAR);
  ir = read16BitRegister(REG_IR);
}

void loop() {
  // 1. 시리얼 포트로 들어오는 명령을 확인하고 처리합니다.
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    command.trim(); // 앞뒤 공백 및 개행 문자 제거

    if (command.equalsIgnoreCase("on")) {
      strip.setPixelColor(0, strip.Color(255, 255, 255)); // 0번 LED를 흰색(중간 밝기)으로 설정
      strip.show(); // 변경된 색상을 LED에 적용
      Serial.println("LED turned ON (White)");
    } else if (command.equalsIgnoreCase("off")) {
      strip.setPixelColor(0, 0, 0, 0); // 0번 LED를 끔
      strip.show(); // 변경된 상태를 LED에 적용
      Serial.println("LED turned OFF");
    } else if (command.startsWith("rgb ")) {
      // "rgb r g b" 형식의 명령을 파싱합니다.
      int r, g, b;
      // "rgb " 다음부터 숫자 부분을 읽습니다.
      int parsed = sscanf(command.c_str(), "rgb %d %d %d", &r, &g, &b);

      if (parsed == 3) {
        // 파싱 성공: 값 범위를 0-255로 제한하고 LED 색상을 설정합니다.
        r = constrain(r, 0, 255);
        g = constrain(g, 0, 255);
        b = constrain(b, 0, 255);

        strip.setPixelColor(0, strip.Color(r, g, b));
        strip.show();

        Serial.print("LED color set to: RGB[");
        Serial.print(r);
        Serial.print("] G[");
        Serial.print(g);
        Serial.print("] B[");
        Serial.print(b);
        Serial.println("]");
      } else {
        Serial.println("Invalid 'rgb' format. Use: rgb <r> <g> <b>");
      }
    } else if (command.equalsIgnoreCase("calibrate start")) {
      isCalibrating = true;
      // 보정 변수 초기화
      redMin = 65535; redMax = 0;
      greenMin = 65535; greenMax = 0;
      blueMin = 65535; blueMax = 0;
      clearMin = 65535; clearMax = 0;
      Serial.println(">>> Calibration started. Show the sensor the brightest and darkest conditions. <<<");
    } else if (command.equalsIgnoreCase("calibrate stop")) {
      isCalibrating = false;
      Serial.println(">>> Calibration finished. <<<");
      Serial.println("Calibrated Min/Max values:");
      Serial.print("Red:   "); Serial.print(redMin); Serial.print(" - "); Serial.println(redMax);
      Serial.print("Green: "); Serial.print(greenMin); Serial.print(" - "); Serial.println(greenMax);
      Serial.print("Blue:  "); Serial.print(blueMin); Serial.print(" - "); Serial.println(blueMax);
      Serial.print("Clear: "); Serial.print(clearMin); Serial.print(" - "); Serial.println(clearMax);
      Serial.println("---------------------\n");
    } else if (command.equalsIgnoreCase("map")) {
      mapdata = !mapdata;
      if(mapdata) {
        Serial.println("mapped data only");
        Serial.println("label MappedR MappedG MappedB\n");
      } else {
        Serial.println("raw data ");
        Serial.println("label Blue Clear Green Red IR MappedR MappedG MappedB\n");
      }
    } else {
      Serial.print("Unknown command: ");
      Serial.println(command);
    }
  }

  // 2. 주기적으로 컬러 센서 데이터를 읽고 출력합니다.
  uint16_t clear, red, green, blue, ir;
  readColorData(clear, red, green, blue, ir);

  // --- 지수 이동 평균(Exponential Moving Average) 적용 ---
  if (firstReading) {
    // 첫 측정값으로 스무딩 변수 초기화
    smoothedClear = clear;
    smoothedRed = red;
    smoothedGreen = green;
    smoothedBlue = blue;
    smoothedIr = ir;
    firstReading = false;
  } else {
    // EMA 공식: NewAverage = (alpha * NewValue) + ((1 - alpha) * OldAverage)
    smoothedClear = (SMOOTHING_FACTOR * clear) + (1 - SMOOTHING_FACTOR) * smoothedClear;
    smoothedRed   = (SMOOTHING_FACTOR * red)   + (1 - SMOOTHING_FACTOR) * smoothedRed;
    smoothedGreen = (SMOOTHING_FACTOR * green) + (1 - SMOOTHING_FACTOR) * smoothedGreen;
    smoothedBlue  = (SMOOTHING_FACTOR * blue)  + (1 - SMOOTHING_FACTOR) * smoothedBlue;
    smoothedIr    = (SMOOTHING_FACTOR * ir)    + (1 - SMOOTHING_FACTOR) * smoothedIr;
  }

  // --- 보정 모드 실행 ---
  if (isCalibrating) {
    // 현재 스무딩된 값으로 최소/최대값 갱신
    redMin   = min(redMin,   (uint16_t)smoothedRed);
    redMax   = max(redMax,   (uint16_t)smoothedRed);
    greenMin = min(greenMin, (uint16_t)smoothedGreen);
    greenMax = max(greenMax, (uint16_t)smoothedGreen);
    blueMin  = min(blueMin,  (uint16_t)smoothedBlue);
    blueMax  = max(blueMax,  (uint16_t)smoothedBlue);
    clearMin = min(clearMin, (uint16_t)smoothedClear);
    clearMax = max(clearMax, (uint16_t)smoothedClear);
  }

  // --- 보정된 값으로 매핑 (0-255 범위) ---
  // 보정값이 유효한 경우(min < max)에만 매핑 수행
  uint8_t mappedRed   = (redMax > redMin)     ? map(constrain(smoothedRed,   redMin,   redMax),   redMin,   redMax,   0, 255) : 0;
  uint8_t mappedGreen = (greenMax > greenMin) ? map(constrain(smoothedGreen, greenMin, greenMax), greenMin, greenMax, 0, 255) : 0;
  uint8_t mappedBlue  = (blueMax > blueMin)   ? map(constrain(smoothedBlue,  blueMin,  blueMax),  blueMin,  blueMax,  0, 255) : 0;

  // 원본 값과 스무딩된 값을 함께 출력하여 비교
  // Serial.print("Raw(B,C,G,R,IR), Smoothed(B,C,G,R,IR), Mapped(R,G,B)");
  // Serial.print(blue);  Serial.print(",");
  // Serial.print(clear); Serial.print(",");
  // Serial.print(green); Serial.print(",");
  // Serial.print(red); Serial.print(",");
  // Serial.print(ir); Serial.print(",");
   if(!mapdata) {
     Serial.print((int)smoothedBlue); Serial.print(",");
     Serial.print((int)smoothedClear);  Serial.print(",");
     Serial.print((int)smoothedGreen);  Serial.print(",");
     Serial.print((int)smoothedRed);  Serial.print(",");
     Serial.print((int)smoothedIr); Serial.print(",");
    }

  Serial.print(mappedRed);   Serial.print(",");
  Serial.print(mappedGreen); Serial.print(",");
  Serial.println(mappedBlue);

  delay(50); // 너무 빠른 루프 방지를 위해 약간의 딜레이 추가
}

캘리브레이션을 끝낸뒤 R/G/B/값 취득 그래프 인데..
맨앞에 100부근 RGB값이 제각각 이던게... 800에 가면 RGB값이 일정하게 나옵니다.

 

위 소스에서는 지수이동평균법을 사용했는데... 튀는값을 효과적으로 잡아준다는 Median 필터 평균법(중앙값)을 사용해봤습니다.

맨 앞부터 중앙값 취득 횟수를 5회 15회 .. 마지막에 31회까지 증가시키니까  파형이 고와졌네요...
샘플링 시간을 단축시켜서 데이타 갱신 시간도 유지시켰습니다.

반응형