Monday, February 27, 2017

Raspberry Pi and the Seeed Motor Drive Controller

Raspberry Pi Motor Board


To control the two drive motors for Alexa M we are using the Raspberry Pi Motor Board from Seeed. It is based on the Freescale MC33932 dual H-Bridge Power IC, which can control inductive loads with currents of up to 5.0A peak per single bridge. It lets you drive two DC motors with your Raspberry Pi B/B+/A+ and Pi 2/3 Model B, controlling the speed and direction of each one independently.

The Raspberry Pi Motor Driver Board v1.0 supports input voltage from 6V~28V, the on board DC/DC converter provides a 5V power supply for the Raspberry Pi with 1000mA maximum current.

Thus, you just need one power supply to drive the motors and power up the Raspberry Pi and Motor Board.


The board has the following features:

  • Operating Voltage: 6V ~ 28V
  • DC/DC output: 5V 1000mA @ "5V" pin
  • Output Current(For Each Channel ): 2A (continuous operation) / 5A(peak)
  • Output Duty Range: 0%~100%
  • Output short-circuit protection (short to VPWR or GND)
  • Over-current limiting (regulation) via internal constant-off-time PWM
  • Temperature dependant current limit threshold reduction





Wiring up the Motor Control Board


Wiring is pretty straight forward. The battery pack plugs into J1. We are using 6 x 1.5 AA batteries in the battery pack (i.e. 9V) which is sufficient to drive both motors without the Raspberry Pi browning out and rebooting.

The motors connect to J2. We connected:

  • OUT1 - left motor red
  • OUT2 - left motor black
  • OUT3 - right motor red
  • OUT4 - right motor black

The Raspberry Pi connects via the header. You need the pin details to control the motor control board and to ensure there are no conflicts with HAT's or other I/O.



The pins used by Alexa M are shown above. The blue highlighted ones are the Motor Control Board. In particular, the GPIO connected to the MC33932 pins are:

        PWMA        = GPIO 25
        PWMB        = GPIO 22
        IN1              = GPIO 23
        IN2              = GPIO 24
        IN3              = GPIO 17
        IN4              = GPIO 27

The block diagram of the MC33932 throttle control H bridge is shown below.




And the logic commands required to operate the chip are:


Luckily we don't have to write our own motor driver code as the folks at Seeed have already provided it. I have tweaked this a bit to make it Python 3 compliant and have also provided the required PiSoftPwm class as this is a bit difficult to track down.

To test your Motor Control Board, run the following:

#!/usr/bin/python
import RPi.GPIO as GPIO
import time
import signal   

from PiSoftPwm import *

#print 'Go_1...'
#frequency = 1.0 / self.sc_1.GetValue()
#speed = self.sc_2.GetValue()

class Motor():
    def __init__(self):
        # MC33932 pins
        self.PWMA = 25  
        self.PWMB = 22
        self._IN1 = 23  
        self._IN2 = 24 
        self._IN3 = 17
        self._IN4 = 27

        # Initialize PWMA PWMB 
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.PWMA, GPIO.OUT)
        GPIO.setup(self.PWMB, GPIO.OUT)
        GPIO.output(self.PWMA, True)
        GPIO.output(self.PWMB, True)

        # Initialize PWM outputs
        self.OUT_1  = PiSoftPwm(0.1, 100, self._IN1, GPIO.BCM)
        self.OUT_2  = PiSoftPwm(0.1, 100, self._IN2, GPIO.BCM)
        self.OUT_3  = PiSoftPwm(0.1, 100, self._IN3, GPIO.BCM)
        self.OUT_4  = PiSoftPwm(0.1, 100, self._IN4, GPIO.BCM)

            # Close pwm output
        self.OUT_1.start(0)
        self.OUT_2.start(0)
        self.OUT_3.start(0)
        self.OUT_4.start(0)

        self.frequency = 0.01
        self.duty = 60

    def Setting(self, frequency, duty):
        self.frequency = frequency
        self.duty = duty

    def Go_1(self):
        self.OUT_1.changeBaseTime(self.frequency)
        self.OUT_2.changeBaseTime(self.frequency)
        self.OUT_1.changeNbSlicesOn(self.duty)
        self.OUT_2.changeNbSlicesOn(0)

    def Back_1(self):
        self.OUT_1.changeBaseTime(self.frequency)
        self.OUT_2.changeBaseTime(self.frequency)
        self.OUT_1.changeNbSlicesOn(0)
        self.OUT_2.changeNbSlicesOn(self.duty)

    def Go_2(self):
        self.OUT_3.changeBaseTime(self.frequency)
        self.OUT_4.changeBaseTime(self.frequency)
        self.OUT_3.changeNbSlicesOn(0)
        self.OUT_4.changeNbSlicesOn(self.duty)

    def Back_2(self):
        self.OUT_3.changeBaseTime(self.frequency)
        self.OUT_4.changeBaseTime(self.frequency)
        self.OUT_3.changeNbSlicesOn(self.duty)
        self.OUT_4.changeNbSlicesOn(0)

    def Stop():
        self.OUT_1.changeNbSlicesOn(0)
        self.OUT_2.changeNbSlicesOn(0)
        self.OUT_3.changeNbSlicesOn(0)
        self.OUT_4.changeNbSlicesOn(0)

