EECS Senior Design RDRIV: Rapidly Developed Reconnaissance Inspection Vehicle Final Design Report Date: 14/04/2015 University of Cincinnati, Ohio
EECS Senior Design RDRIV: Rapidly Developed Reconnaissance Inspection
Vehicle Final Design Report
Date: 14/04/2015
University of Cincinnati, Ohio
UNIVERSITY OF CINCINNATI SCHOOL OF ELECTRONICS & COMPUTING SYSTEMS SENIOR DESIGN FINAL TECHNICAL DESIGN REPORT
Prepared By:
_____________________________________ Thomas M. Braum Electrical Engineering
_____________________________________ Alex M. Jones Electrical Engineering
_____________________________________ Meagan E. Lesher Electrical Engineering
_____________________________________ Nathanial W. Thomas Electrical Engineering
Executive Summary
This project was proposed by the Air Force Research Lab to fulfill a need for a cost-
effective alternative for first response inspection robots that are currently on the market. The
intention was to create a robot that was low cost, using a single-board computer and inexpensive
electronics and motors for object detection and movement with a modular 3D printed chassis.
Their intention was to have a robust robot that could be controlled by an operator to roam in and
around a dangerous environment by using sensors and a camera to observe its surroundings,
allowing the operator to identify any potential threats before humans enter the area.
We accomplished building a fully functional robot. A user can control the robot’s motion
through a G.U.I. on a computer while streaming live video via Wi-Fi. The computer can
communicate with the robot up to 120 feet away using two nRF24l01+ chips which are
connected to the computer using an Arduino Nano, and directly to a Raspberry Pi on the robot.
The Raspberry Pi is used to control all the peripheral electronics on the robot to include four
motors, two infrared sensors, two sonar sensors, and a camera. The robot itself is almost entirely
3D printed; each part takes between twenty five minutes and three and a half hours to print,
allowing for relatively rapid recreation of damaged parts when used in the field. The robot
utilizes treads as its mode of transportation, allowing for use in diverse environments. The
operator can control the motion of the robot while viewing the environment through a camera
mounted on the front of the robot. The sensors are placed on the front and back of the robot to
augment the user’s situational awareness and to determine distances of obstacles from the robot.
This is useful in situations where obstacles are visibly indistinguishable from the background, or
are just outside the camera’s field of view.
Background
First response robots can be used to protect human life by entering a dangerous
environment and alerting the user of potential hazards, allowing a human to enter the
environment prepared to deal with any hazards. They can also be used to enter areas inaccessible
to humans; such as cramped spaces, contaminated areas, and unstable terrain. These robots are
used by several agencies including the military, police, and S.W.A.T teams. First response robots
are able to save lives, but unfortunately, current market models are extremely robust and offer
high functionality, but their cost (starting at $50,000) puts them well out of the reach of small
scale first response units.[1] Repairs, if any, only compound on to that initial cost. The Air Force
Research Lab saw the importance of these robots and set out to create a low cost version of their
own with a given list of priorities and a budget of $300.
The Air Force Research Lab described the features they felt were necessary for the robot
to be competitive. The most important was movement; including rolling forwards and
backwards, turning, traversing obstacles, and determining if a new path is needed using an array
of sensors. The robot needed to stream live video and receive remote commands from a range of
30 feet. The chassis, wheels, and treads needed to be 3D printed, with individual modules to be
printed in under 3 hours.
[1] http://www.homelandsecuritynewswire.com/first-response-law-enforcement-ground-robot-
market-grow
Design Process
The two constraints given by the Air Force Research Lab were that the robot cost less
than $300 and run for at least 15 minutes. The other priorities given to us were:
1. Movement
a. Forward, backward, and turning
b. Able to climb over objects
c. Semiautonomous motion - programed to move freely with a manual override
option
2. Video Feed
3. Small size to fit into tight spaces but large enough to see over a two foot object
4. Operation Range minimum of 30 ft from user
5. Printing time below two hours per component
6. Able to flip over if inverted
Our first task was deciding what requirements from the AFRL we wanted to focus on.
Their idea for this project was ambitious and it became apparent that we needed to narrow down
our priority list to finish before the end of the school year. We decided our number one priority
was user controlled movement. Our next top priority was being able to control the robot
wirelessly. Finally, we wanted to ensure that a video feed could be wirelessly sent from the robot
to the user.
With this in mind we created numerical evaluation matrixes to analyze what components
we needed.
Table 1 - Controller Evaluation Matrix
When choosing a microcontroller we took several factors into consideration. We looked
at cost in order to stay under budget, programmability in order to maximize our limited timeline
for software development, and compatibility with peripheral systems in order to fulfill the
movement, sensory, and video design objectives. The Raspberry Pi B+ became our
microcontroller due to its simple and versatile programmability, with it able to be programmed in
C, C++, and Python, and its compatibility with the camera module, motor shields, and wireless
communication chip. The PIC Microcontroller fell to the wayside mainly due to its poor
compatibility with a camera, or motors, and its programmability requiring us to use assembly
code. The Beaglebone Black was not chosen, despite its impressive number of IO pins and easy
programmability, due to its cost and incompatibility with peripheral systems. The model of
Arduino with a comparable cost to the Raspberry Pi B+ did not have enough IO pins to support
the number of peripheral systems that the Raspberry Pi could and did not have an integrated
camera module which would have further depleted the number of available IO pins.
Movement Priority and Numerical Evaluation Matrix (1 being the worst & 4 Being the
Best)
Design Constraints and
Objectives Priority Wheels Legs Treads Ball
(C) All Terrain
2 2 4 1
(C) 3D printable
4 3 3 1
(O) high Durability 4 1 4 2
(O) high maneuverability 2 3 4 4
Total 12 9 15 8
Table 2 - Movement Evaluation Matrix
In order to fulfill our requirement for the robot to be able to traverse difficult terrain, we
had to choose between wheels, legs, treads, and an encapsulating sphere. The mode of
transportation for the robot needed to be 3D printable, drivable on almost any terrain, durable,
and allow the robot to be highly maneuverable. We chose the Tread design for its high
maneuverability, ability to drive all terrain, and durability. While the Ball design allows for the
maximum amount of maneuverability possible, the design would fail in rough terrain and 3D
printing the design would interfere with streaming video. The Leg design was projected to be
too fragile if 3D printed. The wheel design was not chosen because it lacked in all terrain and
maneuverability despite being easy to 3D print and durable.
Table 3 - User Interface Evaluation Matrix
For the User Interface we required a way to display the camera feed from the robot while
simultaneously allowing for the user to control the robot via directional controls. Our UI consisted of
a console window on a web enabled tablet or laptop computer with 5 buttons commanding the robot
to move forward, backward, left, right, or to stop. Around the buttons were virtual LCD screens
which displayed the values returned from the sensors. The video feed is displayed via a separate web
browser devoted to the video display and settings.
Table 4 - Wireless Data Evaluation Matrix
For the wireless data device we needed it to be low cost with a large signal distance. Since
the robot needed to move in a different room than the operator we knew the signal strength had to be
more than a hundred feet. This eliminated Wi-Fi and Bluetooth devices. Another critical objective
was that the wireless device has to be camera compatible. This left us with the nRF24l01+. After
testing and research we found out that the RF is unable to send the payload of data we would end up
needing. That is why we decided to use Wi-Fi for the video feed. The nRF remains the most cost
efficient solution, and it is capable of transmitting and receiving data at a 2mbps v.s. the other
contender the Xbee which only transmits and receives data at 250kbps.
We also considered the following:
System Software:
Python was chosen because it is Raspberry Pi’s native language and there are extensive
online tutorials and resources
Motors:
DC brushed gear motors were chosen to keep weight and cost down while
maintaining torque strength
Camera:
The Raspberry Pi camera was chosen because of its low cost and simple integration with the
board
The Air Force Research Lab made it clear to us that the end of our project would not be the
end of the development for this robot. We made sure that even though we did not accomplish all of
the goals requested for this robot that it would be easy for someone else to pick up the project and
add to it.
We spent time considering how to create a small robot with a camera being held up to 2ft off
the ground. The robot could not be 2ft tall, so we decided that a retractable arm would be the best
way to accomplish this task. We did not have time or the mechanical knowledge to develop the
retractable arm, but designed the body to be large enough to accommodate the addition of a
retractable arm in the future. This resulted in a body that was larger than what was needed to hold all
of the components we used.
Table 5 - Sensor Evaluation Matrix
For the sensor array the sensors needed to be low cost, sensitive to varying distances, and
able to detect both soft and solid objects. The final design contains both IR and Sonar sensors which
complement each other when dealing with a variety of heat and acoustic insulating surfaces. Both
types are low cost and are easily integrated into the design. Both Radar and LIDAR sensors, while
being extremely accurate, are extremely expensive and would immediately put our design outside of
our budget range.
Requirements/ Assessment Metrics
The requirements we used for are project stemmed from the requirements given to us
from the Air Force Research Lab. These requirements, listed below, ranged from the movement
the robot would be able to achieve, the range it would need to be able to perform in, the time for
printing of pieces, the overall cost of the robot, the run time minimum, and the feedback to the
users.
Able to move forward, backwards, and turn
Able to navigate around objects in a room
Able to navigate over small objects in a room (2” tall object)
Video feed sent from the robot to the operator
Able to navigate the terrain autonomously with the user able to override control at
any time
Small enough to fit into small spaces while still able to see over a 2 foot object
Operating Range of 30 feet from the user
Printing time around or below 2 hours per module
Battery life longer than 15 minutes
Overall cost less than $300
Able to flip over if inverted
With these requirements we were able to start the assessment process to decide on what
route we would like to take for our project. By using the Morph chart shown below we were able
to list all of the plausible possibilities for our project and start a comparison process for each
Means Function. Once we had all the possibilities listed we moved to researching and comparing
the possibilities with each other through the Numerical Evaluation Matrixes, seen above in the
Design Process section. In the morph chart the values in blue are the winning means and show
our choices from the Numerical Evaluation Matrixes. This allows for an easy view of the
options we chose such as IR and Sonar sensors, Raspberry Pi and Arduino processing, Graphical
User Interface for the user, the nRF24L01 chips for wireless, C C++ and Python for
programming languages, treads for movement, and reversible brushed motors.
The boxes that are highlighted in blue in the morph chart are the design, devices, or software that
were a best fit for the RDRIV.
Means
Functions 1 2 3 4 5
Object sensor IR Sensor Sonar Sensor Radar Sensor …. ….
Digitize Sensor
Signal
Integrated w/
sensor
Integrated w/
controller Separate A/D Analog ….
Process Sensor
Data
PIC micro
controller Raspberry Pi Arduino FPGA
Beagle Bone
Black
User Interface Button/LED Character
Display
Graphic
Display
Touch
screen
Joystick w/
screen
Wireless Data Cellular WiFi Bluetooth nRF24l01 Zigbee
System
Software C C++ C# Python Java
Motor Type Brushless Brushed …. …. ….
Motor Direction Continuous Reversable …. …. ….
Movement Wheels Legs Treads Ball ….
Use of Standards
The “IEEE Standard for Safety Levels with Respect to Human Exposure to
Radiofrequency Electromagnetic Fields, 3kHz to 300 GHz” is a standard that defines the
acceptable levels of radio frequencies. The standard rates the levels that are acceptable for
human exposure and is broken down into ranges of electrical field strength, magnetic field
strength, power density, and average time of exposure. The standard specifies how the different
strengths affect the amount of damage that can be done to people.
This standard influenced our design by limiting the types and strengths of signals that
would be acceptable for our control. By limiting us to a small power level of frequencies we
needed to decide on a device that allowed for the 30 ft. minimum range and that didn’t require
licensing. With choosing the nRF24L01+ we were able to get both the overall range we needed
and the ability to have a license free device in an acceptable ranges.These chips are designed to
be ultra-low power which keeps the band well below the acceptable power density of 10
mW/cm2. The nRF24l01+ chip also uses the ISM (Industrial, Scientific and Medical) band
which allows for worldwide license-free band operation.
Link to standard documentation:
http://www.etsist.upm.es/estaticos/catedra-
coitt/web_salud_medioamb/normativas/ieee/C95.1.pdf
Design Overview
Parts List:
The parts list above shows the parts used, the cost of the parts, and links to sites for
purchase. The discrete parts consisted primarily of the 16 gauge connector wires and screws used
to wire and screw the robot together.
Transparent Box Diagram
Transparent Box description:
The transparent box on the previous page shows the input and output of the robot that
was used to clearly see on a higher level what the functionality of the robot was from step to
step. This allows for a clear path to be seen on what should be happening inside the code and
what actions should be performed based on what input.
Circuit Diagram description:
The circuit diagrams on the next page show how the components on the robot are
powered and the pin layouts for the Raspberry Pi. It is very important to get the pinouts on the
Raspberry Pi correct as the code for the Raspberry Pi is GPIO pin specific and will not work
properly if not wired as seen in the diagram. It is also important to point out that the nRF24L01
chips do not run on 5V, but on 3V supplied from the Raspberry Pi and Arduino. If these chips
are hooked up to 5V it may damage chips and cause unpredictable results.
Robot Circuit Diagram:
Laptop Communication Circuit Diagram:
The 3D printed components were designed used Solid Edge. There were
twenty-three unique parts designed. Each part was created to be easily printed and
assembled. Multiples of multiple components were needed, resulting to a total of
126 printed components in the final build. The picture below shows how some of
the components from the body fit together. You can see that each part is designed
to slide into the other components. All of the parts were designed for easy
assembly in this way
Refer to the appendix for the mechanical drawings of all 3D printed components use, as
well as the final assembly.
Test Plan, Cascade Matrix, and Project Limitation
Test Protocols
I/O Protocol 1.0
Structural Stability 2.0
RF Communication 3.0
Graphical User Interface (G.U.I.) 4.0
1. I/O Protocol
1.1Successful communication with sonar sensor
a. Equipment Needed
Sonar Sensor
Raspberry Pi B+
Power supply
Monitor
Measuring tape
Moveable object
b. Setup
1. Wire sonar sensors GPIO pins Trigger and Echo to the designated GPIO
positions on the Raspberry Pi.
2. Create voltage divider between Echo pin and GND pin on the sonar sensor in
order to achieve 3.3V
3. Set object in front of sonar sensor
4. Run code to activate solely the sonar sensor.
c. Procedure
1. Start with object in front of the sonar sensor
2. Run code
3. Record the calculated distance data compared to the measured distance
a. If calculated distance is more than 5 cm off (test fails)
4. Move object the correct incremental distance away from the sonar sensor seen in the table
below.
d. Table
Measured distance Calculated distance from sensor
2 cm
10 cm
20 cm
50 cm
100 cm
200 cm
400 cm
e. Success Criteria
1. The calculated distance for the sensor should be within about 2-5 (cm) of the
actual distance.
2. Should accurately measure up to 4 m
1.2 Successful communication with the IR sensors
a. Equipment Needed
IR Sensor
Raspberry Pi B+
Power supply
Monitor
Measuring tape
Moveable object
b. Setup
1. Wire the IR sensor to the ADC correctly, diagram given in schematic
2. Wire ADC properly to the Raspberry Pi
3. Set object in front of the IR sensor.
4. Run code solely for the IR sensor
c. Procedure
1. Start with object in front of the left IR sensor
2. Run code
3. Record the calculated distance data compared to the measured distance
a. If calculated distance is more than 5 cm off (test fails)
4. Move object the correct incremental distance away from the IR sensor seen in
the table below.
5. Repeat steps 1-4 for the right IR sensor
d. Table
Measured distance Calculated distance from sensor
0 cm
10 cm
20 cm
40 cm
80 cm
e. Success Criteria 1. The calculated distance for the sensor should be within about 2-5 (cm) of the
actual distance.
2. Should accurately measure up to 80 cm
1.3 Successful communication with the motors
a. Equipment Needed
Stepper motors
Raspberry Pi B+
Power supply
Motor shield L298N
Monitor
b. Setup
1. Properly setup the motor shield
2. Wire motors GPIO to Raspberry Pi and power to the motor shield
3. Run code solely for the motors
c. Procedure
1. Run code for the right motor
a. Run motor forwards for 5 seconds
b. Run motor backwards for 5 seconds
2. Run code for right motor and slowly increase speed
a. Slowly increase speed from 0% 100% in 5 seconds
b. Slowly decrease speed from 100% 0% in 5 seconds
3. Repeat steps 1 & 2 for the left motor
d. Success Criteria
1. Motors show difference in full on or full stop verse slowly increasing or
decreasing speed over 5 seconds.
1.4 Successful communication between motors, IR sensors, and sonar sensor
a. Equipment Needed
IR sensors
Sonar sensor
Movable object
Measuring tape
Stepper motors
Raspberry Pi B+
Power supply
Motor shield L298N
Monitor
b. Setup (Via electrical diagram)
1. Properly setup the motor shield
2. Wire motors GPIO to Raspberry Pi and power to the motor shield
3. Wire the IR sensor to the ADC correctly, diagram given in schematic
4. Wire ADC properly to the Raspberry Pi
5. Set object in front of the IR sensor.
6. Wire sonar sensors GPIO pins Trigger and Echo to the designated GPIO
positions on the Raspberry Pi.
7. Create voltage divider between Echo pin and GND pin on the sonar sensor in
order to achieve 3.3V
8. Set a different object a in front of sonar sensor at a different distance than the
IR
9. Load code to run all devices at the same time.
c. Procedure
1. Once code is initialized let all of the motors run forward for three seconds
2. Then the motors will begin to roll backwards for 3 seconds.
3. Let the right side of motors roll forward and the left side motors roll
backwards for three seconds indicating a right turn
4. Let the left side of motors roll forward and the right side motors roll
backwards for three seconds indicating a left turn.
5. Once the motors are running the left sonar sensor will then start collecting
data all within a one second interval, it will take about ten different readings
in an array, and then perform an average of the sum to give you the most
accurate reading.
a. Then move the object further away to the distances specified in the table
below
6. Repeat step 5 for the right sonar sensor.
7. While the motors are running, move an object in front of the IR sensor and
move it between the different zones.
a. Move object between zero to twenty (cm) away. This should all be within
the first zone
b. The second zone will consist of twenty to forty (cm) away
c. The third zone will be from forty (cm) and on
d. Table
Zone(#) IR Confirmed
Zone
Readings
Sonar
Measured
Distance
(L) Sonar Calculated
Distance
(R) Sonar Calculated
Distance
Zone #1
0-20 cm
20 cm
Zone #2
20-40 cm
50 cm
Zone #1
40+ cm
100 cm
200 cm
400 cm
e. Success Criteria
1. Motors run along with sensor data displaying simultaneously.
a. Sensor data comes out accurate from these readings
1.5 Successful communication between Raspberry Pi and Camera
a. Equipment Needed
Raspberry Pi Camera
Raspberry Pi B+
Power supply
Monitor
Wi-Pi Dongle
b. Setup 1. Plug Raspberry Pi Camera into the Raspberry Pi camera slot
2. Install latest software for the camera
3. Stream to web browser
c. Procedure 1. Once the Raspberry Pi is activated the Raspberry Pi camera stream should run
in the background.
2. On the device controlling the robot, type in the IP of the Raspberry Pi.
3. Once connected to the live video stream of the robot, adjust the video
accordingly.
d. Success Criteria 1. The video stream is connected correctly through the web browser and has low
latency.
2. Structural Stability
2.1 3D Printing Accuracy
a. Equipment Needed
Printrbot 3D printer
PLA printing filament
STL files of components
b. Setup
1. Load filament into printer
2. Connect the printer to the computer
3. Open Repetier Host
4. Load STL Program into Repetier Host
5. Set splicer settings to print components with a 3mm brim, 3 layer shell, 0.4
layer height, and 15% rectilinear infill
a. For the cross bars and boxes use a 10% rectilinear infill to reduce weight
c. Procedure
1. Instruct program to begin print
2. Observe the part to ensure there is no warping, broken layers, or any physical
flaws within the part that could damage the structural stability of the part.
3. If the part has holes for hardware, test if the hardware fits properly into the
holes
4. If the part needs to fit with another piece ensure the pieces have enough
tolerance to fit together.
5. If there is an error in the print repeat steps 1 through 4. If the top faces of the
print are not staple increase the infill level. If issues arise from the lack of
preciseness of the component decrease the layer height.
6. If the part is not sized correctly to fit with other components or accommodate
the hardware modify the source file accordingly and re-save as an STL file and
begin again at the Setup.
d. Success Criteria
1. Printed part has little to no errors in print
2. Component did not warp during printing
3. Components fit together where required and hardware fits into proper holes
3. RF Communication
3.1 nRF Communication Between Devices
a. Equipment Needed
Raspberry Pi B+
Arduino Nano
nRF24L01+ wireless chip
Computer Monitor
Computer Mouse
Computer Keyboard
b. Setup
1. Turn on computer
2. Load Arduino compiler program
3. Load program into Arduino
4. Wire nRF24L01+ to Arduino
5. Hook computer keyboard and mouse to Raspberry Pi B+
6. Wire nRF24L01+ to Raspberry Pi B+
7. Turn on Raspberry Pi B+
8. Load program on Raspberry Pi B+
9. Run program on Raspberry Pi B+ in receiving mode
10. Run program on Arduino for transmitting mode
c. Procedure
1. Run program on Raspberry Pi B+ in receiving mode
2. Run program on Arduino for transmitting mode
3. Send command signal through Arduino
4. Watch for signal on Raspberry Pi B+
5. Watch for returned signal from Raspberry Pi B+ on Arduino
d. Success Criteria
1. Signal sent from Arduino received by the Raspberry Pi B+
2. Signal sent from Raspberry Pi B+ received by the Arduino
3. Signals received in same format as sent
4. Graphical User Interface (G.U.I.)
4.1 G.U.I. Operation
a. Software Needed 1. Qt (Software installed on the user’s device of choice)
b. Setup 1. Make sure everything is wired according to the schematic.
2. Run and build code within Qt to execute GUI to control the robot.
3. Make sure Raspberry Pi code is running in the background.
c. Procedure 1. Press the Forward key on the GUI and make sure all the motors run forwards.
2. Press the Backward key on the GUI and make sure all the motors run
backwards.
3. Press the Left key on the GUI and make sure the left motors roll forward and
the right motors roll backwards.
4. Press the Right key on the GUI and make sure the right motors roll forward
and the left motors roll backwards.
5. Make sure that every time a directional key is pressed all of the sensors are
updated with the latest distance or zone measurements.
d. Success Criteria 1. Motors run in the correct direction and all of the sensor data is updated
correctly.
5. Inversion Test
5.1 Successful inversion of the RDRIV
a. Equipment Needed
RDRIV
Arduino Nano
nRF chip
User’s device with G.U.I.
b. Setup
1. Turn on RDRIV
2. Load all code into the Raspberry Pi
3. Load GUI onto user’s device
c. Procedure
1. Once the RDRIV is operational drive it towards a wall.
2. Approach wall at a slow pace and attempt to climb wall and flip over.
3. Approach wall at a moderate pace and attempt to climb wall and flip over.
4. Approach wall at a fast pace and attempt to climb wall and flip over.
d. Table
Speed approaching wall Did it successfully flip over
Slow
Medium
Fast
e. Success Criteria 1. The RDRIV successfully flips over and nothing is damaged in the process.
Cascade Matrix
FUNCTION Requirement
Specification
Design
Verified
Test
Protocol
Device
Validated? Signature
I/O Functionality
Test Motors with
sensors on the
Raspberry Pi side of
the device
Yes
NT 3/21/2015
Document
1.1-1.5 Yes
3D printing
&
Structure stability
Test to make sure
parts are properly
printed and designed
to fit together
correctly
Yes
ML 3/27/2015
Document
2.1 Yes
RF Communication
Integrate
communication
between Arduino and
Raspberry Pi over the
nRF24l01+ chip
Yes
TB 3/15/2015
Document
3.1 Yes
G.U.I.
Creating a G.U.I. to
provide functional
control over the
RDRIV while
enabling live camera
feed
Yes
AJ 4/3/2015
Document
4.1 Yes
Inversion Test
Run the RDRIV into
a wall and see if it is
able to flip over No
Document
5.1 No
Project Limitations
Video for the robot is limited to having Wi-Fi access as the nRF24L01+ wireless chips do
not allow for adequate bandwidth to transmit video. This stipulation could be worked around in
future renditions to enable a RF or direct signal feed for the video that would not require Wi-Fi
access but rather have its own receiver for the video. This option was not a plausible solution for
our rendition as we were trying to keep it as low cost as possible.
The sensors play a limited role in the function of the RDRIV due to the fact that they can
only sense the proximity of a single point and provide an extremely limited view of the RDRIV’s
surroundings. In addition to this, the sonar sensors have a limitation to their accuracy since the
Raspberry Pi does not have a real time operating system. The absence of a real time operating
system allows the Raspberry Pi to place high priority functions ahead of lesser processes. While
in general this may not be an issue, when the sonar sensor is waiting for an echo, an interruption
can cause the sensor to miss the echo and start an infinite loop of waiting, or return a value much
smaller than what would be accurate. In order to combat this, multiple readings from each
sensor are taken and the maximum returned distance is sent back to the user. Taking several
readings requires placing a cap on the maximum distance the sensor can see before sending
another pulse. In future renditions of the RDRIV, small microprocessors devoted to a single
sensor system could be added to reduce any peripheral processes that would corrupt the returned
data.
When designing the body and treads of the robot we did not account for the weight of the
hardware needed to assemble the treads. These added a lot of unwanted weight and made it
necessary for us to use four motors, instead of our original plan to use two. Even with the two
additional motors we did not have enough torque to achieve the full performance that we desired.
The motors were not strong enough to move over objects and we were not able to test if we
could flip the robot over using a wall, like we originally designed our body to do. Fortunately, all
that is needed to solve these limitations is larger motors.
Social Responsibility
This project was created with the intention of helping preserve human life. The intention
of our robot was to allow humans to avoid potentially harmful environments. The importance of
this was a constant consideration for every member of this group. However, the fear that our
robot could be used to create a false sense of security for first response teams by poorly
analyzing a situation was a larger motivator. We ensured that our robot was robust, accurate, and
easy to use to ensure that first responders entering an environment could be sure that they are
safe. We were able to ensure this by using various types of sensors to allow for accurate
feedback of objects in the robots path. The robot is easy to use, with all of the information for the
user right next to the buttons, allowing the operator to see the feedback clearly before making an
decisions. A strong type of 3D printer material was used to allow for a robust design that will not
fall apart during application.
A major criterion for our project was to design a robot that could be built for a reasonable
cost and could be easily maintained. By creating a first response robot that costs 0.5% of the cost
of other first response robots currently on the market we are making this important technology
more easily accessible to those who need it. Our modular design and low cost components also
allows the user to replace individual components on the robot, allowing for low cost
maintenance.
Conclusion
The RDRIV was a success once the requirements were scaled down to more reasonable
and achievable goals. All of the top priority goals were achieved within the given budget
constraint of $300. The final project was an almost completely 3D printed robot capable of
moving using wireless communication. The robot incorporated a sensor array that could be used
for object detection in the path of the robot in conjunction with a camera that streams wirelessly
over Wi-Fi.
A major issue encountered was the wireless transmission over the nRF chips. A lot of
research had been put into learning how RF communications work and how to use the SPI bus
from the Raspberry Pi to the Arduino Nano. For the coding it was not initially understand why
packages had to be sent as a character array of 32-bytes. After overcoming this obstacle the rest
of the project was easy and more necessary tests could be performed.
One of the most critical tests performed was getting the robot to move forwards,
backwards, left, and right. The first attempt at powering the robot was successful in moving it
forwards and backwards, but not at making it turn. Initially two motors were used and did not
have the necessary torque to rotate the robot in different directions. After a critical design
overhaul, an additional two motors were added to the design. It took away from the overall
amperage being distributed across the motor, making the robot slower, yet powerful enough to
make the RDRIV able to rotate left or right.
Once mobility was accomplished it was time for the next testing phase: having the sensor
data come over and be displayed through the G.U.I. Knowing that the sensor data would come
over in a string and each value was separated by a period, from this a function was found within
Qt that lets the data to be parsed by a certain character, such as a period. Once this was
incorporated each value of the string was able to be pushed to its proper place within the G.U.I.
The video feed was the only problem that could not be overcome. A temporary solution
was created by sending the video feed of the Raspberry Pi Camera over a Wi-Fi connection, then
having it run on a web browser using its own IP address. The original goal was to send the video
over the nRF chip, but the bandwidth of the chip was much lower than expected. In hindsight,
creating the code for formatting the video and receiving and organizing the array of pixels would
be a monumental task alone, one that most likely could not have been accomplished in the time
available.
Over all the RDRIV was a success. It is able to move omni-directional while maintaining
a constant rate of speed. The goal was to meet a fifteen minute window of operational time. Not
only was this requirement met, it was surpassed with over 3 hours of functionality observed. In
addition to exceeding the battery expectations, the RDRIV has an operational range of over
100ft, 70ft longer than what was requested by the Air Force Research Lab. A cheaper and more
modular competitor to the reconnaissance vehicles currently on the market was successfully
created.
Appendix:
Design History File Review:
Arduino Code for transmitting to Raspberry Pi B + #include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
//
// Hardware configuration
//
// Set up nRF24L01 radio on SPI bus plus pins 9 & 10
RF24 radio(9,10);
byte addresses[][6] = {"1Node","2Node"};
//
// Setup
//
void setup()
{
Serial.begin(115200);
printf_begin();
//printf("\n\rRF24/examples/led_remote/\n\r");
// Setup and configure rf radio
radio.begin(); // Start up the radio
radio.setAutoAck(1); // Ensure autoACK is enabled
radio.setRetries(15,15); // Max delay between retries & number of retries
radio.openWritingPipe(addresses[0]); //[0] makes it the transmitter 1 the reciever
radio.openReadingPipe(1,addresses[1]); //[1] makes it the transmitter 1 the reciever
radio.startListening(); // Start listening
//radio.printDetails(); // Dump the configuration of the rf unit for debugging
}
char inData[32]; // Allocate some space for the string
char inChar; // Where to store the character read
byte index = 0; // Index into array; where to store the character
void loop()
{
while(Serial.available() > 0) // Don't read unless
// there you know there is data
{ //if statement to populate string to be fed through RF
if(index < 31) // One less than the size of the array
{
inChar = Serial.read(); // Read a character
inData[index] = inChar; // Store it
index++; // Increment where to write next
inData[index] = '\0'; // Null terminate the string
//if statement to send the string through RF chip to PI once a period is seen at end of string
if(inChar == '.')
{
radio.stopListening(); // First, stop listening so we can talk.
//printf("Now sending \n\r");
//fail loop if the radio doesn't write to RF
if (!radio.write( &inData, index )){ printf("failed.\n\r"); }
radio.startListening(); // Now, continue listening
unsigned long started_waiting_at = micros(); // Set up a timeout period, get the current
microseconds
boolean timeout = false; // Set up a variable to indicate if a response was received
or not
while ( ! radio.available() ){ // While nothing is received
if (micros() - started_waiting_at > 2000000 ){ // If waited longer than 2s, indicate timeout and
exit while loop
timeout = true;
break;
}
}
if ( timeout ){ // Describe the results
//printf("Failed, response timed out.\n\r");
}else{
char sensor_data[20]={0}; // Grab the response, compare, and send to debugging
spew
radio.read(sensor_data, 20);
// Spew it
printf("%s, \n",sensor_data);
}
// Try again 1s later
index = 0; //clear index so you can start the process fresh with the string other wise it adds to the
end of the current string
delay(1000);
}}}}
QT Header File: “dialog.h” #ifndef DIALOG_H
#define DIALOG_H
#include <QSerialPort>
#include <QDialog>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private slots:
void on_Forwards_pressed();
void on_Backwards_pressed();
void on_Left_pressed();
void on_Right_pressed();
void on_Stop_pressed();
void updateRDRIV(QString);
void readSerial();
void updateRightSonarSensorLCD(const QString);
void updateLeftSonarSensorLCD(const QString);
void updateFrontIRSensorLCD(const QString);
void updateBackIRSensorLCD(const QString);
private:
Ui::Dialog *ui;
QSerialPort *arduino;
static const quint16 arduino_nano_vendor_id = 1027;
static const quint16 arduino_nano_product_id = 24577;
QString arduino_port_name;
bool arduino_is_available;
QByteArray serialData;
QString serialBuffer;
QString serialBuff1;
QString serialBuff2;
QString serialBuff3;
QString serialBuff4;
};
#endif // DIALOG_H
QT Function File: “dialog.cpp” #include "dialog.h"
#include "ui_dialog.h"
#include <QSerialPort>
#include <QSerialPortInfo>
#include <string>
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QtWidgets>
#include <QTimer>
#include <QObject>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
ui->tempRightSonarSensorLcdNumber->display("---");
ui->tempLeftSonarSensorLcdNumber->display("---");
ui->tempFrontIRSensorLcdNumber->display("-");
ui->tempBackIRSensorLcdNumber_2->display("-");
arduino = new QSerialPort(this);
serialBuffer = "";
bool arduino_is_available = false;
QString arduino_port_name;
/***********DEBUG QSerialPortINFO for serial port info, available ports,
vendor ID, and Product ID******/
/*
qDebug() << "Number of availabl e ports: " <<
QSerialPortInfo::availablePorts().length();
foreach(const QSerialPortInfo &serialPortInfo,
QSerialPortInfo::availablePorts() ){
qDebug() << "Has vendor ID: " <<
serialPortInfo.hasVendorIdentifier();
if(serialPortInfo.hasVendorIdentifier()){
qDebug() << "Vendor ID: " << serialPortInfo.vendorIdentifier();
}
qDebug() << "Has product ID: " <<
serialPortInfo.hasProductIdentifier();
if(serialPortInfo.hasProductIdentifier()){
qDebug() << "Product ID: " << serialPortInfo.productIdentifier();
}
}*/
/****************************************************************************
*****************************/
//arduino_port_name = "";
foreach(const QSerialPortInfo &serialPortInfo,
QSerialPortInfo::availablePorts()){
if(serialPortInfo.hasVendorIdentifier()
&&serialPortInfo.hasProductIdentifier()){
if(serialPortInfo.vendorIdentifier() == arduino_nano_vendor_id){
if(serialPortInfo.productIdentifier() ==
arduino_nano_product_id){
arduino_port_name = serialPortInfo.portName();
arduino_is_available = true;
}
}
}
}
if(arduino_is_available){
//open and configure the serial port
arduino->setPortName(arduino_port_name);
arduino->open(QSerialPort::ReadWrite);
arduino->setBaudRate(QSerialPort::Baud115200);
arduino->setDataBits(QSerialPort::Data8);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneAndHalfStop);
arduino->setFlowControl(QSerialPort::NoFlowControl);
QObject::connect(arduino, SIGNAL(readyRead()), this,
SLOT(readSerial())); //
}else{
//give error message if not available
qDebug() << "Couldnt find the correct por for the arduino. \n";
QMessageBox::information(this, "Serial Port Error", "Couldn't find
the arduino!");
}
}
Dialog::~Dialog()
{
if(arduino->isOpen()){
arduino->close(); //Close serial port if it is open
}
delete ui;
}
void Dialog::updateRightSonarSensorLCD(const QString sensor_reading)
{
ui->tempRightSonarSensorLcdNumber->display(sensor_reading);
}
void Dialog::updateLeftSonarSensorLCD(const QString sensor_reading)
{
ui->tempLeftSonarSensorLcdNumber->display(sensor_reading);
}
void Dialog::updateFrontIRSensorLCD(const QString sensor_reading)
{
ui->tempFrontIRSensorLcdNumber->display(sensor_reading);
}
void Dialog::updateBackIRSensorLCD(const QString sensor_reading)
{
ui->tempBackIRSensorLcdNumber_2->display(sensor_reading);
}
/******************************DEBUG read
serial***********************************/
void Dialog::readSerial()
{
//qDebug() << "serialport works";
serialData = arduino->readAll();
serialBuffer = QString::fromStdString(serialData.toStdString());
qDebug() << serialBuffer;
QStringList list1 = serialBuffer.split(".", QString::SkipEmptyParts);
QString serialBuff1 = list1[0];
QString serialBuff2 = list1[1];
QString serialBuff3 = list1[2];
QString serialBuff4 = list1[3];
Dialog::updateRightSonarSensorLCD(serialBuff1);
Dialog::updateLeftSonarSensorLCD(serialBuff2);
Dialog::updateFrontIRSensorLCD(serialBuff3);
Dialog::updateBackIRSensorLCD(serialBuff4);
}
/****************************************************************************
******/
void Dialog::on_Forwards_pressed()
{
QString forward = "forward.";
Dialog::updateRDRIV(QString(forward));
}
void Dialog::on_Backwards_pressed()
{
QString back = "back.";
Dialog::updateRDRIV(QString(back));
}
void Dialog::on_Left_pressed()
{
QString left = "left.";
Dialog::updateRDRIV(QString(left));
}
void Dialog::on_Right_pressed()
{
QString right = "right.";
Dialog::updateRDRIV(QString(right));
}
void Dialog::on_Stop_pressed()
{
QString stop = "stop.";
Dialog::updateRDRIV(QString(stop));
}
void Dialog::updateRDRIV(QString command)
{
if(arduino->isWritable()){
arduino->write(command.toStdString().c_str());
qDebug()<< "Sending";
}else{
qDebug()<< "Couldn't write to serial.";
}
}
QT GUI code: “main.cpp” #include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
w.setFixedSize(670,270);
w.setWindowTitle("RDRIV GUI");
return a.exec();
}
Raspberry Pi Code Main C++ file handles NRF communication and UDP server
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <RF24/RF24.h>
#include <stdlib.h>
#include <stdio.h>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using namespace std;
using boost::asio::ip::udp;
//
// Hardware configuration
//
// Setup for GPIO 22 CE and CE0 CSN with SPI Speed @ 8Mhz
RF24 radio(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ);
// Radio pipe addresses for the 2 nodes to communicate.
const uint8_t pipes[][6] = {"1Node","2Node"};
//const uint64_t pipes[2] = { 0xABCDABCD71LL, 0x544d52687CLL };
int main(int argc, char** argv){
//bool role_ping_out = true, role_pong_back = false;
//bool role = role_pong_back;
// Print preamble:
//printf("RF24/examples/pingtest/\n");
// Setup and configure rf radio
radio.begin();
// optionally, increase the delay between retries & # of retries
radio.setRetries(15,15);
// Dump the configuration of the rf unit for debugging
radio.printDetails();
/***********************************/
// This simple sketch opens two pipes for these two nodes to communicate
// back and forth.
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1,pipes[0]);
radio.startListening();
//UDP initializations
boost::asio::io_service io_service;
udp::socket socket(io_service, udp::endpoint(udp::v4(), 5005));
boost::array<char, 128> sensor_buf;
udp::endpoint remote_endpoint;
// forever loop
while (1)
{
//
// Pong back role. Receive each packet, dump it out, and send it back
//
// if there is data ready
//printf("Check available...\n");
if ( radio.available() )
{
char instruct[12]= {0}; // ------------------------------------- (EDITED LINE)
// Fetch the payload, and see if this was the last one.
radio.read( instruct, 12 );
radio.stopListening();
// UDP Server communication with pMain.py
// try
// {
remote_endpoint.port(5006);
boost::system::error_code error;
std::string instructs = std::string(instruct, 12);
std::cout << instructs << std::endl;
if(error && error != boost::asio::error::message_size)
throw boost::system::system_error(error);
boost::system::error_code ignored_error;
socket.send_to(boost::asio::buffer(instructs), remote_endpoint, 0, ignored_error);
//send instruct to pMain.py
remote_endpoint.port(5005);
size_t len = socket.receive_from(boost::asio::buffer(sensor_buf),
remote_endpoint, 0, error); //recieve sensor packet from pMain.py
std::string sensor = std::string(sensor_buf.data(), len);
std::cout << sensor << std::endl; // alternate-->
std::cout.write(instruct_buf.data(), len);
// write back sensor packet
radio.write(sensor_buf.data(), len );
// }
// catch (std::exception& e)
// {
// std::cerr << e.what() << std::endl;
// }
// Now, resume listening so we catch the next packets.
radio.startListening();
// Spew it
printf("%s \n", instruct);
delay(925); //Delay after payload responded to, minimize RPi CPU time
}// if radio available
} // forever loop
return 0;
}
Main Python code handles GPIO manipulation and UDP client
import RPi.GPIO as GPIO
import time
import motor
import sensor
import socket
#UDP Setup
UDP_IP = "127.0.0.1"
UDP_PORTIN = 5006
UDP_PORTOUT = 5005
#GPIO Setup
GPIO.setmode(GPIO.BOARD)
Min1 = 35 #Motor Enable Pins
Min2 = 37
Min3 = 32
Min4 = 36
TRIG1 = 38 #Sonar Trigger Pin
ECHO1 = 40 #Sonar Echo Pin
TRIG2 = 16 #Sonar Trigger Pin
ECHO2 = 18 #Sonar Echo Pin
SPICLK = 7 #ADC_CLK
SPIMISO = 29 #ADC_DOUT
SPIMOSI = 31 #ADC_DIN
SPICS = 33 #ADC_Chip_Select
IRS0 = 0
IRS1 = 1
GPIO.setup(Min1, GPIO.OUT)
GPIO.setup(Min2, GPIO.OUT)
GPIO.setup(Min3, GPIO.OUT)
GPIO.setup(Min4, GPIO.OUT)
GPIO.setup(TRIG1, GPIO.OUT)
GPIO.setup(TRIG2, GPIO.OUT)
GPIO.output(TRIG1, 0)
GPIO.output(TRIG2, 0)
GPIO.setup(ECHO1, GPIO.IN)
GPIO.setup(ECHO2, GPIO.IN)
GPIO.setup(SPIMOSI, GPIO.OUT)
GPIO.setup(SPIMISO, GPIO.IN)
GPIO.setup(SPICLK, GPIO.OUT)
GPIO.setup(SPICS, GPIO.OUT)
time.sleep(0.1)
#Motor function definitions
#(Min1,Min2,Min3,Min4)
Forward = motor.DriveForward
Reverse = motor.DriveBackward
Right = motor.TurnRight
Left = motor.TurnLeft
SRight = motor.SoftRight
SLeft = motor.SoftLeft
Stop = motor.StopMotor
#Sensor function definitions
#(TRIG,ECHO)
#(IRS0, SPICLK, SPIMOSI, SPIMISO, SPICS)
SonarSense = sensor.SonarSense
IRSense = sensor.IRSense
#UDP initialization
sock = socket.socket(socket.AF_INET, #Internet
socket.SOCK_DGRAM) #UDP
sock.bind((UDP_IP, UDP_PORTIN))
#Forever loop
dataprev = 'stop'
idist1 = '0'
idist2 = '0'
sensor = "0000"
moving = False
while True:
data, addr = sock.recvfrom(1024) #buffer size is 1024 bytes
print "received message: ", data #print recieved data package
sdist1 = SonarSense(TRIG1,ECHO1) #read sensor data
sdist2 = SonarSense(TRIG2,ECHO2) #read sensor data
#if (moving == True): #read IR sensor if RDRIV is moving
idist1 = IRSense(IRS0, SPICLK, SPIMOSI, SPIMISO, SPICS) #read front IR sensor
#idist2 = IRSense(IRS1, SPICLK, SPIMOSI, SPIMISO, SPICS) #read rear IR sensor
sensor = str(sdist1) + ',' + str(sdist2) + ',' + str(idist1) #+ str(idist2)
sock.sendto(sensor, (UDP_IP, UDP_PORTOUT)) #send sensor string
if (data != dataprev): #---------------- #if new instruction is different from previous
dataprev = data
if (data == "forward."):
Forward(Min1,Min2,Min3,Min4)
print 'moving Forward'
moving = True
elif (data == "back."):
Reverse(Min1,Min2,Min3,Min4)
print 'moving Backwards'
moving = True
elif (data == "right."):
Right(Min1,Min2,Min3,Min4)
print 'moving Right'
moving = True
elif (data == "left."):
Left(Min1,Min2,Min3,Min4)
print 'moving Left'
moving = True
else:
Stop(Min1,Min2,Min3,Min4)
print 'stopped'
moving = False
print sensor
GPIO.cleanup()
RF24 library
#include "./RF24_config.h"
#include "./RF24.h"
#include "./nRF24L01.h"
/****************************************************************************/
uint8_t RF24::read_register(uint8_t reg, uint8_t* buf, uint8_t len)
{
uint8_t status;
uint8_t * prx = spi_rxbuff;
uint8_t * ptx = spi_txbuff;
uint8_t size = len + 1; // Add register value to transmit buffer
*ptx++ = ( R_REGISTER | ( REGISTER_MASK & reg ) );
while (len--){
*ptx++ = NOP ; // Dummy operation, just for reading
}
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, size);
status = *prx++; // status is 1st byte of receive buffer
// decrement before to skip status byte
while ( --size ){
*buf++ = *prx++;
}
return status;
}
/****************************************************************************/
uint8_t RF24::read_register(uint8_t reg)
{
uint8_t result;
uint8_t * prx = spi_rxbuff;
uint8_t * ptx = spi_txbuff;
*ptx++ = ( R_REGISTER | ( REGISTER_MASK & reg ) );
*ptx++ = NOP ; // Dummy operation, just for reading
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, 2);
result = *++prx; // result is 2nd byte of receive buffer
return result;
}
/****************************************************************************/
uint8_t RF24::write_register(uint8_t reg, uint8_t value)
{
uint8_t status;
uint8_t * prx = spi_rxbuff;
uint8_t * ptx = spi_txbuff;
*ptx++ = ( W_REGISTER | ( REGISTER_MASK & reg ) );
*ptx = value ;
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, 2);
status = *prx++; // status is 1st byte of receive buffer
if (debug)
printf("write_register(%02x,%02x)\r\n",reg,value);
return status;
}
/****************************************************************************/
uint8_t RF24::write_register(uint8_t reg, const uint8_t* buf, uint8_t len)
{
uint8_t status;
uint8_t * prx = spi_rxbuff;
uint8_t * ptx = spi_txbuff;
uint8_t size = len + 1; // Add register value to transmit buffer
*ptx++ = ( W_REGISTER | ( REGISTER_MASK & reg ) );
while ( len-- )
*ptx++ = *buf++;
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, size);
status = *prx; // status is 1st byte of receive buffer
return status;
}
/****************************************************************************/
uint8_t RF24::write_payload(const void* buf, uint8_t len, const uint8_t writeType)
{
uint8_t status;
uint8_t * prx = spi_rxbuff;
uint8_t * ptx = spi_txbuff;
uint8_t size ;
const uint8_t* current = reinterpret_cast<const uint8_t*>(buf);
uint8_t data_len = min(len,payload_size);
uint8_t blank_len = dynamic_payloads_enabled ? 0 : payload_size - data_len;
size = data_len + blank_len + 1 ; // Add register value to transmit buffer
if (debug)
printf("[Writing %u bytes %u blanks]",data_len,blank_len);
*ptx++ = W_TX_PAYLOAD;
while ( data_len-- )
*ptx++ = *current++;
while ( blank_len-- )
*ptx++ = 0;
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, size);
status = *prx; // status is 1st byte of receive buffer
return status;
}
/****************************************************************************/
uint8_t RF24::read_payload(void* buf, uint8_t len)
{
uint8_t status;
uint8_t * prx = spi_rxbuff;
uint8_t * ptx = spi_txbuff;
uint8_t size ;
uint8_t* current = reinterpret_cast<uint8_t*>(buf);
uint8_t data_len = min(len,payload_size);
uint8_t blank_len = dynamic_payloads_enabled ? 0 : payload_size - data_len;
size = data_len + blank_len + 1; // Add register value to transmit buffer
if (debug)
printf("[Reading %u bytes %u blanks]",data_len,blank_len);
*ptx++ = R_RX_PAYLOAD;
while(size--)
*ptx++ = NOP;
// Size has been lost during while, re affect
size = data_len + blank_len + 1; // Add register value to transmit buffer
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, size);
// 1st byte is status
status = *prx++;
// Decrement before to skip 1st status byte
while ( --size )
*current++ = *prx++;
return status;
}
/****************************************************************************/
uint8_t RF24::flush_rx(void)
{
uint8_t status;
status = bcm2835_spi_transfer( FLUSH_RX );
return status;
}
/****************************************************************************/
uint8_t RF24::flush_tx(void)
{
uint8_t status;
status = bcm2835_spi_transfer( FLUSH_TX );
return status;
}
/****************************************************************************/
uint8_t RF24::get_status(void)
{
return bcm2835_spi_transfer( NOP );
}
/****************************************************************************/
void RF24::print_status(uint8_t status)
{
printf("STATUS\t\t = 0x%02x RX_DR=%x TX_DS=%x MAX_RT=%x RX_P_NO=%x TX_FULL=%x\r\n",
status,
(status & _BV(RX_DR))?1:0,
(status & _BV(TX_DS))?1:0,
(status & _BV(MAX_RT))?1:0,
((status >> RX_P_NO) & 0b111),
(status & _BV(TX_FULL))?1:0
);
}
/****************************************************************************/
void RF24::print_observe_tx(uint8_t value)
{
printf("OBSERVE_TX=%02x: POLS_CNT=%x ARC_CNT=%x\r\n",
value,
(value >> PLOS_CNT) & 0b1111,
(value >> ARC_CNT) & 0b1111
);
}
/****************************************************************************/
void RF24::print_byte_register(const char* name, uint8_t reg, uint8_t qty)
{
char extra_tab = strlen(name) < 8 ? '\t' : 0;
printf("%s\t%c =", name, extra_tab);
while (qty--)
printf(" 0x%02x",read_register(reg++));
printf("\n");
}
/****************************************************************************/
void RF24::print_address_register(const char* name, uint8_t reg, uint8_t qty)
{
char extra_tab = strlen(name) < 8 ? '\t' : 0;
printf("%s\t%c =",name,extra_tab);
while (qty--)
{
uint8_t buffer[addr_width];
read_register(reg++,buffer,sizeof buffer);
printf(" 0x");
uint8_t* bufptr = buffer + sizeof buffer;
while( --bufptr >= buffer )
printf("%02x",*bufptr);
}
printf("\r\n");
}
/****************************************************************************/
RF24::RF24(uint8_t _cepin, uint8_t _cspin, uint32_t _spi_speed):
ce_pin(_cepin), csn_pin(_cspin), spi_speed(_spi_speed),p_variant(false),
payload_size(32), dynamic_payloads_enabled(false),addr_width(5)//,pipe0_reading_address(0)
{
}
/****************************************************************************/
void RF24::setChannel(uint8_t channel)
{
const uint8_t max_channel = 127;
write_register(RF_CH,min(channel,max_channel));
}
/****************************************************************************/
void RF24::setPayloadSize(uint8_t size)
{
const uint8_t max_payload_size = 32;
payload_size = min(size,max_payload_size);
}
/****************************************************************************/
uint8_t RF24::getPayloadSize(void)
{
return payload_size;
}
/****************************************************************************/
static const char rf24_datarate_e_str_0[] = "1MBPS";
static const char rf24_datarate_e_str_1[] = "2MBPS";
static const char rf24_datarate_e_str_2[] = "250KBPS";
static const char * const rf24_datarate_e_str_P[] = {
rf24_datarate_e_str_0,
rf24_datarate_e_str_1,
rf24_datarate_e_str_2,
};
static const char rf24_model_e_str_0[] = "nRF24L01";
static const char rf24_model_e_str_1[] = "nRF24L01+";
static const char * const rf24_model_e_str_P[] = {
rf24_model_e_str_0,
rf24_model_e_str_1,
};
static const char rf24_crclength_e_str_0[] = "Disabled";
static const char rf24_crclength_e_str_1[] = "8 bits";
static const char rf24_crclength_e_str_2[] = "16 bits" ;
static const char * const rf24_crclength_e_str_P[] = {
rf24_crclength_e_str_0,
rf24_crclength_e_str_1,
rf24_crclength_e_str_2,
};
static const char rf24_pa_dbm_e_str_0[] = "PA_MIN";
static const char rf24_pa_dbm_e_str_1[] = "PA_LOW";
static const char rf24_pa_dbm_e_str_2[] = "PA_HIGH";
static const char rf24_pa_dbm_e_str_3[] = "PA_MAX";
static const char * const rf24_pa_dbm_e_str_P[] = {
rf24_pa_dbm_e_str_0,
rf24_pa_dbm_e_str_1,
rf24_pa_dbm_e_str_2,
rf24_pa_dbm_e_str_3,
};
static const char rf24_csn_e_str_0[] = "CE0 (PI Hardware Driven)";
static const char rf24_csn_e_str_1[] = "CE1 (PI Hardware Driven)";
static const char rf24_csn_e_str_2[] = "CE2 (PI Hardware Driven)";
static const char rf24_csn_e_str_3[] = "Custom GPIO Software Driven";
static const char * const rf24_csn_e_str_P[] = {
rf24_csn_e_str_0,
rf24_csn_e_str_1,
rf24_csn_e_str_2,
rf24_csn_e_str_3,
};
// Display NRF24L01 details
void RF24::printDetails(void)
{
printf("================ SPI Configuration ================\n" );
if (csn_pin < BCM2835_SPI_CS_NONE )
{
printf("CSN Pin \t = %s\n",rf24_csn_e_str_P[csn_pin]);
}
else
{
printf("CSN Pin \t = Custom GPIO%d%s\n", csn_pin,
csn_pin==RPI_V2_GPIO_P1_26 ? " (CE1) Software Driven" : "" );
}
printf("CE Pin \t = Custom GPIO%d\n", ce_pin );
// SPI Bus Speed
printf("Clock Speed\t = " );
switch (spi_speed)
{
case BCM2835_SPI_SPEED_64MHZ : printf("64 Mhz"); break ;
case BCM2835_SPI_SPEED_32MHZ : printf("32 Mhz"); break ;
case BCM2835_SPI_SPEED_16MHZ : printf("16 Mhz"); break ;
case BCM2835_SPI_SPEED_8MHZ : printf("8 Mhz"); break ;
case BCM2835_SPI_SPEED_4MHZ : printf("4 Mhz"); break ;
case BCM2835_SPI_SPEED_2MHZ : printf("2 Mhz"); break ;
case BCM2835_SPI_SPEED_1MHZ : printf("1 Mhz"); break ;
case BCM2835_SPI_SPEED_512KHZ: printf("512 KHz"); break ;
case BCM2835_SPI_SPEED_256KHZ: printf("256 KHz"); break ;
case BCM2835_SPI_SPEED_128KHZ: printf("128 KHz"); break ;
case BCM2835_SPI_SPEED_64KHZ : printf("64 KHz"); break ;
case BCM2835_SPI_SPEED_32KHZ : printf("32 KHz"); break ;
case BCM2835_SPI_SPEED_16KHZ : printf("16 KHz"); break ;
case BCM2835_SPI_SPEED_8KHZ : printf("8 KHz"); break ;
default : printf("Probably Bad !!!"); break ;
}
printf("\n");
printf("================ NRF Configuration ================\n" );
print_status(get_status());
print_address_register("RX_ADDR_P0-1",RX_ADDR_P0,2);
print_byte_register("RX_ADDR_P2-5",RX_ADDR_P2,4);
print_address_register("TX_ADDR",TX_ADDR);
print_byte_register("RX_PW_P0-6",RX_PW_P0,6);
print_byte_register("EN_AA",EN_AA);
print_byte_register("EN_RXADDR",EN_RXADDR);
print_byte_register("RF_CH",RF_CH);
print_byte_register("RF_SETUP",RF_SETUP);
print_byte_register("CONFIG",CONFIG);
print_byte_register("DYNPD/FEATURE",DYNPD,2);
printf("Data Rate\t = %s\r\n",rf24_datarate_e_str_P[getDataRate()]);
printf("Model\t\t = %s\r\n",rf24_model_e_str_P[isPVariant()]);
printf("CRC Length\t = %s\r\n",rf24_crclength_e_str_P[getCRCLength()]);
printf("PA Power\t = %s\r\n",rf24_pa_dbm_e_str_P[getPALevel()]);
}
/****************************************************************************/
bool RF24::begin(void)
{
debug = false;
#if defined(DEBUG)
debug = true;
#endif
// This initialize the SPI bus with
// csn pin as chip select (custom or not)
// Init BCM2835 chipset for talking with us
if (!bcm2835_init()){
return false;
}
switch(csn_pin){ //Ensure valid hardware CS pin
case 0: break;
case 1: break;
case 8: csn_pin = 0; break;
case 7: csn_pin = 1; break;
default: csn_pin = 0; break;
}
bcm2835_spi_begin();
// used to drive custom I/O to trigger my logic analyser
// bcm2835_gpio_fsel(GPIO_CTRL_PIN , BCM2835_GPIO_FSEL_OUTP);
// start the SPI library:
// Note the NRF24 wants mode 0, MSB first and default to 1 Mbps
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);
// Set SPI bus Speed
bcm2835_spi_setClockDivider(spi_speed);
// Choose hardware CSN pin
bcm2835_spi_chipSelect(csn_pin);
// Initialise the CE pin of NRF24 (chip enable) after the CSN pin, so that
// The input mode is not changed if using one of the hardware CE pins
bcm2835_gpio_fsel(ce_pin, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_write(ce_pin, LOW);
// wait 100ms
delay(100);
// Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier
// WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet
// sizes must never be used. See documentation for a more complete explanation.
//printf("write_register(%02X, %02X)\n", SETUP_RETR, (0b0100 << ARD) | (0b1111 << ARC));
setRetries(5,15);
// Determine if this is a p or non-p RF24 module and then
// reset our data rate back to default value. This works
// because a non-P variant won't allow the data rate to
// be set to 250Kbps.
if( setDataRate( RF24_250KBPS ) )
{
p_variant = true ;
}
// Then set the data rate to the slowest (and most reliable) speed supported by all
// hardware.
setDataRate( RF24_1MBPS ) ;
// Initialize CRC and request 2-byte (16bit) CRC
setCRCLength( RF24_CRC_16 ) ;
toggle_features();
write_register(FEATURE,0 );
write_register(DYNPD,0);
// Reset current status
// Notice reset and flush is the last thing we do
write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
// Set up default configuration. Callers can always change it later.
// This channel should be universally safe and not bleed over into adjacent
// spectrum.
setChannel(76);
// Flush buffers
//flush_rx();
flush_tx();
powerUp();
// Enable PTX, do not write CE high so radio will remain in standby I mode ( 130us max to transition to
RX or TX instead of 1500us from powerUp )
// PTX should use only 22uA of power
write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) );
return true;
}
/****************************************************************************/
void RF24::startListening(void)
{
powerUp();
write_register(CONFIG, read_register(CONFIG) | _BV(PRIM_RX));
write_register(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
// Restore the pipe0 adddress, if exists
if (pipe0_reading_address[0] > 0){
write_register(RX_ADDR_P0, pipe0_reading_address,addr_width);
}
// Flush buffers
//flush_rx();
flush_tx();
// Go!
bcm2835_gpio_write(ce_pin, HIGH);
// wait for the radio to come up (130us actually only needed)
delayMicroseconds(130);
}
/****************************************************************************/
void RF24::stopListening(void)
{
bcm2835_gpio_write(ce_pin, LOW);
flush_tx();
flush_rx();
delayMicroseconds(150);
write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) );
delayMicroseconds(150);
}
/****************************************************************************/
void RF24::powerDown(void)
{
bcm2835_gpio_write(ce_pin, LOW);
write_register(CONFIG,read_register(CONFIG) & ~_BV(PWR_UP));
}
/****************************************************************************/
void RF24::powerUp(void)
{
bool up = read_register(CONFIG) & _BV(PWR_UP);
if(! up ){
write_register(CONFIG, ( read_register(CONFIG) | _BV(PWR_UP) ));
delay(5);
}
}
/******************************************************************/
#if defined (FAILURE_HANDLING)
void RF24::errNotify(){
if(debug){ printf("HARDWARE FAIL\n\r"); }
failureDetect = true;
}
#endif
/******************************************************************/
bool RF24::write( const void* buf, uint8_t len, const bool multicast ){
// Begin the write
startFastWrite(buf,len, multicast);
//Wait until complete or failed
#if defined (FAILURE_HANDLING)
uint32_t timer = millis();
#endif
// If this hangs, it ain't coming back, no sense in timing out
while( ! ( get_status() & ( _BV(TX_DS) | _BV(MAX_RT) ))) {
#if defined (FAILURE_HANDLING)
if(millis() - timer > 175){
errNotify();
return 0;
}
#endif
}
bcm2835_gpio_write(ce_pin, LOW);
uint8_t status = write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
//Max retries exceeded
if( status & _BV(MAX_RT)){
flush_tx(); //Only going to be 1 packet int the FIFO at a time using this method, so just flush
return 0;
}
//TX OK 1 or 0
return 1;
}
bool RF24::write( const void* buf, uint8_t len ){
return write(buf,len,0);
}
/****************************************************************************/
//For general use, the interrupt flags are not important to clear
bool RF24::writeBlocking( const void* buf, uint8_t len, uint32_t timeout )
{
//Block until the FIFO is NOT full.
//Keep track of the MAX retries and set auto-retry if seeing failures
//This way the FIFO will fill up and allow blocking until packets go through
//The radio will auto-clear everything in the FIFO as long as CE remains high
uint32_t timer = millis(); //Get the time
that the payload transmission started
while( ( get_status() & ( _BV(TX_FULL) ))) { //Blocking only if FIFO is full. This will
loop and block until TX is successful or timeout
if( get_status() & _BV(MAX_RT)){ //If MAX
Retries have been reached
reUseTX();
//Set re-transmit and clear the MAX_RT interrupt flag
if(millis() - timer > timeout){ return 0; } //If this payload has exceeded
the user-defined timeout, exit and return 0
}
#if defined (FAILURE_HANDLING)
if(millis() - timer > (timeout+75) ){
errNotify();
return 0;
}
#endif
}
//Start Writing
startFastWrite(buf,len,0);
//Write the payload if a buffer is clear
return 1;
//Return 1 to indicate successful transmission
}
/****************************************************************************/
void RF24::reUseTX(){
write_register(STATUS,_BV(MAX_RT) ); //Clear max retry flag
//spiTrans( REUSE_TX_PL );
bcm2835_spi_transfer( REUSE_TX_PL);
bcm2835_gpio_write(ce_pin, LOW);
//Re-Transfer packet
bcm2835_gpio_write(ce_pin, HIGH);
}
/****************************************************************************/
bool RF24::writeFast( const void* buf, uint8_t len, const bool multicast )
{
//Block until the FIFO is NOT full.
//Keep track of the MAX retries and set auto-retry if seeing failures
//Return 0 so the user can control the retrys and set a timer or failure counter if required
//The radio will auto-clear everything in the FIFO as long as CE remains high
#if defined (FAILURE_HANDLING)
uint32_t timer = millis();
#endif
while( ( get_status() & ( _BV(TX_FULL) ))) { //Blocking only if FIFO is full.
This will loop and block until TX is successful or fail
if( get_status() & _BV(MAX_RT)){
//reUseTX();
//Set re-transmit
write_register(STATUS,_BV(MAX_RT) ); //Clear max retry flag
return 0;
//Return 0. The previous payload has been retransmitted
//From the user perspective, if you get a 0, just keep trying to send the same payload
}
#if defined (FAILURE_HANDLING)
if(millis() - timer > 75 ){
errNotify();
return 0;
}
#endif
}
//Start Writing
startFastWrite(buf,len,multicast);
return 1;
}
bool RF24::writeFast( const void* buf, uint8_t len ){
return writeFast(buf,len,0);
}
/****************************************************************************/
//Per the documentation, we want to set PTX Mode when not listening. Then all we do is write data and
set CE high
//In this mode, if we can keep the FIFO buffers loaded, packets will transmit immediately (no 130us
delay)
//Otherwise we enter Standby-II mode, which is still faster than standby mode
//Also, we remove the need to keep writing the config register over and over and delaying for 150 us
each time if sending a stream of data
void RF24::startFastWrite( const void* buf, uint8_t len, const bool multicast){ //TMRh20
//write_payload( buf,len);
write_payload( buf, len,multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD ) ;
bcm2835_gpio_write(ce_pin, HIGH);
}
/****************************************************************************/
void RF24::startWrite( const void* buf, uint8_t len, const bool multicast )
{
// Send the payload
write_payload( buf, len,multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD ) ;
bcm2835_gpio_write(ce_pin, HIGH);
delayMicroseconds(10);
bcm2835_gpio_write(ce_pin, LOW);
}
/****************************************************************************/
bool RF24::txStandBy(){
#if defined (FAILURE_HANDLING)
uint32_t timer = millis();
#endif
while( ! (read_register(FIFO_STATUS) & _BV(TX_EMPTY)) ){
if( get_status() & _BV(MAX_RT)){
write_register(STATUS,_BV(MAX_RT) );
bcm2835_gpio_write(ce_pin, LOW);
flush_tx(); //Non blocking, flush the data
return 0;
}
#if defined (FAILURE_HANDLING)
if( millis() - timer > 75){
errNotify();
return 0;
}
#endif
}
bcm2835_gpio_write(ce_pin, LOW); //Set STANDBY-I mode
return 1;
}
/****************************************************************************/
bool RF24::txStandBy(uint32_t timeout){
uint32_t start = millis();
while( ! (read_register(FIFO_STATUS) & _BV(TX_EMPTY)) ){
if( get_status() & _BV(MAX_RT)){
write_register(STATUS,_BV(MAX_RT) );
bcm2835_gpio_write(ce_pin, LOW);
//Set re-transmit
bcm2835_gpio_write(ce_pin, HIGH);
if(millis() - start >= timeout){
bcm2835_gpio_write(ce_pin, LOW);
flush_tx();
return 0;
}
}
#if defined (FAILURE_HANDLING)
if( millis() - start > (timeout+75)){
errNotify();
return 0;
}
#endif
}
bcm2835_gpio_write(ce_pin, LOW); //Set STANDBY-I mode
return 1;
}
/****************************************************************************/
void RF24::maskIRQ(bool tx, bool fail, bool rx){
write_register(CONFIG, ( read_register(CONFIG) ) | fail << MASK_MAX_RT | tx << MASK_TX_DS
| rx << MASK_RX_DR );
}
/****************************************************************************/
uint8_t RF24::getDynamicPayloadSize(void)
{
spi_txbuff[0] = R_RX_PL_WID;
spi_rxbuff[1] = 0xff;
bcm2835_spi_transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, 2);
if(spi_rxbuff[1] > 32) { flush_rx(); return 0; }
return spi_rxbuff[1];
}
/****************************************************************************/
bool RF24::available(void)
{
return available(NULL);
}
/****************************************************************************/
bool RF24::available(uint8_t* pipe_num)
{
//Check the FIFO buffer to see if data is waitng to be read
if (!( read_register(FIFO_STATUS) & _BV(RX_EMPTY) )){
// If the caller wants the pipe number, include that
if ( pipe_num ){
uint8_t status = get_status();
*pipe_num = ( status >> RX_P_NO ) & 0b111;
}
return 1;
}
return 0;
}
/****************************************************************************/
void RF24::read( void* buf, uint8_t len )
{
// Fetch the payload
read_payload( buf, len );
//Clear the two possible interrupt flags with one command
write_register(STATUS,_BV(RX_DR) | _BV(MAX_RT) | _BV(TX_DS) );
}
/****************************************************************************/
void RF24::whatHappened(bool& tx_ok,bool& tx_fail,bool& rx_ready)
{
// Read the status & reset the status in one easy call
// Or is that such a good idea?
uint8_t status = write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
// Report to the user what happened
tx_ok = status & _BV(TX_DS);
tx_fail = status & _BV(MAX_RT);
rx_ready = status & _BV(RX_DR);
}
/****************************************************************************/
void RF24::openWritingPipe(uint64_t value)
{
// Note that AVR 8-bit uC's store this LSB first, and the NRF24L01(+)
// expects it LSB first too, so we're good.
write_register(RX_ADDR_P0, reinterpret_cast<uint8_t*>(&value), addr_width);
write_register(TX_ADDR, reinterpret_cast<uint8_t*>(&value), addr_width);
write_register(RX_PW_P0,payload_size);
}
/****************************************************************************/
void RF24::openWritingPipe(const uint8_t *address)
{
// Note that AVR 8-bit uC's store this LSB first, and the NRF24L01(+)
// expects it LSB first too, so we're good.
write_register(RX_ADDR_P0,address, addr_width);
write_register(TX_ADDR, address, addr_width);
//const uint8_t max_payload_size = 32;
//write_register(RX_PW_P0,min(payload_size,max_payload_size));
write_register(RX_PW_P0,payload_size);
}
/****************************************************************************/
static const uint8_t child_pipe[] =
{
RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5
};
static const uint8_t child_payload_size[] =
{
RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5
};
static const uint8_t child_pipe_enable[] =
{
ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5
};
void RF24::openReadingPipe(uint8_t child, uint64_t address)
{
// If this is pipe 0, cache the address. This is needed because
// openWritingPipe() will overwrite the pipe 0 address, so
// startListening() will have to restore it.
if (child == 0){
memcpy(pipe0_reading_address,&address,addr_width);
}
if (child <= 6)
{
// For pipes 2-5, only write the LSB
if ( child < 2 )
write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address),
addr_width);
else
write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
write_register(pgm_read_byte(&child_payload_size[child]),payload_size);
// Note it would be more efficient to set all of the bits for all open
// pipes at once. However, I thought it would make the calling code
// more simple to do it this way.
write_register(EN_RXADDR,read_register(EN_RXADDR) |
_BV(pgm_read_byte(&child_pipe_enable[child])));
}
}
/****************************************************************************/
void RF24::setAddressWidth(uint8_t a_width){
if(a_width -= 2){
write_register(SETUP_AW,a_width%4);
addr_width = (a_width%4) + 2;
}
}
/****************************************************************************/
void RF24::openReadingPipe(uint8_t child, const uint8_t *address)
{
// If this is pipe 0, cache the address. This is needed because
// openWritingPipe() will overwrite the pipe 0 address, so
// startListening() will have to restore it.
if (child == 0){
memcpy(pipe0_reading_address,address,addr_width);
}
if (child <= 6)
{
// For pipes 2-5, only write the LSB
if ( child < 2 ){
write_register(pgm_read_byte(&child_pipe[child]), address, addr_width);
}else{
write_register(pgm_read_byte(&child_pipe[child]), address, 1);
}
write_register(pgm_read_byte(&child_payload_size[child]),payload_size);
// Note it would be more efficient to set all of the bits for all open
// pipes at once. However, I thought it would make the calling code
// more simple to do it this way.
write_register(EN_RXADDR,read_register(EN_RXADDR) |
_BV(pgm_read_byte(&child_pipe_enable[child])));
}
}
/****************************************************************************/
void RF24::toggle_features(void)
{
bcm2835_spi_transfer( ACTIVATE );
bcm2835_spi_transfer( 0x73 );
}
/****************************************************************************/
void RF24::enableDynamicPayloads(void)
{
// So enable them and try again
toggle_features();
write_register(FEATURE,read_register(FEATURE) | _BV(EN_DPL) );
if (debug)
printf("FEATURE=%i\r\n",read_register(FEATURE));
// Enable dynamic payload on all pipes
//
// Not sure the use case of only having dynamic payload on certain
// pipes, so the library does not support it.
write_register(DYNPD,read_register(DYNPD) | _BV(DPL_P5) | _BV(DPL_P4) | _BV(DPL_P3) |
_BV(DPL_P2) | _BV(DPL_P1) | _BV(DPL_P0));
dynamic_payloads_enabled = true;
}
/****************************************************************************/
void RF24::enableAckPayload(void)
{
//
// enable ack payload and dynamic payload features
//
// So enable them and try again
toggle_features();
write_register(FEATURE,read_register(FEATURE) | _BV(EN_ACK_PAY) | _BV(EN_DPL) );
if (debug)
printf("FEATURE=%i\r\n",read_register(FEATURE));
//
// Enable dynamic payload on pipes 0 & 1
//
dynamic_payloads_enabled = true;
write_register(DYNPD,read_register(DYNPD) | _BV(DPL_P1) | _BV(DPL_P0));
}
/****************************************************************************/
void RF24::enableDynamicAck(void){
//
// enable dynamic ack features
//
toggle_features();
write_register(FEATURE,read_register(FEATURE) | _BV(EN_DYN_ACK) );
if(debug){printf("FEATURE=%i\r\n",read_register(FEATURE));}
}
/****************************************************************************/
void RF24::writeAckPayload(uint8_t pipe, const void* buf, uint8_t len)
{
uint8_t * ptx = spi_txbuff;
uint8_t size ;
const uint8_t* current = reinterpret_cast<const uint8_t*>(buf);
uint8_t data_len = min(len,payload_size);
size = data_len + 1 ; // Add register value to transmit buffer
if (debug){
printf("[Writing %u bytes]",data_len);
}
*ptx++ = W_ACK_PAYLOAD | ( pipe & 0b111 );
while ( data_len-- ){
*ptx++ = *current++;
}
bcm2835_spi_transfern( (char *) spi_txbuff, size);
}
/****************************************************************************/
bool RF24::isAckPayloadAvailable(void)
{
return ! read_register(FIFO_STATUS) & _BV(RX_EMPTY);
}
/****************************************************************************/
bool RF24::isPVariant(void)
{
return p_variant ;
}
/****************************************************************************/
void RF24::setAutoAck(bool enable)
{
if ( enable )
write_register(EN_AA, 0b111111);
else
write_register(EN_AA, 0);
}
/****************************************************************************/
void RF24::setAutoAck( uint8_t pipe, bool enable )
{
if ( pipe <= 6 )
{
uint8_t en_aa = read_register( EN_AA ) ;
if( enable )
{
en_aa |= _BV(pipe) ;
}
else
{
en_aa &= ~_BV(pipe) ;
}
write_register( EN_AA, en_aa ) ;
}
}
/****************************************************************************/
bool RF24::testCarrier(void)
{
return ( read_register(CD) & 1 );
}
/****************************************************************************/
bool RF24::testRPD(void)
{
return ( read_register(RPD) & 1 ) ;
}
/****************************************************************************/
void RF24::setPALevel(uint8_t level)
{
uint8_t setup = read_register(RF_SETUP) & 0b11111000;
if(level > 3){ // If invalid level, go to max PA
level = (RF24_PA_MAX << 1) + 1; // +1 to support the SI24R1 chip extra bit
}else{
level = (level << 1) + 1; // Else set level as requested
}
write_register( RF_SETUP, setup |= level ) ; // Write it to the chip
}
/****************************************************************************/
uint8_t RF24::getPALevel(void)
{
return (read_register(RF_SETUP) & (_BV(RF_PWR_LOW) | _BV(RF_PWR_HIGH))) >> 1 ;
}
/****************************************************************************/
bool RF24::setDataRate(rf24_datarate_e speed)
{
bool result = false;
uint8_t setup = read_register(RF_SETUP) ;
// HIGH and LOW '00' is 1Mbs - our default
setup &= ~(_BV(RF_DR_LOW) | _BV(RF_DR_HIGH)) ;
if( speed == RF24_250KBPS )
{
// Must set the RF_DR_LOW to 1; RF_DR_HIGH (used to be RF_DR) is already 0
// Making it '10'.
setup |= _BV( RF_DR_LOW ) ;
}
else
{
// Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1
// Making it '01'
if ( speed == RF24_2MBPS )
{
setup |= _BV(RF_DR_HIGH);
}
}
write_register(RF_SETUP,setup);
// Verify our result
if ( read_register(RF_SETUP) == setup )
{
result = true;
}
return result;
}
/****************************************************************************/
rf24_datarate_e RF24::getDataRate( void )
{
rf24_datarate_e result ;
uint8_t dr = read_register(RF_SETUP) & (_BV(RF_DR_LOW) | _BV(RF_DR_HIGH));
// switch uses RAM (evil!)
// Order matters in our case below
if ( dr == _BV(RF_DR_LOW) )
{
// '10' = 250KBPS
result = RF24_250KBPS ;
}
else if ( dr == _BV(RF_DR_HIGH) )
{
// '01' = 2MBPS
result = RF24_2MBPS ;
}
else
{
// '00' = 1MBPS
result = RF24_1MBPS ;
}
return result ;
}
/****************************************************************************/
void RF24::setCRCLength(rf24_crclength_e length)
{
uint8_t config = read_register(CONFIG) & ~( _BV(CRCO) | _BV(EN_CRC)) ;
// switch uses RAM (evil!)
if ( length == RF24_CRC_DISABLED )
{
// Do nothing, we turned it off above.
}
else if ( length == RF24_CRC_8 )
{
config |= _BV(EN_CRC);
}
else
{
config |= _BV(EN_CRC);
config |= _BV( CRCO );
}
write_register( CONFIG, config ) ;
}
/****************************************************************************/
rf24_crclength_e RF24::getCRCLength(void)
{
rf24_crclength_e result = RF24_CRC_DISABLED;
uint8_t config = read_register(CONFIG) & ( _BV(CRCO) | _BV(EN_CRC)) ;
if ( config & _BV(EN_CRC ) )
{
if ( config & _BV(CRCO) )
result = RF24_CRC_16;
else
result = RF24_CRC_8;
}
return result;
}
/****************************************************************************/
void RF24::disableCRC( void )
{
uint8_t disable = read_register(CONFIG) & ~_BV(EN_CRC) ;
write_register( CONFIG, disable ) ;
}
/****************************************************************************/
void RF24::setRetries(uint8_t delay, uint8_t count)
{
write_register(SETUP_RETR,(delay&0xf)<<ARD | (count&0xf)<<ARC);
}
Python Motor Library
import RPi.GPIO as GPIO
## Motor Control Functions
#Forward
#Reverse
#Turn Right
#Turn Left
#Soft Right
#Soft Left
#All Stop
def DriveForward(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 1)
GPIO.output(Min2, 0)
GPIO.output(Min3, 0)
GPIO.output(Min4, 1)
def DriveBackward(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 0)
GPIO.output(Min2, 1)
GPIO.output(Min3, 1)
GPIO.output(Min4, 0)
def TurnRight(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 0)
GPIO.output(Min2, 1)
GPIO.output(Min3, 0)
GPIO.output(Min4, 1)
def SoftRight(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 0)
GPIO.output(Min2, 1)
GPIO.output(Min3, 0)
GPIO.output(Min4, 0)
def TurnLeft(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 1)
GPIO.output(Min2, 0)
GPIO.output(Min3, 1)
GPIO.output(Min4, 0)
def SoftLeft(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 0)
GPIO.output(Min2, 0)
GPIO.output(Min3, 1)
GPIO.output(Min4, 0)
def StopMotor(Min1,Min2,Min3,Min4):
GPIO.output(Min1, 0)
GPIO.output(Min2, 0)
GPIO.output(Min3, 0)
GPIO.output(Min4, 0)
Python Sensor Library
import RPi.GPIO as GPIO
import time
#Reads the Sonar Sensor
def readsonar(TRIG,ECHO):
GPIO.output(TRIG,1) #begin Trigger pulse
time.sleep(0.00001)
GPIO.output(TRIG,0) #end Trigger pulse
begin = time.time()
while GPIO.input(ECHO) == 0: #wait for Trigger pulse
if ((time.time() - begin) * 1700) >= 30.0: # if no object is encountered.. stop looking
return 30
start = time.time()
while GPIO.input(ECHO) == 1:
pass
stop = time.time()
dist = (stop - start) * 17000
time.sleep(0.05)
return int(dist)
#SonarSense averages ten measurements
def SonarSense(TRIG,ECHO):
L = [0,0,0,0,0,0,0,0,0,0,0]
for x in range(11):
dist_new = readsonar(TRIG,ECHO)
L[x] = dist_new
L = sorted(L)
dist = L[10]
return dist
# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
def readadc(adcnum, clockpin, mosipin, misopin, cspin):
if ((adcnum > 7) or (adcnum < 0)):
return -1
GPIO.output(cspin, True)
GPIO.output(clockpin, False) # start clock low
GPIO.output(cspin, False) # bring CS low
commandout = adcnum
commandout |= 0x18 # start bit + single-ended bit
commandout <<= 3 # we only need to send 5 bits here
for i in range(5):
if (commandout & 0x80):
GPIO.output(mosipin, True)
else:
GPIO.output(mosipin, False)
commandout <<= 1
GPIO.output(clockpin, True)
GPIO.output(clockpin, False)
adcout = 0
# read in one empty bit, one null bit and 10 ADC bits
for i in range(12):
GPIO.output(clockpin, True)
GPIO.output(clockpin, False)
adcout <<= 1
if (GPIO.input(misopin)):
adcout |= 0x1
GPIO.output(cspin, True)
adcout >>= 1 # first bit is 'null' so drop it
return adcout
def IRSense(IRS0, SPICLK, SPIMOSI, SPIMISO, SPICS):
measure_tot = 0
for x in range(30):
measure_new = readadc(IRS0, SPICLK, SPIMOSI, SPIMISO, SPICS)
measure_tot = measure_tot + measure_new
measure_avg = (measure_tot / 30)
if (measure_avg < 200):
zone = 3
elif ((measure_avg < 350) & (measure_avg > 200)):
zone = 2
elif ((measure_avg < 1024) & (measure_avg > 350)):
zone = 1
else:
return -1
return zone
Users Guide for product:
Step 1.) Turn on Raspberry Pi and connect it to a keyboard, mouse, and monitor.
Step 2.) Load the cMain file and the pMain.py file.
Step 3.) Find the IP address of the Raspberry Pi at the given time by typing in “hostname –I”.
Remember the IP address.
Step 4.) Once the Raspberry Pi code is loaded up, disconnect the keyboard, mouse, and monitor.
Place the RDRIV on the ground for it is ready to be activated.
Step 5.) Attach Arduino to USB port and upload ArduinoRDRIVTwo.ino
Figure 1-Open Arduino compiler program
Figure 2- Program will look like this when opened
Figure 3- Open the code for the RDRIV
Figure 4- Select the ArduinoRDRIVTwo program or most up to date version
Figure 5- Code will open in a new window that looks like this
Figure 6- Make sure the port the Arduino s connected to is the same port the program is talking to
Figure 7- Upload code to Arduino
Step 6.) Load the Qt program up on the user’s device of choice
Figure 8- Open project in QT
Figure 9- Choose QT Project file named RDRIV_4_5_15 or latest version
Figure 10- QT should look like this when loaded
Step 7.) Build and Run the GUI
Figure 11-Click the run arrow in lower left of the screen
Figure 12- Project will build with progress bar in the lower right hand corner
Figure 13- Progress bar will become green when done building
Figure 14- The GUI will appear when built sucessfully and can be used from there
Step 8.) Once connected to the same Wi-Fi network, enter in the same IP address the Raspberry
Pi has, into the user’s url window.
Step 9.) Since the Raspberry Pi Camera is placed upside down you will need to click on settings
once connected to the Raspberry Pi Camera.
Step 10.) Go down to where it says “Rotate”, rotate the camera by 180 degrees. Now the
Raspberry Pi Camera video feed will come in at the right perspective.
Step 11.) Navigate to your destination with the G.U.I.
Robot Circuit Diagram
Laptop Communication Circuit Diagram
Mechanical Drawings