ラジコンカーをPWM制御して走行させる

##概要
RaspberryPiのDCモータを**PWM制御**することでラジコンカーを動かします。今回はMacからRaspberryPiにSSHでリモート接続し手元のキーボードから操作できるようにしています。コースを以下のように作って実際に走らせてみました。
<img width="400
" alt="course.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402261/fd5dedf0-bf1f-3a8f-9565-c35f9e923467.png">

##PWM制御とは

PWM制御とは、スイッチのオンとオフのスイッチングを高速で行い、擬似的に出力電圧を制御するというものです。出力電圧を制御することによってラジコンカーを右や左方向に曲がる動作を行うことができます。例えば左のDCモータの電圧出力を右のDCモータの出力より大きくすれば、右に曲がることができます。詳しい説明は他の方のサイトをご覧ください。私はこちらのサイトを参考にさせて頂きました。
[https://dotstud.io/docs/pulse-width-modulation/](https://dotstud.io/docs/pulse-width-modulation/)


##環境
* Raspberry Pi 3B raspbian 9.4
* Python 3.5.3

##アーキテクチャ
<img width="807" alt="architecture1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402261/70ffc077-f393-34b0-93cd-6ab6c8166c97.png">

##KeyBoard操作
|Key|動作|
|:--:|:--:|
|↑|前進|
|→|右に少し移動|
|←|左に少し移動|
|↓|後退|
|r|右回転|
|l|左回転|
|return|停止|
|q|終了|

##ソースコード

```python:main.py
# import curses and time and GPIO
import curses
import time
import RPi.GPIO as GPIO


def go_straight(rightDC, leftDC):

    GPIO.output(Lpin1, True)
    GPIO.output(Lpin2, False)
    GPIO.output(Rpin1, True)
    GPIO.output(Rpin2, False)

    pwmL.ChangeDutyCycle(leftDC)
    pwmR.ChangeDutyCycle(rightDC)

def go_back(rightDC, leftDC):

    GPIO.output(Lpin1, False)
    GPIO.output(Lpin2, True)
    GPIO.output(Rpin1, False)
    GPIO.output(Rpin2, True)

    pwmL.ChangeDutyCycle(leftDC)
    pwmR.ChangeDutyCycle(rightDC)

def turn_left(rightDC, leftDC):

    GPIO.output(Lpin1, True)
    GPIO.output(Lpin2, False)
    GPIO.output(Rpin1, True)
    GPIO.output(Rpin2, False)

    pwmL.ChangeDutyCycle(leftDC)
    pwmR.ChangeDutyCycle(rightDC)

def turn_right(rightDC, leftDC):

    GPIO.output(Lpin1, True)
    GPIO.output(Lpin2, False)
    GPIO.output(Rpin1, True)
    GPIO.output(Rpin2, False)

    pwmL.ChangeDutyCycle(leftDC)
    pwmR.ChangeDutyCycle(rightDC)

def rotation_right():

    GPIO.output(Lpin1, True)
    GPIO.output(Lpin2, False)
    GPIO.output(Rpin1, False)
    GPIO.output(Rpin2, True)

    pwmL.ChangeDutyCycle(70)
    pwmR.ChangeDutyCycle(70)

def rotation_left():

    GPIO.output(Lpin1, False)
    GPIO.output(Lpin2, True)
    GPIO.output(Rpin1, True)
    GPIO.output(Rpin2, False)

    pwmL.ChangeDutyCycle(70)
    pwmR.ChangeDutyCycle(70)

def stop():

    GPIO.output(Lpin1, False)
    GPIO.output(Lpin2, False)
    GPIO.output(Rpin1, False)
    GPIO.output(Rpin2, False)

#set GPIO numbering mode and define output pins
Lpin1 = 16
Lpin2 = 18
Lpin3 = 33
Rpin1 = 15
Rpin2 = 13
Rpin3 = 12
leftDC  = 60
rightDC = 60

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(Lpin1,GPIO.OUT)
GPIO.setup(Lpin2,GPIO.OUT)
GPIO.setup(Lpin3,GPIO.OUT)
GPIO.setup(Rpin1,GPIO.OUT)
GPIO.setup(Rpin2,GPIO.OUT)
GPIO.setup(Rpin3,GPIO.OUT)

pwmL = GPIO.PWM(Lpin3, 100)
pwmL.start(0)
pwmR = GPIO.PWM(Rpin3, 100)
pwmR.start(0)

pwmR.ChangeDutyCycle(0)
pwmL.ChangeDutyCycle(0)

# Get the curses window, turn off echoing of keyboard to screen, turn on
# instant (no waiting) key response, and use special values for cursor keys
screen = curses.initscr()
curses.noecho()
curses.cbreak()
screen.keypad(True)

try:
    while True:
        char = screen.getch()
        if char == ord('q'):
            break

        elif char == curses.KEY_UP:
            leftDC  = 60
            rightDC = 60
            go_straight(leftDC, rightDC)

        elif char == curses.KEY_DOWN:
            leftDC  = 50
            rightDC = 50
            go_back(leftDC, rightDC)

        elif char == curses.KEY_RIGHT:
            leftDC  = leftDC  + 10
            rightDC = rightDC - 10

            if leftDC >= 100:
                leftDC = 100

            if rightDC <= 30:
                rightDC = 30

            turn_right(leftDC, rightDC)

        elif char == curses.KEY_LEFT:
            leftDC  = leftDC  - 10
            rightDC = rightDC + 10

            if leftDC <= 30:
                leftDC = 30

            if rightDC >= 100:
                rightDC = 100

            turn_right(leftDC, rightDC)

        elif char == ord('l'):
            rotation_left()
            leftDC  = 60
            rightDC = 60

        elif char == ord('r'):
            rotation_right()
            leftDC  = 60
            rightDC = 60

        # return
        elif char == 10:
            stop()
            leftDC  = 60
            rightDC = 60

finally:

    # Close down curses properly, inc turn echo back on!
    curses.nocbreak(); screen.keypad(0); curses.echo()
    curses.endwin()
    GPIO.cleanup()
```

##結果
[https://youtu.be/b4VyaNFIfI8](https://youtu.be/b4VyaNFIfI8)
<img width="1112" alt="rCar2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402261/26e15ba0-105e-3926-68b9-3013f085df4f.png">

##最後に
このコースも自動で走らせてみたいですね。

##参考
[https://www.youtube.com/watch?v=j6mglfhWZrQ&t=243s](https://www.youtube.com/watch?v=j6mglfhWZrQ&t=243s)

## 追記
ブログの方でも紹介しています。
こちらもぜひよろしくお願いします。
[ラジコンカーをPWM制御して走らせる - Revel Tech Blog](https://reveltb.com/ikath/pwm/)