見出し画像

Raspberry Piでやってみた2(電子工作):サーボモーター制御によるシリアル通信およびPWM信号の理解


1.概要

 「Rasberry Piでサーボモーター制御」を実装することで下記を学習していきます。

  • サーボモーター

  • シリアル通信(I2C)

  • PWM信号

  • その他:Rasberry Pi4、簡単なスクリプト、Bluetoothでの接続

 基本的に下記記事を参照させていただいておりますが、躓いた部分や自分の学習部分を追加しました。

2.購入品一覧

 購入品一覧は下記の通りです。Rasberry Piは購入済みであることを前提としています。

2-1.必須購入品

 必須品(Rasberry Piを除く)※としては下記の通りです。
※全章を実装する場合。

  1. スイッチングACアダプター5V4A AD-A50P400:サーボモーターへの電源供給用

  2. 2.1mm標準DCジャック ⇔ スクリュー端子台:ACアダプタからの電源を+とーのスクリュー端子台に変換してくれる部品

    • ACアダプターとPCA9685を繋ぐための変換部品みたいな感じ

  3. サーボ&PWM駆動キット PCA9685:モーター制御するためのドライバ

  4. マイクロサーボモーター SG90:小型モーター

    • サーボモーターは回転を制御できるモーター

    • 他にDCモーターやステッピングモーター、振動モーター等がある。

  5. ジャンパー線:装置同士を簡単(差し込み)だけで結合できる部品

    • オス-オス2本:スクリュー端子台とPCA9685を接続

    • メス-メス4本:Rasberry PiとPCA9685の制御関係を接続

  6. Dual Shock 4 CUH-ZCT2J:ワイヤレスコントローラー

    • 無線規格:Bluetooth Ver2.1+EDR準拠

2-2.持ってて当たり前のもの:工具

 (普通の人は持ってなくても)必要な工具は下記の通りです。私ははんだ付けの工具は持ってなかったため一式購入しました。

  1. ドライバー:端子台のねじ締め

  2. はんだ付け用製品一式: PCA9685のヘッダーをはんだ付け

    • 無いと発生する問題は5章参照

    • 必要な一覧は下記記事参照し、追加で吸煙器も購入しました。

2-3.任意品

 任意(あったら便利)の物は下記の通りです。

  1. オシロスコープ:入力した信号の電圧の変化を時間の関数として視覚的に表示する電気計器

  2. 延長コード:自分の作業机とコンセントの位置が微妙なのでアダプターが届かない。

3.メモ:技術情報

 備忘録として本学習で関連する技術情報を記載しました。詳細が不明な点もある(特にシリアル通信)ため、深い記載まではできておりません。

3-1.サーボモーター(SG90)とは

 サーボモータは、特定の角度や位置に回転できる装置でありロボットアーム、ステアリングホイール、カメラのジンバルなどに使用されます。配線の色は用途に応じて割り振られております。

3-1-1.SG90の仕様確認

 SG90の制御を行うために下記数値の確認が必要です。

  1. 周波数[Hz]

    • 下図より、50Hz(周波数のパルス幅は計算より20msとなる)

  2. 設定角度のパルス幅[ms]

    • 下図より、中央=1.45ms, 右(90°)=2.4ms, 左(-90°)=0.5ms

    • デューティ比(Duty Cycle)とは、周期的な現象において、"ある期間" に占める "その期間で現象が継続される期間" の割合

  3. 分解能[-]

    • 分解能はPCA9685のパラメータとなります。

https://akizukidenshi.com/download/ds/towerpro/SG90_a.pdf
https://dotstud.io/docs/pulse-width-modulation/

3-1-2.工業用モーターの種類

 参考までに、工業用で使用されるモーター(電動機)は下記の通りです。価格も記載順に高くなっていきます。

  1. 三相モーター:汎用モーター

    • 通信や制御もない。回転数制御をする場合はインバータが必要

  2. ギヤードモーター:三相モーターにギアを追加してトルクを変えることができるもの

    • トルクが変わると回転数も変わることに注意

  3. サーボモーター:通信信号をやり取りしながら速度や位置を制御

    • 自由度が高く高精度の位置決めができるため半導体工場などの自働化ロボットに使用されたりする

    • 高機能な分、価格は最も高い

https://www.yaskawa.co.jp/product/servomotor/about

