あづみのメモ帳

イヤホンジャックからPWM信号を出力してサーボを動かしてみた

(最終更新: )

電子工作でよく使われるラジコンサーボは、PWM信号を入力することで角度や回転速度を制御できる。通常の直流モータであれば電源をつなぐだけで回ってくれるが、サーボはちょっとした動作確認などのためにもPWM信号を生成する必要がある。任意のパルス幅のPWM信号を吐けるマイコンを作ろうにも、マイコンに指示を出す方法は扱いがめんどくさいシリアル通信や、接続操作が煩雑なWi-FiやBluetoothのような手軽とは言い難いものばかりである。

PCやスマホに紐をつないで画面をぽちぽちするだけで操作できたらとても便利そうだが、普通のPCやスマホからはGPIOは生えておらず、出力として使える端子はUSBかイヤホンジャックくらいしかない。しかし、USBは出力するのも受け取るのもめんどくさい。では、イヤホンジャックはどうだろうか。音声出力はブラウザからでも好き放題でき、プラットフォームを気にせずに使えるのはよさそうだ。とはいえ、音声出力は音声出力であり、他の機器との通信に使うことを想定したものではない。今回は、そんなイヤホンジャックからサーボモータへ指令を出せないものかと試行錯誤してみた。

イヤホンジャックの出力電圧

まずはイヤホンジャックからどれだけの電圧が出力できるかを調べてみる。ノートPCの音量を最大にし、ffmpegで正弦波を出力し、GNDと出力との電圧を交流レンジで測定してみたところ、振幅は1 V程度であった。また、左右逆相の信号をffplay -f lavfi -i "aevalsrc=sin(2*PI*50*t)|-sin(2*PI*50*t)のように出力して左右の電位差をとると、順当に2倍の2 V程度になった(単相3線式の200 Vみたいな)。

ブラウザの波形生成ツールがよくなかったらしくffplayで流したらちゃんと交流電圧で見えた
50Hz
2024年10月12日 10時17分

周波数変調してみる

ChatGPTに「ブラウザでイヤホンジャックからPWMを吐きたいぜ!」と聞いてみたところ、Web Audio APIのOscillatorNodeを使えば任意の周波数の矩形波を出力できると教えてくれた。しかし、デューティー比は50%固定であり、任意の値にする方法は分からなかった。そこで、矩形波の周波数に意味を持たせる(いわゆるFM?)ことでいったんマイコンに情報を送り、マイコンからPWM信号を吐いてもらうことを考えた。

左右の差分が2 Vくらいなので、一方を電源(5 V)を分圧して作った2.5 Vにつなげば、もう一方は0.5-4.5 Vくらいの範囲の、マイコンで読み取りやすい信号になる。ADCピンで読み取る方法と、デジタルピンの割り込みを使う方法の2つを実装してみた。アナログのほうは無信号状態を判定して無視することができる一方で、読み取りに時間がかかるため分解能が低かった(波長112 μs単位)。デジタルのほうは無信号状態だと値が不定になるのでその対策が必要であったが、分解能は高かった(4 μs単位)。いずれの場合も、ノイズとして乗りにくい周波数帯を選び、そこを任意の数に分割して変調したうえで、帯域外の周波数を無視することでノイズ耐性を高めることができた。デューティー比は50%で固定のため、HIGHの時間とLOWの時間をそれぞれ取得し、差が大きければノイズとして扱うことも有効であった。

これで、ひとまずイヤホンジャックを介して情報を送ることができるのは実証できた。結果からいえばこれは実践投入しなかったが、送信機と受信機をとりあえずGitHubに投げておいた。

PWM信号の出力

結局マイコンを噛ませてしまうのでは面白くないので、なんとかしてPWM信号を直接吐きたい。聖典ことMDN Web Docsを読み漁っていたところ、任意の波形を生成できそうなAudioBufferなるものの存在を発見した。ChatGPTに「オイ! どうしてAudioBufferの存在を隠していた! 使い方を教えろ!」と問い詰めたところ、任意のPWM信号を生成する方法を白状した。

