アナログ出力(PWM)

 ディジタル出力は H と L の2種類です。アナログ出力はこの中間の電圧を出力する機能ですが、中間の電圧ではなく、出力する時間を制御する PWM 方式を利用します。

  1. PWM方式

     出力電圧の調整法にPWM:(Pulse Width Modulation) があります。この方法は、一定区間の中で通電する割合(時間幅)を変更して平均的な電力を調整する方法です。下図は、25%、50%、75% の電力制御の説明図です。横方向が時間で、立ち上がっている間スイッチをオンにして通電します。

    この通電時間の制御により、平均的な出力電圧を変化させることができます。

  2. アナログ出力関数

     デジタル出力端子の一部(D3,D5,D6,D9,D10,D11)がアナログ出力(PWM)可能な端子です。この端子に次の命令を実行します。端子番号 は利用するアナログ出力端子、bright は アナログの強さ(0-255)です。端子のモードは出力に設定しておきます。
     pinMode(端子番号, OUTPUT);
     analogWrite(端子番号, bright); 
     すると、指定した出力端子には、bright で指定した割合でPWM 出力されます。
    PWM の繰り返し周期は端子により異なります。5,6 のみ1KHzで、残りの 3,9,10,11 は 500Hz となっています。

  3. 白色LEDの光量とPWM発光

     最初LEDが発明されたときは赤色のLEDのみでした。その後、緑、近年には青色の発光ダイオードが開発され、赤、青、緑、の色の強さを調節することで、白色の発光が可能になりました。
     赤色LEDの場合、点灯に必要な最低電圧は1.5V程度でしたが、白色LEDの場合 3.1V 程度が必要です。ここでは、この白色発光ダイオード(OSPW5111A )を用いて、PWM で発光強度を変化させる手法を紹介します。
     明るさの単位はルックスで、1平方メートルに平均1ルクスの明るさとなる光量(光束)を 1ル-メン:lm と呼びます。ここで用いる白色LEDは20lm(ルーメン)程度の光量です。これを20個並べると 400lm となり白熱電球40W相当の光量となります。

  4. 白色LEDの調光プログラム

     プログラムでは 9番のアナログ端子を利用し、5 づつ大きくします。
    20または255に達したら、変化の符号を反転します。
    // white LED
    int wled=9; 
    int bright = 0;  
    int fade = 5;    //変化量 
    
    void setup()  { 
      bright=20;
      pinMode(wled, OUTPUT);//LED.V
      Serial.begin(9600);
    } 
    
    void loop()  { 
      
      analogWrite(wled, bright); 
      bright = bright + fade;
      // 20以下になったときの処理
     if (bright<= 20 ) {
        fade = -fade ; 
      }
      // 255以上になったときの処理
      if (bright >= 255) {
        fade = -fade ; 
      }
      Serial.println(bright);
      // 100mS待つ  
      delay(100);                            
    }

     プログラムを入力後UNOボードに送りこみます。明るさが変化すれば成功です。LEDは光が点光源なので、ティシューや発泡スチロールで覆うと見やすくなります。

  5. フルカラーLED

     フルカラーLED は 赤、緑、青、の3色のLEDを封じ込んだ 「フルカラー発光」可能なLEDです。各色をPWM発光することで、「フルカラー発光」が可能です。ここでの「フルカラー」は多くの色(物理的には 256*256*256))の異なる状態の発光ができる意味で、「人が見ることがすべての色」の意味ではありません。たとえば、純度の高い黄色や緑色の発光は不可能です。
     色は 波長で定まり 450nm(ナノメートル)あたりが青色、520nmあたりが緑色、660nm あたりが赤色になります。発光する波長は,半導体に添加する不純物(Ga、In、Al、Pなど)で変化します。
     ここで紹介するフルカラーLED(OSTA5131A)は以下のような特性です。緑、青の順方向降下電圧は 3.6V なので5Vの電源が必要です。

    各色の波長 赤:635nm  緑:525nm   青:470nm
    各色の輝度 赤:2000mcd  緑:7000mcd  青:2500mcd
    順方向電圧 赤:2.0V  緑:3.6V 青:3.6V
    標準電流 20mA

    端子は、下の状態で、上から 赤(アノード)、GND(共通カソード)、青、緑、で、GNDが 一番長い端子です。

  6. フルカラーLED接続回路

     一番長い端子を左から2番にして、ブレッドボードに挿入します。LEDの一番長い端子を UNO のGND 端子(下側)に接続します。LEDの赤端子(一番左)を抵抗 330Ωを通して UNO のD11端子に接続します。どうように、青を D10、緑をD9、に接続します。ここでは、すべて同じ抵抗の値にしましたが、各色同じ強さの電流にした時、無色になるよう、抵抗を調整すると良いでしょう。

       フルカラーLED発光回路

  7. 色モデルと色発光

     色の変化をプログラムします。色の変化には RGB モデルでなく、HSV(Hue:色、Saturation:彩度、Value:明るさ) モデルを利用します。H は 0..360度、S、V は 0.0-1.0 まで変化します。S は半径方向、V は上下方向の変化になります。

     画像は WikiPediaより

    メソッド hsv2rgb(int h,float s,float v, int *rp, int *gp, int *bp) は HSV の値から、RGB の値を返します。プログラムは S=V=1.0 で h の値を 0..360 まで変化させます。
    //FullColor LED 
    //色の変化
    
    int h;
    //int i,vi;
    //int p1,p2,p3;
    int r,g,b;
    
    float s,v;
    int rLED=11;
    int bLED=10;
    int gLED=9;
    
    void setup(){
     Serial.begin(9600);
     h=0;
     s=1.0;
     v=1.0;
    }
    
    void loop(){
      //hsvからrgbへの変換  
      hsv2rgb(h,s,v,&r,&g,&b);
    /*
      Serial.print(" ");
      Serial.print(g,HEX);
      Serial.print(" ");
      Serial.println(b,HEX);
    */        
      analogWrite(rLED,r);
      analogWrite(gLED,g);
      analogWrite(bLED,b);
      
      //s=s-0.02;
      //if(s<0.0) s=1.0;  
      h=h+2;
      if(h>=360) h=0;
      delay(100);   
    }
    
    void hsv2rgb(int h,float s,float v, int *rp, int *gp, int *bp){
      int i,r,g,b,vi;
      int p1,p2,p3;
      float f;
      i = (int)(h/60.0);
      f = h/60.0 -i; 
      p1=(int)(v*(1.0-s)*255.0);
      p2=(int)(v*(1.0-s*f)*255.0);
      p3=(int)(v*(1.0-s*(1.0-f))*255.0);
      vi=(int)(v*255.0);        
      if(i==0) {r = vi ;g = p3 ;b = p1;}
      if(i==1) {r = p2 ;g = vi ;b = p1;}
      if(i==2) {r = p1 ;g = vi;b = p3;}
      if(i==3) {r = p1 ;g = p2 ;b = vi;}
      if(i==4) {r = p3 ;g = p1 ;b = vi;}
      if(i==5) {r = vi ;g = p1 ;b = p2;}
      *rp=r;
      *gp=g;
      *bp=b;
    }

  8. 正弦波による色発光

     sin() 命令を用いて、正弦波の波形をあらかじめ sinw[] に記録しておきます。 R,B,G 3色の値を120度ずつ、ずらしてsinw[]の配列を読み出して設定します。 実行時に sin() 命令を実行すると時間がかかりますから、あらかじめ計算しておくことで、実行時に処理を効率的に行うことができます。

    //sinw[]の配列に、sinの値を2度単位に0..255の値の範囲に変換して記録
    //R,B,G 3色の値を120度ずつ、ずらしてsinw[]の配列から設定する。
    
    int rLED=11;
    int bLED=10;
    int gLED=9;
    
    int sinw[180];
    int brightR = 0;
    int brightG = 0;
    int brightB = 0;
    int ang;
    long t1;
    
    void setup()  {
       Serial.begin(9600);
    
      //2度単位のsinのテーブルを作成
      t1=millis();
      for(ang=0;ang<180;ang=ang+1){
        sinw[ang]=127*(sin(ang * 3.14/90)+1);
      }//time 30mS
      //Serial.print("time:");
      //Serial.println(millis()-t1);
      brightR=0;
      brightG=60;
      brightB=120;
    
      pinMode(6, OUTPUT);//LED 接続
      pinMode(5, OUTPUT);//
      pinMode(3, OUTPUT);//
    }
    
    void loop()  {
     
      analogWrite(rLED, sinw[brightR]);
      analogWrite(gLED, sinw[brightG]);
      analogWrite(bLED, sinw[brightB]);
     
      //Serial.print(sinw[brightR]);
      //Serial.print(",");
      //Serial.print(sinw[brightG]);
      //Serial.print(",");
      //Serial.println(sinw[brightB]);
     
      //R,G,,Bの表を参照する番号を更新する
      brightB = update(brightB);
      brightG = update(brightG );
      brightR = update(brightR) ; 
      delay(100);                           
    }
    
    //表をひく番号を進める
    int update(int bright){
      bright = bright+1;
      if(bright>180) bright=0;
      return bright;
    }

  9. 発展

    1. アナログ出力のDA変換モジュールはありませんか?

      MCP4922は12ビットのSPI接続のアナログ出力DA変換素子です。波形を利用した和音の合成などにも利用できます。

    2. PWMでDCモーターの回転速度を変更できますか?

       UNO の端子で直接モーターの制御はできませんが、トランジスタを利用した電力制御である程度の回転制御は可能です。
    3. PWMの周期は変更できませんか?

       タイマーの関連のレジスタの値を直接変更する方法はありますが、delay やタイマー関連の機能にも萍郷があります。

    4. メソッド hsv2rgb() の & は何ですか?

       h,s,v はメソッドに値を渡す役目ですが、r,g,b はメソッドから呼び出し側に値を返したいパラメータです。この場合、呼び出す側で & をつけてパラメータの番地を渡し、メソッド側では *rp などで、渡された番地に値を戻します。