Elixirでサーボ制御

[パルス幅変調（PWM）でなめらかにLチカ](https://dev.to/mnishiguchi/elixir-nerves-smoothly-dimming-leds-using-a-servo-driver-5gid)をしたあと、ホビー用サーボモーターを [Elixir](https://elixir-lang.org/)で動かしたくなりました。いろいろ戸惑った事や気づいた事があったのでメモします。

ハードウエアや電子工作に関してはど素人なもので様々な部分で厳密性がないことを予めご了承お願いします。

[2021/3/22(月)開催のautoracex #18](https://autoracex.connpass.com/event/207969/)での成果です。

https://autoracex.connpass.com/

https://join.slack.com/t/elixirjp/shared_invite/zt-ae8m5bad-WW69GH1w4iuafm1tKNgd~w

[English edition](https://dev.to/mnishiguchi/controlling-servo-in-elixir-nerves-h47)

## やりたいこと

[Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/)ボードと[PCA9685 PWM コントローラ](https://www.adafruit.com/product/815)とを[I2C](https://ja.wikipedia.org/wiki/I2C)で接続し、ホビー用デジタルサーボモータ[SG90](http://www.towerpro.com.tw/product/sg90-7/)を[Elixir](https://elixir-lang.org/)コードで動かしたい。

個人的に[Elixir](https://elixir-lang.org/)コードでのIoTファームウエアの開発を可能にする[Nerves](https://www.nerves-project.org/)フレームワークで作ったファームウエアを SDカードに焼いて、Raspberry Piに搭載していますが、そこは今回の内容には直接は関係ありません。

## サーボモータについて

世の中には様々なサーボモータがあるようですが、ここで使うのはホビー用のデジタルサーボです。概要についてはネットで調べたら出てくるのでここで説明するまでもないと思います。

Sparkfun の[Hobby Servo Tutorial](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all)と[Basic Servo Control for Beginners](https://learn.sparkfun.com/tutorials/basic-servo-control-for-beginners/servo-motor-basics)が簡潔にまとまっていてよいです。

大きく分けて 2 種類あります。

### 標準タイプ（例：SG90）

![standard_servo](https://user-images.githubusercontent.com/7563926/111398863-89ce0d00-869a-11eb-9073-678ff1dba90f.gif)

### 連続回転タイプ（例：FS90R）

![continuous_servo](https://user-images.githubusercontent.com/7563926/111398873-8c306700-869a-11eb-8c73-e4c9e60639e5.gif)

## パルス幅変調（PWM）

一般的なホビー用サーボはパルス幅変調（PWM）により操作します。PWMとはデジタル信号の電圧のハイとローの幅を動かすことです。

[![](https://upload.wikimedia.org/wikipedia/commons/b/b8/Duty_Cycle_Examples.png)](https://en.wikipedia.org/wiki/Pulse-width_modulation)

[なめらかなLチカ](https://dev.to/mnishiguchi/elixir-nerves-smoothly-dimming-leds-using-a-servo-driver-5gid)を実装してみて感覚的にPWMが理解できるようになりました。

![servo-driver-pwm-leds](https://user-images.githubusercontent.com/7563926/105923166-f36e5b00-6009-11eb-9a0a-18e4e7c0c839.gif)

## サーボ制御でのPWM

一般的にホビー用サーボモータの周波数は 50Hz で周期は 20ms のようです。問題はその後です。多くの記事や資料には下記の様に書かれていますが、実際のサーボは異なった挙動をするのです。

- パルス幅 1.0ms -> 時計回りフル
- パルス幅 1.5ms -> ニュートラル
- パルス幅 2.0ms -> 反時計回りフル

手元の SG90 はパルス幅1.0msだとちょこっとしか動きません。いくつのパルス幅で何度動くのか？ ここが最大の謎です。深く考えると気が狂いそうになるので、細かいことは気にせず個体ごとに補正するのが現実的だと考えています。[autoracex](https://autoracex.connpass.com/event/207969/)主催者@torifukukaiouさんの[アドバイス](https://qiita.com/torifukukaiou/items/0d9ee663680aba97c4af#%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8D%E3%81%BE%E3%81%99)を思い出します。

同じような問題に直面している人もいるようです。

- [What is the proper calculation of duty cycle range for the sg90 servo? | https://raspberrypi.stackexchange.com](https://raspberrypi.stackexchange.com/questions/106858/what-is-the-proper-calculation-of-duty-cycle-range-for-the-sg90-servo)

なぜこの問題の答えがネット上にないのか？ 製品によりスペックが異なる？ Who knows? ¯\_(ツ)_/¯



ご存知の方教えて下さい。:bow:

いろいろ調べた結果と手元のサーボを観察した結果をまとめます。

| パルス幅 | デューティー比 | メモ                                             |
| -------- | -------------- | ------------------------------------------------ |
| 400µs    | 2%             | 手元のSG90の反応する下限                            |
| 500µs    | 2.5%           | 僕はこれを0度としています。                    |
| 1500µs   | 7.5%           | 一般的にここがニュートラルとされるらしい (90度)        |
| 2500µs   | 12.5%          | 僕はこれを180度としています。若干角度が足らないので170ぐらいかも |
| 2800µs   | 14%            | 手元のSG90の反応する上限                           |

いくつかの資料ではSG90の定格パルス幅が500..2400µsとなってます。手元のSG90は最大で400..2800µs動きます。

## サーボテスター

おそらくプロは波形を測定したりするのでしょうが、そこまで本格的にやるつもりはないので、簡易的なサーボテスターをアマゾンで買いました。これがサーボの特性を理解する上で大いに役に立ちました。確か１０USドルくらいだったと思います。パルス幅を800..2200µsの間で動かせて、サーボがどう動くのかが簡単に確認できます。サーボテスターの値が正しいのかどうかしりませんが、正しいと信じるようにしています。最低限サーボの挙動が見えてきます。

## PWMコントローラー

どうやったらElixirでPWMを操作できるのかよくわからなかったので、Elixir/Nervesコミュニティに質問してみました。何人か親切に教えて下さいました。ラズパイのビルトインPWMより外付けのPWMボードをI2Cで接続したほうが良いとのことでした。[Nerves project](https://www.nerves-project.org/)共同作者の[Frank Hunleth](https://twitter.com/fhunleth)さんも自身の経験上、LinuxのPWMサポートがあまり良くないといってました。

最終的に[PCA9685: 16-channel 12-bit PWM controller](https://www.adafruit.com/product/815)をラズパイにI2Cでつなげることにしました。16チャネルあるので16個のサーボを同時に操作することができます。ロボットとか作るとチャンネルが多く必要になるのですかね？

## ライブラリ

Elixirでサーボを動かすための既存のライブラリは見つけられませんでした。[jimsynz/pca9685.ex](https://github.com/jimsynz/pca9685.ex)がありましたが、残念ながら依存性が更新されておらずメンテもされてません。ですので[自作](https://hexdocs.pm/servo_kit/0.2.0/readme.html)することにしました。詳しいことはよく理解してませんが、[PCA9685データシート](https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf)片手に他の言語で書かれた既存ライブラリを参考に、最低限動くものはできました。

ラズパイとPCA9685とのI2C通信には[elixir-circuits/circuits_i2c](https://github.com/elixir-circuits/circuits_i2c)があるので、それを信頼して使用しています。

## デモ

**hardware**

- [SG90](http://www.towerpro.com.tw/product/sg90-7/)
- [Raspberry Pi Zero W](https://www.adafruit.com/product/3708)
- [Adafruit 16-Channel PWM / Servo Bonnet for Raspberry Pi](https://www.adafruit.com/product/3416)
- [AC adapter](https://www.adafruit.com/product/276)

**firmware**

- [Nerves project](https://www.nerves-project.org/)

**software**

- [ServoKit](https://hexdocs.pm/servo_kit/0.2.0/readme.html) - Use PCA9685 PWM/Servo Controller in Elixir

![servo-demo](https://user-images.githubusercontent.com/7563926/111891281-33c5d600-89c8-11eb-8f1f-cc33cae9f442.gif)

ラズパイの[IEx](https://elixirschool.com/en/lessons/basics/basics/#trying-interactive-mode)からこんなElixirスクリプトでサーボを動かすことができました！

```elixir
# Start a pwm control process
ServoKit.start_link()

# Define a function that changes duty cycle and delays a little
set_pwm_duty_cycle = fn x, ch ->
  ServoKit.set_pwm_duty_cycle(x, ch: ch)
  Process.sleep(50)
end

# Iterate changing duty cycle with 0.5 step betweem 2.5 and 12.5 for channel 15
list1 = 2.5  |> Stream.iterate(&(&1 + 0.5)) |> Enum.take(21)
list2 = 12.5 |> Stream.iterate(&(&1 - 0.5)) |> Enum.take(21)
0..99 |> Enum.each(fn _ ->
  list1 |> Enum.each(&set_pwm_duty_cycle.(&1, 15))
  list2 |> Enum.each(&set_pwm_duty_cycle.(&1, 15))
end)
```

以上

https://elixir-lang.org/

https://www.nerves-project.org

https://hexdocs.pm/servo_kit/readme.html

http://www.towerpro.com.tw/product/sg90-7/

https://www.adafruit.com/product/3708

https://www.adafruit.com/product/3416

https://www.adafruit.com/product/276

https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf

https://autoracex.connpass.com/

https://join.slack.com/t/elixirjp/shared_invite/zt-ae8m5bad-WW69GH1w4iuafm1tKNgd~w