さくっとPWM波形を生成するWebアプリを作り、イヤホンジャックの出力をオシロスコープ(部室にちっちゃい奴が転がっていたので使ってみた)で表示してみた。デューティー比が小さくなる、つまり、HIGHの時間が短くなるにつれて、ピーク間電圧は4 Vくらいのまま、全体的にGNDに対する電位が上がることが分かった。どうやら直流成分がカットされているようである。

サーボに送るPWM信号は周波数50 Hz (振幅20 ms)に対してパルス幅1.5 ms前後なので、デューティー比は非常に小さく、LOW, HIGHの電位はそれぞれ0 V, 4 Vに近くなる。これならそのままサーボの信号入力に入れても動作する可能性が高い。

これならむしろレベルシフトしないで0として扱っていい説ある
2024年10月14日 15時14分

サーボを回す

Webアプリを扱いやすく改修し、ちゃんとしたオシロスコープを引っ張り出してもう少し詳しく波形を見てみた。負方向に飛び出ている部分があったので雑にダイオードを噛ませた。ぱっと見の波形は正しいのに、周波数表示がまったく違う値になっているので拡大してみると、完全に1往復するレベルの派手なリンギング(?)が発生していた。コンデンサをかましてみたりしたがちょうどいい感じにならなかったので、一旦見なかったことにした。

周波数とかの判定おかしいなと思ったらこれで草
2024年10月15日 13時56分

この信号をそのへんに転がっていたサーボに入力してみたが、サーボは動いてくれなかった。オシロスコープに見えていても、信号としては弱すぎる可能性を考え、増幅を試みた。オペアンプをコンパレータとして用い、信号が正のときは電源電圧、負の時はGNDとなるようにした。この方式では0 V前後で振動されるとひどいことになるが、イヤホンジャックの出力ではLOWは若干負に寄り、また、ダイオードを入れていても順方向電圧は0ではないので、正しくLOWを判定することができた。(なんでプローブ1xで使ってるんだろう)

オペアンプがころがってたのでぶちこんでみた
2024年10月15日 14時50分

しかし、これでもサーボは動かなかった。もしやと思いArduinoにサーボを動かすコードを焼いて繋いでみると、それでも動かなかった。原因は信号ではなくサーボがお亡くなりになっていたことだった。生きているサーボも転がっていたので再度試してみたところ、オペアンプで増幅した信号はもちろん、左右逆位相にして差分を取る方式でも、単にGNDからの電位を見る方法でも、想定通りに動作することが確認できた。

生きてる野良サーボを発見したのでしばいた
動いたぞ
2024年10月15日 15時18分
電源とイヤホンジャックだけでサーボは動く‼️
2024年10月15日 15時29分

これで、マイコンを介さずスマホやPCから直接サーボを動かせることが実証できた。

実装する

以上の回路を基板に実装し、紐をつないでサーボをぶっ刺せばすぐに動かせるデバイスを作成する。

まず、電源はUSB Type-Cから取ることにした。サーボの電源電圧はたいてい5 V程度で、USBの標準での電圧も5 Vのため、そのままつなぐだけでよい。ただし、Type-CでないUSBでは電流は500 mAか900 mAしか取れずサーボを動かすには心許ないため、最低でも1.5 A取れるType-Cを使うのが無難である。

信号については、2チャンネルあることを活かすため左右の差分を取る方法は使わないことにした。この場合、検証環境での素の状態での電圧は2 V程度で、サーボによっては動作しない可能性がある。また、再生機器によっては2 Vも出ない可能性もある。よって、汎用性を高めるため、信号を増幅することした。

信号の増幅について、信号を出力している間は上記の通りコンパレータでも問題ない。しかし、音声を出力していない状態では、イヤホンジャックからの信号は0 Vにノイズが乗ったものになる。これをコンパレータに通してしまうと、開ループゲインで増幅されたノイズという凄まじいものが出力されてしまう。これではサーボが暴れることがあるので、増幅率を5倍程度に設定した非反転増幅回路を組むことにした。

例のごとくユニバーサル基板上での配置をKiCadで検討し、下手くそなはんだ付けをして回路が完成した。配線が交差しないように設計したが、設計ミスと組立ミスで結局交差が発生することとなった(部品の足の切れ端で配線しているのでカプトンテープで絶縁した)。最初からエナメル線等を使う前提で交差を許容した配線をするべきだったかもしれない。