3-2.PCA9685キット

 3-2-1.Adafruitとは

 Adafruit(エイダフルート)は、2005年に設立されたオープンソースのエレクトロニクスハードウェアの開発と製造に注力する企業であり、製品の開発・製造はアメリカ合衆国で行っています。

 今回購入した物とは異なりますが、Adafruitでも同様にモーター制御するためのドライバとして"Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685"を販売しています。

 今回使用するPCA9685制御用のPythonライブラリはAdafruitがGitHubに公開しているものを利用します(上:DEPRECATED、下:最新)。

 3-2-2.NXP社とは

 NXPセミコンダクターズN.V.はオランダに拠点を置く半導体メーカーであり、2006年にフィリップスの半導体部門より分社化して誕生した。主にマイクロコントローラのNXP LPCシリーズや、ディスクリートなどの汎用製品を提供している。

 3-2-3.PCA9685とは

 PCA9685はNXP社が提供している「6-Channel, 12-Bit PWM Fm+ I²C-Bus LED Controller」であり下記特徴があります(出典:PCA9685データシート)。

  1. 12-bit resolution for each output - for servos, that means about 4us resolution at 60Hz update rate

    • 分解能[ステップ数]は12bit

  2. 1 MHz Fast-mode Plus compatible I2C-bus interface with 30 mA high drive capability on SDA output for driving high capacitive buses

  3. 4096-step (12-bit) linear programmable brightness per LED output varying from fully off (default) to maximum brightness

  4. External 50 MHz (max.) clock input

  5. Noise filter on SDA/SCL inputs

  6. Operating power supply voltage range of 2.3 V to 5.5 V 

  7. 5.5 V tolerant inputs

 PCA9685は基板上にある集積回路のことだと思います。なお、PCA9685のピンは片面14pin(計28pin)であり下記配置になっています。

 3-2-4.サーボ&PWM駆動キット

 今回購入した「I2C接続16チャンネル サーボ&PWM駆動キット」は、回路図よりPCA9685を使用したモーター制御用回路を意味していると思います。

 本キットにより下記事項を簡単に実装できるようになります。

  1. マイコンボード(Rasberry Pi)と$${I^{2}C}$$接続

  2. 1つのボードで最大16個(16チャンネル)の機器(モーター)を制御可能

    • $${I^{2}C}$$アドレス設定することで最大62ボード(992チャンネル)の出力を出すことができる

  3. PWM(Pulse Width Modulation)信号を出力

3-3.シリアル通信

 3-3-1.シリアル通信の種類

 シリアル通信とは、1つのデータ伝送路を使い1ビットずつデータを送信する方式です。シリアル通信には下記方式があります。

  1. SPI通信:4つの信号線(SIMO、SOMI、SCLK、SS)を使用して通信を行う同期式シリアル通信の一つ

    • SIMO(Slave In Master Out):マスターモード時はデータ出力ピンになり、スレーブモード時はデータ入力ピンになる。

    • SOMI(Slave Out Master In):マスターモード時はデータ入力ピンになり、スレーブモード時はデータ出力ピンになる。

    • SCLK(Serial Clock):マスターモード時はクロック出力ピンになり、スレーブモード時はクロック入力ピンになる。

    • SS(Slave Select):Lowアクティブの信号になり、マスターは複数あるスレーブ機器のなかで、通信したいスレーブ機器のSS端子をLowに制御し通信を行う

  2. I2C(Inter-Integrated Circuit)通信:2つの信号線を使って通信を行う

    • フィリップス社(現NXP社)が提唱する通信インターフェースでクロックに同期させてデータの通信を行う同期式シリアル通信のひとつ

    • SDA(Serial Data):メインとノードがデータを送受信するために使用するライン

    • SCL(Serial Clock):クロック信号の伝送に使用するライン。マスター側がSCLをスレーブに出力し通信を行う

  3. UART通信:非同期でシリアル通信であり、信号線は送信用のデータラインと受信用のデータラインの2線で通信を行い、クロック用のラインは存在しない

    • TXD(Transmit Data):データ送信に使用

    • RXD(Receive Data):データ受信に使用

    • UARTは”Universal Asynchronous Receiver/Transmitter”の略語

  4. CAN(Controller Area Network):ISOで国際的に標準化されたシリアル通信プロトコル。非同期通信でありクロック信号線は無い。

    • 車の内部機器などのデータ転送に多く使われる規格

    • 複数ユニットとの通信でもケーブルが共通ネットワークに繋がってさえいれば通信可能なことや、ID を設定することによって必要なデータのみ受信出来る特徴がある

https://emb.macnica.co.jp/articles/8191/

 3-3-2.シリアル通信のインターフェース

 シリアルインターフェースの規格については下記の通りです。

  1. RS-232:EIA(米国電子工業会)によって定められている、コンピュータ等のデータ通信機器間のインターフェイス条件(電気的、機械的な接続条件)

  2. RS-422:RS-232Cよりもノイズに強い平衡型のインターフェイス

  3. RS-485:基本的にRS-422と同じだが、RS-422が1対1の伝送に適用されるのに対し、RS-485はバス方式に適用可能

https://fiberopticnetworking.hatenablog.com/entry/2018/10/19/111930
https://www.upcomnet.com/blog/difference-between-rs232-rs422-rs485-converters-2.html

