Hello!
This video will show you how to control DC and Servo motors using the Raspberry Pi and a joystick.
Knowing how to control motors is foundational to robotics and you can use the approach
I will demonstrate to build mechanical devices such as remote-controlled vehicles and robotic arms.
Let's start with the circuit!
The MCP3002 chip is an analog-to-digital converter, which converts the voltage provided by the
joystick to a digital value between 0 and 1 for each joystick axis.
I use a standard joystick available in typical electronics kits, but I replaced the angled headers
with straight ones using a soldering iron.
This allows placing the joystick flat on the breadboard.
The L293D chip is a bi-directional motor controller, which allows controlling the speed and direction
of up to two standard DC motors.
I use a small propeller so you can actually see the motor movement.
I use a standard DC motor which only has two pins for 5V power and GND.
A servo motor is simple to use, as it does not use a controller chip.
This SG90 servo model has 3 connector wires.
Mine has red for 5V power, brown for GND and orange for angle control
but these colours vary between manufacturers.
This project requires an external power supply as the Raspberry Pi cannot power motors directly.
This power supply will provide the breadboard power rails with 5 and 3.3V.
The power supply and the Pi need to share the GND but the Raspberry Pi 5 V and 3.3 V pins must not be used here at all,
otherwise the power supply will back-feed the Pi and damage it.
The MCP3002 analog-to-digital chip uses the SPI protocol which needs to be enabled on the Raspberry Pi
if you want to use the hardware implementation which provides the best performance.
Time to write some code!
Here is a standard skeleton program which I use for most of my Raspberry Pi projects.
Such code gives me the basic program layout and it handles typical program exit scenarios.
The gpiozero library provides the simplest programming model for this type of the project
as it has build-in classes for my ADC chip and both motors.
I will define two instances of the MCP3002 class, one for each of the analog inputs.
Input 0 will be used for the DC motor, which I will implement now,
while input 1 will be used for the servo, which will be implemented in the second half of this video.
Let's call the motor "fan" since it has a propeller.
The Motor needs two GPIO pins as it will be operated in 2 directions.
The Pi will generate pulses of varying duty cycles,
which will be processed by the motor controller chip to provide the power to the motor.
If you are interested in a detailed explanation of pulse-width modulation,
I have a dedicated video explaining it.
I want to use the joystick to control the motor direction and speed,
where the motor rotation will be following the vertical position of the joystick.
The motor source property will continuously obtain the joystick value, provided by a generator function.
I will write the run_fan() function in a moment, but I need to take care of some house-keeping first,
as the components used need need to be deactivated correctly on program exit.
Lets write the run_fan() function now...
As I mentioned already, the joystick position is a float value between 0 and 1,
with 0.5 being the neutral position of the joystick.
gpiozero motors require that value to be between -1 and 1,
so this function will need to perform a simple calculation to interface the joystick with the motor directly.
The yield keyword will return that value every time the motor calls for it,
which happens automatically with the source property.
Lets test it!
The fan blades rotate with varying speed, according to the joystick input
and the motor stops when the joystick returns to the neutral position.
However, the motor makes a buzzing noise in the neutral position, trying to rotate very slowly.
This behaviour is due to the joystick drift, where the joystick value fluctuates slightly in neutral position.
Drift affects all analog joysticks.
You can see that if I temporarily display the joystick value to the terminal.
It looks like mine drifts between 0.46 and 0.54...
Drift needs to be compensated and the simplest method relies the rounding the drift range the the middle value of 0.5.
I will write a simple function that will take care of that.
I do see that on occasion my joystick drifts pastthe current parameters so I need to widen the compensation range a bit.
Your joystick may go even wider but I would not worry about loss of input sensitivity here.
Even a small motor has inertia and it would not be able to start within the drift range anyway.
Let's move on to the servo motor...
The servo is controlled using a modulated signal, similar to the one used with the DC motor
but the servo movement is different.
Most servos do not rotate 360 degrees, but have their range limited to 180 degrees.
As before, I need to properly deactivate the servo on program exit.
Setting the servo source property to None will decouple it from the input.
I will also return the servo position to the middle.
The small delay gives the servo time to get there before the program exits.
In a basic sense, the servo driver function can be identical to what I used with the DC motor.
The only difference is the analog input. Here input "1" refers to the horizontal axis of the joystick.
That works ok, but the servo moves only about 90 degrees.
Each servo is different, so the gpiozero library is very conservative with the default range of movement.
You need to always calibrate your program to the specific servo used by providing the minimum and maximum pulse values.
Most of the SG90 servos I have used, work well with the values you see.
If you find that your servo tries to move to far or does not quite have the range you want,
you would need to adjust these value slightly up or down.
This works well and such approach would work in a remote-controlled vehicle where a servo would be used to steer it,
but what I want it to use a joystick to control a robotic arm.
In such scenario the arm should stay put when the joystick is released
and the speed of movement should be proportional how far the joystick is pushed.
Let's change this program for such scenario...
The servo arm movement will be determined relative to the current servo arm and joystick positions.
Adjusting these values slightly up or down will make the arm move faster or slower,
but the value ratio needs to be 2-to-1 for the left and right movement ranges to be symmetric.
This function will pass on the new position to the servo, but it is possible that it will
fall outside of the -1 to 1 valid range, which will produce a run-time error.
An added if statement will take care of that by rounding the new position up or down as needed.
That works great! I am able to control the speed of the servo arm movement and the arm stays put
when the joystick is released.
As you can see, controlling motors with Raspberry Pi is very simple.
There is a small problem though with the control signal.
By default gpiozero library uses a software-based signal which is subject to jitter.
This is different from the drift.
It happens across the entire movement range, not just in neutral
and it largely depends on the overall Raspberry Pi system load
which is unpredictable and cannot be compensated for.
This was not really visible with the DC motor, but the servo shows that flaw very clearly -
you can see the arm moving slightly back and forth continuously.
You can always get an add-on board for the Pi to get the stable hardware pulse-width-modulation signal,
but Raspberry Pi is capable of generating them on its own.
You just need to use a different software library.
Only a minor adjustment to the program is needed.
gpiozero is actually an overlay over other libraries with the default being Rpi.GPIO,
which can only generate software PWM
and the recommended sub-library for stable PWM pulses is pigpio.
Switching to another sub-library with gpiozero is accomplished by providing a non-default "Pin Factory".
This program as is will not run yet successfully.
pigpio library communicates with the GPIO pins through a server daemon which needs to be enabled.
This daemon in turn relies on remote GPIO, which must to be enabled in the Raspberry Pi configuration.
You can start and stop the pigpiod daemon as needed or enable/disable it permanently.
I will use mine temporarily as I do not want this daemon to interfere with other programs that do not use it.
The servo is now jitter-free!
The best part about using the pigpio library is that you can split a project across separate Raspberry Pi systems.
This way you can have the controller in your hand while the motors
are part of a Raspberry Pi vehicle, operated remotely through Wi-Fi.
I explain the remote access in another video and I highly recommend that you check it out.
That is all for this video!
Please give it thumbs up if you liked it and subscribe!
Thanks for watching!