среда, 20 мая 2015 г.

Интерактивная RGB лента (определение цвета пикселя под курсором и передача в Arduino)

Сперва следует познакомиться с цветовой моделью RGB. RGB (Red, Green, Blue) – это аддитивная (англ. addition) цветовая модель , как правило, описывающая способ синтеза цвета для цветовоспроизведения. Цвета в такой модели получаются путем добавления к черному цвету, за черный цвет принят ноль. Интенсивность основных цветов принято измерять целыми числами в диапазоне от 0 до 255. Ноль означает отсутствие данной цветовой составляющей, соответственно число 255 – максимальную интенсивность. Выбор основных цветов обусловлен особенностями физиологии восприятия цвета сетчаткой человеческого глаза. При смешении основных цветов максимальной интенсивности получаем белый, а производные цвета получаются в результате сложения или смешения базовых, основных цветов.



Поскольку максимальная интенсивность каждого из основных цветов равна 255 ($FF или один байт), то вся цветовая модель RGB имет размерность 3 байта (в числовом эквиваленте дополняется до 4-х, тип DWORD) и следующую структуру: нулевой байт R, первый байт G, второй байт B.

Верхний уровень

Если лента RGB, то очевидно интенсивность каждого цвета задавать ШИМ-ом, а оттенки R/G/B отдельными байтами интенсивности [0..255]. Для того, чтобы не усложнять пакет данных введением идентификаторов - каждый из байтов R, G и B можно задавать числами в разных поддиапазонах. Для:
  1. R [0..255]
  2. G [256+0..256+255]
  3. B [512+0..512+255]
Нижний уровень

На нижнем уровне для восстановления Integer числа из потока байт можно воспользоваться методом ParseInt() объекта SerialPort. В "буратино" обмен по UART идет побайтно в ASCII. Число с типом Integer передается разделением побайтно каждого из составляющих символов. Например нужно передать число (в DEC) 511, оно выходит за рамки байта (255), поэтому оно будет представлено как отдельные 5, 1, 1 и передано аски-кодом каждого символа в байтах (DEC) 35 31 31. Функция ParseInt() восстановит из пакета (HEX) 0х23 0х1f 0x1f число 511, с которым и можно будет дальше работать.

Интенсивностью свечения светодиодов будем управлять функцией AnalogWrite() на ШИМ выходах "буратины". Функция циклически вызывается в теле скетча и принимает два аргумента: номер выхода и значение ширины импульса ШИМ в диапазоне от 0 до 255. Не забудьте при подключении светодиодов к МК использовать токоограничительные сопротивления (светодиод - токовый прибор) номиналом 330 Ом.

Скетч парсинга данных и контроля RGB-cветодиода в "буратине":
int rx=0;      // начальное значение
int rx2=1;    // temp при новой
int rr = 9;    // красный на PWM
int gg = 10; // зеленый на PWM
int bb = 11; // синий на PWM

void setup() {

   Serial.begin(115200);
   Serial.setTimeout(5);
   pinMode(rr, OUTPUT);
   pinMode(gg, OUTPUT);
   pinMode(bb, OUTPUT);
}
void loop()
{
   // чтение пакета с преобразованием массива принятых в единое число в INT
   if (Serial.available() > 0) {
   rx = Serial.parseInt();
   Serial.print(rx); // контроль эха в ASCII
   }

   // если предыдущее значение <> новому
   if (rx!= rx2){

    if (rx>=0 && rx<=255) {
       analogWrite(rr, rx);
    }
    if (rx>=256 && rx<=511) {
       analogWrite(gg, 255-(511-rx));
    }
    if (rx>=512 && rx<=767) {
       analogWrite(bb, 255-(767-rx));  
    }
    rx2 = rx; // запоминаем новое значение для последующего сравнения
   }
}
Набросаем на WinAPI примитивное нативное приложение получения цвета под курсором и отправки данных в "буратину" по последовательному порту (физическому или виртуальному over USB или over Bluetooth, скажем с использованием модуля прозрачного UART поверх блютуз - HC05):
// Цвет под курсором и отправка данных в Arduino
// Разработчик: Бадло Сергей Григорьевич

// H-page: http://raxp2.blogspot.com

program getcolortoarduino;

uses Messages, Classes, Windows, sysutils;

type
 TF = class
 protected
  FWnd: HWnd;
  constructor Create;
  destructor Destroy;
  // подписываемся на завершение работы и перезагрузку
  procedure WndProcc(var Msgs:TMessage); message WM_QUERYENDSESSION;