【参考:USBとは】
 USBはユニバーサル・シリアル・バス(Universal Serial Bus)という規格の略称であり、コンピュータに周辺機器を接続するためのシリアルバス規格の1つです。従来から多く使用されてきた、汎用シリアルインターフェース(RS232C, RS422/485)などに代わって、PCインターフェースの主流になっています。

 3-3-3.シフトレジスター

 一般的にマイコン内部ではパラレルで出力(データを8bitのような単位でまとめて処理)されます。しかし、通信距離が長くなるとコスト増となるためシリアル通信の方がパラレル通信より優れています。そのためマイコン等にはシリアルとパラレルを変換できる機器がついており、順序論理回路(シフトレジスター)と呼ばれます。
 シフトレジスターはシリアル-パラレル変換 (SI-PO) やパラレル-シリアル変換 (PI-SO) を行います。

3-4.GPIO(General Purpose Input/Output)

 GPIO(General Purpose Input/Output)とは、Raspberry Pi上に搭載されている信号ピンのことであり、GPIOを通じて電気信号を送受信することで、様々なデバイスを制御することが可能になります。
 ピンの配置は下記の通りであり、詳細(回路図)は「schematic diagrams」に記載されています。GPIOの主要な機能は下記4つに分類できます。

  1. 電源ピン: 電源を供給するピン

    • 3V:①,⑰、5V:②,④

  2. GNDピン: グランドピン(⑥,⑨,⑭,⑳,㉕,㉚,㉞,㊴)

    • 回路内の基準となる電位との差が「0V(ボルト)」になる部分

    • アースとGNDは別物

  3. DNC: ユーザが使用できないピン(do not connect)

    • 旧ラズパイにはあったがRasberry Pi4には無し

  4. I/O: 汎用入出力ピン

https://www.raspberrypi.com/documentation/computers/os.html

 またGPIOの特徴は下記の通りです。

  • 3.3Vの出力に設定されたGPIOピンは、全てのピンを合わせて約50mAの電流しか取り出せない

  • 出力はhigh (3.3V) or low (0V)

  • 1本のGPIOピンが扱える電流は16mA

  • いくつかのGPIOピンは特殊な機能を併せ持ち、プログラムから切り替えて使用可能(詳細は下図参照)

https://pinout.xyz/

3-5.PWM(Pulse Width Modulation)信号

 PWM(Pulse Width Modulation)信号とは、パルス幅を変えることでFETなどの素子に流れる電流の時間を変化させ、ヒーターやモーターを制御する信号です。
 参考としてRasberry Pi4はGPIO12pinの1つのみPWM信号を出力可能です。

3-6.回路図のVCC、VDD、VEE、VSS、VBAT

 各種用語の意味は下記記事参照させていただきました。

  1.  VCC:バイポーラトランジスタ回路のプラス電源。NPNトランジスタのコレクタ側の電源。cを2つ繰り返すのは、単にVcだとコレクタ電圧と紛らわしいため。

  2. VEE:バイポーラトランジスタ回路のマイナス電源。片(単)電源方式ならGND。NPNトランジスタのエミッタ側の電源。

  3. VDD:FET回路のプラス電源。NチャネルFETのドレイン側の電源。

  4. VSS:FET回路のマイナス電源。片(単)電源方式ならGND。NチャネルFETのソース側の電源。

4.装置の組み立て

  装置の全体ブロック図は下記の通りです。記事に詳細はありますが、電子工作初心者なので細かい部分を記載しました。

https://note.com/yokoyan_pws/n/nd689ab1f199f

4-1.2.1mm標準DCジャック⇔スクリュー端子台

 サーボモーターを動かすために電源を供給する必要があります。4A以上電流を流すことができる「スイッチングACアダプター(図下)」を用意しましたが、PCA9685に電流を流すための「ターミナルブロック(図右上)」に直接接続できないため「2.1mm標準DCジャック ⇔ スクリュー端子台(図左上)」を使用しました。
 スクリュー端子台とターミナルブロックはジャンピングワイヤーを差し込んでねじ止めするだけで簡単に結合できました。

 PCA9685のCN4(外部電源供給用端子)に接続すると電流が流れて青色LEDが光ります(はんだ付けしていないためLEDの光度が不安定)

4-2.サーボ&PWM駆動キット PCA9685の組み立て

 サーボ&PWM駆動キットには下記部品が付属されています。

https://akizukidenshi.com/download/ds/akizuki/AE-PCA9685.pdf

 下図の通り、基板とピンヘッダは分離されておりはんだ付けされておりません。後述の通り、はんだ付けは必須のため最低でも使用する部分は全てはんだ付けしました。

5.Rasberry Piの環境構築

