Tanuki_Bayashin’s diary

電子工作を趣味としています。最近はラズベリーパイPicoというマイコンを使って楽しんでいます

【模型】クリスマスツリーを作ろう~プログラム編

※なにか気になる点がありましたらコメント欄からお伝えください。

目次

1.はじめに

 前回、前々回とクリスマスツリーに関する記事を書いてきましたが、今回はRaspberry Pi Pico(ラズベリーパイピコ:以下Picoと記載)のコーディングに関する説明をしていきます。

前回の記事
【模型】クリスマスツリーを作ろう~本体の製作 - Tanuki_Bayashin’s diary

前々回の記事
【模型】クリスマスツリーを作ろう~回路編 - Tanuki_Bayashin’s diary

プログラムの仕様をまとめると、

  1. 単色のLEDを徐々に明るくし、徐々に暗くしていく
  2. 複数のLEDを用いる
  3. 明滅する周期やタイミングをLEDごとに変化させる
  4. 1つだけフルカラーLEDを用いる

としました。以下、これを満たすコードを解説していきます。(他にもいろいろな点滅の仕方があると思います。試してみてください)

2.単色のLEDを徐々に明るくし、徐々に暗くしていく

2.1 PWMについて

※「PWM やさしく解説」などで検索していただくと、PWMについて詳しく解説しているサイトがあるかと思います

 PWMとはPuls Width Moduration の略で、パルス幅変調と訳されます。一定の周期が定められていて、その周期のうちに、オンである時間幅とオフである時間幅があり、その比率で出力される信号のオンの時間の比率を変化させます。

 一定の周期のうち、オンの時間が占める割合をデューティー比と言います。デューティー比が高いほど出力されるデジタル信号のオンである割合が高いので、コンデンサなどで平滑すると、高い電圧が出力されます。直流モーターの制御や音の大きさの制御などに使われています。

 今回はPicoのPWMの機能を用いて、LEDの明るさを調節しています。

2.2 Picoにおけるプログラミング

 以下にPicoにおける、PWMに関する記述を示します。

リスト1

from machine import PWM, Pin    # ライブラリのインポート

LED = PWM(Pin(15))    #  GP15にてPWM制御のインスタンスを生成
LED.freq(1000)        # PWMの周波数を1KHzに設定しています
LED.duty_u16(0)       #  PWMのデューティー比を0とする

 PWMの機能を有効にするにはライブラリmachine 内のPWMというモジュールのインスタンスを生成します。(1行目、3行目)

 4行目ではPWMの周波数を設定しています。周波数の逆数が周期となるので、ここでは
 1 ÷ 1000 = 1 [ms]

 となります。LEDの明るさの制御にはもっと周期は大きくてもよいかと思いますが、今回はこの値を用いました。

 5行目でPWM信号を出力しています。duty_u16() 内の値に応じてデューティー比を設定できます。0~65535の値が設定できて、その値をvalue とすると、
 デューティー比 = value / 65535

にて求まります。逆に、デューティー比をduty とすると、
 LED.duty_u16( int(65535 * duty))

としてもよいでしょう。ただし、0.0 ≦ duty ≦ 1.0 であり、計算結果をint() にて整数型に直します。

2.3 ぼんやり光らせる仕組み

 次に、LEDをぼんやり光らせる仕組みについて見ていきます。

 まず、時間に比例して大きくなる変数をK と置きました。名前の由来は特にありません。K にループを1回回るごとにPhase (位相差から取りました)という値を加えていきます。

 Phase の値はランダムで5~10(整数値)として、K が1500 を超えると1500 を引いています。したがってKの値は1500にて1周期となります。なので、1500 割る 5~10 ということで、150~300 ループで1周期となります。1ループで約14[ms] としているので、2.1秒~4.2秒で明るくなってから、暗くなることになります。(10 や1500 という値はアバウトに決めました)

 その間に、以下に示す関数から、0~1の値をとるようにしています。図示すると図1のようになります。

スト2

def houbutsu(x):
    y = 1 - (1 - x) * x * 4
    return y

f:id:Tanuki_Bayashin:20220102215302p:plain:w500
図1 ぼんやり明るくするための関数

 これと類似する関数を図2に示します。

