PICマイコンで電子ホタルを作る【PIC16F1938】

PIC16F1938で電子ホタルを作る

電子ホタルとは?

電子ホタルは、LEDが蛍の光のようにじわじわと明るくなったり暗くなったりするプログラムのことを言います。街中にある高層ビルの上の方に、赤い灯りが徐々に明るくなり、そして徐々に暗くなるのは見たことがあると思います。あれを作ってみましょう。完成した様子↓


電子ホタルの原理(PWM)

「徐々にLEDの明るさを変える」なら、電圧を変えればいいと思うのが普通です。小学生の実験でも、電池の数を変えれば明るさが変わることはやりましたね。遠い昔の思い出です。

しかし、これをPICにやらせるのは難しいのです。そこで、人間の錯覚を利用してこれを実現します。人間は、短い期間に入ってきた光の量を明るさと認識します。もっと簡単な言葉でいえば光の明るさの平均=人間が認識する明るさとなります。下の図を見てください。

明るさが100の時、電圧は5Vとしましょう。赤い線は明るさが50で、電圧は2.5Vです。しかし、マイコンは基本HIGH(5V)とLOW(0V)しか出力できません。そこで、青い線のように高速でオンオフをして平均化してみましょう。「これではただの点滅に見えるのでは…?」と思うでしょう。しかし、高速でこれをすれば人間には赤い線(2.5V)の時の明るさと区別がつかないのです。

このように高速でスイッチングをして、あたかもその平均値の電圧が出力されているように見せる技術をPWMと言います。実際には超高速点滅です。点滅の周期のうち、HIGHになっている時間の割合をduty比(デューティ比)と呼んだりします。

色々な明るさの表現

まず、このようなpwmLED関数を作ってみましょう。


void pwmLED(int duty,int time){
  int i=0;
  int j=0;
  for(j=1;j<time;j++){
      for(i=1;i<100;i++){
          if(i<duty){
              RA1 = 1;    //LEDをオン
          }else{
              RA1 = 0;    //LEDをオフ
          }
          __delay_us(100);
      }
  }
}
   

まず、引数にdutyを取ります。これは先程説明したduty比で、明るさと思えばいいでしょう。

中のiを使ったfor文を見てみます。ここでは見てのとおり、iがdutyよりも小さいときはLEDをオンにしています。ここで__delay_us()関数が出てきます。これは、μ秒の遅延を発生させてくれる関数です。100を入れているので、一回のduty判定につき100μ秒=0.1ms待ってくれます。これをfor文で100回繰り返すので、一回の点滅周期は100×0.1ms=10msとなりますね。

これを囲むjのfor文は、その点滅セットを何回繰り返すか、つまりどのくらいの時間その明るさを維持するかを決めています。

では、前回書いたプログラムを以下のように書き換えてみましょう。


     // PIC16F1938 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config VCAPEN = OFF     // Voltage Regulator Capacitor Enable (All VCAP pin functionality is disabled)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#define _XTAL_FREQ 8000000

void PICinit(){
  OSCCON = 0b01110000;
  ANSELA = 0b00000000;
  ANSELB = 0b00000000;
  TRISA  = 0b00000000;
  TRISB  = 0b00000000;
  TRISC  = 0b00000000;
  PORTA  = 0b00000000;    //2進数で書いた場合
  PORTB  = 0x00;          //16進数で書いた場合
  PORTC  = 0;            //10進数で書いた場合
}

void pwmLED(int duty,int time){
    int i=0;
    int j=0;
    for(j=1;j<time;j++){
        for(i=1;i<100;i++){
            if(i<duty){
                RA1 = 1;    //LEDをオン
            }else{
                RA1 = 0;    //LEDをオフ
            }
            __delay_us(100);
        }
    }

}
int main(void){
  PICinit();      //PICを初期化
  while(1){
      pwmLED(10,100);
      pwmLED(30,100);
      pwmLED(50,100);
      pwmLED(80,100);
      pwmLED(100,100);
  }


  return 0;
}
   

