トワイライト(TWELITE)のPWMを使ってブザーを鳴らす

組み込みエンジニア

こんにちは、ENGかぴです。

ZigBeeモジュールであるトワイライト(TWELITE)はタイマ機能を備えておりPWM波形を生成することができます。PWMでLEDの明るさなどを調整したりできます。PWMで振動波形を模擬することでブザーを鳴らして動作を確かめました。

ただPWMを発生させてブザーを鳴らすだけでは面白くないのでPWMを発生されるタイミングは照度センサの値が一定値を超えた時(明るいと判断したとき)にしています。消費電流を抑えるためにスリープ機能を使用しています。

トワイライト(TWELITE)のスリープとウェイクアップを実装する

トワイライトを太陽光パネルで動作させたことやMWSTAGEの環境でソフト開発して無線通信したことなどについてまとめています。

トワイライト(TWELITE)のソフト開発と無線通信でできること

PWM機能を実装する

PWMの考え方
PWMの考え方

トワイライトにはタイマー機能がありPWM波形が生成できます。開発環境であるMWSTAGEのアクトのサンプルにはタイマー機能が紹介されておりTimerに関するAPIを発行するだけでPWM波形が出力されます。PWM波形の生成の仕組みについては下記記事に説明しています。

PICマイコン(PIC12F675)を使ってブザーを鳴らす方法

PWMで振動波形を模擬することでブザーを鳴らすことができます。音程はキャリア周波数で決まり周波数を高くするほど音程が高くなります。今回はTimer1を使用し1kHzをキャリア周波数としています。デューティ比は50%にしています。

Timer1.setup();
Timer1.begin(1000,false,false); //PWMを発生させたいときに第3引数をtrueにする

デューティ比は50%にするため512をセットしています。なにも設定しない場合1024が指定されるようになっています。

Timer1.change_duty( 512 );

ディーディ比を高くすると平均電圧が上がるため音量がわずかに大きくなりますが、変化したのかわからない程度の微々たるものです。

照度センサの値をアナログ入力する

照度センサーNJL7302-F5

トワイライトのAD変換機能を使って照度センサ(フォトトランジスタ)の値を取得します。

照度センサは新日本無線のNJL7302-F5を使用しています。今回はエミッタフォロワとして使用します。

トワイライトの電源電圧はDC3.0Vを基準にするとAD変換のMAX値はデータシートによるとVREFが1.26VでADC入力レンジが2×VREF=2.52Vとなります。

照度センサを飽和領域で使用することを考えるとVCEが0.6V程度になるので2.4V程度がトワイライトのAD変換への入力の最大値となります。

照度センサーの使い方(エミッタフォロワ)
照度センサーの使い方(エミッタフォロワ)

照度センサのデータシートを確認するとコレクタの電源がVceと表記されています。エミッタコレクタ間の飽和電圧と同じなので間違えてしまいそうですが、コレクタに接続しているのはプラス電源なのでDC3.0Vになります。

Vceが3.0Vになる点から流したいIL(uA)の点までを線で結んだ時のEVとの交点が飽和電圧もしくはアナログ領域での値になります。EV=10luxで飽和領域となるようにILが20uA程度とすることでほぼ2値で判定するように使用します。

電流を絞ると抵抗が大きくなり電圧変化が大きくなり照度を細かく判定できないためほぼONかOFFかの2値での判断になります。一方抵抗を小さくすると消費電流が増えますが電圧の変化量が少なくなるため照度を比較して処理を変えるような使い方ができます。

照度センサとPWMの関係

スリープと照度センサの関係
スリープと照度センサの関係

照度センサに常に電源を入れていると消費電流が増えてしまいます。照度センサの電流は20uAほどですが電源を長持ちさせるために不要な電流をカットすることが必要です。

トワイライトがスリープしている間はAD変換は行わないため電源を効率よく使用するためスリープ中は照度センサへの電源を停止することを考えます。

スリープと照度センサの関係

照度センサはエミッタフォロワで使用しますが、グランドをトワイライトのDOに接続することで動作と停止を使い分けるようにします。スリープと照度センサの動きは以下のようになります。

  1. 初期設定後にスリープする
  2. ウェイクアップ時に照度センサが動作するようにDOをLにする
  3. ショートスリープする
  4. ウェイクアップ後に照度センサーの値からPWM出力の有無を判断する
  5. 一定時間後(100ms)に照度センサが停止するようにDOをHにスリープする
  6. 以降は2から5を繰り返し

ウェイクアップしたとき100ms経過するまでの間AD変換を定期的(10ms)に行い移動平均をとりながら照度センサーの値を取得しています。

データシートを確認するとDIO吸い込み電流はVCC2.7~3.6Vにおいて4mAとなっています。今回はDOをLにしたときの吸い込み電流は(シンク電流)20uA程度なので問題になりません。抵抗を小さくしたとき4mA以上とならないように注意が必要です。

照度センサとPWM出力

照度センサの値が一定値を超えるとPWM出力するようにしていますが、照度センサの値がPWM出力の判定条件の電圧付近で前後することでONとOFFを繰り返してしまうことがあります。この対策としてPWM出力の判定条件にヒステリシス特性を持たせています。

初期条件は1.2V以上であればPWM出力するようにしています。周りが暗くなり1.2V以下になるとPWM出力を停止しPWM出力の判定条件を2.0Vに変更します。

照度センサの値が2.0V以上になるとPWM出力の判定条件を満たすためPWM出力が復帰します。この時PWM出力判定条件を1.2Vに変更します。

ONする電圧とOFFする電圧を変更することでヒステリシス特性を持たせることができ判定値付近でのチャタリング(ONとOFFが頻繁に切り替わる)などを防止することができます。