5-1.Rasberry Piの準備

 Rasberry Piはプログラミングできるところまで準備します。環境構築の方法は下記記事の通りです。

 また、Rasberry PiとPCA9685でI2Cによるシリアル通信を行うため、「インターフェイスの有効化」でI2Cを有効化しておきます。

5-2.Adafruit_PCA9685のPythonライブラリ

 AdafruitのPCA9685 PWMサーボ/LEDコントローラードライバーのPythonライブラリをインストールします。参照記事をベースにしておりますが、中テインは下記の通りです。

  1. Adafruit_Python_PCA9685.git”は既に非推奨

  2. 最後のセットアップの実行は"pyrhon"->"python3"に修正

    • pythonのままだと”error: Setup script exited with 1”が発生

[Terminal]
sudo apt-get install git build-essential python-dev
git clone https://github.com/adafruit/Adafruit_Python_PCA9685.git
cd Adafruit_Python_PCA9685
sudo python3 setup.py install

 エラーが出た場合は追加で下記対応のこと。

[Terminal]
sudo pip install adafruit-pca9685

6.モーター制御

6-1.設計方針の確認

 モーター制御のPythonスクリプトを記事に従って作成します。

 設計に必要な情報は下記の通りです。

  1. SG90仕様書より、設定角度のパルス幅[ms]は中央=1.45ms, 右(90°)=2.4ms, 左(-90°)=0.5ms

  2. サーボモーター1°あたりのパルス間隔 [ms]は、SG90は最大180°(-90°~90°)動くため、設定角度のパルス幅から計算

  3. SG90仕様書より、周波数[Hz]は50Hzより周波数のパルス幅[ms]は20ms

  4. PCA9585仕様書より、分解能[ステップ数]は12bitから$${2^{12}=4096}$$

  5. PMWをセットする関数はpwm.set_pwm

    1. channel引数(0~15):サーボモーターを接続してある「チャンネル番号」を指定。PCA9685キットにSG90を接続した場所の番号を指定

    2. on引数(0~4095):開始値であり、通常は「0」を指定

    3. off引数(0~4095):サーボモーターの動作終了時のパルス値を入力(※パルス値は指定したい角度から計算)

【パラメータ計算】

$$
周波数のパルス幅[ms] = \frac{1 秒}{周波数[Hz]} \times 1000 = \frac{1000}{50}= 20 ms
$$

$$
分解能[ステップ数]= 2^{bit数} = 2^{12} = 4096
$$

$$
サーボモーター1°あたりのパルス間隔 [ms]= \frac{設定角度の最大パルス幅 - 設定角度の最小パルス幅}{180}
$$

【入力値換算】

$$
要求角度のパルス幅 = 中心パルス幅 + (指定角度(入力値) \times 1度あたりのパルス幅)
$$

【出力値:PMW信号】
 数式は下記の通りですが、結果的に20msで4096-step (12-bit)を単回帰で取り、回転角度->パルス幅->tick(step数)に変換している式と思います。

$$
\begin{aligned}
PCA9685に設定するPWM値
&= \left(\frac{設定角度のパルス幅[ms]}{\frac{1000}{PWM信号の周波数} \times \frac{1}{PCA9685の分解能[ステップ数]}}\right)
\\
\\&=\left( \frac{設定角度のパルス幅[ms]}{\lparen\frac{周波数のパルス幅[ms]}{PCA9685の分解能[ステップ数]}\rparen}\right)
\end{aligned}
$$

6-2.Pythonスクリプト

 moter.pyをいれたディレクトリに移動して下記実行しました。設定の通り-45°~45の範囲でモーターが動いていることを確認できました。
 参考までに計算書も作りました。数値のずれは四捨五入(round)や切り捨て(int)によるものと思います。

https://docs.sunfounder.com/projects/ultimate-sensor-kit/ja/latest/components_basic/27-component_servo.html
[moter.py]
import Adafruit_PCA9685
from time import sleep

# 設定周波数(Hz)
set_freq = 50

# 動作角度からPCA9685に渡す値を変換
def convert_deg(deg,freq):

	# 分解能(ステップ数)
	step = 4096 # 12bit=2^12=4096

	# 接続サーボモーターの最大最小角度時のパルス間隔(ms)※値はSG90の仕様に基づくため必要に応じて調整
	max_pulse = 2.4 # 90°時
	min_pulse = 0.5 # -90°時

	# サーボモーターの0°時のパルス間隔(ms)※仕様書記載の通り1.45ms(0°)
	center_pulse = (max_pulse - min_pulse) / 2 + min_pulse  

	#サーボモーター1°あたりのパルス間隔(ms)
	one_pulse = round((max_pulse - min_pulse) / 180, 2)

	# 要求角度のパルス間隔(ms)を算出しPCA9685に渡す値を算出
	deg_pulse = center_pulse + deg * one_pulse
	deg_num = int(deg_pulse / (1.0 / freq * 1000 / step))

	# デバッグ
	print(f'角度(Input):{deg}°, パルス間隔(ms):{deg_pulse:.3f}, PCA9685(Out):{deg_num}')

	return deg_num