とりあえずこれで組むか pic.x.com/OMUbDpIBt9
いろいろガバリまくって結局裏面で立体交差してる
あとは電源とGNDつないだらおわり?(授業
2024年12月02日 14時41分
パソコン/スマホにType-Cとイヤホンジャックを繋ぐだけでサーボをいじれる物体できたぞ
(Type-Cコネクタだけ借り物なのでソケットにしてる
2024年12月02日 16時52分

プリント基板版の製作も試みているが、工研の単電源オペアンプの最後の在庫を壊れていたか破壊したかで止まっている。

オペアンプ破壊したかも pic.x.com/n5ehNzGQnB
あーあ
回路の問題じゃなくてよかった
2025年02月27日 17時27分

動作確認

この後しばらく実用しており、動作確認時も使用した正体不明のサーボやSG90、FS90(R)など有名どころでは問題なく使用でききた。しかし、MS18というサーボではほとんどまともに動作しなかった。信号の入力仕様はSG90などのサーボと同じであり、実際マイコンからの信号ではSG90と同様に動作したため、作成したデバイス側の問題であると考えられる。詳細な検証は行えていないが、波形の振動などを放置していることが原因の可能性もあり、もうすこしちゃんとした信号を出力する必要があるのかもしれない。

波形データを1と-1だけにするのではなく、直流成分が0となるように調整すると、波形の振動を抑えることができた。よく分からないが、直流成分をカットする回路によって電圧がシフトし、それにより限界突破することで波形が乱れているような印象を受けた。波形が綺麗な状態でならMS18でも正常に動作した。

直流成分カットでMS18も動くようになったぞ
2025年02月26日 16時37分

先行研究

後出しになるが、実はほとんど同じことをしていた先行例が存在した。GlueMotorというもので、現在は公式サイトが滅びたりしていてあまり情報がないように見えたので、発想だけ参考にして自力で作ろうとしたのが今回のきっかけであった。

作った後で知ったが、公式サイト亡き後もプログラムはGitHubリポジトリで公開され続けていた。しっかりJavaScript版も存在するようである。また、リポジトリからリンクされている記事にはハードウェアの作り方も記載されている。

ただし、リポジトリにはサーボによっては動作しない、オーディオ出力が弱い端末だと使えない、という制約が記載されており、また、記事を見る限り特に信号の増幅は行われていなかった。今回のサーボとPCではそのままでも動いたが、やはりオペアンプをかませたほうが汎用性は高そうである。ソフトウェアに関しても8年間更新されておらず、JavaScript版は非推奨となったScriptProcessorなるものを使用しているようである。ScriptProcessorの後継としてはAudioWorkletなるものが存在するようで、今回使用したAudioBufferよりも自由度が高そうである。

結局今回したことは車輪の再発明であったが、より丸く使いやすい車輪ができたので無意味でもなかったと思っている。

あとがき

スマホなどから直接信号を出力できるのは地味に便利である。スマホにはマイコンとは比べ物にならない処理能力があり、直接操作できるUIがあり、バッテリがある。たとえばLANに接続するために接続情報をハードコードしたり、USBやBluetoothで接続して情報を書き込んだりする必要がない。たとえばちょっとサーボを動かしてみたいだけのとき、電源もスマホのUSB Type-Cから取れば、電源装置すら要らずスマホ単体で動作確認ができる(自分が使っているスマホにはイヤホンジャックもType-C端子もないのは内緒)。

ちなみに、今回のデバイスは結局イヤホンジャックの信号を増幅するだけのものなので、PWM以外のいろいろな波形を入力できる。サーボに音楽を聴かせることや、実はPWMなんて生成しなくても普通に矩形波で動かせることを明らかにすることもできる。負電圧は出力できないのでファンクションジェネレータとまでは言えないが、さまざまな波形を2チャンネル出力できるなかなかの可能性を秘めた物体かもしれない。まあ大きい電圧が必要なければイヤホンジャックそのままでもおてがる2chファンクションジェネレータになるのだが。

PWMではなく音楽も流し込めます(サーボは困っています
nico.ms/sm42769085
2024年12月02日 20時45分
こいつパルス幅しか見てないっぽくて周波数はどうでもいいっぽいから普通に矩形波でいけるオチきたかもしれん
2024年12月02日 20時52分