end;

var
  FWnd: HWnd;
  Com: THandle = 0;
  DCB: TDCB;
  t: tf;
  Msg: tmsg;
  fl: textfile;
  SL: TStringList;
  bd: string;

  FTimer,KEY: integer;

type
  TFNTimeCallBack = procedure(uTimerID, uMessage: UINT;
    dwUser, dw1, dw2: DWORD) stdcall;

 function timeSetEvent(uDelay, uResolution: UINT;
  lpFunction: TFNTimeCallBack; dwUser: DWORD; uFlags: UINT): uint; stdcall; external 'winmm.dll'
 function timeKillEvent(uTimerID: UINT): uint; stdcall; external 'winmm.dll'

procedure WriteComm(t: string);
var buf: array [1..255] of byte;
    TX_Count : cardinal;
    i: integer;
begin
 for i:= 1 to length(t) do
  buf[i]:= Ord(t[i]);

 WriteFile(Com, buf, length(t), TX_Count, nil);
end;

procedure setdcb;
begin
 GetCommState(Com, DCB);

 DCB.BaudRate:= CBR_115200;
 DCB.Parity  := NOPARITY;
 DCB.ByteSize:= 8;
 DCB.StopBits:= OneStopBit;
 DCB.EvtChar := chr(13);
 // устанавливаем DCB
 SetCommState(Com, DCB)
end;

// СКЕЛЕТ ===============================================
procedure RemoteTimeProc(uID,uMsg:UINT;dwUser,dw1,dw2:DWORD); stdcall;
var t: tpoint;
    r,g,b: integer;
    DC: HDC;
    Color: Cardinal;
begin
 getcursorpos(t);
 DC := GetDC(0);
 Color := GetPixel(DC, t.x, t.y);
 //
 r:= GetRValue(color);
 g:= GetGValue(color);
 b:= GetBValue(color);

 WriteComm(inttostr(r)); sleep(5);
 WriteComm(inttostr(256+g)); sleep(5);
 WriteComm(inttostr(512+b)); sleep(5);
 DeleteDC(DC);
end;

// отслеживаем перезагрузку и выключение ПК
procedure tf.WndProcc(var Msgs: TMessage);
begin
 case Msgs.Msg of
  WM_QUERYENDSESSION: begin
                       t.Destroy;
                       Msgs.Result:= 1
                      end
 end
end;

constructor TF.Create;
begin
 inherited Create;
 FWnd:= AllocateHWnd(WndProcc);
 FTimer:= timeSetEvent(150,0, @RemoteTimeProc, 0, 1)
end;

destructor TF.Destroy;
begin
 inherited;
 timeKillEvent(FTimer);
 deAllocateHWnd(fWnd);
 // закрываем порт
 CloseHandle(Com);

 SL.Free;
end;


begin
 // подгружаем параметры настройки
 bd:= ExtractFilePath(paramstr(0))+ 'settings.ini';
 if not fileexists(bd) then begin
  AssignFile(fl, bd);
  ReWrite(fl);
  writeln(fl,
            'PORT=COM1'#13#10+
          'Разработчик=Бадло Сергей Григорьевич'#13#10+
          'H-page=http://raxp.radioliga.com'
           );
  CloseFile(fl)
 end;
 SL:= TStringList.Create;
 SL.LoadFromFile(bd);

 // восстанавливаем настройки-
 Com:= CreateFile(
 pchar('\\.\'+uppercase(SL.Values['PORT'])),
                GENERIC_READ or GENERIC_WRITE,
                0,
                nil,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                0);

  if Com < 0 then begin
   messagebox(0,
              'COM порт занят или не существует.',
              'Выберите другой...',
              MB_ICONERROR);
   exit
  end;
  // задаем настройки RS-232 (стоп-бит, четность, скорость)
  setdcb;

 t:= tf.Create;

 try while GetMessage(Msg, 0, 0, 0) do begin
  TranslateMessage(Msg);
  DispatchMessage(Msg)
 end finally t.destroy end
// END СКЕЛЕТ ============
end.
Видеотест-1 (разных светодиодов у меня не было, на 3-х красных):


Видеотест-2 (на куске реальной RGB-ленты):


p.s.: под NET можете использовать обертку SerialPort. Ее методы достаточно обсосаны в MSDN.

Скачать сабж

Комментариев нет:

Отправить комментарий

В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.