main関数の前に先程作成したpwmLED関数を入れ、main関数に違う明るさのpwmLED関数を入れただけです。例えばpwmLED(10,100)であれば、明るさ10%で、100*10ms=1s維持するという意味になります。回路は前回と全く同じです。これをMPLAB X IDEで書き込んで、動かしてみてください。以下のようになります。

蛍のような明るさの変化

ここまでくれば簡単です。

  • 段々と明るくする
  • 段々と暗くする

を繰り返せば、蛍のように光らせることができます。段々と明るさを変えるには、for文でpwmLED関数を囲み、dutyにiを代入すればOKですね。以下のようにmain関数を書き換えましょう。


int main(void){
  PICinit();      //PICを初期化
  while(1){
      for(int i = 1;i<100;i++){     //段々明るく
          pwmLED(i,4);
      }
      for(int i = 100;i>1;i--){     //段々暗く
          pwmLED(i,2);
      }
  }


  return 0;
}
    

pwmLEDの第二引数の値が大きければ、より遅く変化しますね。明るくなる速度が遅く(4)、暗くなる速度が早い(2)方がなんとなく私の持つ蛍のイメージに近かったので、こうしました。ぜひこの値を変えて、好きな周期を見つけてみてください。

完成したプログラム

前述のmain関数以外を含めた全プログラムは以下のようになりました。これをコピペして書き込めば、すぐに電子ホタルが出来上がります!

PICへの書き込み方法はこちら PICにプログラムを書き込んでみよう


    // PIC16F1938 Configuration Bit Settings

    // 'C' source line config statements

    // CONFIG1
    #pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
    #pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
    #pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
    #pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
    #pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
    #pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
    #pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
    #pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
    #pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
    #pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

    // CONFIG2
    #pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
    #pragma config VCAPEN = OFF     // Voltage Regulator Capacitor Enable (All VCAP pin functionality is disabled)
    #pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
    #pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
    #pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
    #pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.

    #include <xc.h>
    #define _XTAL_FREQ 8000000

    void PICinit(){
      OSCCON = 0b01110000;
      ANSELA = 0b00000000;
      ANSELB = 0b00000000;
      TRISA  = 0b00000000;
      TRISB  = 0b00000000;
      TRISC  = 0b00000000;
      PORTA  = 0b00000000;    //2進数で書いた場合
      PORTB  = 0x00;          //16進数で書いた場合
      PORTC  = 0;            //10進数で書いた場合
    }

    void pwmLED(int duty,int time){
        int i=0;
        int j=0;
        for(j=1;j<time;j++){
            for(i=1;i<100;i++){
                if(i<duty){
                    RA1 = 1;    //LEDをオン
                }else{
                    RA1 = 0;    //LEDをオフ
                }
                __delay_us(100);
            }
        }

    }
    int main(void){
      PICinit();      //PICを初期化
      while(1){
          for(int i = 1;i<100;i++){     //段々明るく
              pwmLED(i,4);
          }
          for(int i = 100;i>1;i--){     //段々暗く
              pwmLED(i,2);
          }
      }


      return 0;
    }
  

実際に動いている動画

まとめ

いかがでしたでしょうか。意外と、単純な原理で明るさを滑らかに変えることができました。人間の目は意外と騙されやすいということも実感できる、面白い実験ではないでしょうか。

PWMは、殆どの製品に使われている技術です。例えば、モーターの制御もPWMを使っています。電池をいちいちつなぎ変えるわけにはいきませんからね。

これで、LEDを使ったプログラムはいったんおしまいです。後半には三原色が出せるLEDを使って電子蛍を作ってみようかと考えています。

次回は、スピーカーを使います。スピーカーも音を鳴らしますが、これもオンオフの繰り返しをするだけです。→

スピーカーの使い方