投稿者「mars」のアーカイブ

ISS撮影挑戦(失敗)

国際宇宙ステーション(ISS)は、日本、米国、ロシア、カナダ、欧州の15カ国が協力して建設した、地上約400km上空にある人類史上最大の有人実験施設。その大きさは約108.5m×72.8mとほぼサッカー場ほどの大きさとなり、質量は約420トンにもなる。

ArduinoでEFレンズを制御する実験(2)

注文していたマクロアダプターが追跡情報の更新がないまま届きました。13/21/31mmの3個がセットとなっています。左側がアダプター、右側がZWOのCCDカメラで、色が良く似ています。

 本来、レンズとカメラボディの間に取り付けるアダプターなので、CCDカメラと接続するには工夫が必要です。13mmと21mmのアダプターを使って、CCDカメラと接続してみたところ、望遠端付近ではピントが出ました。広角側ではピントが出ませんでした。

強度などの問題があるかもしれませんが、3DプリンターでマクロアダプターとCCDカメラを接続するアダプターを作ってみました。2つのパーツで構成し、M3ネジで接続しています。CCDカメラとは3Dプリンターで作成したM42,0.75Pのネジで接続しています。CADソフトはホビー用途であれば無料で利用できるFusion360を使っています。

CCDカメラ側のパーツを取り付けるまえに、マクロアダプターの接点にリード線を半田づけして引き出し、レンズの制御と電源を供給するコネクタと接続します。マクロアダプターとこのパーツを固定するために、6mmのネジで締め付けています。何回か試作を繰り返した結果、青のフィラメントが無くなったので、手持ちの白にチェンジ!

EFレンズに接続した様子と、フォーカスのハンド・コントローラ。
ソースコードの最新版は、こちら

組み立ててはみたものの、残念ながら動作が不安定です。今のところ、EF-S 18-55mmと同ISは、それなりに制御できますが、EF 35-80mmとEF70-200 1:28Lはまったく動きません。ロジックアナライザーでデコードしたSPIのデータ観測すると、どのレンズでも同じように見えるのですが、、、
 本来、SPIの信号はボード内の短い距離の伝送用なので、コントローラの線が長すぎるのかもしれません。今後、配線の短縮またはバッファーやレベル変換を追加して、改善するかどうか確認したいと思っています。
(追記)ケーブルを約70cmから約25cmに変更したら、手持ちのレンズ全てが一応動くようになりました。ただし、まだ不安定な面もあるので、さらに短くする必要がありそうです。SPIの信号を生成するマイクロプロセッサー(Arduino nano)を、レンズに近接して配置できるよう取り付け方法の変更が必要かもしれません。この場合、ピントの調整を操作するSW付きのロータリーエンコーダの部分だけ、ケーブルで接続した別の箱に格納した方が良さそうです。

ArduinoでEFレンズを制御する実験

ArduinoでEFレンズを制御する実験のメモです。カメラレンズで星雲などを撮影しようとすると、手動でピントを合わせる必要がありますが、微妙な操作が必要でなかなかピントを合わせるのに苦労します。そこで、ステッピングモータでピントを調整しようと、3Dプリンターでこんなものを作ってみました。そうこうしているうちに、CANONのEFレンズをArduinoで制御できるらしいとの情報を得て、自分でも実験してみることに。

レンズの制御信号を取り出せるマウントアダプターを注文したのですが、到着にはしばらく時間がかかりそうなので、手持ちのカメラレンズ(EF-S 18-55mm USM)から信号を取り出すために、7本のリード線を半田づけします。(あくまでも自己責任で、、)
この作業には、こちらのリンクが非常に参考になりました。大型のレンズでは、レンズを駆動するモータ用として6Vの別電源が必要のようですが、実験に使ったレンズではロジック回路と同じ5Vで問題なく駆動できています。

主な材料
秋月の2色LED付きロータリーエンコーダ
OLED  128×64 I2C ディスプレイ
Arduino NANO

回路図

実験に使ったArduno NANOのスケッチ
不用な変数などが残っていますが、とりあえず公開します。(実用化には、まだまだ課題が残っています。)制御の対象はフォーカスと絞りの2種類です。ロータリーエンコーダのSWの長押しで、赤と緑のLEDがトグルで点灯します。赤の点灯中にSWを押してフォーカスを変更し、緑のLED点灯中にSWを押すと絞り値を変更できます。