動作確認用の回路図

照度センサとブザーの動作確認の回路図
照度センサとブザーの動作確認の回路図

電源はTWE-EH-Sを使用します。TWELITEに電源が供給されると動作開始で周りが明るければブザーが鳴ります。ブザーはショートスリープからウェイクアップしている期間のみなります。SW1を押すとPWM出力を停止しブザーが鳴らないようにしています。

暗いと判定した後再び明るくなったと判定したときにPWM出力が復帰しブザーが鳴るようにしています。

照度センサを手で覆って暗くしたり紙袋で隠したりして動作を確認しました。ブザーは少しの間鳴っているだけなら気になりませんでしたが、周期的に「ピッ」となっていると次第に気になってきました。停止ボタンは必須だと思います。

動作確認をして楽しむには短めがいいのですが、照度は頻繁に変わるものではないためスリープの間隔を長くすることで消費電流を抑えることも有効です。

ボタンを増やしてPWM出力が任意のタイミングで切り替えるようにしたり抵抗値を低くすることで明るさによってブザーの音程を変えたりするなど知恵を絞ればいろいろなことができそうです。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <TWELITE>
#include <NWK_SIMPLE>

#define TIME_UP 0
#define TIME_OFF -1
#define TIMER_WAIT_MAX 100
#define JUDGE_MAX 1200
#define JUDGE_MAX2 2000
#define ADC_FLT_MAX  4

const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18; //Boot
const uint8_t PIN_DO2 = mwx::PIN_DIGITAL::DIO19; //light
const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO12; //SW1--wakeup
const uint8_t PIN_AD1 = mwx::PIN_ANALOGUE::A1; //温度

struct ADC_MEAN
{
    uint8_t wp;
    uint16_t buf[ADC_FLT_MAX];
    uint16_t dat;
};

// application use
ADC_MEAN adLight;
bool ad_finish = false;
uint16_t au16AI[2];
int16_t timzigwait = TIME_OFF;
bool b_senser_started = false;
bool buzzerOn = false;
bool btnpress = false;
uint16 judgeLight = JUDGE_MAX;

/*** Local function prototypes */
void sleepNow();
void napNow();
/*** the setup procedure (called on boot) */
void setup() {

    for(auto&& x : adLight.buf) x = 0xFFFF;

    pinMode(PIN_DO1,OUTPUT_INIT_LOW);
    pinMode(PIN_DO2,OUTPUT_INIT_HIGH);
    pinMode(PIN_DI1,WAKE_FALLING_PULLUP);

    Timer1.setup();
    Timer1.begin(1000,false,false);

    Analogue.setup(true, ANALOGUE::KICK_BY_TICKTIMER);
    Analogue.begin(pack_bits(PIN_AD1,PIN_ANALOGUE::VCC),10);

    Serial << "--- Light-com->start ---" << mwx::crlf;
}

void begin(){
    sleepNow();
}

/*** the loop procedure (called every event) */
void loop() {

    if(TickTimer.available()){
        if(timzigwait > TIME_UP){
            --timzigwait;
        }

        if( timzigwait == TIME_UP){
            timzigwait = TIME_OFF;
            digitalWrite(PIN_DO2,HIGH);
            Serial << "..light " << int(adLight.dat) << "mv." << mwx::crlf << mwx::flush;
            sleepNow();
        }

        if(Analogue.available()){
            au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC); //VCCの値
            au16AI[1] = Analogue.read(PIN_AD1);	//温度センサーの値
            adLight.buf[adLight.wp] = Analogue.read(PIN_AD1); //温度センサーの値を格納

            uint16_t sum;
            ad_finish = true;
            for( uint8_t i=0; i < ADC_FLT_MAX; i++ ){
                if( adLight.buf[i] == 0xFFFF){
                    ad_finish = false;
                    break;
                }else{
                    sum += adLight.buf[i]; //合計を算出
                }
            }

            if( ad_finish ){
                adLight.dat = sum >> 2; //4で割る(2回右にシフトする)
            }

            if( ++adLight.wp >= ADC_FLT_MAX){ //値の格納場所を更新する
                adLight.wp = 0;
            }
	}  
    }
}
// perform sleeping
void sleepNow() {
    uint32_t u32ct = 5000 + random(200); //5000ms+ランダム間スリープ
    Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;
    b_senser_started = false;
    Timer1.begin(1000,false,false);
    the_twelite.sleep(u32ct);
}
// perform wakeup
void wakeup(){
	
    if( !digitalRead(PIN_DI1)){
        btnpress = true;
        b_senser_started = true;
    }
  
    if (!b_senser_started ){
        b_senser_started = true;
        digitalWrite(PIN_DO2,LOW);
        napNow();
    }else{
        Serial << "..wakeup " << mwx::crlf;
        timzigwait = TIMER_WAIT_MAX;

        if( adLight.dat >= judgeLight){
            judgeLight = JUDGE_MAX;
            if( btnpress == false){
                Timer1.begin(1000,false,true);
                Timer1.change_duty( 512 );
            }
        }else{
            Timer1.begin(1000,false,false);
            judgeLight = JUDGE_MAX2;
            btnpress = false;
        }
    }
}
/* perform short period sleep */
void napNow() {
    uint32_t u32ct = 100;
    Serial << "..nap " << int(u32ct) << "ms." << mwx::crlf;
    Timer1.begin(1000,false,false);
    the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}

関連リンク

トワイライトを太陽光パネルで動作させたことやMWSTAGEの環境でソフト開発して無線通信したことなどについてまとめています。

トワイライト(TWELITE)のソフト開発と無線通信でできること

テックジム-将来のためにプログラミングを学ぶ

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました