This project is an autonomously navigating robot that tries to reach its goal position while avoiding obstacles on its way. The robot will be equipped with a LiDAR sensor that will be used to detect objects in its surroundings. As objects are detected and the robot moves around, a real time map will be updated. The map will be used to save the locations of obstacles that have been identified. This way, the robot will not re-attempt a failed path to the goal position. It will instead attempt paths that either have no obstacles or paths that have not yet been checked for obstacles.
The robot will move by two DC motor driven wheels and two caster wheels. The motors will be attached to the bottom of a circular platform. The motors will be controlled by two motor drivers. The motor drivers will receive PWM commands from the Zynq Processor. Encoders on each motor all be used to keep track of the vehicles position and orientation. The whole system will be powered a LiPo battery.
Teachers! Did you use this instructable in your classroom?
Add a Teacher Note to share how you incorporated it into your lesson.
Step 1: Assembling the Vehicle
The robot is powered by two motors attached to the side wheels and then is additionally supported by two caster wheels, one in the front and one in the back. The platform and motor mounts were made out of sheet metal Aluminum. A motor hub was purchased to attach the wheels to the motor. However, a custom intermediate coupler needed to be made because the hole pattern of the hub was different than the hole pattern of the wheel.
The motor selected was an Port Escap 12V DC motor with built in encoders. This motor can be purchased on ebay for a very reasonable price (see Bill of Materials). Search keywords “12V Escap 16 Coreless Geared DC Motor with Encoders” on ebay to find the motor. There are usually a fair amount of sellers to select from. The specs and pinouts of the motors are shown in the diagrams below.
The assembly of the robot began with a CAD model
design of the chassis. The model below shows the top view of the 2D shape profile designed for the chassis.
It is suggested that the chassis be designed as a 2D
profile so that it can be easily manufactured. We cut a 12”X12” sheet of Aluminum into the shape of the chassis by using a water-jet cutter. The chassis platform could also be cut with a band saw.
Step 2: Mounting Motors
The next step is to make the motor mounts. It is suggested that the motor mounts be made out of 90-degree Sheet Metal Aluminum. Using this part, the motor can be attached cantilever on one face of the sheet metal using the two
M2 holes of the motor and the other face can be bolted to
the platform. Holes must be drilled into the motor mount so that screws can be used to fasten the motor onto the motor mount and the motor mount onto the platform. The motor mount can be seen in the figure above.
Next the Pololu Motor Hub (see Bill of Materials) is placed
on the motor shaft and tightened with the provided set screw and Allen wrench. The hole pattern of the Pololu motor hub does not match the hole pattern of the VEX wheel so a custom intermediate coupler must be made. It is suggested that the scrap sheet metal Aluminum used to make the chassis platform be used to make the coupler. The hole pattern and dimensions of this couple is shown in the figure below. The outside diameter and shape (does not need to be a circle) of the custom aluminum coupler does not matter as long as all holes fit on the part.
Step 3: Creating Vivado Block Design
- Start by creating a new Vivado project and select the Zybo Zynq 7000 Z010 as the target device.
- Next click on create new block design, and add the Zynq IP. Double click on the Zynq IP and import the provided XPS settings for the Zynq. Then enable UART0 with MIO 10..11 under the MIO configurations tab, and also make sure that Timer 0 and the Watchdog timer are enabled.
- Add two AXI GPIOS to the block design. For GPIO 0 enable dual channel and set both to all outputs. Set the GPIO width for channel 1 to 4 bits and for channel 2 to 12 bits, these channels will be used to set the motor direction and send the amount of ticks the encoder measures to the processor. For GPIO 1 set just one channel to all inputs with a channel width of 4 bits. This will be used to receive data from the encoders. Make all GPIO ports external.
- Next Add two AXI Timers. Make the pwm0 ports on both timers external. These will be the pwms that control the speed the motors rotate at.
- Finally Run the block automation and connection automation. Verify that the block design you have matches the one provided.
Step 4: Communicating With the LiDAR
This LiDAR uses a SCIP 2.0 protocol to communicate through UART, the attached file describes the entire protocol.
To communicate with the LiDAR we will be using UART0. The LiDAR returns 682 data points each representing the distance to an object at that angle. The LiDAR scans counter clockwise from -30 degrees to 210 degrees with a step of 0.351 degrees.
- All communication to the LiDAR is done with ASCI characters, refer to the SCIP protocol for the format used. We start by sending the QT command to turn on the LiDAR. We then send the GS command several times requesting 18 data points at a time to ft in the UARTS 64 byte FIFO. The data returned from the LiDAR is then parsed and stored into the SCANdata global array.
- Each data point stored is 2 bytes of encoded data. Passing this data into the decoder will return a distance in millimeters.
In the main_av.c file you will find the following functions to communicate with the LiDAR
- This will send the input string to the LiDAR through the UART0
- This will receive data after a command has been sent to the LiDAR and store the data in the RECBuffer
- This function will send a series of commands to retrieve all 682 data points. After each set of 18 data points is received parseLIDARinput() is called to parse the data and incrementally store the data points in SCANdata.
Step 5: Populating Grid With Obstacles
The GRID that is stored is a 2D array with each index value representing a location. The data stored in each index is either a 0 or a 1, No obstacle and obstacle respectively. The square distance in millimeters that each index represents can be changed with the GRID_SCALE definition in the vehicle.h file. The size of the 2D array can also be varied to allow the vehicle to scan a larger area by modifying the GRID_SIZE definition.
After a new set of distance data is scanned from the LiDAR updateGrid() is called. This will iterate through every data point stored in the SCANdata array to determine which indexes in the grid have obstacles. Using the current orientation of vehicle we can determine the angle that corresponds to each data point. To determine where an obstacle is you then simply multiply the corresponding distance by cos/sin of the angle. Adding these two values to the vehicles current x and y position will return the index in the grid of the obstacle. Dividing the distance returned by this operation by the GRID_SCALE will allow us to vary how large the square distance of every index is.
The above pictures show the vehicles current environment and the resulting Grid.
Step 6: Communicating With Motors
To communicate with the motors we start by initializing the GPIOs to control was direction the motor spins in. Then writing directly to the base address of the PWMs in the AXI Timer allows us to set things like the period and the Duty cycle which directly control the speed the motor rotates at.
Step 7: Path Planning
To be implemented in the near future.
Using the grid and motor functionality previously described, it is very easy to implement algorithms such as A*. As the vehicle moves it will continue to scan the surrounding area and determine if the path it is on is still valid