/*
   ロータリーエンコーダの参照コード
   https://jumbleat.com/2016/12/17/encoder_1/
*/
/*
   EFレンズの制御関連参考リンク
   ASCOM EF Lens Controller
   http://www.indilib.org/media/kunena/attachments/3728/ascom_efEN.pdf

   EFレンズから信号線の引き出の参照資料
   How to Move Canon EF Lenses Yosuke Bando
   http://web.media.mit.edu/~bandy/invariant/move_lens.pdf

   Canon EFレンズのArduino制御
   http://otobs.org/hiki/?EOS_model
   Technical aspects of the Canon EF lens mount
   http://www.eflens.com/lens_articles/ef_lens_mount.html
*/
#include <SPI.h>
#include <EEPROM.h>
#include <math.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
//Adafruit_SSD1306 display(1);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define RUN

#define LED_SW   7
#define ENC_A  2
#define ENC_B  3
#define LED_Red 14
#define LED_Green 15

volatile byte pos;
volatile int  enc_count;
boolean    sw = false;
int mode = 0;
int mode_counter[2];
int focuserPosition, targetPos, apValue, offset, apAddr, x, y;
String targetStr, apStr, gStr;
boolean IsMoving, IsFirstConnect;
char inStr[6];

void InitLens()
{
  SPI.transfer(0x0A);
  delay(30);
  SPI.transfer(0x00);
  delay(30);
  SPI.transfer(0x0A);
  delay(30);
  SPI.transfer(0x00);
  delay(30);
}

int ENC_COUNT(int incoming) {
  static int enc_old = enc_count;
  int val_change = enc_count - enc_old;

  if (val_change != 0)
  {
    incoming += val_change;
    enc_old   = enc_count;
  }
  return incoming;
}

void ENC_READ() {
  byte cur = (!digitalRead(ENC_B) << 1) + !digitalRead(ENC_A);
  byte old = pos & B00000011;
  byte dir = (pos & B00110000) >> 4;

  if (cur == 3) cur = 2;
  else if (cur == 2) cur = 3;

  if (cur != old)
  {
    if (dir == 0)
    {
      if (cur == 1 || cur == 3) dir = cur;
    } else {
      if (cur == 0)
      {
        if (dir == 1 && old == 3) enc_count++;
        else if (dir == 3 && old == 1) enc_count--;
        dir = 0;
      }
    }
    pos = (dir << 4) + (old << 2) + cur;
  }
}

void setup() {
  digitalWrite(13, LOW); // SPI Clock PIN
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  pinMode(LED_Red, OUTPUT);
  pinMode(LED_Green, OUTPUT);
  pinMode(LED_SW, INPUT_PULLUP);
//  pinMode(MIN, INPUT_PULLUP);
//  pinMode(MAX, INPUT_PULLUP);
  attachInterrupt(0, ENC_READ, CHANGE);
  attachInterrupt(1, ENC_READ, CHANGE);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();

  // テキストサイズを設定
  display.setTextSize(3);
  // テキスト色を設定
  display.setTextColor(WHITE);
  display.setCursor(0, 10);
  display.println("EF-LensFocuser");
  display.display();
  delay(1000);
  mode = 0;
  apAddr = 0;
  focuserPosition = 5000;
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV128);
  SPI.setDataMode(SPI_MODE3);
  digitalWrite(12, HIGH);
  InitLens();
  digitalWrite(LED_Red, HIGH);
  digitalWrite(LED_Green, LOW);

  apValue=EEPROM.read(apAddr);
  Serial.begin(9600);
  Serial.println(apValue);
  // nothing to do inside the setup
}