if __name__=="__main__":
    motor=Motor()
    # Called on process interruption. Set all pins to "Input" default mode.
    def endProcess(signalnum = None, handler = None):
        motor.OUT_1.stop()
        motor.OUT_2.stop()
        motor.OUT_3.stop()
        motor.OUT_4.stop()
        GPIO.cleanup()
        exit(0)

    # Prepare handlers for process exit
    signal.signal(signal.SIGTERM, endProcess)
    signal.signal(signal.SIGINT, endProcess)
    signal.signal(signal.SIGHUP, endProcess)
    signal.signal (signal.SIGQUIT, endProcess)

    motor.Setting(0.01, 60)
    print('motor start...')
    while True:
        print('turning direction...')
        motor.Go_1()
        time.sleep(1)
        motor.Back_1()
        time.sleep(1)
        motor.Go_2()
        time.sleep(1)
        motor.Back_2()
        time.sleep(1)

The PiSoftPwm class needs to be saved in the same directory as the motor test code above.

# The original is aboudou ,the Source code is here : https://goddess-gate.com/dc2/index.php/pages/raspiledmeter.en
# The modifier is ukonline2000

import RPi.GPIO as GPIO
import threading
import time
 
class PiSoftPwm(threading.Thread):

  def __init__(self, baseTime, nbSlices, gpioPin, gpioScheme):
     """ 
     Init the PiSoftPwm instance. Expected parameters are :
     - baseTime : the base time in seconds for the PWM pattern. You may choose a small value (i.e 0.01 s)
     - nbSlices : the number of divisions of the PWM pattern. A single pulse will have a min duration of baseTime * (1 / nbSlices)
     - gpioPin : the pin number which will act as PWM ouput
     - gpioScheme : the GPIO naming scheme (see RPi.GPIO documentation)
     """
     self.sliceTime = baseTime / nbSlices
     self.baseTime = baseTime
     self.nbSlices = nbSlices
     self.gpioPin = gpioPin
     self.terminated = False
     self.toTerminate = False
     GPIO.setmode(gpioScheme)

  def start(self, nbSlicesOn):
    """
    Start PWM output. Expected parameter is :
    - nbSlicesOn : number of divisions (on a total of nbSlices - see init() doc) to set HIGH output on the GPIO pin
    
    Exemple : with a total of 100 slices, a baseTime of 1 second, and an nbSlicesOn set to 25, the PWM pattern will
    have a duty cycle of 25%. With a duration of 1 second, will stay HIGH for 1*(25/100) seconds on HIGH output, and
    1*(75/100) seconds on LOW output.
    """
    self.nbSlicesOn = nbSlicesOn
    GPIO.setup(self.gpioPin, GPIO.OUT)
    self.thread = threading.Thread(None, self.run, None, (), {})
    self.thread.start()

  def run(self):
    """
    Run the PWM pattern into a background thread. This function should not be called outside of this class.
    """
    while self.toTerminate == False:
      if self.nbSlicesOn > 0:
        GPIO.output(self.gpioPin, GPIO.HIGH)
        time.sleep(self.nbSlicesOn * self.sliceTime)
      if self.nbSlicesOn < self.nbSlices:
        GPIO.output(self.gpioPin, GPIO.LOW)
        time.sleep((self.nbSlices - self.nbSlicesOn) * self.sliceTime)
    self.terminated = True

  def changeNbSlicesOn(self, nbSlicesOn):
    """
    Change the duration of HIGH output of the pattern. Expected parameter is :
    - nbSlicesOn : number of divisions (on a total of nbSlices - see init() doc) to set HIGH output on the GPIO pin
    
    Exemple : with a total of 100 slices, a baseTime of 1 second, and an nbSlicesOn set to 25, the PWM pattern will
    have a duty cycle of 25%. With a duration of 1 second, will stay HIGH for 1*(25/100) seconds on HIGH output, and
    1*(75/100) seconds on LOW output.
    """
    self.nbSlicesOn = nbSlicesOn

  def changeNbSlices(self, nbSlices):
    """
    Change the number of slices of the PWM pattern. Expected parameter is :
    - nbSlices : number of divisions of the PWM pattern.
    
    Exemple : with a total of 100 slices, a baseTime of 1 second, and an nbSlicesOn set to 25, the PWM pattern will
    have a duty cycle of 25%. With a duration of 1 second, will stay HIGH for 1*(25/100) seconds on HIGH output, and
    1*(75/100) seconds on LOW output.
    """
    if self.nbSlicesOn > nbSlices:
      self.nbSlicesOn = nbSlices

    self.nbSlices = nbSlices
    self.sliceTime = self.baseTime / self.nbSlices

  def changeBaseTime(self, baseTime):
    """
    Change the base time of the PWM pattern. Expected parameter is :
    - baseTime : the base time in seconds for the PWM pattern.
    
    Exemple : with a total of 100 slices, a baseTime of 1 second, and an nbSlicesOn set to 25, the PWM pattern will
    have a duty cycle of 25%. With a duration of 1 second, will stay HIGH for 1*(25/100) seconds on HIGH output, and
    1*(75/100) seconds on LOW output.
    """
    self.baseTime = baseTime
    self.sliceTime = self.baseTime / self.nbSlices


  def stop(self):
    """
    Stops PWM output.
    """
    self.toTerminate = True
    while self.terminated == False:
      # Just wait
      time.sleep(0.01)
  
    GPIO.output(self.gpioPin, GPIO.LOW)
    GPIO.setup(self.gpioPin, GPIO.IN)

Next up we will pull everything together in a Robot python class.

No comments:

Post a Comment