# PCA9685の初期化
pwm = Adafruit_PCA9685.PCA9685() #PWM:Pulse Width Modulation
pwm.set_pwm_freq(set_freq) #周波数を設定

#サーボモーター制御:0°->45->0->-45
try:
	while True:
		pwm.set_pwm(channel=0, on=0, off=convert_deg(0,set_freq)) #サーボモーターを0°に設定
		sleep(1)
		pwm.set_pwm(channel=0, on=0, off=convert_deg(45,set_freq)) #サーボモーターを45°に設定
		sleep(1)
		pwm.set_pwm(channel=0, on=0, off=convert_deg(0,set_freq)) #サーボモーターを0°に設定
		sleep(1)
		pwm.set_pwm(channel=0, on=0, off=convert_deg(-45,set_freq)) #サーボモーターを-45°に設定
		sleep(1)

except KeyboardInterrupt:
	pwm.set_pwm(channel=0, on=0, off=0) #サーボモーターを0°に設定
	print("KeyboardInterrupt")
	pass
[terminal]
python3 moter.py 

[Out]
角度(Input):0°, パルス間隔(ms):1.450000, PCA9685(Out):296
角度(Input):45°, パルス間隔(ms):1.900000, PCA9685(Out):389
角度(Input):0°, パルス間隔(ms):1.450000, PCA9685(Out):296
角度(Input):-45°, パルス間隔(ms):1.000000, PCA9685(Out):204
角度(Input):0°, パルス間隔(ms):1.450000, PCA9685(Out):296
角度(Input):45°, パルス間隔(ms):1.900000, PCA9685(Out):389
角度(Input):0°, パルス間隔(ms):1.450000, PCA9685(Out):296
角度(Input):-45°, パルス間隔(ms):1.000000, PCA9685(Out):204
角度(Input):0°, パルス間隔(ms):1.450000, PCA9685(Out):296
角度(Input):45°, パルス間隔(ms):1.900000, PCA9685(Out):389
角度(Input):0°, パルス間隔(ms):1.450000, PCA9685(Out):296
角度(Input):-45°, パルス間隔(ms):1.000000, PCA9685(Out):204

6-3.メモ_エラー対応

 私が確認できたエラーを備忘録で載せておきます。

 6-3-1.ModuleNotFoundError: No module named 'Adafruit_GPIO'

 ターミナルから実行する場合、Conda環境だとGPIOとの相性が悪そうなので"conda deactivate"で仮想環境から抜けて実行しました。

 6-3-2.OSError: [Errno 121] Remote I/O error

 結論は「ちゃんとはんだ付けできていない」です。
(他はジャンピングワイヤーの差し方が甘いこともあります)

 上記エラーが発生したためRaspberry Piで接続されているI2Cデバイスを検出するために、以下のコマンドを実行しました。これでPCA9685(Rasberry Piがマスターのためスレーブ側)のアドレスが確認できます。

[Terminal]
sudo i2cdetect -y 1

 結果として、ちゃんとはんだ付けしないと出力がなく、はんだ付け後はI2Cアドレス 0x400x70 でデバイスが検出されていることが確認できました。アドレスは一意のアドレス(通常16進数)で表現されます。

  • 0x40 アドレス:PCA9685のデフォルトアドレス

    • 16進数:40の2進数:0100 0000

  • 0x70 アドレス:おそらくPCA9685で使用する特別なアドレス

    • 16進数:70の2進数:0111 0000

【アドレスの0xの意味】
 アドレス"0x40 "の"0x"は16進数であることを意味します。つまり"0x40 "は16進数の40であることを示します。
 なお"0b"の場合は二進数を表します。

 6-3-3.コードは動作するのにサーボモーターが動かない

 これも上記と同じくはんだ付けしていないと動きません。今回は0と1を使用する(番号はボード表に記載あり)ため、最低でもこのピンは全てはんだ付けする必要があります。
 ちなみに当然ですがサーボモーター用の5V電源を繋ぐことも必要です。

7.Dual Shock 4からBluetoothで制御

 最後にPS4コントローラ(Dual Shock 4)からBluetoothでRasberry Piと接続して、Dual Shock 4からサーボモーターを制御します。

7-1.環境構築

 環境構築は下記記事を参照しました。丁寧に記載されているため、コードのみ下記に記載しました。

 コマンドは下記の通りです(Rasberry PiのBluetoothは事前にONに設定)。

