├── lib ├── __init__.py ├── xbox_read.py ├── Adafruit_PWM_Servo_Driver.py └── Adafruit_I2C.py ├── lego-controller.sh ├── README.markdown └── control.py /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lego-controller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | modprobe i2c-dev 4 | modprobe i2c-bcm2708 5 | 6 | # Get my directory 7 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | 9 | python $DIR/control.py 10 | 11 | # attempt to clean up 12 | killall xboxdrv 13 | 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | This repository is attached to my Lego + Raspberry Pi + Xbox Controller project. 2 | 3 | * Read the [full article](http://blog.zephod.com/post/37120089376/lego-xbox-raspberry-pi) on my blog. 4 | * Watch it [in action](http://www.youtube.com/watch?feature=player_embedded&v=X7YRqCCBDBU) on YouTube. 5 | 6 | ## Overview 7 | 8 | #### `lego-controller.sh` 9 | 10 | Main script to execute the RC car controller. 11 | 12 | #### `control.py` 13 | 14 | Reads `Event` objects from the Xbox controller, and updates the GPIO/I2C outputs on the Raspberry Pi as appropriate. 15 | 16 | #### `lib/xbox_read.py` 17 | 18 | Wrapper around `xboxdrv` driver. Reads from the controller and emits `Event` objects, which have a `key` (`A`, `X1`, or other controller button) and a `value` (`0` or `1` for buttons. `0` to `256` for triggers. `-32767` to `32768` for thumbsticks. 19 | 20 | 21 | 22 | ## Installation on a fresh Raspberry Pi 23 | 24 | #### Get pip. Update Python. Get C headers. Install the interface library. 25 | sudo apt-get install python-pip 26 | sudo easy_install -U distribute 27 | sudo apt-get install python-dev 28 | sudo pip install RPi.GPIO 29 | 30 | #### Install I2C communications libraries 31 | sudo apt-get install python-smbus 32 | sudo apt-get install i2c-tools 33 | sudo modprobe i2c-bcm2708 34 | sudo modprobe i2c-dev 35 | 36 | #### Install the XBox controller driver 37 | sudo apt-get install xboxdrv 38 | 39 | #### Prove that it works... (wiggle the controller sticks) 40 | sudo xboxdrv --wid 0 -l 2 --dpad-as-button --deadzone 12000 41 | 42 | #### Execute the motor controller 43 | sudo ./lego-controller.sh 44 | -------------------------------------------------------------------------------- /lib/xbox_read.py: -------------------------------------------------------------------------------- 1 | from os import popen 2 | from sys import stdin 3 | import re 4 | 5 | s = re.compile('[ :]') 6 | 7 | class Event: 8 | def __init__(self,key,value,old_value): 9 | self.key = key 10 | self.value = value 11 | self.old_value = old_value 12 | def is_press(self): 13 | return self.value==1 and self.old_value==0 14 | def __str__(self): 15 | return 'Event(%s,%d,%d)' % (self.key,self.value,self.old_value) 16 | 17 | def apply_deadzone(x, deadzone, scale): 18 | if x < 0: 19 | return (scale * min(0,x+deadzone)) / (32768-deadzone) 20 | return (scale * max(0,x-deadzone)) / (32768-deadzone) 21 | 22 | def event_stream(deadzone=0,scale=32768): 23 | _data = None 24 | subprocess = popen('nohup xboxdrv','r',65536) 25 | while (True): 26 | line = subprocess.readline() 27 | if 'Error' in line: 28 | raise ValueError(line) 29 | data = filter(bool,s.split(line[:-1])) 30 | if len(data)==42: 31 | # Break input string into a data dict 32 | data = { data[x]:int(data[x+1]) for x in range(0,len(data),2) } 33 | if not _data: 34 | _data = data 35 | continue 36 | for key in data: 37 | if key=='X1' or key=='X2' or key=='Y1' or key=='Y2': 38 | data[key] = apply_deadzone(data[key],deadzone,scale) 39 | if data[key]==_data[key]: continue 40 | event = Event(key,data[key],_data[key]) 41 | yield event 42 | _data = data 43 | 44 | # Appendix: Keys 45 | # -------------- 46 | # X1 47 | # Y1 48 | # X2 49 | # Y2 50 | # du 51 | # dd 52 | # dl 53 | # dr 54 | # back 55 | # guide 56 | # start 57 | # TL 58 | # TR 59 | # A 60 | # B 61 | # X 62 | # Y 63 | # LB 64 | # RB 65 | # LT 66 | # RT 67 | -------------------------------------------------------------------------------- /control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import RPi.GPIO as GPIO 4 | from lib.Adafruit_PWM_Servo_Driver import PWM 5 | from lib import xbox_read 6 | import time 7 | 8 | # Initialise the PWM device using the default address 9 | pwm = PWM(0x40, debug=True) 10 | 11 | # Initialise the GPIO output channels 12 | GPIO.setmode(GPIO.BCM) 13 | FORWARD = 21 14 | BACKWARD = 17 15 | GPIO.setup(FORWARD, GPIO.OUT) 16 | GPIO.setup(BACKWARD, GPIO.OUT) 17 | direction = None 18 | 19 | # Setup direction of travel 20 | def setDirection(new_direction): 21 | global direction # One global property, cut me some slack 22 | if direction: 23 | GPIO.output(direction,False) 24 | GPIO.output(new_direction,True) 25 | direction = new_direction 26 | 27 | setDirection(FORWARD) 28 | 29 | # Default calibration values 30 | servoMid = 425 31 | servoWidth = 180 32 | 33 | pwm.setPWMFreq(60) # Set frequency to 60 Hz 34 | 35 | rt_intensity = 0 36 | lt_intensity = 0 37 | steer = servoMid 38 | 39 | for event in xbox_read.event_stream(deadzone=12000): 40 | # Triggers control speed 41 | if event.key=='RT' or event.key=='LT': 42 | if event.key=='RT': 43 | rt_intensity = event.value 44 | else: 45 | lt_intensity = event.value 46 | # Change direction outputs when one trigger is pulled harder than the other 47 | new_direction = FORWARD if rt_intensity>=lt_intensity else BACKWARD 48 | if not direction==new_direction: 49 | print 'set direction: %s' % {FORWARD:'FORWARD',BACKWARD:'BACKWARD'}[new_direction] 50 | setDirection(new_direction) 51 | intensity = max(rt_intensity,lt_intensity) * 16 52 | pwm.setPWM(0, 0, intensity) 53 | print 'set speed: %d' % intensity 54 | # Left thumbstick controls the steering 55 | if event.key=='X1': 56 | steer = int( servoMid + (servoWidth*-event.value)/32768 ) 57 | pwm.setPWM(1, 0, steer) 58 | if event.key=='Y' and event.is_press(): 59 | servoMid = steer 60 | print 'Calibrate steering around %d' % steer 61 | 62 | -------------------------------------------------------------------------------- /lib/Adafruit_PWM_Servo_Driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import time 4 | import math 5 | from Adafruit_I2C import Adafruit_I2C 6 | 7 | # ============================================================================ 8 | # Adafruit PCA9685 16-Channel PWM Servo Driver 9 | # ============================================================================ 10 | 11 | class PWM : 12 | i2c = None 13 | 14 | # Registers/etc. 15 | __SUBADR1 = 0x02 16 | __SUBADR2 = 0x03 17 | __SUBADR3 = 0x04 18 | __MODE1 = 0x00 19 | __PRESCALE = 0xFE 20 | __LED0_ON_L = 0x06 21 | __LED0_ON_H = 0x07 22 | __LED0_OFF_L = 0x08 23 | __LED0_OFF_H = 0x09 24 | __ALLLED_ON_L = 0xFA 25 | __ALLLED_ON_H = 0xFB 26 | __ALLLED_OFF_L = 0xFC 27 | __ALLLED_OFF_H = 0xFD 28 | 29 | def __init__(self, address=0x40, debug=False): 30 | self.i2c = Adafruit_I2C(address) 31 | self.address = address 32 | self.debug = debug 33 | if (self.debug): 34 | print "Reseting PCA9685" 35 | self.i2c.write8(self.__MODE1, 0x00) 36 | 37 | def setPWMFreq(self, freq): 38 | "Sets the PWM frequency" 39 | prescaleval = 25000000.0 # 25MHz 40 | prescaleval /= 4096.0 # 12-bit 41 | prescaleval /= float(freq) 42 | prescaleval -= 1.0 43 | if (self.debug): 44 | print "Setting PWM frequency to %d Hz" % freq 45 | print "Estimated pre-scale: %d" % prescaleval 46 | prescale = math.floor(prescaleval + 0.5) 47 | if (self.debug): 48 | print "Final pre-scale: %d" % prescale 49 | 50 | oldmode = self.i2c.readU8(self.__MODE1); 51 | newmode = (oldmode & 0x7F) | 0x10 # sleep 52 | self.i2c.write8(self.__MODE1, newmode) # go to sleep 53 | self.i2c.write8(self.__PRESCALE, int(math.floor(prescale))) 54 | self.i2c.write8(self.__MODE1, oldmode) 55 | time.sleep(0.005) 56 | self.i2c.write8(self.__MODE1, oldmode | 0x80) 57 | 58 | def setPWM(self, channel, on, off): 59 | "Sets a single PWM channel" 60 | self.i2c.write8(self.__LED0_ON_L+4*channel, on & 0xFF) 61 | self.i2c.write8(self.__LED0_ON_H+4*channel, on >> 8) 62 | self.i2c.write8(self.__LED0_OFF_L+4*channel, off & 0xFF) 63 | self.i2c.write8(self.__LED0_OFF_H+4*channel, off >> 8) 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/Adafruit_I2C.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import smbus 4 | 5 | # =========================================================================== 6 | # Adafruit_I2C Base Class 7 | # =========================================================================== 8 | 9 | class Adafruit_I2C : 10 | 11 | def __init__(self, address, bus=smbus.SMBus(0), debug=False): 12 | self.address = address 13 | self.bus = bus 14 | self.debug = debug 15 | 16 | def reverseByteOrder(self, data): 17 | "Reverses the byte order of an int (16-bit) or long (32-bit) value" 18 | # Courtesy Vishal Sapre 19 | dstr = hex(data)[2:].replace('L','') 20 | byteCount = len(dstr[::2]) 21 | val = 0 22 | for i, n in enumerate(range(byteCount)): 23 | d = data & 0xFF 24 | val |= (d << (8 * (byteCount - i - 1))) 25 | data >>= 8 26 | return val 27 | 28 | def write8(self, reg, value): 29 | "Writes an 8-bit value to the specified register/address" 30 | try: 31 | self.bus.write_byte_data(self.address, reg, value) 32 | if (self.debug): 33 | print("I2C: Wrote 0x%02X to register 0x%02X" % (value, reg)) 34 | except IOError, err: 35 | print "Error accessing 0x%02X: Check your I2C address" % self.address 36 | return -1 37 | 38 | def writeList(self, reg, list): 39 | "Writes an array of bytes using I2C format" 40 | try: 41 | self.bus.write_i2c_block_data(self.address, reg, list) 42 | except IOError, err: 43 | print "Error accessing 0x%02X: Check your I2C address" % self.address 44 | return -1 45 | 46 | def readU8(self, reg): 47 | "Read an unsigned byte from the I2C device" 48 | try: 49 | result = self.bus.read_byte_data(self.address, reg) 50 | if (self.debug): 51 | print "I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg) 52 | return result 53 | except IOError, err: 54 | print "Error accessing 0x%02X: Check your I2C address" % self.address 55 | return -1 56 | 57 | def readS8(self, reg): 58 | "Reads a signed byte from the I2C device" 59 | try: 60 | result = self.bus.read_byte_data(self.address, reg) 61 | if (self.debug): 62 | print "I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg) 63 | if (result > 127): 64 | return result - 256 65 | else: 66 | return result 67 | except IOError, err: 68 | print "Error accessing 0x%02X: Check your I2C address" % self.address 69 | return -1 70 | 71 | def readU16(self, reg): 72 | "Reads an unsigned 16-bit value from the I2C device" 73 | try: 74 | hibyte = self.bus.read_byte_data(self.address, reg) 75 | result = (hibyte << 8) + self.bus.read_byte_data(self.address, reg+1) 76 | if (self.debug): 77 | print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg) 78 | return result 79 | except IOError, err: 80 | print "Error accessing 0x%02X: Check your I2C address" % self.address 81 | return -1 82 | 83 | def readS16(self, reg): 84 | "Reads a signed 16-bit value from the I2C device" 85 | try: 86 | hibyte = self.bus.read_byte_data(self.address, reg) 87 | if (hibyte > 127): 88 | hibyte -= 256 89 | result = (hibyte << 8) + self.bus.read_byte_data(self.address, reg+1) 90 | if (self.debug): 91 | print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg) 92 | return result 93 | except IOError, err: 94 | print "Error accessing 0x%02X: Check your I2C address" % self.address 95 | return -1 96 | --------------------------------------------------------------------------------