Raspberry Pi Pico Wを利用してモーターを遠隔制御する

## はじめに

Raspberry Pi Pico W を利用してプラレールをラジコン化している記事を見かけて自分もやってみたい!と急に思い立ちました。

今回はその過程として、Rasberry pi pico w で立ち上げたWebサーバー上からモーター制御する方法をまとめます。

## 部品

* [ピンヘッダー付きRaspberry Pi Pico W](https://www.amazon.co.jp/gp/product/B09C5MSYCC/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1)
* [モータードライバー TB6612FNG](https://www.amazon.co.jp/TB6612FNG%E6%90%AD%E8%BC%89-%E3%83%87%E3%83%A5%E3%82%A2%E3%83%AB%E3%83%A2%E3%83%BC%E3%82%BF%E3%83%BC%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90-%E3%83%94%E3%83%B3%E3%83%98%E3%83%83%E3%83%80%E4%BB%98%E3%81%8D-%E5%AE%9F%E8%A3%85%E6%B8%88%E3%81%A7%E3%81%99%E3%81%90%E4%BD%BF%E3%81%88%E3%81%BE%E3%81%99-1-2A%C3%972%E5%87%BA%E5%8A%9B/dp/B09C5MSYCC)
* [DCモーター](https://www.amazon.co.jp/dp/B00288BIE4)
* [電池ボックス (単4 x 2)](https://www.amazon.co.jp/gp/product/B0BZR55P98)
* 単４電池 2個
* [ブレッドボード](https://www.amazon.co.jp/dp/B08B63VNCH)
* [ジャンパー線](https://www.amazon.co.jp/dp/B08B63VNCH)

## 回路図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29755/e8ae971d-d50b-1758-bfa5-d5b376bed721.png)

## プログラム

Raspberry Pi Pico WをPC(Mac)に接続し、Thonny経由でプログラムを保存しています。

```python:main.py
import network
import socket
import time
from machine import Pin, PWM
from time import sleep

# Wi-Fi接続情報を設定
SSID = '#{ssid}'
PASSWORD = '#{password}'

# GPIOピンの設定
STBY = Pin(13, Pin.OUT)
AIN1 = Pin(14, Pin.OUT)
AIN2 = Pin(15, Pin.OUT)
PWMA = PWM(Pin(16))
LED = Pin('LED', Pin.OUT)

def motor_init():
    STBY.on()
    PWMA.freq(1000)

def control_motor(action, speed):
    speed = int(speed)  # speedは0から100の範囲であると仮定
    duty = int(speed * 65535 / 100)
    if action == 'forward':
        motor_forward(duty)
    elif action == 'backward':
        motor_backward(duty)
    elif action == 'stop':
      motor_stop()

def motor_forward(speed):
    AIN1.on()
    AIN2.off()
    PWMA.duty_u16(speed)
    sleep(1)

def motor_stop():
    AIN1.off()
    AIN2.off()
    PWMA.duty_u16(0)
    sleep(1)

def motor_backward(speed):
    AIN1.off()
    AIN2.on()
    PWMA.duty_u16(speed)
    sleep(1)

def connect_wifi():
  # Wi-Fi接続の初期化
  wlan = network.WLAN(network.STA_IF)
  wlan.active(True)
  wlan.connect(SSID, PASSWORD)
  # Wi-Fi接続が確立されるのを待つ
  while not wlan.isconnected():
      print('Connecting to WiFi...')
      time.sleep(1)
  print('Connection successful')
  ip = wlan.ifconfig()[0]
  print('IP Address:', ip)
  return ip

def load_html():
    with open('motor_control.html', 'r') as file:
        return file.read()

def start_web_server(ip):
  addr = socket.getaddrinfo(ip, 80)[0][-1]
  s = socket.socket()
  try:
    s.bind(addr)
    print('Listening on', addr)
  except OSError as e:
    if e.errno == 98:  # EADDRINUSE
      print('Address already in use, skipping bind')
    else:
      raise
  s.listen(1)
  return s

def parse_post_data(post_data):
    # POSTデータをパースしてパラメータを取得
    params = {}
    pairs = post_data.split('&')
    for pair in pairs:
        key, value = pair.split('=')
        params[key] = value
    return params

def handle_request(client):
    request = client.recv(1024)
    request_str = request.decode('utf-8')
    print('Full Request:\n', request_str)
    
    try:
        headers, body = request.split(b'\r\n\r\n', 1)
        body = body.decode('utf-8')
        print('Headers:\n', headers.decode('utf-8'))
        print('Body:\n', body)
        
        # POSTデータのパース
        post_data = parse_post_data(body)
        action = post_data.get('action', 'unknown')
        speed = post_data.get('speed', '0')
        control_motor(action, speed)
        print(f"Action: {action}, Speed: {speed}")
        
    except (ValueError, IndexError) as e:
        print(f'Error processing request: {e}')

    response = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n' + html
    client.send(response)
    client.close()

motor_init()
ip = connect_wifi()
html = load_html()
socket = start_web_server(ip)
LED.on()

try:
    while True:
        client, addr = socket.accept()
        print('Client connected from', addr)
        handle_request(client)
except KeyboardInterrupt:
    print("Server shutting down...")
    socket.close()
    LED.off()


```

```html:motor_control.html
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body>
<h1>PLARAIL Controller</h1>
<form action="./" method="post">
  <input type="hidden" name="action" value="forward">
  <input type="hidden" name="speed" value="50">
  <input type="submit" value="前進(50%)">
</form>

<form action="./" method="post">
  <input type="hidden" name="action" value="stop">
  <input type="submit" value="停止">
</form>
</body>
</html> 
```

## 画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29755/6d2cacdc-9129-6ea8-5369-fa6e95d11eb4.png)


## 実装のポイント

### Webサーバーが起動したらLEDを点灯

LEDが点灯したら準備完了したことが把握できるようにし、問題の有無が分かるようにしました。

### POSTパラメーターでモーターを制御できるようにした

`action` と `speed` の２つのパラーメーターを渡すことで、モーターの前後進・モーターの回転速度が制御できるようにしました。こうすることでプログラムの改変をせずに、モーターが自由に制御できるようになります。

```html
  <input type="hidden" name="action" value="forward">
  <input type="hidden" name="speed" value="50">
```

## 実物写真

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29755/6d6d95d2-7e14-c89c-83d0-19c491e523fa.png)


## 学び

今回の基本回路の開発の過程で学んだこと

* ハード
    * ハードウェアの各々には適切な電圧での電源の供給が必要なこと
        * 今回でいうとRaspberry Pi Pico W, モータードライバー, モーターの３種類であるが、配線を通じて適切な電圧の電源が供給されないと動かないです。電子工作においては当たり前のことではありますが、ソフトウェア開発をしていると電源のことは考えたことがなかったので、学びでした
    * PMWでは周波数とデューティー比の両方の設定が必要なこと。各々の設定の比率に応じてモーターの回転速度が変わる。


## まとめ

これでプラレールのラジコン化に向けた基本回路とプログラムの用意ができました。次はいよいよプラレール本体への組み込みです。今ちょうど組み込み用のプラレールと貨車を調達中です。まだまだ悩みどころはありますが、あと少しです!

続く...