[Terminal]
sudo pip install pyPS4Controller
sudo pip install ds4drv
sudo ds4drv

 PS4コントローラーのPSボタンとSHAREボタンを同時に長押し(5sくらい)すると、接続が完了します。

 最後に動作チェックします。下記コマンドを実行し、js0と出る(js1などではない)ことを確認します。

[Terminal※PS4コントローラーのPSボタンとSHAREボタンを同時に長押し+接続後]
ls -la /dev/input

[OUT]
合計 0
drwxr-xr-x   2 root root     100  121 14:15 .
drwxr-xr-x  16 root root    4180  120 20:00 ..
crw-rw----+  1 root input 13, 64  121 14:15 event0
crw-rw----+  1 root input 13,  0  121 14:15 js0
crw-rw----   1 root input 13, 63  120 20:00 mice

 最後にテストとして下記Pythonスクリプト実行後に PS4コントローラーを触って出力が出ること確認出来たら正常終了です。

[chech_PS4controller.py]
from pyPS4Controller.controller import Controller


class MyController(Controller):

    def __init__(self, **kwargs):
        Controller.__init__(self, **kwargs)


controller = MyController(interface="/dev/input/js0", connecting_using_ds4drv=False)
controller.listen()
[Terminal]
python3 chech_PS4controller.py

7-2.Pythonスクリプト

 元記事を参照して、コード作成しました。

[moter_PS4.py]
from __future__ import division
import Adafruit_PCA9685
from time import sleep
from pyPS4Controller.controller import Controller

# 設定周波数(Hz)
set_freq = 50

# 動作角度からPCA9685に渡す値を変換
def convert_deg(deg,freq):

	# 分解能(ステップ数)
	step = 4096 # 12bit=2^12=4096

	# 接続サーボモーターの最大最小角度時のパルス間隔(ms)※値はSG90の仕様に基づくため必要に応じて調整
	max_pulse = 2.4 # 90°時
	min_pulse = 0.5 # -90°時

	# サーボモーターの0°時のパルス間隔(ms)※仕様書記載の通り1.45ms(0°)
	center_pulse = (max_pulse - min_pulse) / 2 + min_pulse  

	#サーボモーター1°あたりのパルス間隔(ms)
	one_pulse = round((max_pulse - min_pulse) / 180, 2)

	# 要求角度のパルス間隔(ms)を算出しPCA9685に渡す値を算出
	deg_pulse = center_pulse + deg * one_pulse
	deg_num = int(deg_pulse / (1.0 / freq * 1000 / step))

	# デバッグ
	print(f'角度(Input):{deg}°, パルス間隔(ms):{deg_pulse:.3f}, PCA9685(Out):{deg_num}')

	return deg_num

# PCA9685の初期化
pwm = Adafruit_PCA9685.PCA9685() #PWM:Pulse Width Modulation
pwm.set_pwm_freq(set_freq) #周波数を設定

#DualShock4コントローラーで制御するためのコード追加
## コントローラーの入力値をモーターの出力値に変換
def transf(raw):
    temp = (raw+32767)/65534
    # Filter values that are too weak for the motors to move
    if abs(temp) < 0.9:
        return 0
    # Return a value between 0.3 and 1.0
    else:
        return round(temp, 1)

def transf1(raw):
    temp = (abs(raw)+32767)/65534
    # Filter values that are too weak for the motors to move
    if abs(temp) < 0.9:
        return 0
    # Return a value between 0.3 and 1.0
    else:
        return round(temp, 1)

