カテゴリー: Arduino

  • PWM出力を使ってパルス波の周波数, Duty比を変更する [Arduino UNO]

    PWM出力を使ってパルス波の周波数, Duty比を変更する [Arduino UNO]

    PWM出力とは

    PWMとはpulse width modulationの略で、パルス幅変調と日本語では言います。
    波長のデューティー比(Duty cycle)を変えることによって、変調を行う方法です。

    詳しくは前回の記事で説明されています。

    ArduinoのPWM出力

    Arduino UNOではATmega328と呼ばれるMicrochip社のマイコンをメインチップとして使っています。
    Arduinoのプログラムを考える上で、深みにハマっていくとマイコンのデータシートを見ていかなければならなくなりますので、一回見てみると面白いかと思います。(事実今がそう)

    Arduinoには三つのタイマー(Timer/Counter)があります。
    このタイマーはArduinoプログラムの時間に関係しています。
    delay()やtone()などの関数はこのタイマーを使って計測されています。

    Timer/CounterPin番号ビット数役割PWM周波数
    Timer05, 68 bitArduinoの時間を管理
    delay(), millis(), micros()など
    977 Hz
    Timer19, 1016 bitServoライブラリなど490 Hz
    TImer23, 118 bittone()など490 Hz

    今回このタイマーをいじることでPWMの出力を変えるということをします。
    ちなみに、応用するとタイマーの根本を変えるので、delay()などの関数もいい感じに変えたりすることができたりするかも?(どちらかというと影響を受けて狂ったりすることの方が多そう)

    TImer0は基本的にシステム全体に関わることが多いので、TImer1を使用することをお勧めします。

    参考になるサイト集
    https://playground.arduino.cc/Main/TimerPWMCheatsheet/
    https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
    https://atooshi-note.com/arduino-1hz-pwm/
    http://blog.kts.jp.net/arduino-pwm-change-freq/
    http://garretlab.web.fc2.com/arduino/inside/hardware/arduino/avr/cores/arduino/wiring_analog.c/analogWrite.html

    プログラムの概要

     プログラムの全体の方針として、Arduinoのタイマーをレジスタの設定を変えることで自由にPWM出力の周波数を変更することができるようにします。

    目的としては、低い周波数を出力できるようにしたく、10 Hzを出力できるようなプログラムにします。

    今回は10番ピンにLEDを繋いで、その光の周波数、Duty比を自由に変更できるようにするプログラムを作成します。
    10番ピンなので、TImer1を使用します。

    プログラムコード

    //レジスタの設定を変えるためのもの
    #include <avr/io.h>
    int PWMPin = 10;
    
    //関数の定義
    //frq:周波数 (1Hz~指定できる)
    //duty:指定したいduty比
    void HzWrite(int frq, float duty) { 
    
        // モード指定
      TCCR1A = 0b00100001;
      TCCR1B = 0b00010100; //分周比256を用いる
    
      // TOP値指定
      OCR1A = (unsigned int)(31250 / frq);
    
      // Duty比指定
      OCR1B = (unsigned int)(31250 / frq * duty);
    }
    
    
    void setup() {
      pinMode(PWMPin, OUTPUT);
    }
    
    void loop() {
      HzWrite(10, 0.5);
      delay(5000);
      digitalWrite(PWMPin, LOW);
      delay(5000);
    
    }

    プログラムの説明

    まず、レジスタの設定を変えるために、<avr/io.h>をインクルードします。

    #include <avr/io.h>

    次に、delay()と一緒に使って、10 Hzの出力を5秒ごとに繰り返すという関数を作るため、HzWriteという関数を指定します。カッコ内には周波数、Duty比を指定できるようにします。

    void HzWrite(int frq, float duty) { 
    
    }

    次にモードの指定です、。
    今回使うレジスタはTCCR1A/TCCR1Bです。(TCCR: Timer/Counter Control Register)
    ここで1というのはTImer1を示しており、Timer2を使いたければTCCR2A/TCCR2Bを使います。

    PWM周波数を〇〇Hzとして設定するには、TOP値を自分で指定する必要があります。
    TOP値が大きければ大きいほど出力される周波数は低くなります。
    今回は10 Hzで例を取っているので、16MHzというシステムクロックと比べると非常に遅いので、大きなTOP値と分周を指定する必要がある。よって、なるべく大きいTimer1を使用します。


    Timer0, 2の8 bitではTOP値の最大値は255(2^8 – 1)であり、Timer1の16 bitでは65535(2^16 – 1)となる
    (最大値が-1となるのは、0~255もしくは0~65535となるため、数字の数が2^xになるが、最大値は-1しなければならない。)

    システム的には、TOP値までカウンタをインクリメント(0, 1, 2, と数えていく)し、OCRxA/OCRxB(xはカウンタ番号、カウンタについて2つの出力ピンA, Bが割り当てられている。)と一致した時にピンの出力を変化させる(LOW→HIGHのように)。そして、カウンタがTOP値まで到達すると今度は0までデクリメント(65535, 65534, 65533と上から数えていく)し、インクリメントの時と同様にOCRxA/OCRxBと一致した時にピンの出力を変化させます。

    Arduino UNOではこのカウンタのインクリメントするスピードは分周比の設定を変えることによって、ある程度変えることができます。(ある程度とは、1/8/64/256/1024 の中から選択できるということ)
    分周比は周波数を分周(周波数を1/n倍すること)するときの比(n)です。
    つまり、1000 Hzを分周比10で分周すると100 Hzになります。

    ちなみに、分周比1ではArduino UNO(ATmega328)のシステム動作クロックである16MHzで操作します。

    つまり、TOP値とOCRxA/OCRxBの値、分周比を変えることによって、自由に出力の変換点を変えることができます。

    今回は10Hzを指定したいので余裕を持って分周比256で行います。
    1カウントする時間は、Unoなら16MHzなので1 / 16MHz = 62.5ns(分周1)
    分周比を256にすると、TOPまでカウントした際には62.5ns x 256 x 65535 = 1.04856 secなので、1 Hzを指定できるようになる。

    今回このプログラムでは1 Hz ~ 31250 Hzまでの周波数を作り出すことができる。
    しかし、31250に近づくにつれてDuty比の指定ができなくなっていきます。
    Duty比を細かく設定したい際には300 Hz程度までしか作成することができません。

    このプログラムでは分周比の設定を変更することで、自分の使用用途にあった周波数帯を作れるようなプログラムを作ることができます。

    TCCRaA/TCCR1Bには何を指定するのかを書きます。
    ここら辺の細かいところはなんか難しいので、データシートを使って、なんとなく指定していきましょう。

    今回は2進数で示しているので、最初に0bがついています。TCCR1AではCOM1A1, COM1A0, COM1B, 無指定, 無指定, WGM11, WGM10 を 1 か 0 で設定します。
    TCCR1B には それぞれ、無指定(ICNC1), 無指定(ICES1), 無指定, WGM13, WGM12, CS12, CS11, CS10 を設定します。

    TCCR1Aの指定(ATmega328データシートより)
    TCCR1Bの指定(ATmega328データシートより)

    まず、今回はModeはPWMモードがPhase Correct and Frequency Correct であるMode9を指定する。
    今回TOP値OCR1Aに指定する。

    モードの指定(ATmega328データシートより)

    よって、WGM13 / WGM12 / WGM11 / WGM10 はそれぞれ、1,0,0,1 

    出力の指定(ATmega328データシートより)

    COM1B0 / COM1B0 は 0, 0 が 無出力、0, 1 が トグル動作 (一致時の出力を反転)、
    1, 0 は カウンタ がOCR1A/B – TOP間 にある場合 LOW、0 – OCR1A/B間 は HIGH となるよう出力、
    1, 1 は1, 0 の逆です。

    今回は周波数をOUTPUTのLEDをLOWとHIGHで変えるため、1B0 / COM1B0 は1, 0の値を指定します。

    スケッチを書く際にわかりやすいので 1, 0 を選択します。

    分周比の指定(ATmega328データシートより)

    今回分周比は256を指定するため、CS12/CS11/CS10は1, 0, 0を指定する。

    まとめると、以下のようになる。

    TCCR1A = 0b00100001;
    TCCR1B = 0b00010010;

    次にOCR1AとOCR1Bを設定し、周波数とDuty比を指定したもので出力してもらうように指定します。

      // TOP値指定
      OCR1A = (unsigned int)(31250 / frq);
    
      // Duty比指定
      OCR1B = (unsigned int)(31250 / frq * duty);

    今回Phase and Frequency Correct PWMを用いる際には、インクリメントしディクリメントすることから、出力される周波数は以下のように示すことができます。

    周波数frq = IC の動作周波数 / (分周比 * TOP値 * 2)

    逆にTOP値を指定するときは

    TOP値 = IC の動作周波数 / (分周比 x frq x 2)

    今回Arduino UNOで分周比256とした際には

    TOP値 = OCR1A = 16,000,000 / (256 x frq x 2) = 31250 / frq

    LOWとHIGHが入れ替わるタイミングはOCR1A/OCR1B = Duty比としたいため、

    OCR1B = 31250 / frq x duty

    オーバーフローを防ぐため、Unsigned intを指定している。

    出力のメインはこちら

    void setup() {
      pinMode(PWMPin, OUTPUT);
    }
    
    void loop() {
      HzWrite(10, 0.5);
      delay(5000);
      digitalWrite(PWMPin, LOW);
      delay(5000);
    
    }

    PWMPinつまり10ピンをOUTPUTで指定し、HzWrite()で周波数とDuty比を指定する。
    delay()でまった後に、digitalWrite(PWMPin, LOW)で出力をなくし、delay()する。

    以上がプログラムの全貌と解説です。

    後書き

    PWM出力の周波数を変更する方法のブログに関して、Arduinoで検索するよりも、AVRとか、ATmega328, 328Pなどのように検索した方が多く出てきました。

    今回はざっくりとしたブログでしたが、より深く知りたい人は調べてみてください。

  • Arduionoで連続パルス波を作成する方法

    Arduionoで連続パルス波を作成する方法

    はじめに

    Arduinoを使って、連続パルス波を出力したりしたくなる時があると思います。
    LEDを点滅させたり、音を出力したり、タイマーとして使ってみたり・・・

    何かと使うことが多く、そして簡単なようにも思えますが、調べていくと非常に奥深いことがわかります。

    今回は、Arduinoで連続パルス波を出力する方法をいくつか紹介していきます。

    Delayを使って時間を変える

    最もシンプルかつ、簡単な方法で、delay関数を使って出力のONとOFFを切り替える手法です。

    //pinはピン番号
    void loop(){
        digitalWrite(pin, HIGH);
        delay(1000);
        digitalWrite(pin, LOW);
        delay(1000);
    }

    上記のプログラムでは、出力がHIGHとLOWとで切り替えています。
    delayは単位はmsなので、delay(1000)では1秒間待つというプログラムになります。

    つまり、1Hzでついたり消えたりするプログラムになります。

    ただ、周波数の指定にdelay()関数を使うことは問題点が多いです。
    また、この方法では、60Hzの信号を5秒間出力といったように、出力の時間を変更するのが、for構文を使って指定する必要があり、そういった面で不便さがあります。

    ただ、シンプルが故、とりあえずやってみたいという時にはおすすめです。

    tone()を使って指定する

    tone()関数はブザー音などを作成するときによく使われる関数です。
    Arduino公式の解説

    この関数では、周波数、継続時間を指定することができます。
    なので、delayを使った時とは違い、周波数を計算せずとも直接指定することができます。

    //pinはピン番号
    void loop() {
         tone(pin,60);
    }

    tone(pin, frequency)や、tone(pin, frequency, duration) といったように指定することができます。

    durationはms単位で指定するため、delayと同様の記載方です。

    この関数の問題は、指定できる周波数が31Hz以下ではできないという点です。
    つまり、1Hzなどの出力を作ることができません。

    音などの31Hzより大きい周波数を指定する時には非常に簡単に周波数を指定できますし、delay関数を使う時よりも非常に短く、正く指定することができます。

    PWM出力を使って、PWM出力の周波数を変える

    PWM出力を使う方法は最も自由度が高く、そして奥深すぎる原因です。

    PWMとはpulse width modulationの略であり、日本語ではパルス幅変調です。
    詳しくはwikipediaより

    この言葉だけ聞いてもなんのことかよくわかりませんが、簡単にいうと、PWMとはデューティー(Duty)比を変更して色々変調する方法です。
    Arduino公式の説明はこちらから

    LEDの明るさを指定するときに、本来であれば電流値を変えることで明るくしたり、暗くしたりできます。
    しかし、電流値が一定だった時に、でもLEDの明るさを変えたいとなった際に、めっちゃ早い周波数で(人が点滅しているのがわからなくなるのが30~60Hzと呼ばれている)点滅させます。

    そして、ONとOFFが通常であれば1:1(Duty比が50%)となっていますが、これを4:1(Duty比が80%)とか1:4(Duty比が20%)とかにしてみるとどうでしょうか。
    前者では明るく見え、後者では暗く見えるでしょう。

    このようにして、パルスの幅を変えることで出力を変調するというのがPWMによる出力です。

    そして、Arduinoではこの出力ができるとともに、PWM出力の周波数を変えることもできます。
    レジスタ設定と呼ばれる、まあArduinoの元の部分を変えちゃえばPWMの出力周波数を変えることができるっしょっていう考えがこの方法です。
    ちなみにデフォルトでは出力周波数は490Hz、一部のピンでは980Hzです。

    今後の記事でやり方を詳しく説明します。
    「PWM Arduino 周波数 変更」で調べるとたくさん説明が出てきます。

    4回ぐらい読むとなんとなくわかるようになると思います。

    基本的にはこれらの方法で自分の目的にあったプログラムを探してみてはどうでしょうか。

  • PythonでArduinoを制御したいのだ!

    PythonでArduinoを制御したいのだ!

    ArduinoはArduino言語によって制御します。
    ただ、もっと複雑な処理をしたい、何かのプログラムと一緒にArduinoを制御したいという時が訪れることでしょう。

    今回紹介するpySerialというモジュールはRaspberry PiやArduinoとシリアル通信を行うことができます。
    シリアル通信をすることによって、pythonのプログラムからArduinoやRaspberry Piに指令を出し、pythonプログラムを通して制御することができます。

    今回はその基本的なプログラム紹介します。
    これが使えるだけで、プログラムでできることの幅がかなり広がるでしょう。

    インストール方法

    pip install pyserial

    ターミナルやコマンドプロントでpythonを開き、インストールできます。
    もしくは、PyCharmなどのターミナルでインストールもできます。

    プログラム例

    今回は1秒ずつLEDをつけたり消したりするプログラムです。
    もともとArduinoについているLEDの13ピンに出力して検証したいと思います。
    Pythonのプログラムでは

    import serial, time
    
    def main():
        #  COMポートを開く
        print("Open Port")
        ser = serial.Serial("COM3", 9600)
        while True:
            #  LED点灯
            ser.write(b"1")
            time.sleep(1)
            #  LED消灯
            ser.write(b"0")
            time.sleep(1)
    
        print("Close Port")
        ser.close()
    
    if __name__ == '__main__':
        main()

    serial.Serialでポートと、シリアル通信の設定を行います。
    Arduino UNOでは9600を指定します。
    これはボードによって異なるので、Arduino IDEで確認しましょう。

    b”1″のbは非常に重要な役割を担っています。
    serial.write()関数では、シリアル通信をするためにはbyte列にして数字や文字列を送信しなければなりません。
    このようにして、byte列であることを示す必要性があります。

    このプログラムは無限ループなので、1秒ごとのLEDのオンオフがプログラムを止めるまで続きます。

    次にArduino側のプログラムです。

    void setup() {
      Serial.begin(9600);
      pinMode(13, OUTPUT);
      digitalWrite(13, LOW);  //  初期化
    }
    
    void loop() {
      byte var;
      var = Serial.read();
      switch(var){
        case '0':
          digitalWrite(13, LOW);
          break;
        case '1':
          digitalWrite(13, HIGH);
          break;
        default:
          break;
      }
    }

    ここではswitch case構文を使っています。
    このほうがプログラムがわかりやすいです。
    詳しくは前回のブログを見てみてください。

    ここでは0が送信されるとLOW、1が送信されるとHIGHとなります。
    このプログラムをベースに使うことによって、色々なパターンでArduinoをPythonプログラムで制御することができます。

  • Arduino言語におけるswitch case 構文

    Arduino言語におけるswitch case 構文

    Arduinoにおいて、さまざまな条件分けをしたいとき、if文で構成することがほとんどだと思います。
    しかし、条件分岐が10とか20とか(あんまりないとは思いますが)あった時はどうしましょう。

    C/C++言語にはswitch文があり、C/C++言語をベースとしているArduino言語も同じ構文が使えます。

    以下にプログラム例を記載します。

    switch (var) {
      case 1:
        // varが1のときに実行する処理
        break;
      case 2:
        // varが1のときに実行する処理
        break;
      default: 
        // どのcaseラベルにも一致しないときに実行する処理
        // defaultは、省略可能
      }

    varが変数であり、varが1の時、case 1の処理が実行されます。
    varが2の時、case 2の処理が実行されます。
    varが1, 2以外の時は、defaultの処理が実行されます。

    case 1, case 2の処理後の “break;”はそれぞれのcaseの処理が終了したということを示す文です。
    この文がないと、エラーを起こしたりします。

    defaultの処理において、break; が必要ないのは、defaultは最後の条件分岐になり、最後の処理というのが明白なためです。
    ただ、書いてはいけないわけではなく、書いても問題なくプログラムは動きます。

    また、default: 自体も省略することができます。
    if else構文のelseの省略のような感覚です。

    個人的にはswitch case構文はif構文よりもプログラムを見たときのスッキリさはあると思います。
    そこまで難しいコードでもないので、ぜひ使ってみてください。