void loop() {
  int sw_count;
  short counter_now;
  sw_count = 0;
  while (digitalRead(LED_SW) == LOW) {
    sw_count++;
    if (sw_count > 50) {
      if (mode == 1) {    // 新モードはフォーカス制御
        digitalWrite(LED_Red, HIGH);
        digitalWrite(LED_Green, LOW);
      } else {            // 新モードは絞り制御
        digitalWrite(LED_Green, HIGH);
        digitalWrite(LED_Red, LOW);
      }
    }
    delay(10);
  }
  delay(100);
  if (sw_count > 50) {
    if (mode == 0) {
      mode = 1;   // 絞りモード
      digitalWrite(LED_Green, HIGH);
      digitalWrite(LED_Red, LOW);
    } else {
      mode = 0;   // フォーカスモード
      digitalWrite(LED_Red, HIGH);
      digitalWrite(LED_Green, LOW);
    }
  }
  if (sw_count != 0 && (sw_count < 50) ) {
    if  (mode == 0 ) { // Send command to LENS フォーカス
      targetPos =  mode_counter[mode] ;
      offset = mode_counter[mode] ;
      x = highByte(offset);
      y = lowByte(offset);
      InitLens();
      IsMoving = true;
      Serial.print(offset); Serial.print(",");
      Serial.print(x); Serial.print(",");
      Serial.println(y);
      SPI.transfer(68);       delay(30);
      SPI.transfer(x);        delay(30);
      SPI.transfer(y);        delay(30);
      SPI.transfer(0);        delay(100);
      IsMoving = false;
      focuserPosition = targetPos;
    } else {              // 絞り
      apValue = mode_counter[mode] % 20;
      if (apValue != EEPROM.read(apAddr))
      {
        InitLens();
        Serial.println("AP");
        SPI.transfer(0x07);          delay(10);
        SPI.transfer(0x13);          delay(10);
        SPI.transfer((apValue - EEPROM.read(apAddr)) * 3);
        delay(100);
        SPI.transfer(0x08);          delay(10);
        SPI.transfer(0x00);          delay(10);
        EEPROM.write(apAddr, apValue);
      }
    }
  }

  counter_now = ENC_COUNT(mode_counter[mode]);
  if (mode_counter[mode] != counter_now)
  {
    mode_counter[mode] = counter_now;
  }
  disp_update();
}

void disp_update() {
  display.clearDisplay();
  display.setCursor(0, 10);
  display.print(" F:");
  display.println( mode_counter[0] );
  display.print(" A:");
  display.println(mode_counter[1]);
  display.display();
}

fl2kでgps-sdr-sim

osmo-fl2kで生成したGPS信号の周波数補正

osmo-fl2kには、fl2k_testコマンドが用意されていて、実行すると10秒間隔でPPMエラーを表示します。

$ fl2k_test
Kernel mass storage driver is attached, detaching driver. This may take more than 10 seconds!
Reporting PPM error measurement every 10 seconds...
Press ^C after a few minutes.
real sample rate: 99647567 current PPM: -3524 cumulative PPM: -3524
real sample rate: 99909730 current PPM: -903 cumulative PPM: -2187
real sample rate: 99976679 current PPM: -233 cumulative PPM: -1527
real sample rate: 99994151 current PPM: -58 cumulative PPM: -1156
real sample rate: 100001137 current PPM: 11 cumulative PPM: -921
real sample rate: 100003392 current PPM: 34 cumulative PPM: -761
real sample rate: 100007238 current PPM: 72 cumulative PPM: -641


これは、USB-VGAデバイスを接続した直後の実行例ですが、かなり変動しています。しばらくして試すと、次のように安定してきます。(ただし、CPUの負荷による影響もあるようなので、テスト中は、負荷の重いプロセスの起動は避けたほうがよさそう、、、)

$ fl2k_test
Reporting PPM error measurement every 10 seconds...
Press ^C after a few minutes.
real sample rate: 100006806 current PPM: 68 cumulative PPM: 68
real sample rate: 100006831 current PPM: 68 cumulative PPM: 68
real sample rate: 100007186 current PPM: 72 cumulative PPM: 69
real sample rate: 100005995 current PPM: 60 cumulative PPM: 67

この値を(平均値など)、フローグラフの変数 ID:ppmへセットし、ID:out_rateに表示される(この例では138.01e6)を fl2k_fileで送信する際のサンプルング周波数にセットしたところ、GSP受信機でfixできました。

$fl2k_file -s 138.01e6  ファイル名
安定度などの問題があるかもしれませんが、GPS-SDR-SIMとUSB-VGAアダプタの組み合わせで、超安価なGPS信号シミュレータが実現できそうです。