class MyController(Controller):
    def __init__(self, **kwargs):
        Controller.__init__(self, **kwargs)
    
    #Channel:0のサーボモーター、
    def on_L3_down(self, value):
        value = transf(value)
        if value == 0:
            channel_moter = 0
            pwm.set_pwm(channel_moter, 0, convert_deg(0,set_freq))
            channel_moter = 15
            pwm.set_pwm(channel_moter, 0, convert_deg(0,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')
        else:
            channel_moter = 0
            pwm.set_pwm(channel_moter, 0, convert_deg(90,set_freq))
            channel_moter = 15
            pwm.set_pwm(channel_moter, 0, convert_deg(90,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')
            
    def on_L3_up(self, value):
        value = transf1(value)
        if value == 0:
            channel_moter = 0
            pwm.set_pwm(channel_moter, 0, convert_deg(0,set_freq))
            channel_moter = 15
            pwm.set_pwm(channel_moter, 0, convert_deg(0,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')
        else:
            channel_moter = 0
            pwm.set_pwm(channel_moter, 0, convert_deg(-90,set_freq))
            channel_moter = 15
            pwm.set_pwm(channel_moter, 0, convert_deg(-90,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')

	#Channel:1のサーボモーター、
    def on_R3_down(self, value):
        value = transf(value)
        if value == 0:
            channel_moter = 1
            pwm.set_pwm(channel_moter, 0, convert_deg(0,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')
        else:
            channel_moter = 1
            pwm.set_pwm(channel_moter, 0, convert_deg(90,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')
            
    def on_R3_up(self, value):
        value = transf1(value)
        if value == 0:
            channel_moter = 1
            pwm.set_pwm(channel_moter, 0, convert_deg(0,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')
        else:
            channel_moter = 1
            pwm.set_pwm(channel_moter, 0, convert_deg(-90,set_freq))
            print(f'channel_moter:{channel_moter}, value:{value}')

    def on_x_press(self):
       pwm.set_pwm(0, 0, convert_deg(90,set_freq))

    def on_x_release(self):
       pwm.set_pwm(0, 0, convert_deg(-90,set_freq))

controller = MyController(interface="/dev/input/js0", connecting_using_ds4drv=False)
controller.listen()
[Terminal]
python3 moter_PS4.py

 サーボモーターが壊れたりChannel1が動かなかったりトラブルはあるのですが、とりあえずBluetoothからの動作と、PS4コントローラーの出力->PWM信号へ変換できることを確認できました。

8.オシロスコープによる波形確認

 オシロスコープの使い方もよくわからないため、とりあえず触って波形を見るだけ見てみました。

8-1.RIGOL デジタル・オシロスコープ DS1054Z

 記事参照して「RIGOL デジタル・オシロスコープ DS1054Z」を購入しました。特徴は下記の通りです。

https://jpmall.rigol.com/item.html?item_id=1203

 付属品は下図+USBケーブルになります。なお電源は3極のため接地があるコンセントが必要になります。

8-2.取り付け

 下記記事参照しながらオシロスコープに取り付けてみました。
 プローブは引っ張ってあげると端子が出るため、PCA9685のChannel0のGNDをワニ口クリップ、プローブの方をSIGの方に取り付けました。

【参考記事】

8-3.動作確認

 6章で実行したmoter.pyをそのまま実行しました。Rasberry Piを通じてPCA9685からPWM信号が出力されるため、それをオシロスコープで計測できると思います。
 あってるかどうか分かりませんが(理解したら補足書きます)それっぽい波形が出ていること確認できました。多分PWM信号を確認できています。
 GPIOの電圧は3.3Vのためおそらくの電圧が確認できると思います。

[terminal]
python3 moter.py 

9.補足:Rasberry Pi4のPWM信号によるサーボモーター駆動

 Rasberry Pi4はPWM信号を送付できるGPIOピンがあるため、これを用いることでサーボモーターSG90を動作させることが出来ます。
 注意点として今回はPCA9685のような集積回路を使用せず、Rasberry Piから直接PWM信号をサーボモーターSG90に渡す(つまり、複数のデバイス間でデータを送受信しない)ためシリアル通信(I2C)は使用しません。よって下記特徴があります。

  1. 信号線はPWM信号の1本のみであり、残りはSG90電源用の5VとGND

    • I2Cは使用しないためSDAやSCLの信号線は不要

    • sudo i2cdetect -y 1”で何も検出されない(I2Cデバイスが無いため)

  2. PWM信号(+電源)があれば、SG90は動作させることが可能

  3. PCA9685を使用しないため、計算式(出力する値)は

9-1.GPIOピンの接続

 結論は下図の通りに接続します。

  • Rasberry Pi:5V->SG90の赤(Vcc)

  • Rasberry Pi:Ground->SG90の茶(Ground)

  • Rasberry Pi:GPIO18ピン->SG90の橙(PWM)

 おさらいとすると、SG90の信号及び角度の仕様は下記の通りです。

https://akizukidenshi.com/download/ds/towerpro/SG90_a.pdf
https://dotstud.io/docs/pulse-width-modulation/

 Rasberry Pi4のGPIOピンにはそれぞれ役割があり、配列は下図の通りです。Raspberry Pi Documentation(Raspberry Pi hardware)より、PWMは「Hardware PWM available on GPIO12, GPIO13, GPIO18, GPIO19」と記載があります。今回はGPIO18ピンをPWM信号用として接続しました。

https://www.raspberrypi.com/documentation/computers/raspberry-pi.html

9-2.設計思想および計算式詳細

 スクリプトの設計思想は下記の通りです。

  1. PWM信号をだすGPIO番号を指定して、GPIOモジュールにセット

  2. GPIO.PWM(channel, frequency)を使用してPWMインスタンスを初期化

  3. 動かしたい角度からデューティ比を計算する関数作成

  4. 繰り返し動作実装

  5. 修了用の動作実装

【デューティサイクル(デューティ比)の計算】
 デューティー比(デューティーサイクル)とは、一定周期で連続するパルス波において、パルスがオンになる時間とオフになる時間の比率のことです。

https://dotstud.io/docs/pulse-width-modulation/

 計算式は下記の通りですが、グラフで可視化すると理解しやすいと思います。分母の20msはサーボモーター周波数=50Hzより$${\frac{1}{50}\times1000=20ms}$$であり、これを100%として算出できます。

$$
デューティサイクル[%] = \left( \frac{パルス幅}{20 ms} \right) \times 100
$$

【GPIO.PWM】
 GPIO.PWM(channel, frequency)でPIOピン番号とPWM信号の周波数を設定します。このPWM信号を出力する時はpwm.ChangeDutyCycle(<デューティ比>)を使用しますが、PCA9685の時とは異なり引数はデューティ比を入力するため0~100%の値となります。

9-3.Pythonスクリプト/動作確認

 上記の設計をベースとして"0->45°->0->-45°"を繰り替えす処理を作成しました。

[moter_rasp.py]
import RPi.GPIO as GPIO
from time import sleep

 #GPIOビンの設定 
numPin_PWM = 18  #PWM信号を出力するGPIOピン番号 
GPIO.setmode(GPIO.BCM)  #GPIO番号で指定 
GPIO.setup(numPin_PWM, GPIO.OUT)  #GPIOピンを出力に設定 
# 設定周波数(Hz)
set_freq = 50
pwm = GPIO.PWM(numPin_PWM, set_freq) #GPIOピン番号, PWM信号の周波数を設定
#GPIO.PWMの値(デューティ比)は0~100%の間で指定
pwm.start(0) #PWM信号のデューティ比を0%に設定


# 動作角度からデューティ比を算出する関数
def convert_deg2duty(deg, verbose=False):
	# パラメータ:接続サーボモーターの最大最小角度時のパルス間隔(ms)※値はSG90の仕様に基づくため必要に応じて調整
	max_pulse = 2.4 # 90°時
	zero_pulse = 1.45 # 0°時
	min_pulse = 0.5 # -90°時

	pulse_width = (max_pulse - min_pulse) / 180 * deg + zero_pulse #deg°のパルス間隔を算出
	duty_cycle = (pulse_width / 20) * 100  #パルス間隔をデューティ比に変換 
	# デバッグ
	if verbose:
		print(f'角度(Input):{deg}°, パルス間隔(ms):{pulse_width:.3f}, デューティ比(Out):{duty_cycle:.3f}')

	return duty_cycle

#サーボモーター制御:0°->45->0->-45
try:
	while True:
		duty_cycle = convert_deg2duty(0, verbose=True) #サーボモーターを0°に設定
		pwm.ChangeDutyCycle(duty_cycle)  #デューティ比を変更 		sleep(1)
		duty_cycle = convert_deg2duty(45, verbose=True) #サーボモーターを45°に設定
		pwm.ChangeDutyCycle(duty_cycle)
		sleep(1)
		duty_cycle = convert_deg2duty(0, verbose=True) #サーボモーターを0°に設定
		pwm.ChangeDutyCycle(duty_cycle)
		sleep(1)
		duty_cycle = convert_deg2duty(-45, verbose=True)
		pwm.ChangeDutyCycle(duty_cycle)
		sleep(1)

except KeyboardInterrupt:
	pwm.stop()  #PWM信号を停止 
	GPIO.cleanup()  #GPIOピンの設定を初期化 
	pass
[Terminal]
python3 moter_rasp.py

 結果として、PCA9685をI2C通信して制御しなくても、Rasberry Pi4から直接PWM信号を出力して(一方的に)SG90を制御できることが確認できました。
 PCA9685はRasberry PiとI2Cで接続することで、Rasberry Piから複数(計16個)のPWM信号を出力でき、複数のサーボモーター(PWM信号で動く機器)を制御できるものと理解できます。

10.別添

10-1.SG90の分解

 下記記事参考に分解してみましたのでご参考までに。

 ヘッドギアにあるストッパーで回転角度を制限する構造になってます。

https://burariweb.info/gadget/3d-printer/sg90-servomotor-complete-reproduction.html

10-2.SG90の直し方

追って(既に不調なやつが2つあるが、使い方が悪いっぽいので)


参考資料

あとがき

 規格と用語が多すぎて微妙に勘違いしている可能性があるので、適宜修正予定。あと、十分理解出来たら追記したい内容は下記にメモ

  • PCA9685に設定するPWM値の計算式の意味

  • Dual Shock4の出力や信号、および変換式

  • オシロスコープの使い方

修正履歴

  • 2024年1月21日:初版発行

  • 2024年1月27日:9章:Rasberry Pi4のPWM信号追加

  • 2024年2月3日:10章:サーボモーターの分解追加

  • 2024年2月7日:誤字修正(誤:基盤、正:基板)

この記事が気に入ったらサポートをしてみませんか?