#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ AMSpi class - Python class for controlling Arduino Motor Shield L293D from Raspberry Pi .. Licence MIT .. codeauthor:: Jan Lipovský <janlipovsky@gmail.com>, janlipovsky.cz .. contributors: Daniel Neumann """ try: import RPi.GPIO as GPIO except RuntimeError: print("Error importing RPi.GPIO! This is probably because you need superuser privileges. " "You can achieve this by using 'sudo' to run your script") class AMSpi: """ Main class for controlling Arduino Motor Shield L293D via Raspberry Pi GPIO using RPi.GPIO """ # Motor numbering DC_Motor_1 = 1 DC_Motor_2 = 2 DC_Motor_3 = 3 DC_Motor_4 = 4 # Shift register _DIR_LATCH = None _DIR_CLK = None _DIR_SER = None # constants used in dictionary _PIN_ = 1 _DIRECTION_ = 2 _IS_RUNNING_ = 3 _RUNNING_DIRECTION_ = 4 _PWM_ = 5 _PWM_FREQUENCY_ = 6 _PWM_DUTY_CYCLE_ = 7 # DC Motors states and settings # pin - PIN on which is DC Motor connected # direction - List of numbers that set direction of motor (clockwise, counterclockwise, no spin) # is_running - True if motor is running # running_direction - Direction of the motor # pwm_frequency - Frequency of pulse-width modulation (pwm) # pwm - Un-/set pwm object _MOTORS = { DC_Motor_1: {_PIN_: None, _DIRECTION_: [4, 8, 4 | 8], _IS_RUNNING_: False, _RUNNING_DIRECTION_: None, _PWM_FREQUENCY_: 10, _PWM_DUTY_CYCLE_: 100, _PWM_: None}, DC_Motor_2: {_PIN_: None, _DIRECTION_: [2, 16, 2 | 16], _IS_RUNNING_: False, _RUNNING_DIRECTION_: None, _PWM_FREQUENCY_: 10, _PWM_DUTY_CYCLE_: 100, _PWM_: None}, DC_Motor_3: {_PIN_: None, _DIRECTION_: [32, 128, 32 | 128], _IS_RUNNING_: False, _RUNNING_DIRECTION_: None, _PWM_FREQUENCY_: 10, _PWM_DUTY_CYCLE_: 100, _PWM_: None}, DC_Motor_4: {_PIN_: None, _DIRECTION_: [1, 64, 1 | 64], _IS_RUNNING_: False, _RUNNING_DIRECTION_: None, _PWM_FREQUENCY_: 10, _PWM_DUTY_CYCLE_: 100, _PWM_: None} } # indexes of motor direction list _clockwise = 0 _counterclockwise = 1 _stop = 2 def __init__(self, use_board=False): """ Initialize function for AMSpi class :param bool use_board: True if GPIO.BOARD numbering will be used """ if use_board: GPIO.setmode(GPIO.BOARD) print("PIN numbering: BOARD") else: GPIO.setmode(GPIO.BCM) print("PIN numbering: BCM") def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): try: if self._test_shift_pins(): self._shift_write(0) self.stop_dc_motors([self.DC_Motor_1, self.DC_Motor_2, self.DC_Motor_3, self.DC_Motor_4]) GPIO.cleanup() except RuntimeWarning: return True def _test_shift_pins(self): """ Test if PINs of shift register were set :return: True if test passed, False otherwise :rtype: bool """ if self._DIR_LATCH is None: return False if self._DIR_CLK is None: return False if self._DIR_SER is None: return False return True def _shift_write(self, value): """ Write given value to the shift register :param int value: value which you want to write to shift register """ if self._test_shift_pins() is False: print("ERROR: PINs for shift register were not set properly.") self.__exit__(None, None, None) GPIO.output(self._DIR_LATCH, GPIO.LOW) for x in range(0, 8): temp = value & 0x80 GPIO.output(self._DIR_CLK, GPIO.LOW) if temp == 0x80: # data bit HIGH GPIO.output(self._DIR_SER, GPIO.HIGH) else: # data bit LOW GPIO.output(self._DIR_SER, GPIO.LOW) GPIO.output(self._DIR_CLK, GPIO.HIGH) value <<= 0x01 # shift left GPIO.output(self._DIR_LATCH, GPIO.HIGH) def set_74HC595_pins(self, DIR_LATCH, DIR_CLK, DIR_SER): """ Set PINs used on Raspberry Pi to connect with 74HC595 module on Arduino Motor Shield :param int DIR_LATCH: LATCH PIN number :param int DIR_CLK: CLK PIN number :param int DIR_SER: SER PIN number """ self._DIR_LATCH = DIR_LATCH self._DIR_CLK = DIR_CLK self._DIR_SER = DIR_SER GPIO.setup(self._DIR_LATCH, GPIO.OUT) GPIO.setup(self._DIR_CLK, GPIO.OUT) GPIO.setup(self._DIR_SER, GPIO.OUT) def set_L293D_pins(self, PWM0A=None, PWM0B=None, PWM2A=None, PWM2B=None): """ Set PINs used on Raspberry Pi to connect with L293D module on Arduino Motor Shield :param int PWM0A: PWM0A PIN number :param int PWM0B: PWM0B PIN number :param int PWM2A: PWM2A PIN number :param int PWM2B: PWM2B PIN number """ # self.PWM0A = PWM0A self._MOTORS[self.DC_Motor_4][self._PIN_] = PWM0B # self.PWM0B = PWM0B self._MOTORS[self.DC_Motor_3][self._PIN_] = PWM0A # self.PWM2A = PWM2A self._MOTORS[self.DC_Motor_1][self._PIN_] = PWM2A # self.PWM2B = PWM2B self._MOTORS[self.DC_Motor_2][self._PIN_] = PWM2B if PWM0A is not None: GPIO.setup(PWM0A, GPIO.OUT) if PWM0B is not None: GPIO.setup(PWM0B, GPIO.OUT) if PWM2A is not None: GPIO.setup(PWM2A, GPIO.OUT) if PWM2B is not None: GPIO.setup(PWM2B, GPIO.OUT) def _get_motors_direction(self, dc_motor, directions_index): """ Compute number that should be writen to shift register to run/stop motor :param int dc_motor: number of dc motor :param int directions_index: index to motor direction list :return: number for shift register, motors direction value :rtype: tuple """ direction_value = self._MOTORS[dc_motor][self._DIRECTION_][directions_index] all_motors_direction = direction_value for tmp_dc_motor in [self.DC_Motor_1, self.DC_Motor_2, self.DC_Motor_3, self.DC_Motor_4]: if tmp_dc_motor == dc_motor: continue if self._MOTORS[tmp_dc_motor][self._RUNNING_DIRECTION_] is not None: all_motors_direction += self._MOTORS[tmp_dc_motor][self._RUNNING_DIRECTION_] return all_motors_direction, direction_value def set_pwm_frequency(self, motors_freq): """ Sets the pulse-width modulation (pwm) frequencies for each motor. :param dict motors_freq: Motors and its values for pwm frequencies. Should be high enough to run smoothly, but too high values can cause RPi.GPIO to crash. Example: {AMSpi.DC_Motor_1: 50, AMSpi.DC_Motor_2: 50, AMSpi.DC_Motor_3: 50, AMSpi.DC_Motor_4: 50} :raise: AssertionError """ assert all([True if x in self._MOTORS.keys() else False for x in motors_freq.keys()]), "Unknown Motor was set." for motor in motors_freq.keys(): self._MOTORS[motor][self._PWM_FREQUENCY_] = motors_freq[motor] def get_pwm_frequency(self): """ Returns the current pulse-width modulation (pwm) frequencies for each motor. :return: Current pwm frequency for each motor in dict. :rtype: dict """ return {motor: self._MOTORS[motor][self._PWM_FREQUENCY_] for motor in self._MOTORS.keys()} def get_pwm_duty_cycle(self): """ Returns the current duty cycle lengths for each motor. :return: Length of duty cycle for each motor in dict. :rtype: dict """ return {motor: self._MOTORS[motor][self._PWM_DUTY_CYCLE_] for motor in self._MOTORS.keys()} def run_dc_motor(self, dc_motor, clockwise=True, speed=None): """ Run motor with given direction :param int dc_motor: number of dc motor :param bool clockwise: True for clockwise False for counterclockwise :param int speed: pwm duty cycle (range 0-100) :return: False in case of an ERROR, True if everything is OK :rtype: bool """ if self._MOTORS[dc_motor][self._PIN_] is None: print("WARNING: Pin for DC_Motor_{} is not set. Can not run motor.".format(dc_motor)) return False all_motors_direction, direction_value = self._get_motors_direction(dc_motor, int(not clockwise)) # set motors direction self._shift_write(all_motors_direction) # turn the motor on (if speed argument is not given then full speed, otherwise set pwm according to speed) if speed is None: # stop PWM if was used before if self._MOTORS[dc_motor][self._PWM_] is not None: # noinspection PyUnresolvedReferences self._MOTORS[dc_motor][self.__PWM__].stop() self._MOTORS[dc_motor][self._PWM_] = None GPIO.output(self._MOTORS[dc_motor][self._PIN_], GPIO.HIGH) elif 0 <= speed <= 100: self._MOTORS[dc_motor][self._PWM_DUTY_CYCLE_] = speed if self._MOTORS[dc_motor][self._PWM_] is None: self._MOTORS[dc_motor][self._PWM_] = GPIO.PWM(self._MOTORS[dc_motor][self._PIN_], self._MOTORS[dc_motor][self._PWM_FREQUENCY_]) # noinspection PyUnresolvedReferences self._MOTORS[dc_motor][self._PWM_].start(self._MOTORS[dc_motor][self._PWM_DUTY_CYCLE_]) else: # noinspection PyUnresolvedReferences self._MOTORS[dc_motor][self._PWM_].ChangeDutyCycle(self._MOTORS[dc_motor][self._PWM_DUTY_CYCLE_]) else: print("WARNING: Speed argument must be in range 0-100! But %s given. " "Keeping previous setting (%s)." % (speed, self._MOTORS[dc_motor][self._PWM_DUTY_CYCLE_])) self._MOTORS[dc_motor][self._IS_RUNNING_] = True self._MOTORS[dc_motor][self._RUNNING_DIRECTION_] = direction_value return True def run_dc_motors(self, dc_motors, clockwise=True, speed=None): """ Run motors with given direction :param list[int] dc_motors: list of dc motor numbers :param bool clockwise: True for clockwise, False for counterclockwise :param int speed: pwm duty cycle (range 0-100) :return: False in case of an ERROR, True if everything is OK :rtype: bool """ for dc_motor in dc_motors: self.run_dc_motor(dc_motor, clockwise, speed) def stop_dc_motor(self, dc_motor): """ Stop running motor :param int dc_motor: number of dc motor :return: False in case of an ERROR, True if everything is OK :rtype: bool """ if self._MOTORS[dc_motor][self._PIN_] is None: # print("WARNING: Pin for DC_Motor_{} is not set. Stopping motor could not be done".format(dc_motor)) return False all_motors_direction, direction_value = self._get_motors_direction(dc_motor, self._stop) # set motors direction self._shift_write(all_motors_direction) if self._MOTORS[dc_motor][self._PWM_] is None: GPIO.output(self._MOTORS[dc_motor][self._PIN_], GPIO.LOW) else: # noinspection PyUnresolvedReferences self._MOTORS[dc_motor][self._PWM_].stop() self._MOTORS[dc_motor][self._PWM_] = None self._MOTORS[dc_motor][self._IS_RUNNING_] = False self._MOTORS[dc_motor][self._RUNNING_DIRECTION_] = None return True def stop_dc_motors(self, dc_motors): """ Stop motors set in list :param list[int] dc_motors: list of dc motor numbers :return: False in case of an ERROR, True if everything is OK :rtype: bool """ for dc_motor in dc_motors: if not self.stop_dc_motor(dc_motor): return False return True