f:id:Tanuki_Bayashin:20220102215246p:plain:w500
図2 ぼんやり明るくするための関数の候補

 はじめ、図2の関数を用いたのですが、少し明るい時間が長く感じたので、図1の関数に変更しました。(リスト3にこれらのコードを示しておきます)

 ※これらの関数はどちらも、実数の区間[0, 1] から [0, 1] への写像となっています。

リスト3

import matplotlib.pyplot as plt

def houbutsu(x):
    y = (1 - x) * x * 4
    return y

def houbutsu2(x):
    y = 1 - (1 - x) * x * 4
    return y

x = [i * 0.01 for i in range(100)]
y1 = []
y2 = []

for i in range(100):
    y1.append(houbutsu(x[i]))

for i in range(100):
    y2.append(houbutsu2(x[i]))

plt.plot(x,y1)
plt.show()

plt.plot(x,y2)
plt.show()

 この関数の引数をK/1500 とし、この関数値をデューティー比としています。リスト4に、ここまでの内容をまとめたコードを示します。

リスト4

from machine import PWM, Pin    # ライブラリのインポート
import urandom    #  乱数を使うためのライブラリ
import utime      # sleep_ms() を使うためのライブラリ

def houbutsu(x):    #  ぼんやり明るくするための関数
    y = 1 - (1 - x) * x * 4
    return y

def limit(x):      #  Kの値を0~1500としています
    if x >= 1500.0:
        y = x - 1500.0
    return y

Phase = urandom.uniform(5, 10)    #  Phase の値を決定(5以上10以下の乱数)
K     = urandom.uniform(120, 150)  #  K の値を決定(120以上150以下の乱数)

max_duty = 65355        #  デューティー比が1.0のときのメンバ関数に与える値
while True:
    K = limit(K + Phase)          #  Kの値が1500を超えたとき、1500を引きます。
    y = houbutsu(K / 1500.0)      #  ぼんやり明るくするための関数
    LED.duty_u16(int(max_duty * y))  #  デューティ比をセットしています
    utime.sleep_ms(14)        #LEDが明滅する間の周期に比例する値

 Kの値をランダムで決めているのは、プログラムの開始時のLEDの明るさをランダムにするためです。

3. 複数のLEDを用いる、明滅する周期やタイミングをLEDごとに変化させる 件について

 複数のLEDに対応させるために、リスト型の変数を用意しました。また、明滅する周期やタイミングをLEDごとに変化させるのも、リスト型の変数を用いることで解決できます。

 まず、Pin_Noという変数に、LEDに接続するピン番号を要素とするリストを用いました。また、LED_NUM にLEDの個数をセットしました。(フルカラーLEDを接続するピンは除きます)

 そのうえで、LEDというリストにLED用のPWMモジュールのインスタンスを生成し、PhaseやK といったリストも用意しました(要素の数はLED_NUM の数と同じです)。

 初期設定ではPWMのインスタンスの生成、PWMの周波数の設定、PWMの出力を0としています。また、Phase やK の値は乱数で決めています。urandom というモジュールを用いました。(リスト5)

リスト5 複数のLEDに対応したコード(抜粋)


Pin_No = [14, 15, 16, 17, 18, 20, 21]  # LEDが接続されているピン番号(GPIO)
LED_NUM = len(Pin_No)                  # LED の数(フルカラーLEDの数は入っていない)

LED   = [0 for _ in range(LED_NUM)]    #  LEDの明るさを制御するインスタンス用
Phase = [0 for _ in range(LED_NUM)]    #  明るさの周期を決める変数
K     = [0 for _ in range(LED_NUM)]    #  明るさの周期にかかわる変数

for i in range(LED_NUM):
    LED[i] = PWM(Pin(Pin_No[i]))    #  Pin_No のピンごとにPWM制御のインスタンスを生成
    LED[i].freq(1000)               #  PWMの周波数を1KHzにする
    LED[i].duty_u16(0)              #  PWMのデューティー比を0とする
    Phase[i] = urandom.uniform(5, 10)     #  Phase の値を決定(5以上10以下の乱数)
    K[i]     = urandom.uniform(120, 150)  #  K の値を決定(120以上150以下の乱数)

4. 1つだけフルカラーLEDを用いる

 フルカラーLEDについては前回の記事(回路編)の2.回路について にて説明してますが、クリスマスツリーの星のところだけ、フルカラーLEDを用いました。