実行中の動画

osmo-fl2kの実験メモ

2018年04月24日21:58カテゴリSDR

RTL-SDR.COMで紹介されていたosmo-fl2kを試してみました。

基本波としてHF~約157MHzまでの周波数を生成することができ、その高調波でさらに1.7GHz程度まで利用できるらしい、、、
osmo-fl2kのWikiはこちら:SDR (Software Defined Radio) osmo-fl2k
https://osmocom.org/projects/osmo-fl2k/wiki

Patech USB3.0 to VGA変換アダプター ビデオグラフィックカード。アマゾンで1780円で購入。

VGAコネクタの①端子へアンテナ用の短いリード線を接続

実験環境
VirtualBOX内のUbuntu16.04で実験(ホストWindows10)
仮想マシンのUSBデバイスとして接続すると、不明なデバイスDevice 002: ID 1d5c:2000 として検出される。

$ lsusb
Bus 002 Device 002: ID 1d5c:2000

次の手順でosmo-fl2kをインストール

$ git clone git://git.osmocom.org/osmo-fl2k
$ cd osmo-fl2k/
$ mkdir build
$ cd build
$ cmake .. -DINSTALL_UDEV_RULES=ON
$ make
$ sudo make install

デバイス名の確認

$ pacmd list-sources | grep name | grep monitor
name:

この時に、permission errorなどとなったら、インストールしたユーザのディレクトリのパーミッションをチェックし、必要に応じて次を実行(ユーザ名 user1の場合)
 $sudo chown  -R user1:user1 /home/user1

サンプルのWBFM信号の生成に必要なpv, soxをインストール

$ sudo apt install pv sox

WBFM信号生成のテスト;
例えばRhymbox(ミュージックプレイヤー)などで音楽を再生すると、その音声で変調された信号が生成される。簡単なshell scriptを作る

$ cat start.sh #!/bin/bashpacat -r -d alsa_output.pci-0000_00_05.0.analog-stereo.monitor | \pv -B 256k | \sox -t raw -r 44100 -e signed-integer -L -b 16 -c 2 - -c 1 -e signed-integer -b 16 -t raw - \biquad 4.76963 -2.98129 0 1 0.78833 0 sinc -15k loudness 5 | \fl2k_fm - -s 130e6 -c 35e6 -i 44100

$ ./start.sh./start.sh Samplerate: 130.00 MHzCarrier: 35.00 MHzFrequencies: 95.00 MHz, 165.00 MHz

この例ではキャリアー周波数を -c 35e6(=35MHz)としているが、試してみると多数の他の周波数でも受信できてしまう。スプリアスが多いようなので実験には注意が必要。(アンテナは5cm程度のリード線で十分)
スペクトラムを観測した動画 (youtube)

VirtualBOXの中で実験している影響のせいか、他のプロセスの起動や操作を行うと、buffer underrunが
発生することがあり、buffer underrunが発生すると信号の生成が停止するようだ。

 wavファイルがあれば、次のように実行するこもできる。


$ fl2k_fm strings.wavSamplerate:     100.00 MHzCarrier:        97.00 MHzFrequencies:    3.00 MHz, 197.00 MHz


他に用意されているコマンドのhelpを表示

$ fl2k_test -helpfl2k_test, clock accuracy test for FL2K VGA dongles,also outputs a square wave at fs/2
Usage: [-d device_index (default: 0)] [-s samplerate (default: 100 MS/s)]
$ fl2k_fm --fl2k_fm, an FM modulator for FL2K VGA dongles
Usage: [-d device index (default: 0)] [-c carrier frequency (default: 9.7 MHz)] [-f FM deviation (default: 75000 Hz, WBFM)] [-i input audio sample rate (default: 44100 Hz for mono FM)] [-s samplerate in Hz (default: 100 MS/s)] filename (use '-' to read from stdin)
$ fl2k_file --fl2k_file, a sample player for FL2K VGA dongles
Usage: [-d device_index (default: 0)] [-r repeat file (default: 1)] [-s samplerate (default: 100 MS/s)] filename (use '-' to read from stdin)


