Raspberry Pi Pico/PicoWの機能を最大限に活かし、シーケンサー(PLC:Programmable Logic Controller)として使用するための便利な動作確認基板について詳しく紹介します。
LEDや端子台、OLED表示器等を実装しているため、「ラズパイPico」の機能をフル活用するための動作確認用基板としても便利に使用することができます。
基板を製造するためのガーバーデータも公開していますので、このデータを使用して基板を発注することもできます。
シーケンサーは、産業用途や自動化システムで広く普及しており、複数の入出力を容易に制御するために使用されています。
産業用なので価格は高価ですが、小規模な用途では「ラズパイPico」の入出力点数(入力12点/出力6点)があれば十分な場合も多く、安価にシーケンサーと同じ動作を実現することができます。
「Python」でラダーのようにプログラムを描く方法や、今回動作確認するシーケンサ基板の回路や仕様については、以下のリンクで詳しく紹介しています。
「ラズパイPico」の使い方については、以下のリンクで詳しく紹介しています。
1.シーケンサー基板の詳細
2.サンプルプログラム(基本動作確認)
・Lチカ
・OLEDディスプレイ表示
・アナログ入力
3.シーケンサーとしての使い方
4.サンプルプログラム(シーケンサー)
・ラダー図での動作確認(Pythonでラダー動作を描く方法)
・タイマ、カウンタの使い方
・本格的なシーケンサとしての使用例
5.ガーバーデータの公開
6.ガーバーデータから基板を発注する方法
・基板製造・発注なら「PCBGOGO」
・見積もり価格の確認
7.部品リスト
8.部品の半田付け手順
9.まとめ
1.シーケンサー基板の詳細
今回紹介するシーケンサー基板の仕様は以下になります。
・出力6点、LED付(トランジスタアレーで電流増幅)
・アナログ入力2点
・I2C通信
・UART通信
・OLED(SSD1306)表示
基本的な機能は「Raspberrey Pi Pico/PicoW」そのものなので、以下から「ラズパイPico」の端子配列と合わせて「LogikaraーPLC」の端子配列や回路について紹介します。
Raspberry Pi Pico 端子配列
「Raspberry Pi Pico」の端子配列は下画像のようになります。(公式サイトより抜粋)
シーケンサー基板(Logikara-PLC)端子配列
「LogikaraーPLC」の端子配列は下画像のようになります。
「Raspberry Pi Pico」のほぼ全ての端子が端子台と繋がっているため、配線も簡単にできます。
上画像の中の「オレンジ色」で表示したものが「LogikaraーPLC」の端子記号で、「ピンク色」で表示したものが「Raspberry Pi Pico」の端子機能です。
入力端子12点(X00〜X07、X10〜X13)と出力端子6点(Y00〜Y05)には動作確認用のLEDが実装されています。
画像内には実装されていませんが、OLED表示器も実装することができ、タイマーやカウンターの値、アナログ入力値等を表示させて確認することができます。
OLED表示器(SSD1306)も実装した実際の基板は下画像のようになります。
Raspberry Pi Pico 電源回路
「Raspberry Pi Pico」基板には以下のような電源回路が実装されています。(pico-datasheetより抜粋)
回路図のように「Raspberry Pi Pico」基板上には「VBUS」「VSYS」「3V3」の端子があり、以下のような機能があります。
- VBUS:USBの5Vがそのまま出力されます。
- VSYS:VBUSからショットキーバリアダイオード(D1)経由で約5V(5V-約0.3Vのため約4.7V)が出力されます。外部から1.8V〜5.5Vを供給して使用することもできます。
- 3.3V:VSYS電源から電源IC(U2)によって生成された3.3Vが出力されており、コントローラICの電源として使用されています。
各電源端子は「LogikaraーPLC」基板では以下のように使用しています。
- VBUS:「OUT 5V」と表示しています。外部デバイスやセンサ等に5Vを供給する時に使用します。
- VSYS:「VSYS 5V」と表示しています。入出力端子のLED用電源や各端子台の5V電源出力用として使用しています。
- 3.3V:「Raspberry Pi Pico」本体基板ではコントローラ制御用の3.3Vとして使用しているため、外部ノイズの影響を受けないように、極力「LogikaraーPLC」基板では使用せず、OLED表示器の電源用としてのみ使用し、基板外にも引き出さないようにしています。
「LogikaraーPLC」基板の入出力回路は以下になります。
入力回路
外部配線からのノイズの影響を受けないようにフォトカプラを使用して電気的に絶縁しています。
出力回路
トランジスタアレーを使用して「Raspberry Pi Pico」基板単体では扱えない大きな電流を扱えるようにしています。(5Vリレーの駆動を想定しています。)
出力回路で
「LogikaraーPLC」の回路詳細については、以下のリンクで詳しく紹介しています。
2.サンプルプログラム(基本動作確認)
「LogikaraーPLC」基板を使用して、基本的な「Lチカ」「OLED表示」「アナログ入力」についてサンプルプログラムで動作確認を行なっていきます。
・Lチカ
基板上に実装されている「LED」を使用して下画像のように「Lチカ」の動作確認を行います。
電源を入れると「ラズパイPico」の「本体LED(緑)」が点灯します。
「X00ーCOM」間にスイッチを接続しておきます。
スイッチを押すと「X00」の「LED(緑)」が点灯し「Y00」の「LED(赤)」も点灯します。
スイッチはなんでもいいですが、今回は「ジャンパーリード(オスーメス)」に「タクトスイッチ(サンハヤト製)」を接続して下画像のように準備しています。
「Lチカ」のサンプルプログラムは以下になります。
※下の黒塗り部の右上アイコンクリックでコピーできます。
from machine import Pin # 入出力モジュールを準備
# 入力ピン設定
sw = Pin(6, Pin.IN, Pin.PULL_UP) # スイッチのピン番号を指定してswとして入力設定(プルアップ)
# 出力ピン設定
led0 = Pin("LED", Pin.OUT) # 本体LEDピンをledとして出力に設定
led1 = Pin(18, Pin.OUT) # 外部LEDのピン番号を指定してledとして出力設定
# メイン処理 ---------------------------------------------------------
while True: # ずっと繰り返し
led0.value(1) # 本体LEDを点灯
if sw.value() == 0: # スイッチが押されていたら
led1.value(1) # LEDを点灯
else: # スイッチが押されてい なければ
led1.value(0) # LEDを消灯
・OLEDディスプレイ表示
OLEDディスプレイ(SSD1306)の動作は下画像のように「Hello!!」を表示するプログラムで確認を行いました。
OLEDディスプレイ(SSD1306)を使用するには「ライブラリ」のインストールが必要です。詳しい使用方法は以下のリンクで紹介しています。
サンプルプログラムは以下になります。
※下の黒塗り部の右上アイコンクリックでコピーできます。
# Thonnyの「パッケージ管理」からssd1306で検索して「micropython-ssd1306」をインストールしておく
from machine import I2C # I2C制御用モジュールを準備
import ssd1306 # 液晶表示器用ライブラリを使用
import time # タイマーモジュールを準備
# I2C設定
i2c = I2C(1, sda=2, scl=3) # (I2C識別ID 0or1, SDA, SCL)
# 使用するSSD1306のアドレスを取得して表示(通常は0x3C)
addr = i2c.scan()
print("OLED I2C Address :" + hex(addr[0]))
# ディスプレイ設定(幅, 高さ, 通信仕様)
display = ssd1306.SSD1306_I2C(128, 64, i2c)
# メイン処理 ---------------------------------------------------------
while True: # ずっと繰り返し
display.fill(0) # 画面表示初期化
# 文字表示("表示内容", x座標, y座標, 色, フォント)
display.text("OLED SSD1306", 17, 0, True)
display.text("Hello!!", 37, 36, True)
# 線の描画(始点x, 始点y, 終点x, 終点, 色)
display.line(0, 9, 128, 9, True)
# 四角の描画(x座標, y座標, 幅, 高さ, 色)
display.rect(5, 20, 118, 40, True)
display.show() # 画面表示実行
time.sleep(0.5) # 待ち時間
・アナログ入力
アナログ入力は下画像のように、ボリュームを使用して、2つのアナログ入力値(A1、A2)と電圧換算値をOLEDディスプレイに表示しています。
ボリュームは10kΩのものを使用し、抵抗4.7kΩをブレッドボードで接続して使用しています。
ボリュームはサンハヤト製のブレッドボード用部品(SBS-P01)のものを使用しています。
ボリューム(可変抵抗器)の使い方は以下のリンクで詳しく紹介しています。
ボリュームやタクトスイッチは以下のサンハヤト製ブレッドボード用部品(SBS-P01)が使いやすいです。
サンプルプログラムは以下になります。
※下の黒塗り部の右上アイコンクリックでコピーできます。
# Thonnyの「パッケージ管理」からssd1306で検索して「micropython-ssd1306」をインストールしておく
from machine import Pin, ADC, I2C # 使用する制御用モジュールを準備
import ssd1306 # 液晶表示器用ライブラリを使用
import time # タイマーモジュールを準備
# I2C設定
i2c = I2C(1, sda=2, scl=3) # (I2C識別ID 0or1, SDA, SCL)
# 使用するSSD1306のアドレスを取得して表示(通常は0x3C)
addr = i2c.scan()
print("OLED I2C Address :" + hex(addr[0]))
# ディスプレイ設定(幅, 高さ, 通信仕様)
display = ssd1306.SSD1306_I2C(128, 64, i2c)
# アナログ入力ピン設定
adc1 = ADC(27) # ADC1:アナログ入力(A0)の端子番号27を設定
adc2 = ADC(28) # ADC2:アナログ入力(A1)の端子番号28を設定
# メイン処理 ---------------------------------------------------------
while True: # ずっと繰り返し
adc_val1 = 65535 - adc1.read_u16() # アナログ入力(ADC)値1を取得
adc_volt1 = (adc_val1 * 3.3) / 65535 # アナログ入力(ADC)値1を電圧(3.3V)に変換
adc_val2 = 65535 - adc2.read_u16() # アナログ入力(ADC)値2を取得
adc_volt2 = (adc_val2 * 3.3) / 65535 # アナログ入力(ADC)値2を電圧(3.3V)に変換
print("adc_val = {:5d}, {:5d} adc_volt = {:5.2f}V, {:5.2f}V".format(adc_val1, adc_val2, adc_volt1, adc_volt2)) # 結果を表示する
# 液晶画面表示内容設定
display.fill(0) # 表示内容消去
display.text('Analog Input', 15, 0, True) # タイトル表示
display.hline(0, 10, 128, True) # 指定座標から横線
display.text('ADC1 ={:6d}'.format(adc_val1), 17, 15, True) # アナログ入力1値表示
display.text('VOLT1={:5.2f}'.format(adc_volt1), 17, 27, True) # 電圧1表示
display.hline(10, 38, 108, True) # 指定座標から横線
display.text('ADC2 ={:6d}'.format(adc_val2), 17, 43, True) # アナログ入力2値表示
display.text('VOLT2={:5.2f}'.format(adc_volt2), 17, 55, True) # 電圧2表示
# 設定した内容の表示実行
display.show()
time.sleep(1.0) # 待ち時間
この他にも、「I2C通信」や「UART通信」についても、以下のリンクでサンプルプログラムを使用して詳しく紹介しています。
3.シーケンサーとしての使い方
このまま「ラズパイPico」の動作確認用基板としても使用できますが、この基板は複数の入出力を制御することに特化して設計したため、小規模なシーケンサーとして使用することができます。
次から、シーケンサーとして使うための方法を詳しく紹介します。
以下のような「ラダー図」を「Python」で書いてみます。
まずは、この動作を「if〜else 文」で書くと以下になります。
if (x0 == True and x2 == False) or m0 == True:
m0 = True
if x1 == True:
m0 = False
if m0 == True:
y0 = True
else:
y0 = False
これでもラダー図の動作を再現できていますが、毎回これを書くのは大変ですし、読みにくいです。
このため、以下のように省略して書くことで、簡単に書くことができ、簡潔で見やすくすることができます。
m0 = (x0 and not x2) or m0 if not x1 else False
y0 = m0
上記の書き方を、ラダー図と見比べると、1行づつがラダー図そのものを表現していることが確認できると思います。
慣れないうちは大変に感じるかもしれませんが、パターンは決まっているので、大規模な「ラダー図」でも、簡単に「Python」で置き換えることができます。
シーケンサーのラダー図の動作を「Python」で書く方法は、ラダー図の基本から以下のリンクで詳しく紹介しています。
4.サンプルプログラム(シーケンサー)
実際にシーケンサーとしての動作を「Python」で書く方法を、サンプルプログラムを使用して確認していきましょう。
・ラダー図での動作確認(Pythonでラダー動作を描く方法)
基本的なラダー動作を「Python」で書いて、サンプルプログラムで確認していきます。
今回のプログラムでは以下の動作を確認します。
・b接点:「X0」がONで「Y1」がOFF
・AND:「X0」と「X1」がONで「Y2」がON
・OR:「X2」または「X3」がONで「Y3」がON
サンプルプログラムは以下になります。
※下の黒塗り部の右上アイコンクリックでコピーできます。
from machine import * # 制御用モジュールを準備
# 本体LED(GP25)を出力設定
led = Pin("LED", Pin.OUT)
# INPUT:入力端子(x0〜x3)設定(プルアップ)
input_pins = [6, 7, 8, 9] # 使用する入力端子を設定(リスト作成)
inputs = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in input_pins]
# OUTPUT:出力端子(y0〜y3)設定
output_pins = [18, 19, 20, 21] # 使用する出力端子を設定(リスト作成)
outputs = [Pin(pin, Pin.OUT) for pin in output_pins]
# 初期化
x0 = x1 = x2 = x3 = False # 入力接点(4点)
y0 = y1 = y2 = y3 = False # 出力リレー(4点)
m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = \
m10 = m11 = m12 = m13 = m14 = m15 = m16 = m17 = m18 = m19 = False # 補助リレー(20点)
# メイン処理 ---------------------------------------------------------------
while True: # ずっと繰り返し
# 入力状態スキャン
x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()
# ************* シーケンス処理(ラダーから変換) *************
'''
# a接点、b接点 *********************
x0
--| |------------------( y0 )--
x0
--|/|------------------( y1 )--
'''
# Pythonへ変換(基本ラダー例)
y0 = x0
y1 = not x0
'''
# AND、OR *************************
x0 x1
--| |---| |------------( y2 )--
x2
--| |-┬----------------( y3 )--
x3 |
--| |-┘
'''
y2 = x0 and x1
y3 = x2 or x3
# 本体LED 点灯
led.value(True)
# ************* シーケンス処理 ここまで *************
# 出力実行
outputs[0].value(y0)
outputs[1].value(y1)
outputs[2].value(y2)
outputs[3].value(y3)
・タイマ、カウンタの使い方
・タイマー割り込みによる常時タイマカウント
「ラズパイ Pico」のプログラムで遅延時間を簡単に作る時は「time.sleep(1.0)」のように書きますが、この場合、タイマカウント中は割り込み処理を設定しない限り他の処理を受け付けなくなります。
入力端子ごとに割り込み処理を設定しても良いですが、「タイマー割り込み」を使用して、シーケンサの「リフレッシュ方式」を実装することで、全ての入出力端子で並列処理しているような動作を実現することができます。
サンプルプログラム内では以下のようにタイマー割り込みを実装しています。
#タイマON/OFF状態格納変数
t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = False # タイマ(10点)
# タイマ割り込み間隔設定(ms)
PERIOD = 100
# 汎用タイマ初期設定
timer_start = [False] * 10 # タイマカウント開始信号
TIM_SET = [0] * len(timer_start) # タイマカウント値設定用初期値0(x100ms)
time_value = [0] * len(timer_start) # タイマカウント現在値格納用
# インターバルタイマ初期設定
TIM_INTERVAL0 = 5 # インターバルタイマON/OFF間隔(x0.1s)
tim_interval0 = 0 # インターバルタイマカウント現在値格納用
flicker = False # フリッカON/OFF出力用
# タイマー割り込み発生時実行関数 ----------------------------------------------
def timer_callback(timer):
# グローバル変数を宣言
global TIM_INTERVAL0, tim_interval0, flicker
global timer_start, time_value
# 汎用タイマカウント処理
t_values = [] # タイマのON/OFF結果を保持するリストを準備
# 汎用タイマカウント(汎用タイマの数だけループ)
for i in range(len(timer_start)):
if timer_start[i] and time_value[i] < TIM_SET[i]: # カウント条件ONでカウント値が設定値より小さければ
time_value[i] += 1 # タイマカウント+1
#print("T{} = {}".format(i, time_value[i]))
t_values.append(time_value[i] == TIM_SET[i]) # タイマカウントが完了したかを確認しON/OFF結果を格納
time_value[i] = 0 if not timer_start[i] else time_value[i] # カウント条件OFFなら初期化
# 汎用タイマのON/OFF結果をタイマ変数に設定(汎用タイマの数だけループ)
for i, value in enumerate(t_values):
globals()[f"t{i}"] = value
# インターバルタイマ(フリッカ)
if not flicker: # インターバルOFFならカウントアップ
tim_interval0 += 1
flicker = (tim_interval0 == TIM_INTERVAL0) # カウントMAXでインターバルOFF
else: # インターバルONならカウントダウン
tim_interval0 -= 1
flicker = not (tim_interval0 == 0) # カウントMAXでインターバルOFF
# タイマー割り込み設定(間隔, モード, コールバック関数)
timer = Timer() # タイマーオブジェクトを作成
timer.init(period=PERIOD, mode=Timer.PERIODIC, callback=timer_callback)
「2行目」で「タイマON/OFF状態格納変数」を準備しています。
タイマはラダー処理の条件式内で複数回記述することになるので、簡潔に書くためにリストではなく変数で個別に宣言しています。
「5行目」でタイマ割り込み時間間隔を「ms」単位で設定します。
この時間がタイマカウントの最小値になります。
時間を短くする場合はラダー処理の「1スキャンタイム」を超えないように注意する必要があります。
「8行目」で使用するタイマの数を指定しています。初期値は10点です。
タイマを増やす場合は「タイマON/OFF状態格納変数」を増やして、ここの要素数も増やします。
「13〜15行目」では汎用的に使用できる「0.5秒のフリッカ(ON/OFF繰り返し)タイマ」を準備しています。必要に応じて使用してください。
サンプルプログラムでは「ラズパイ Pico」本体のLEDを点滅させるために使用しています。
「18行目」からタイマー割り込み発生時に呼び出す関数を定義しています。
ラダー処理内でタイマのカウント条件が成立している時に、各タイマに設定された時間カウント処理が行われます。フリッカタイマのカウントもここで行います。
「47,48行目」でタイマー割り込みを使用するための設定を行います。
タイマーを使用するにはメインループ内の「シーケンス処理」部に以下のように記入します。
以下は「T0:タイマ」を使用する例です。
'''
# T0 タイマ例 ****************************
x2 t0
--| |-┬-|/|-┬----------( m1 )--
m1 | |
--| |-┘ └--------(t0 K20)--
m1
--| |------------------( y1 )--
'''
# Pythonへ変換(T0 タイマ例)
m1 = x2 or m1 if not t0 else False
TIM_SET[0] = 20 # カウント時間設定(x0.1s)
timer_start[0] = m1 # タイマ0 のカウント条件として m1 を指定
y1 = m1
上コードの「12行目」でタイマのカウント時間を設定しています。
タイマ「T0」のカウント時間を設定するには「TIM_SET」リストの要素「0」に数値を指定します。
「13行目」でタイマカウントを開始する条件を設定しています。
タイマ「T0」のカウント条件は「timer_start」リストの要素「0」に指定し、この条件が「True」になるとカウントが開始されます。
タイマカウントが終了するとタイマ変数「t0」が「True」になります。
カウント条件が「False」になると「t0」は「False」になり、カウント時間もリセットされます。
・カウンタークラス作成によるカウンタ
カウンタの回数カウントは「カウンタークラス」を作成して、必要な時に呼び出してカウントを行います。
# カウンタークラス
class Counter:
# インスタンス変数
def __init__(self):
self.c_state = [False] * 10 # カウンタ状態格納用(10点)
self.cnt = [0] * 10 # カウンタカウント数保持用(10点)
# カウント実行(self, カウント条件, カウンタ番号, カウント値)
def counter(self, coil, num, value):
"""
カウンタの値を1つ増やします。
Args:
coil (bool): カウント条件が満たされたかどうかを表すブール値
num (int): カウンタの番号
value (int): カウントする最大値
Returns:
bool: カウンタが指定の最大値に達した場合はTrue、それ以外の場合はFalse
"""
if coil and not self.c_state[num] and self.cnt[0] < value:
self.cnt[num] += 1
self.c_state[num] = True
print("C = ", self.cnt[num])
if self.cnt[num] == value:
print("C{} ON!".format(num))
return True
if not coil: # カウント条件がOFFなら カウンタ状態をFalseへ
self.c_state[num] = False
return False
# カウント現在値取得
def get_counter_value(self, num):
return self.cnt[num]
# カウント数リセット
def reset_counter(self, num):
self.cnt[num] = 0
# -------------------------------------------------------------------
count = Counter() # 呼び出し元のプラグラムでカウンタクラスのインスタンスを作成
「カウンタークラス」内の「5,6行目」で使用するカウンタの数を指定しています。初期値は10点です。
カウンタを増やす場合は呼び出し元のカウンタ数に合わせて、要素数も増やします。
「9行目」の「count.counter()」関数を呼び出すことでカウンタの回数カウントを実行します。
この関数は以下のように呼び出します。
・カウント条件:カウントする条件を設定します。x1 やm1 のように接点変数を指定してもいいですし、「x1 and not m1(x1がON かつ m1がOFF なら)」のように条件式を設定してもいいです。
・カウンタ番号:使用するカウンタ番号を、1 や2 のように数値で設定します。
・カウント数:カウント回数の設定値を、3 や10 のように数値で設定します。
実際は以下のように指定します。
以下の例ではカウンタ「c0」を使用して「x1」がONした回数を3回カウントします。
if not c0: # カウンタC0がOFFなら
c0 = count.counter(x1, 0, 3) # カウント実行関数呼び出し(カウント条件, カウンタ番号, カウント数)
「31行目」でカウント現在値を取得する「count.get_counter_value()」関数を定義しています。
現在のカウント値を取得したい場合はカウンタ番号を指定して以下のように「count.get_counter_value()」呼び出します。
counter_value = count.get_counter_value(0) # カウンタC0のカウント現在値を取得
「35行目」でカウンタをリセットする「count.reset_counter()」関数を定義しています。
カウンタをリセットするときは、リセットしたいカウンタ番号を指定して以下のように「count.reset_counter()」呼び出します。
if x1: # X1がONなら
c0 = False # カウンタC0をOFF
count.reset_counter(0) # カウンタC0のカウント数を0リセット
上コードでは「x1」がONしたらカウンタ「c0」を「False」に設定し、リセット関数を呼び出し、カウント数をリセットしています。
・本格的なシーケンサとしての使用例
「タイマ」や「カウンタ」を含んだ本格的なラダー動作をサンプルプログラムで確認します。
サンプルプログラムでは以下の3つの基本動作のラダーが書いてあります。
(ラダー図はコード内にコメントで書いています。)
「X1」がONすると「M0」の自己保持が解除され「Y0」がOFFします。
②タイマ動作:「X2」がONすると「M1」が自己保持され「T0」タイマカウントが開始し「Y2」がONします。タイマのカウントが終了すると「M1」の自己保持が解除され「Y2」がOFFします。
③カウンタ動作:「X3」を押すごとに「C0」カウンタが+1され「Y2」が点灯します。
カウント値が設定値に達すると「C0」がONし「Y3」がONします。
「X1」がONすると「C0」カウントがリセットされます。
OLED表示器には下画像のように「アナログ入力値」と「タイマ」「カウンタ」の設定値(括弧書き)と現在値を表示しています。
「ラズパイPico」の「マルチスレッド(並列)処理」については以下のリンクで詳しく紹介しています。
サンプルプログラムは以下になります。
※下の黒塗り部の右上アイコンクリックでコピーできます。
import _thread # スレッド(並列動作)用モジュールを準備
from machine import Pin, I2C, ADC, Timer # 制御用モジュールを準備
import ssd1306 # 液晶表示器用ライブラリ
# I2C設定
i2c1 = I2C(1, sda=2, scl=3) # SSD1306用 (I2C識別ID 1, SDA, SCL)
# OLED 表示設定
i2c1_addr = i2c1.scan()[0] # 使用するSSD1306のアドレスを取得して表示(通常は0x3C)
print("OLED I2C1 Address :" + hex(i2c1_addr))
display = ssd1306.SSD1306_I2C(128, 64, i2c1) # ディスプレイ設定(幅, 高さ, 通信仕様)
# アナログ入力ピン設定
adc1 = ADC(27) # アナログ入力(A0)の端子番号27を設定
# adc2 = ADC(28) # アナログ入力(A1)の端子番号28を設定
# 本体LED(GP25)を出力設定
led = Pin(25, Pin.OUT)
# INPUT:入力端子(x0〜x3)設定(プルアップ)
input_pins = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] # 使用する入力端子を設定(リスト作成)
inputs = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in input_pins]
# OUTPUT:出力端子(y0〜y3)設定
output_pins = [18, 19, 20, 21, 22, 26] # 使用する出力端子を設定(リスト作成)
outputs = [Pin(pin, Pin.OUT) for pin in output_pins]
# グルーバル変数初期化
x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x10 = x11 = x12 = x13 = False # 入力接点(12点)
y0 = y1 = y2 = y3 = y4 = y5 = False # 出力点数(6点)
m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = \
m10 = m11 = m12 = m13 = m14 = m15 = m16 = m17 = m18 = m19 = False # 補助リレー(20点)
t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = False # タイマ(10点)
c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = False # カウンタ(10点)
# タイマ割り込み間隔設定(ms)
PERIOD = 100
# 汎用タイマ初期設定
timer_start = [False] * 10 # タイマカウント開始信号
TIM_SET = [0] * len(timer_start) # タイマカウント値設定用初期値0(x100ms)
time_value = [0] * len(timer_start) # タイマカウント現在値格納用
# 汎用カウンタ初期設定
count_value = [0] * 10 # タイマカウント現在値格納用
CNT_SET = [0] * len(count_value)
# インターバルタイマ初期設定
TIM_INTERVAL0 = 5 # インターバルタイマON/OFF間隔(x0.1s)
tim_interval0 = 0 # インターバルタイマカウント現在値格納用
flicker = False # フリッカON/OFF出力用
# タイマー割り込み発生時実行関数 ----------------------------------------------
def timer_callback(timer):
# グローバル変数を宣言
global TIM_INTERVAL0, tim_interval0, flicker
global timer_start, time_value
# 汎用タイマカウント処理
t_values = [] # タイマのON/OFF結果を保持するリストを準備
# 汎用タイマカウント(汎用タイマの数だけループ)
for i in range(len(timer_start)):
if timer_start[i] and time_value[i] < TIM_SET[i]: # カウント条件ONでカウント値が設定値より小さければ
time_value[i] += 1 # タイマカウント+1
#print("T{} = {}".format(i, time_value[i]))
t_values.append(time_value[i] == TIM_SET[i]) # タイマカウントが完了したかを確認しON/OFF結果を格納
time_value[i] = 0 if not timer_start[i] else time_value[i] # カウント条件OFFなら初期化
# 汎用タイマのON/OFF結果をタイマ変数に設定(汎用タイマの数だけループ)
for i, value in enumerate(t_values):
globals()[f"t{i}"] = value
# インターバルタイマ(フリッカ)
if not flicker: # インターバルOFFならカウントアップ
tim_interval0 += 1
flicker = (tim_interval0 == TIM_INTERVAL0) # カウントMAXでインターバルOFF
else: # インターバルONならカウントダウン
tim_interval0 -= 1
flicker = not (tim_interval0 == 0) # カウントMAXでインターバルOFF
# タイマー割り込み設定(間隔, モード, コールバック関数)
timer = Timer() # タイマーオブジェクトを作成
timer.init(period=PERIOD, mode=Timer.PERIODIC, callback=timer_callback)
# カウンタ処理クラス ---------------------------------------------------------
class Counter:
# インスタンス変数
def __init__(self):
self.c_state = [False] * 10 # カウンタ状態格納用(10点)
self.cnt = [0] * 10 # カウンタカウント数保持用(10点)
# カウント実行(self, カウント条件, カウンタ番号, カウント値)
def counter(self, coil, num, value):
"""
カウンタの値を1つ増やします。
Args:
coil (bool): カウント条件が満たされたかどうかを表すブール値
num (int): カウンタの番号
value (int): カウントする最大値
Returns:
bool: カウンタが指定の最大値に達した場合はTrue、それ以外の場合はFalse
"""
if coil and not self.c_state[num] and self.cnt[0] < value:
self.cnt[num] += 1
self.c_state[num] = True
print("C = ", self.cnt[num])
if self.cnt[num] == value:
print("C{} ON!".format(num))
return True
if not coil: # カウント条件がOFFなら カウンタ状態をFalseへ
self.c_state[num] = False
return False
# カウント現在値取得
def get_counter_value(self, num):
return self.cnt[num]
# カウント数リセット
def reset_counter(self, num):
self.cnt[num] = 0
counter = Counter() # カウンタのインスタンスを作成
# カウンタ処理関数 ----------------------------------------------------------
def count(coil, num, value): #カウント処理 (カウント条件, カウンタ番号, カウント設定値)
global count_value # カウント現在値(液晶表示用)
global CNT_SET # カウント設定値(液晶表示用)
CNT_SET[num] = value # カウンタ設定値の取得(液晶表示用)
result = counter.counter(coil, num, value) # カウント結果の取得
count_value[num] = counter.get_counter_value(num) # カウント現在値の取得
return result
def count_reset(num): # カウントリセット処理
global count_value # カウント現在値(液晶表示用)
count_value[num] = 0 # カウンタ0リセット(液晶表示用)
counter.reset_counter(num) # カウンタリセット(カウンタ番号)
# ラダー処理(Core0) ---------------------------------------------------------------
def ladder():
global m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15,m16,m17,m18,m19
global c0,c1,c2,c3,c4,c5,c6,c7,c8,c9
while True: # ずっと繰り返し
# 入力状態スキャン
x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()
x4, x5, x6, x7 = not inputs[4].value(), not inputs[5].value(), not inputs[6].value(), not inputs[7].value()
x10, x11, x12, x13 = not inputs[8].value(), not inputs[9].value(), not inputs[10].value(), not inputs[11].value()
# ************* シーケンス処理(ラダーから変換) *************
'''
# 自己保持動作確認 *************************
x0 x1
--| |-┬-|/|------------( m0 )--
m0 |
--| |-┘
m0
--| |------------------( y0 )--
'''
# Pythonへ変換(基本ラダー例)
m0 = x0 or m0 if not x1 else False
y0 = m0
'''
# T0 タイマ例 ****************************
x2 t0
--| |-┬-|/|-┬----------( m1 )--
m1 | |
--| |-┘ └--------(t0 K20)--
m1
--| |------------------( y1 )--
'''
# Pythonへ変換(T0 タイマ例)
m1 = x2 or m1 if not t0 else False
TIM_SET[0] = 20 # カウント時間設定(x0.1s)
timer_start[0] = m1 # タイマ0 のカウント条件として m1 を指定
y1 = m1
'''
# C0 カウンタ例 ****************************
x3
--| |---------------( c0 K3 )--
x1
--| |--------------( RST c0 )--
x3 c0
--| |---|/|------------( y2 )--
c0
--| |------------------( y3 )--
'''
# Pythonへ変換(C0 カウンタ例)
if not c0: # カウント開始条件
c0 = count(x3, 0, 3) # (カウント条件, カウンタ番号, カウント設定値)
if x1: # カウンタリセット条件
c0 = False
count_reset(0) # カウンタリセット(カウンタ番号)
y2 = x3 and not c0
y3 = c0
# 本体LED動作(常時点滅、m0がONなら点灯)
led_state = flicker or m0
led.value(led_state)
# ************* シーケンス処理 ここまで *************
# 出力実行
outputs[0].value(y0)
outputs[1].value(y1)
outputs[2].value(y2)
outputs[3].value(y3)
# 補助機能処理 A/D変換、OLED表示(Core1) ----------------------------------------------------
def sub_function(t_set, t, c_set, c):
CNT_DISP = 10 # ADC1更新間隔
cnt_disp = CNT_DISP # ADC1更新間隔初期化
adc_volt1 = 0
while True: # ずっと繰り返し
# 液晶画面表示内容設定
display.fill(0) # 表示内容消去
# ADC表示
cnt_disp -= 1 # カウント -1
if cnt_disp == 0: # ADC1値更新
cnt_disp = CNT_DISP # 更新間隔初期化
adc_val1 = adc1.read_u16() # アナログ入力(ADC)値1を取得
adc_volt1 = (adc_val1 * 3.3) / 65535 # アナログ入力(ADC)値1を電圧(3.3V)に変換
display.text('ADC1:{:5.2f}V'.format(adc_volt1), 5, 0, True) # ADC1電圧表示
display.hline(0, 10, 128, True) # 指定座標から横線
# タイマ表示
display.text('T0:{:3d} ({:d})'.format(t[0], t_set[0]), 5, 15, True)
display.text('T1:{:3d} ({:d})'.format(t[1], t_set[1]), 5, 27, True)
display.hline(0, 38, 128, True)
# カウンタ表示
display.text('C0:{:3d} ({:d})'.format(c[0], c_set[0]), 5, 43, True)
display.text('C1:{:3d} ({:d})'.format(c[1], c_set[1]), 5, 55, True)
# 設定した内容の表示実行
display.show()
_thread.start_new_thread(sub_function, (TIM_SET, time_value, CNT_SET, count_value)) # Core1処理
ladder() # Core0処理
5.ガーバーデータの公開
この基板を製造・発注するためのガーバーデータを以下からダウンロードできます。
以下のリンク先で基板の仕様や回路図をサンプルプログラムと一緒に公開しています。
実際に基板製造を依頼する場合は、以下の注意点をご確認の上、自己責任にてご使用をお願いします。
ガーバーデータの確認方法
基板製造を行うための「ガーバーデータ」の確認は「ガーバービューワー」ソフトで行います。
ガーバーデータの確認には以下のリンクをクリックします。
以下のようなページが表示されたら画面中央の「+」アイコンをクリックします。
パソコンのフォルダが表示されたら、読み込むガーバーデータを選択します。
データが読み込まれると以下のように、基板の表「top」画像が表示されます。
「bottom」をクリックすると基板裏面のデータが確認できます。
「layers」をクリックすると、各レイヤーのデータが個別に確認できます。
初期画面は見にくいので、以下画面左のレイヤー項目右のアイコン部で「Shift+Click」すると、圧縮ファイルの中の各レイヤーのデータが個別に確認できます。
6.ガーバーデータから基板を発注する方法
・基板製造・発注なら「PCBGOGO」
基板製造業者はたくさんありますが「安価」で「短納期」の「PCBGOGO」さんがおすすめです。
ウェブサイトでは、24時間のオンラインカスタマーサービスもあり、発注ごとに日本語のできるスタッフが担当してくれるため、何かあったときはメールで問い合わせすることもできます。
ガーバーデータから基板を発注する方法は、以下のリンクでより詳しく紹介していますので、こちらを参照してください。(この基板と同じガーバーデータで紹介しています。)
・見積もり価格の確認
基板製造の見積もり価格は、以下ボタンから「PCBGOGO」さんのホームページで「基板寸法」と「枚数」を入力するだけで簡単に確認できるので、価格だけでも確認してみてください。安いです♪
以下のようなページが表示されたら[自動お見積もり]ボタンをクリックします。
以下のページが表示されたら[外形寸法(長さ×幅)]と[枚数]を入力して、[見積作成]ボタンをクリックします。
(今回はページ上の「5.ガーバーデータの公開」でデータを使用して、基板寸法「90 x 80.7mm」枚数「5枚」で確認しています。)
以下のようにページ右に見積もり金額が表示されます。
新規会員登録の場合は初回のみ割引が受けられるので、必ず以下のように2箇所の[新規ユーザー割引]に「チェック」を入れましょう。
「$1」で基板製造を依頼するには「新規会員登録」が必要なため[今すぐ登録!]をクリックして、会員登録を行いましょう。
会員登録から基板発注までの手順は以下のリンクでより詳しく紹介しています。
7.部品リスト
「LogikaraーPLC」に使用した部品リストは以下になります。
全て「秋月電子通商」さんで購入できるので、秋月電子さんの品番も載せています。
記号 | 品名 | 型式 | メーカ | 秋月電子品番 | 数量 | 単価 | 合計 | 値 | 備考 |
---|---|---|---|---|---|---|---|---|---|
C1, C3, C4 | セラミックコンデンサ | RDER71H104K0P1H03B | 村田製作所 | P-13582 | 3 | ¥10 | ¥30 | 0.1uF | 10個入 100円 |
C2 | 電解コンデンサ | 16MH5100MEFC6.3X5 | ルビコン | P-05002 | 1 | ¥20 | ¥20 | 100uF | |
D1〜14 | 発光ダイオード | OSG8HA3Z74A | OptoSupply | I-11637 | 12 | ¥10 | ¥180 | 緑 | |
D13〜18 | 発光ダイオード | OSR5JA3Z74A | 〃 | I-11577 | 6 | ¥10 | ¥60 | 赤 | |
R1〜18 | 炭素皮膜抵抗 | 1/6W 390Ω | – | R-16391 | 18 | ¥1 | ¥18 | 390Ω | 100本 100円 |
R19 | 炭素皮膜抵抗 | 1/6W 10kΩ | – | R-16103 | 1 | ¥1 | ¥1 | 10kΩ | 100本 100円 |
U1〜12 | フォトカプラ | TLP785 GBランク | 東芝 | I-07554 | 12 | ¥20 | ¥240 | ||
U13 | Raspberry Pi Pico | Raspberry Pi Pico | Raspberry Pi | M-16132 | 1 | ¥770 | ¥770 | ||
SSD1306 | OLEDディスプレイ | SSD1306 | SUNHOKEY | P-12031 | 1 | ¥580 | ¥580 | ||
U14 | トランジスタアレー | ULN2003AN | HTC Korea | I-15064 | 1 | ¥40 | ¥40 | ||
RESET1 | タクトスイッチ | DTS-63-F-N-V-RED(TS-0606-F-N-R | Cosland | P-03646 | 1 | ¥10 | ¥10 | 赤 | 黒、白、青、黄もあり |
J1〜9,11〜13 | ターミナルブロック 2P | APF-102 | PHOENIX CONTACT | P-08369 | 4 | ¥20 | ¥80 | ||
ターミナルブロック 3P | APF-103 | PHOENIX CONTACT | P-08370 | 10 | ¥30 | ¥300 | |||
J6 | ピンソケット 4P | FH-1x4SG/RH | Useconn Electronics | C-10099 | 1 | ¥20 | ¥20 | ||
(Pico) | ピンソケット 20P | FH-1×20 | 〃 | C-03077 | 2 | ¥50 | ¥100 | ||
(Pico) | ピンヘッダ 40P | PH-1x40SG | 〃 | C-00167 | 2 | ¥35 | ¥70 | 4Pと 20Px2に 分割して使用 | |
ー | ゴム足 | ー | ー | P-10080 | 1 | ¥60 | ¥60 | 4個入 | |
合計 | ¥2519 |
8.部品の半田付け手順
基板に実装する部品は全てDIP部品を使用しているので、比較的簡単に半田付けできると思いますが、背の低い部品から順番に半田付けしていくと、より簡単に半田付けできるので、おすすめの手順を以下で紹介します。
実装する部品は以下のようになります。
まずは一番背の低い抵抗から、リードを曲げて基板に差し込んで半田付けしていきます。
「R19」のみ「10kΩ」なので先に差し込み、残りは全て「390Ω」を差し込みます。
ひっくり返して押し付けると、抵抗が浮かずに半田付けできるのでこの状態でまとめて半田付けしましょう。
抵抗を全て半田付けしたら、リードをカットします。
次に背の低い「フォトカプラ」と「トランジスタアレー」を半田付けします。
「フォトカプラ」と「トランジスタアレー」には向きがあります。
「フォトカプラ」の「丸印」や「トランジスタアレー」の「切り欠き」を、基板のシルク印刷の切り欠き部と合わせて差し込んで半田付けします。
「発光ダイオード(LED)」にも向きがあります。
リード(足)の長い方を丸ランド(基板の外側)に差し込みましょう。
全てリードの長い方を外側に差し込んで、基板裏に出たリードは曲げておくと安定します。
ひっくり返して半田付けしますが、リードの長い方を先に半田付けして、向きを整えてからもう片方のリードを半田付けすると綺麗に半田付けできます。
次に「コンデンサ」を半田付けしていきます。
「電解コンデンサ(C2)」には向きがあるので、上画像の向きで差し込みます。
「セラミックコンデンサ」には向きはありません。
「コンデンサ」もリードを曲げておき、ひっくり返して半田付けします。
次に、端子台を上画像のように準備します。
ここで「タクトスイッチ」を基板に差し込んでおきましょう。
「端子台」は組み合わせることができるので、上画像のように組み合わせておきます。
「端子台」を組み合わせたら、基板に差し込んで、ひっくり返して、「タクトスイッチ」と一緒に半田付けします。
次に「ラズパイPico」に「ピンヘッダー」を半田付けします。「40Pのピンヘッダー」を20Pづつに割って準備しておきます。
「ピンヘッダー」は1個づつ半田付けすると位置合わせが大変なので、上画像のようにブレッドボードに挿しておくと安定します。
「ピンヘッダー」に「ラズパイPico」を載せて半田付けします。
「ピンヘッダー」が半田付けできたら、「20Pのピンソケット」を準備します。
「ピンソケット」は上画像のように「ピンヘッダー」に差し込んでおきます。
「ラズパイPico」は実装向きがあるので注意しましょう。
上画像のような向きで基板にのせて、ひっくり返して半田付けします。
最後に「OLED表示器」と「4Pのピンソケット」を半田付けします。
「ピンソケット」は「OLED表示器」に取り付けておきましょう。
「OLED表示器」は安定しないので、1箇所半田付けして位置を整えながら、残りの箇所を半田付けしていきます。
以上で全ての部品の半田付けが完了しました。
9.まとめ
Raspberry Pi Picoの機能を最大限に活かし、シーケンサ(PLC)として簡単にラダープログラムをPythonで作成、実行できる基板について詳しく紹介しました。
この基板は、LEDや端子台、OLED表示器等を実装しているため、「ラズパイPico」の動作確認用基板としても便利に使用することができます。
シーケンサーは、産業用途や自動化システムで広く普及しており、複数の入出力を容易に制御するために使用されていますが、産業用なので価格は高価です。
小規模な用途で安価に、シーケンサのように使えるものがあれば便利と思い作ったものが今回の基板で、「ラダー図」程ではないですが「Python」でラダーのようにプログラミングが書けるため、小規模な用途では手軽に使えて、動作の修正やメンテナンスも容易にできるので、便利に使えるのではと思います。
基板製造のための「ガーバーデータ」も公開しました。今は個人でも基板製造を安価で発注できるようになってますので、興味のある方は基板を作って動作確認してみてください。
コメント