リスト6 フルカラーLEDへの出力をオンとする

from machine import Pin    # ライブラリのインポート

#  GP19 を有効とし、出力ポートとする
LED_FullColour = machine.Pin(19, machine.Pin.OUT)

#  フルカラーLEDへの出力をオンとする
LED_FullColour.value(1)

 これにてフルカラーLEDの電源がオンとなり、自動で色が変化するようになります。

5.全体のプログラム

リスト5に全体のプログラムを示します。

リスト7 全体のプログラム

from machine import PWM, Pin    # ライブラリのインポート
import utime
import urandom

Pin_No = [14, 15, 16, 17, 18, 20, 21]  # LEDが接続されているピン番号(GPIO)
LED_NUM = len(Pin_No)                  # LED の数(フルカラーLEDの数は入っていない)

LED   = [0 for _ in range(LED_NUM)]    #  LEDの明るさを制御するインスタンス用
Phase = [0 for _ in range(LED_NUM)]    #  明るさの周期を決める変数
K     = [0 for _ in range(LED_NUM)]    #  時間に比例する変数(0以上、1500以下)

for i in range(LED_NUM):
    LED[i] = PWM(Pin(Pin_No[i]))    #  Pin_No のピンごとにPWM制御のインスタンスを生成
    LED[i].freq(1000)               #  PWMの周波数を1KHzにする
    LED[i].duty_u16(0)              #  PWMのデューティー比を0とする
    Phase[i] = urandom.uniform(5, 10)    #    Phase の値を決定(5以上10以下の乱数)
    K[i]     = urandom.uniform(120, 150)  #  K の値を決定(120以上150以下の乱数)

def houbutsu(x):
    y = 1 - (1 - x) * x * 4
    return y

def limit(x):
    if x >= 1500.0:
        y = x - 1500.0
    return y

j = 0
max_duty = 65535
LED_FullColour = machine.Pin(19, machine.Pin.OUT)

if __name__ == '__main__':
    # Process arguments
    print('Press Ctrl-C to quit.')

    try:
        LED_FullColour.value(1)
        while True:
            j += 1
            if j >= 5000:
                j = 0
                for i in range(LED_NUM):
                    Phase[i] = urandom.uniform(5, 10)

            for i in range(LED_NUM):
                K[i] = limit(K[i] + Phase[i])
                y = houbutsu(K[i] / 1500.0)
                LED[i].duty_u16(int(max_duty * y))
                utime.sleep_ms(2)

    except KeyboardInterrupt:
        for i in range(LED_NUM):
            LED[i].duty_u16(0)        #  LED をオフとする
        LED_FullColour.value(0)    #  フルカラーLEDをオフとする

        print('program is finished')  #  'program is finished' と表示する

リスト7で初めて出てくるコードについて解説します。

解説:
28行目:
 無限ループ内で使用する変数です。0に初期化しています。
32~36行、51行:
 このように記述することで Ctrl + C と押すことにより、プログラムが終了します。
37行目:
 フルカラーLEDの端子をオンとします。
38~49行:
 無限ループ内の処理です。
39~43行:
 5000回ループするごとにj の値を0にします(約70秒)。その都度、Phase[i]の値を設定し直しています。70秒は適当に決めました。
 Phase[i]の値を変えることで、点滅の仕方に動きが出てくるかと思い、このようなコードとしました。
49行目:
 1ループにつき、LED_NUM × 2ms = 14[ms] だけ、処理を止めます。点滅の周期を調整するためです。
52~54行:
 全てのLEDへの出力を0にしています。
56行目:
 シェルにメッセージを出力します。プログラム終了です。

 以上でPico内のプログラムに関する説明は終わりです。

 ※このプログラムを電池駆動で動かすときは、Pico内のメモリには”main.py” というファイル名で保存します。
  このことに関して、次の記事で取り上げています。
Raspberry Pi Pico を単体にて、電池駆動で動かす - Tanuki_Bayashin’s diary

6.まとめ

 今回はプログラムの内容について解説しました。解説の内容は目次から分かるかと思います。

 また、クリスマスツリーの製作全体を通してみると、文章でまとめると結構な量になったことと、技術的にはさほど、重要なこともなかったかな、と思いました。

 ただ、回路を作るにあたって、ひとつのまとまった作品を作り上げることができたように思います。楽しい時間を体験できました。