fl2k_file コマンドを利用してGPS信号なども生成できるようなので試してみたが、周波数がずれてしまう。 (https://youtu.be/Xh6Xl3mB0c8)

無線でAirGapを超える

無線でAirGapを超える実験(その1 Raspberry Pi)

イスラエスのベン・グリオン大学の研究チームが様々な手段でAirGapを超える実験を発表していて、これまでに、電波、音波、熱などの例が紹介されています。
かねてから、ネタの一つとして無線で実験してみたいと思っていたのですが、最近になってようやく実験を開始しました。

最初は、GPIOを備え、敷居が低そうなRaspberry Piで実験を試みました。RaspberrPiでは、FM変調で音楽などを流す試みが発表されています。手始めに、これを実験してみましたが、githubからダウンロードして、簡単に再現することができました。このソフトはあらかじめ用意したwavファイルの音楽を流したり、マイクロフォンやLINE入力からのオーディオ信号をFM変調で送信できるようになっています。調べてみると、RaspberryPiには3個のGPCLKポートが備わっていて、このポートから、内蔵のクロックを基にしてプログラマブルに周波数を設定可能なハードウェアとなっています。

GPCLK General Purpose CLock https://pinout.xyz/pinout/gpclk

GPIO端子に出力される信号は、基本的には矩形波なので、RF信号の振幅が変化するような変調は、外部のハードウェアなしには実装できません。FM電波とした理由も、そこにあるものと思われます。プログラマブルにクロックを設定できる仕組みを利用し、GPCLK端子へ出力する周波数をうまく制御してFM変調を実現しているようです。クロック生成の詳細は、この記事などが参考になります

RaspberryPiのFM送信機のソフトを利用してデジタルデータを送信するには、データからQPSKなどの信号を音声データとして生成し、wavファイルに落として変調用のファイルとすれば、比較的簡単に実現できそうな気がします(APSK変調)。もっと安直な方法として、GPCLK端子から、あるの周波数の信号を断続して出力すれば、搬送波の断続による原始的なCW変調が実現できそうなので、こちらの方式で実験をしてみました。クロックの生成で、参考にた記事は、こちらの Miscellaneous related code セクションにあるMinimal Clock Accessのコードです。

クロックの制御部分だけをC言語で記述し、残りの部分は、これまた安直にPHPで記述しています。

無線でAirGapを超える

(その2 Windows10 RS-232CポートのON/OFF)

PCには、Raspberry Piと違ってGPIOのような物理的にアクセス可能なデバイスは用意されていない。そこで、最近のPCの標準装備からはほとんど姿を消したものの、RS232C(特に法人向けのPCには、数年前まで、標準装備されていたように記憶していますが、、、?)ポートには、ハードウェアによるフロー制御に利用可能なRTS端子があることを思い出した。

この端子を、高速でON/OFFすることで、電波として放射できないか、実験をしてみた。RTS端子をオシロで観測した様子。46KHz程度で、周期がかなり変動している。

動画の左側は、Raspberry Pi3のGPIO端子をソフトウエアでOn/Offした時のスペクトラム波形。右側がPCのRS-232C端子をOn/Offした際の、スペクトラム波形。WindowsではOn/Off周期の変動が大きく、スペクトラムが大きく広がっている。

プログラム例


#include <windows.h>
#include<stdio.h>
int main()
{
  HANDLE hComm;
  int Status;
  int n;
  unsigned clockDivisor = 0;
  hComm = CreateFile("COM1",                //port name
                      GENERIC_READ | GENERIC_WRITE, //Read/Write
                      0,                            // No Sharing
                      NULL,                         // No Security
                      OPEN_EXISTING,// Open existing port only
                      0,            // Non Overlapped I/O
                      NULL);        // Null for Comm Devices
  if (hComm == INVALID_HANDLE_VALUE)
      printf("Error in opening serial port");
  else {
      printf("opening serial port successful");
      for(n=0;n<100000;n++){                         // ここのループでRF信号を放射
     	Status = EscapeCommFunction(hComm, SETRTS);  // RTS ON
      	Status = EscapeCommFunction(hComm, CLRRTS);  // RTS OFF
      }
      printf("Done...\n");
      uSleep(5000000);
      }
  CloseHandle(hComm);//Closing the Serial Port
  return 0;
}

LANのパケット監視

WannaCryは、LANの中ではsmb(445/TCP)で感染するということなので、簡易な監視のツールを作ってみました。
ミラーしたセグメントのパケットをtcpdumpでキャプチャし、PHPのスクリプトで観測したパケットの数を可視化します。
使い方は、次のような感じ、、、

#tcpdump -ntttti eth0 port 445 | visualize.php

visualize.php は、定期的にhtmlファイルを生成ます
利用者はブラウザでhtmlファイルを閲覧します。
htmlファイルは (例えば10秒毎に)自身をrefreshします。

表示される、画像の1マスは、ノード(PCやサーバ)間のパケット数を色で表現しています。
通信がまったく観測されない場合は濃い青色で観測されたパケット数が多いと黄色~赤に変化します。
また、マスの中の1文字は、観測したパケットの数に応じて、0-9,A-Z…..と表示しています。

#!/usr/bin/php
<?php
/*
        Usage:
        tcpdump -nttttr xxxxx.pcap port 445 | ./p445.php
*/

$MIN_TH = 5;
$fp=fopen("php://stdin","r");
for($i=1;$i<255;$i++){
        for($j=1;$j<255;$j++){
                $buf[$i][$j]=0;
        }
}
$n=0;$prev_min=0;
while($in=fgets($fp)){
        $t=explode(" ",$in);
        $date=$t[0]." ".$t[1];
        $tmp =explode(":",$t[1]);       // hh:mm:ss.xxxxx
        $min =$tmp[1];                  // min
        if(isset($t[3])){
                $s=explode(".",$t[3]);
                if(isset($s[3])) {
                        $ip1=$s[3];
                        $d=explode(".",$t[5]);
                        $ip2=$d[3];
//                      echo "$ip1,$ip2\n";
                        $buf[$ip1][$ip2]++;
                }
        }

        $n++;
        if(($n % 2000)==0 || ($min - $prev_min) > $MIN_TH) {
                MkHTML($buf,$date);
                $prev_min = $min;
        }
}

fclose($fp);
MkHTML($buf,$date);
echo "Finished!!!\n";


function MkHTML($buf,$date){

        $date="<H3>$date</H3>";
        $header="<!DOCTYPE HTML><HTML><HEAD><meta http-equiv=\"refresh\" content=\"10\"></HEAD><BODY>";
        $msg="<H3>$date</H3>\n<TABLE>\n";
        for($i=1;$i<255;$i++){
                $sum=0;
                for($j=1;$j<255;$j++){
                        $sum+=$buf[$j][$i];
                }
                $iSum[$i]=$sum;
//              echo "$i,$iSum[$i]\n";
        }

        for($i=1;$i<255;$i++){
                $tmp="<TR><TD>$i</TD>";$sum=0;
                for($j=1;$j<255;$j++){
                        $var=$buf[$i][$j];
                        $pt=".";
                        if($var<63) {
                                if($var<10) {
                                        $pt=$var;
                                } else {
                                        $pt=chr(ord('A')+$var-10);
                                }
                        }
                        $sum+=$var;
                        $var=20*log($var+1);
                        $color=set_color($var);
                        if($iSum[$j]!=0) $tmp.= "<TD BGCOLOR=$color>10.8.0.$j\">$pt</TD>";

                }
                if($sum !=0 ) {
                        $msg.=$tmp;
                        $msg.="</TR>\n";
                }
        }

        $msg.="</TABLE>\n<H3>$date</H3></BODY></HTML>\n";
        $fw=fopen("/var/www/html/cross.html","w");
        fputs($fw,$header);
        fputs($fw,$msg);
        fclose($fw);
}


function set_color($x){
   if ($x<64) {
       $r=0; $g= $x*4 ; $b=255;
   } else {
       if ($x<128){
           $r=4*( $x -64 );$g=255;$b=255-$r;
       } else {
           if ($x<192){
               $b=4*( $x - 128 );$r=255;$g=255-$b;
           } else {
               $r=255;$g=0;$b=255-4*( $x -192);
           }
       }
   }
   return "#".sprintf("%02x%02x%02x",$r,$g,$b);
}
?>