Remote Controlled Car
Contents
Choosing the components
Building the chassis
Putting the circuit and chassis together
Writing the code
Making adjustments to the code
The code used to move the RC-Car
Learning outcomes and what I would do different
After purchasing a Raspberry Pi 4 with extra external modules, I began teaching myself to code little projects and tasks using Python. These small tasks helped me develop a fundamental understanding of Python and Linux, which I then used to set myself a more challenging task in building a remote-controlled car.
Choosing the components
To start of, I decided I wanted to run the RC-Car using two 3-12V DC motors. The voltage range would allow me to increase or decrease the power supply accordingly to what I desired. Powering the motor directly through the Raspberry Pi was not possible since the GPIO pins on the board have a limited current supply, doing so would have most likely damaged the pins and rendered the Pi unusable.
My first attempt of powering the motor was by using a breadboard and breadboard power module, teo 10KΩ resistors, a PCF8591 chip (ADC chip) and a L293D chip (motor controller chip). I followed a freenove starter kit tutorial in setting up the circuit, this tutorial can be found here on page 162.
The circuit worked, and with the potentiometer, it allowed me to control the output of the motor. The main problem I encountered with this circuit, however, was the amount of wires being used to construct the circuit. It meant that if the circuit broke, locating the issue would be very time consuming and inefficient. Also, even though voltage would be capped to 9v here, having too many wires can be hazardous for overheating. I came to the conclusion that using this setup was not ideal for those reasons and more.
The L298N motor controller board is a H-Bridge motor driver which can start, stop and, crucially, regulate the speed of more than one motor. Using this board meant it would condense the previous circuit I built down, whilst still carrying out the same functions. Below, using fritzing, is a circuit diagram incorporating the L298N motor controller board. The image below is the circuit from fritzing constructed.
Building the chassis
Next I needed to construct the chassis to house all the components. Since I was keeping costs low as possible, cardboard was the best alternative to acrylics and plastics. In designing the chassis my main priority was having easy access to the major components so I could locate any issues without difficulty. Therefore, I chose to have two layers, the first base would store the battery pack (to power the Pi) and hold both motors which would be glued to the cardboard. It would also have a ball cast acting as a front pivot to allow the car to turn. The second layer would have the Pi, motor board and motor board power bank.
Putting the circuit and chassis together
I flipped the bottom layer so that the battery pack was facing the ground and glued the top layer onto the bottom. I then used the holes I had made in the frame to wrap cable tidies around the car, making it more secure and rigid. Before securing in the motor and Pi wires down with cable tidies, I made sure to check the circuit was still intact, this was indicated by the Pi and L298N motor red lights turning on.
Writing the code
Now that the circuit and chassis has been built, I moved onto coding the motors with Python. Firstly, I needed to check whether both motors were spinning in the right direction. To do this I used the following code:
I imported the Robot and sleep classes from the gpiozero library to allow me to use functions to control the movement of the motor. Next I declared the GPIO pins for both the left and right motor and named the function robot. I began a loop with 'While True' and called the robot function to drive the motors forward. This is the result I got: Both wheels turned at the same rate in the right direction which was perfect as it showed that the GPIO pins, motor board and motors were functional. Had either motor rotated backwards, swapping over the polarity with the wires would have fixed that issue.Now that I knew the RC-Car worked, it was time to detail the code, to have more control over the car.from gpiozero import Robot from time import sleep robot = Robot(left = (7,8), right = (9,10)) while True: robot.forward() sleep(5) robot.stop()
The L298N motor controller boards have enabler pins next to the input pins. This allows the user to control the output of the motor should the script be coded properly. I wanted the user to be able to easily direct the car forwards, backwards and both to the left and right. The user would also be able to stop the car easily whenever they pleased. Most crucially, I wanted the user to be able to increase or decrease the power output to their liking, the enabler pins would help me do this. Below is the code I wrote to do carry out these tasks:
#importing Python module to control GPIO pins and sleep module to allow for delays import RPi.GPIO as GPIO #Assigning each input from the L298N motor-board to RPi GPIO pins in1 = 7 in2 = 8 enA = 20 in3 = 9 in4 = 10 enB = 21 temp1 = 1 #For GPIO numbering GPIO.setmode(GPIO.BCM) #setting GPIO pins as outputs GPIO.setup(in1,GPIO.OUT) GPIO.setup(in2,GPIO.OUT) GPIO.setup(in3,GPIO.OUT) GPIO.setup(in4,GPIO.OUT) #setting enabler pins as output GPIO.setup(enA,GPIO.OUT) GPIO.setup(enB,GPIO.OUT) #setting default output to low GPIO.output(in1,GPIO.LOW) GPIO.output(in2,GPIO.LOW) GPIO.output(in3,GPIO.LOW) GPIO.output(in4,GPIO.LOW) #Assigning PWM frequency [p = GPIO.PWM(channel, frequency)] pA = GPIO.PWM(enA,100) pB = GPIO.PWM(enB,100) #p.start(DutyCycle) pA.start(25) pB.start(25) print('\n') print('Default speed and direction is LOW and FORWARD') print('r-run, s-stop, f-forward, b-backward, l-low, m-medium, h-high, e-exit, right- Turn right, left- Turn left') print('\n') while(1): x = input() if x=='r': print('Run') if(temp1==1): GPIO.output(in1,GPIO.HIGH) GPIO.output(in2,GPIO.LOW) GPIO.output(in3,GPIO.HIGH) GPIO.output(in4,GPIO.LOW) print('Forward') x='z' else: GPIO.output(in1,GPIO.LOW) GPIO.output(in2,GPIO.HIGH) GPIO.output(in3,GPIO.LOW) GPIO.output(in4,GPIO.HIGH) print('Backward') x='z' elif x=='right': print('Turn right') GPIO.output(in1,GPIO.HIGH) GPIO.output(in2,GPIO.LOW) GPIO.output(in3,GPIO.LOW) GPIO.output(in4,GPIO.HIGH) x='z' elif x=='left': print('Turn left') GPIO.output(in1,GPIO.LOW) GPIO.output(in2,GPIO.HIGH) GPIO.output(in3,GPIO.HIGH) GPIO.output(in4,GPIO.LOW) x='z' elif x=='s': print('Stop') GPIO.output(in1,GPIO.LOW) GPIO.output(in2,GPIO.LOW) GPIO.output(in3,GPIO.LOW) GPIO.output(in4,GPIO.LOW) x='z' elif x=='f': print('Forward') GPIO.output(in1,GPIO.HIGH) GPIO.output(in2,GPIO.LOW) GPIO.output(in3,GPIO.HIGH) GPIO.output(in4,GPIO.LOW) temp1=1 x='z' elif x=='b': print('Backward') GPIO.output(in1,GPIO.LOW) GPIO.output(in2,GPIO.HIGH) GPIO.output(in3,GPIO.LOW) GPIO.output(in4,GPIO.HIGH) temp1=1 x='z' elif x=='l': print('Low') pA.ChangeDutyCycle(45) pB.ChangeDutyCycle(45) x='z' elif x=='m': print('Medium') pA.ChangeDutyCycle(75) pB.ChangeDutyCycle(75) x='z' elif x=='h': print('High') pA.ChangeDutyCycle(100) pB.ChangeDutyCycle(100) x='z' elif x=='e': print('Exit') GPIO.cleanup() print('GPIO cleanup') break else: print('--- Wrong data ---') print('--- Please enter defined data to continue ---')
Explaining the code
To start of I have imported classes from the RPi.GPIO library to give me full control over the Pi's pins. I then assigned the GPIO pins I used to the different inputs on the L298N board. These numbers depend on what GPIO pins you use to connect to the motor controller board. Next, I made sure to setup the input and enabler ports on the motor controller board as outputs and afterwards set them to be off as a default setting. This would mean when I run the code, the motors wont start spinning straight away. I set the frequency of the enabler pins for motor A and B at 100Hz, this sets the motor speed and current.
Now that I've setup the pins, I can write the code which the user will engage with. First, I set the output to low by changing the DutyCycle to a low number, the number inside the DutyCycle function ranges from 0-100, with 100 being 100% output for the motor and 0 being 0%. Using print(), I explained to the user what controls they had, this text would be printed into the console. Controlling the motors output and direction was simple, moving the car forwards would be done by setting the output of In1 and In3 as HIGH and In2 and In4 as LOW. Backwards would be the opposite, In1 and 3 as LOW and In2 and 4 as HIGH. To stop, the output would be setting In1,2,3 and 4 as LOW. To turn left, the left motor would rotate backwards and the right would rotate forwards, this was done by setting In2 and 3 as HIGH. To turn right the opposite would need to be done.
Next, using ChangeDutyCycle, I created three functions which could be called to change the speed of the motors. These functions were labelled 'High', 'Medium', and 'Low', this gave the user a variety of speed cycles to choose from. Finally, I added in the GPIO cleanup function to stop the code from running entirely, and a message to be printed into the console should the user enter an incorrect key.
I have used comment lines throughout the script to explain what the code is doing, this script can be found on GitHub here as 'L298N.py'.
Making adjustments to the code
The code used worked well and did exactly what I wanted it to. However, any piece of code written for motor 1 would then need to be rewritten for motor 2, this made the script unecessarily long. This is not good practice of writing code as it is not modular and compact, this can be improved. Instead rewriting the code by constructing a class using __init__ and having the parameters set to the enabler and input pins would mean I would have to describe the functions (forwards, backwards, stop, etc...) once. The code would look something like this:
This code is much easier to read. After defining the left and right motor pins you would now be able to call any of the functions created for both motors, e.gimport RPi.GPIO as GPIO from time import sleep GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) class Motor(): def __init__(self,Ena,In1,In2): self.Ena = Ena self.In1 = In1 self.In2 = In2 GPIO.setup(self.Ena,GPIO.OUT) GPIO.setup(self.In1,GPIO.OUT) GPIO.setup(self.In2,GPIO.OUT) self.pwm = GPIO.PWM(self.Ena,100) self.pwm.start(0) def moveF(self, x=50, t=0): GPIO.output(self.In1,GPIO.LOW) GPIO.output(self.In2,GPIO.HIGH) self.pwm.ChangeDutyCycle(x) sleep(t) def moveB (self, x=50, t=0): GPIO.output(self.In1,GPIO.HIGH) GPIO.output(self.In2,GPIO.LOW) self.pwm.ChangeDutyCycle(x) sleep(t) def stop(self, t=0): self.pwm.ChangeDutyCycle(0) sleep(t) motor_left = Motor(20,7,8) motor_right = Motor(21,9,10)
motor_left.moveF()
. This script can also be found on GitHub here as 'L298N_V2.py'.
Below is a video displaying car going forwards, backwards, rotating right and left using the script
And there you have it, a fully functional remote controlled car!
Learning outcomes and what I would do different
What I learnt doing this project:
- Managing and following a structured plan helps in being organised
- Furthered my knowledge in Python and Linux
- Improved my ability to manage files using terminal for both windows and linux
- Having a support system is key when building a project. In this case, having spare parts and an easily accessible build was very important
- Creating blueprints (e.g the fritzing diagram) makes it easy for others and yourself to understand what you have built
- Cardboard was too fragile and bent too easily, it was one of the reasons why the wheels werent straight. Using acrylic would have been the best alternative as it isnt too costly and it is rigid
- Incorporated an axel of somesort to make sure the motors were parallel to eachother all the time. This, along with the cardboard issue, meant that the RC-Car tended to drift off to the left or right went going forwards or backwards
- Used more durable or stronger motors. The ones used utilised plastic cogs, which, after sometime, began to grind down and become useless after some time.
- I had anticipated the RC-Car to use a lot of battery, hence why I bought a large battery pack. This was not the case and this only ended up weighing down the car