Orbweaver Rover - Concept
1. Introduction
Hi! This will be my first blog for the Just Encase design challenge. In this first blog, I want to go over some details about what my project will be, how I plan on accomplishing that, ideas for the project, as well as some rough calculations and simulations I've done for it. I'm really happy to be selected as one of the challengers for this competition, and I'll do a deeper dive into the starter kit in one of the next blogs since it will be here in a few days because of various delays with the customs here. I'm starting a bit late with the blogs for this design challenge, but I hope to catch up and finish the project by the end of the deadline. I've seen a lot of amazing blogs and projects posted by other people and would just like to wish good luck to everyone participating in this design challenge!
2. Contents
3. Idea
First, let's begin with the theme for this design challenge. The theme is to design and make a monitoring system that uses LoRa and that demonstrates the robustness of the Hammond electronic enclosures. The theme of the design challenge is excellent as it's pretty open and provides a wide range of possibilities for all kinds of projects.
To explain my idea, I'll begin with the name of my project - Orb-weaver Rover. The rover part is self-explanatory, but why orb-weaver? Orb-weaver spiders are the most common group of builders of spiral wheel-shaped webs often found in gardens. My idea for this project is to make an autonomous/RC rover that can go around "weaving" (casting) a net of sensor boxes around the targeted area that can provide monitoring in multiple locations covering a much greater area. As for what I'm going to monitor with my system, I will be monitoring general air quality and hazardous chemicals in the air.
An example where a concept system like this can be useful is monitoring toxic gas leaks, the rover (or multiple rovers) can cast a giant net of sensors boxes in a wide area, and the hazard can be tracked in real-time. Of course, what I'm making is a proof of concept for this system.
I loved seeing the NASA mission style of the logo in feiticeir0 blogs both in this design challenge and the 1 Meter of Pi, so I had to give it a try myself for this project! I tried drawing a bit of inspiration from the amazing small army of robots that were sent to the red planet and their mission patches, so I hope you like it!
4. Design
There are 3 main things that this project can be separated into:
- Rover
- Sensor Boxes
- GCS and GUI (Ground control station and Graphical user interface)
Rover
Since the idea of the rover is inspired by the Mars rovers as mentioned, I wanted to incorporate their looks and some of their design features. One of those things will be the rocker-bogie suspensions that I will cover in one of the later blogs. I will be using 4 instead of 6 wheels to save on cost and complexity a bit, but here are some initial idea sketches I made for this project.
The rover will have a geared DC motor for every wheel and will also have a servo motor which means it will be 4WD and will have 4 wheel steering. The brain of the rover will be a Raspberry Pi 4B running ROS, while a side computer will be one of the Arduino MKR WAN1300. Besides leaving the sensor boxes around, the rover will also be equipped with sensors to perform measurements. As for energy, I will either use a LIPO battery or LION batteries as shown on the sketch above. I will be using standard-sized servos, as for motors, I went with:
The motors are from DIGILENT, they run on 12V and already have a reduction ratio of 1:19. One great thing about these motors is the documentation that is behind them, with all of the torque, RPM, and current curves that one can ask for. I later used that data to perform some basic simulations as you will see later in this blog. Another great thing about these motors is that they have an encoder already attached to the back. The encoder is made out of 2 hall effect sensors that are placed at 90 degrees (which will let us look at the spinning direction, not just the speed of the rotation) and a magnet that is attached to the rotor of the motor. Since the rover will have independent 4WD, I will be using a PID controller for each of the wheels to keep the rover going straight. Of course, when steering, the speeds on the wheels won't be the same, but for that, there are some simple calculations that I will be showing later in this blog.
Even though the motor is geared down 1:19, it is still way too fast for what I plan on using it for. Here is a short video demonstrating how fast it actually is when run at 12V from a bench power supply.
To slow down the motors a bit, I will design and make small gearboxes that will reduce the speed further, and also, work like portal axels giving the rover better ground clearance which is of course a big plus!
To show the strength of Hammond enclosures, my plan is to order 2 more of their enclosures to use for the sensor boxes, as for those from the starter kit, I will utilize one of them as the main body of the rover, while the other one I plan on using as a GCS (Ground Control Station). But I'll go more into detail about that as the project progresses further.
Sensor Box
The sensor boxes will be small nodes with an Arduino MKR WAN1300 inside that will conduct the necessary measurement and send back all of the data using LoRa. One thing that will require a bit of tinkering is to find a good way of carrying the boxes and deploying them easily. A big plus would also be to easily pick up the boxes as well, but that's secondary compared to the first 2.
I'm still thinking and experimenting with different sensors I plan on using in each of the boxes. My main idea is for the boxes to use the same sensors so that each of the measurements can be tracked based on location. Some sensors that I'm considering:
- DHT22 - temperature and humidity
- BMP280 - air pressure sensor
- MQ line of sensors - air quality, hazardous gasses
- Air particle sensors
GCS and GUI
The GCS will need to have some kind of a LoRa device, either an Arduino MKR WAN1300 or a Raspberry with a LoRa hat (I had some trouble getting the Dragino hat to work for me, but maybe I manage to get it going). The main purpose of the GCS is to provide a connection between my GUI, the rover, and the sensor boxes. As for the GUI, I plan on making it using Python, I want to use it to control the rover using NRF modules as well as collect all of the LoRa data both from the rover and the sensor boxes.
5. Calculations
As I've mentioned above, the rover will have 4WD drive and 4 wheel steering, so there are some necessary calculations that need to be performed. This usually isn't necessary, since there are differentials, that "eat up" everything they need when the car steers to one side, but in this case, I will need to simulate that. So let's begin with the simple, 2 wheel steering as seen on most ordinary cars (manufacturers started integrating rear-wheel steering into some higher-end models).
One thing to point out is that all of these calculations are performed with an assumption that the wheels aren't skidding side to side while the rover is driving. Taking that information into consideration means that our center of rotation needs to be on the same axis as the back wheels, where we can find the angles for both front wheels easily using some simple geometry. When a car steers to one side, all of the wheels are actually going at different speeds. The speed is easily calculated using the same drawing as we did for the angle calculations.
We don't have to use only 2 wheel steering, if we want, we can also use 4 wheel steering to get tighter radiuses when turning. I've chosen to calculate the radius from the center of rotation to the center of the rover. A thing that doesn't change compared to the 2 wheel steering is that wheels can't move to the side, so the axis going through all wheels need to come together in a single point which is our center of rotation. One thing that is different compared to 2 wheel steering is that rather than 4 speeds as we had there, now we need to calculate only 2 speeds since the inner wheels are on the same radius as are both of the outer wheels. The calculation is not much different from the 2 wheel calculation and uses the same geometry principles.
Besides the 4 wheel steering I've shown above, where the rover would again move like an ordinary car, just with a smaller turn radius, there are some more cool things we can do with 4 wheel steering. One of those things can be for example positioning all of the wheels at 90 degrees, meaning that the rover can move from side to side without having to actually turn. Of course, we don't have to turn the wheels by 90 degrees, we can just turn them all at any angle (same for all wheels) and just translate the rover in any direction. Besides that, we can also position the wheels in such a way that the rover turns in circles around its center. This can be really useful if we don't want to back out of somewhere, but rather just turn around by 180 degrees on the spot and continue driving forward. The calculation for those angles can be found in the picture to the left (above).
6. Simulations
Since I saw that the wheel was spinning too fast for my liking (I don't want the rover to be fast, what I want from the rover is to have enough torque to climb over any obstacles), I wanted to use the data from the motor datasheet and perform some simple simulations using python, to see what kind of gear ratio I should be targetting as well as what kind of general performance I can expect from this rover. Here is the data that I will be using.
Tipping over simulation
One of the things I was really curious about is at what angles will the rover be able to go. The maximum incline that the rover can tackle comes to a few different things:
1. How steep is the incline before the rover tips over
2. What is the maximum incline angle where the rover stalls without going backward
3. What is the maximum angle where there is enough traction
Since the third one is highly dependant on the surface and on the tiers, I decided to skip it, since I really can't get even any ballpark figures for that. The first calculation is rather simple and the angle is dependant on 3 main factors: COG - height, COG position on the rover, is it more towards front or rear or in the middle, wheelbase. Getting the COG as low as possible is great for being able to go steep inclines, but the lower the COG will also probably mean the worse the ground clearance. This is something I will be tweaking along the way as I build the rover, but here are some rough simulations for this. I deliberately put the COG off-center, so it shows the difference when the rover is ascending and descending.
The other calculation and simulation that we need to perform are for the stall. This is a rather simple simulation where we just need to use the stall torque provided in the datasheet and multiply it by 4 to get the total torque. Of course, a big role here will play the additional gear ratio I plan on adding with my small gearbox for the motor.
Max incline for a set speed
For this simulation, I wanted to see a bit of the dynamics of the rover. In previous simulations, I can get the angles at which the rover stop, with this simulation I wanted to see what kind of incline could the rover handle at max load with a set speed. For example, the results of this simulation tell us what's the incline angle where the max speed is 1.2m/s. This is calculated by first using the gearing finding the right RPM needed for the motor, then finding the torque at that RPM, and then using that torque and the mass of the rover, calculating the max incline angle.
Max speed on a level surface
Looking online, I've found some ballpark values for a car driving on a flat surface, so I thought I would try and calculate the max speed of the rover based on that. This calculation is done by figuring out the minimum torque required to overcome the friction force and then using that torque to find the matching RPM which will give us our top speed for the rover.
Code for all of the simulations:
### Libraries import math import numpy as np import matplotlib.pyplot as plt ### Motor Parameters torque_nm = [0.00, 0.01, 0.03, 0.06, 0.07, 0.08, 0.09, 0.1, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.2, 0.22] # [N/m] torque_kgcm = [0.00, 0.12, 0.35, 0.58, 0.69, 0.81, 0.93, 1.04, 1.27, 1.39, 1.50, 1.62, 1.74, 1.85, 2.08, 2.20] # [kg/cm] rpm = [789.00, 748.21, 664.99, 581.51, 540.29, 498.15, 457.23, 415.47, 332.34, 290.84, 249.35, 207.75, 165.96, 124.67, 41.56, 0.00] # [RPM] current = [0.20, 0.32, 0.55, 0.78, 0.89, 1.01, 1.13, 1.24, 1.47, 1.59, 1.71, 1.82, 1.94, 2.05, 2.29, 2.40] # [A] ### Rover Parameters wheel_circumference = 0.35 # [m] wheel_diameter = wheel_circumference / math.pi # [m] wheel_radius = wheel_diameter / 2 # [m] rough_exp_mass = 3 # [kg] cog_height = 0.15 # [m] cog_len = 0.45 # relative wheelbase = 0.4 # [m] track = 0.25 # [m] wheel_width = 0.045 # [m] reduction_ratio = 3 # Ratio between the number of teeth on the big gear and small gear ground_clearence = 0.1 # [m] ### Constants g = 9.81 # [m/s^2] c = 0.015 # Friction coefficient ### Simulation Parameters incline = 30 # [degrees] ### Tipping over simulation - single value max_ascend_angle_rad = math.atan(wheelbase * (1 - cog_len) / cog_height) max_descend_angle_rad = math.atan(wheelbase * cog_len / cog_height) max_ascend_angle_deg = math.degrees(max_ascend_angle_rad) max_descend_angle_deg = math.degrees(max_descend_angle_rad) print('Ascend and descend calculations') print('Max ascend angle is: ' + str(max_ascend_angle_deg) + '°') print('Max descend angle is: ' + str(max_descend_angle_deg) + '°') max_side_angle_rad = math.atan(wheelbase * (track + wheel_width / 2) / cog_height) max_side_angle_deg = math.degrees(max_side_angle_rad) print('') print('Side incline calculation') print('Max degree before tipping over is: ' + str(max_side_angle_deg) + '°') ### Stall simulation - single value force = [] num_of_points = len(torque_kgcm) for i in range(num_of_points): force.append(4 * reduction_ratio * torque_kgcm[i] / (wheel_radius * 100)) print(force) ### Tipping over simualtion - range of values min_height = 0.1 max_height = 0.3 resolution = 0.0005 num_of_points = 401 cog_h = np.linspace(min_height, max_height, num_of_points) max_ascend_angle_deg_range = np.array([]) max_descend_angle_deg_range = np.array([]) for i in range(num_of_points): max_ascend_angle_deg_range = np.append(max_ascend_angle_deg_range, [math.degrees(math.atan(wheelbase * (1 - cog_len) / cog_h[i]))]) max_descend_angle_deg_range = np.append(max_descend_angle_deg_range, [math.degrees(math.atan(wheelbase * cog_len / cog_h[i]))]) #print(cog_h * 100) #print(max_ascend_angle_deg_range) # plot fig, ax = plt.subplots() ax.grid(axis = 'both', color = 'black', linestyle = '-', linewidth = 0.2) ax.set_title('Tipping over simulation') ax.set_xlabel('COG height [cm]') ax.set_ylabel('Max incline [°]') ax.plot(cog_h * 100, max_ascend_angle_deg_range, linewidth = 2.0, color = 'r', label = 'ascending') ax.plot(cog_h * 100, max_descend_angle_deg_range, linewidth = 2.0, color = 'g', label = 'descending') ax.legend() ax.set(xlim = (10, 30), ylim = (30, 70)) fig.savefig('TippingOverSimulation.png', dpi = 200) plt.show() ### Stall simulation - range min_mass = 2.0 max_mass = 10.0 num_pnts = 8001 mass_rvr = np.linspace(min_mass, max_mass, num_pnts) min_reduction = 1.0 max_reduction = 5.0 num_reds = 9 reductions = np.linspace(min_reduction, max_reduction, num_reds) # plot fig, ax = plt.subplots() ax.grid(axis = 'both', color = 'black', linestyle = '-', linewidth = 0.2) ax.set_title('Stall simulation') ax.set_xlabel('Rover mass [kg]') ax.set_ylabel('Max incline [°]') for j in range(num_reds): max_force = 4 * reductions[j] * torque_nm[-1] / wheel_radius phi = np.array([]) for i in range(num_pnts): if max_force >= mass_rvr[i] * g: phi = np.append(phi, 90) else: phi = np.append(phi, math.degrees(np.arcsin(max_force / (mass_rvr[i] * g)))) ax.plot(mass_rvr, phi, linewidth = 2.0, label = 'Rdc: ' + str(reductions[j])) ax.legend(loc = 'upper right', prop={'size': 8}) ax.set(xlim = (2, 10), ylim = (0, 100)) fig.savefig('StallSimulation.png', dpi = 200) plt.show() ### Max incline for a set speed simulation set_speed = 1.2 min_reduction = 1.0 max_reduction = 5.0 num_reds = 9 reductions = np.linspace(min_reduction, max_reduction, num_reds) min_mass = 2.0 max_mass = 8.0 num_pnts = 6001 mass_rvr = np.linspace(min_mass, max_mass, num_pnts) # plot fig, ax = plt.subplots() ax.grid(axis = 'both', color = 'black', linestyle = '-', linewidth = 0.2) ax.set_title('Max incline for a set speed: ' + str(set_speed) + 'm/s') ax.set_xlabel('Rover mass [kg]') ax.set_ylabel('Max incline [°]') for j in range(num_reds): # speed = rpm * wheel_circumference / 60 target_rpm = 60 * reductions[j] * set_speed / wheel_circumference if target_rpm > rpm[0]: # This is the case where we can't reach the speed even at zero load print('Can\'t calculate the value for this set of parameters') print('Reduction ratio: ' + str(reductions[j])) print('Required RPM: ' + str(target_rpm)) continue # If that's not the case, we can interpolate to find the torque that corresponds to this RPM torque_val = np.interp(target_rpm, rpm, torque_nm) force_val = 4 * reductions[j] * torque_val / wheel_radius phi = np.array([]) for i in range(num_pnts): if force_val >= mass_rvr[i] * g: phi = np.append(phi, 90) else: phi = np.append(phi, math.degrees(np.arcsin(force_val / (mass_rvr[i] * g)))) ax.plot(mass_rvr, phi, linewidth = 2.0, label = 'Rdc: ' + str(reductions[j])) ax.legend(loc = 'upper right', prop={'size': 10}) ax.set(xlim = (2, 8), ylim = (0, 100)) fig.savefig('SetSpeedMaxInclineSimulation.png', dpi = 200) plt.show() ### Predicted max speed on level surface depsning on mass and the reduction ratio min_reduction = 1.0 max_reduction = 5.0 num_reds = 9 reductions = np.linspace(min_reduction, max_reduction, num_reds) min_mass = 2.0 max_mass = 8.0 num_pnts = 6001 mass_rvr = np.linspace(min_mass, max_mass, num_pnts) # plot fig, ax = plt.subplots() ax.grid(axis = 'both', color = 'black', linestyle = '-', linewidth = 0.2) ax.set_title('Max speed in m/s for c: ' + str(c)) ax.set_xlabel('Rover mass [kg]') ax.set_ylabel('Max speed [m/s]') for i in range(num_reds): # For the rover to have a const max speed, the pull force needs to match the friction force # We can then translate that force into torque achieved_speed = np.array([]) for j in range(num_pnts): achieved_speed = np.append(achieved_speed, np.interp(mass_rvr[j] * g * c * wheel_radius / 4, torque_nm, rpm) * wheel_circumference / (60 * reductions[i])) ax.plot(mass_rvr, achieved_speed, linewidth = 2.0, label = 'Rdc: ' + str(reductions[i])) ax.legend(loc = 'upper right', prop={'size': 8}) ax.set(xlim = (2, 8), ylim = (0.7, 5)) fig.savefig('PredictedMaxSpeedSimulationms.png', dpi = 200) plt.show() # plot in km/h fig, ax = plt.subplots() ax.grid(axis = 'both', color = 'black', linestyle = '-', linewidth = 0.2) ax.set_title('Max speed in km/h for c: ' + str(c)) ax.set_xlabel('Rover mass [kg]') ax.set_ylabel('Max speed [km/h]') for i in range(num_reds): # For the rover to have a const max speed, the pull force needs to match the friction force # We can then translate that force into torque achieved_speed = np.array([]) for j in range(num_pnts): achieved_speed = np.append(achieved_speed, 3.6 * np.interp(mass_rvr[j] * g * c * wheel_radius / 4, torque_nm, rpm) * wheel_circumference / (60 * reductions[i])) ax.plot(mass_rvr, achieved_speed, linewidth = 2.0, label = 'Rdc: ' + str(reductions[i])) ax.legend(loc = 'upper right', prop={'size': 8}) ax.set(xlim = (2, 8), ylim = (2.5, 18)) fig.savefig('PredictedMaxSpeedSimulationkmh.png', dpi = 200) plt.show() # plot in mi/h fig, ax = plt.subplots() ax.grid(axis = 'both', color = 'black', linestyle = '-', linewidth = 0.2) ax.set_title('Max speed in mi/h for c: ' + str(c)) ax.set_xlabel('Rover mass [kg]') ax.set_ylabel('Max speed [mi/h]') for i in range(num_reds): # For the rover to have a const max speed, the pull force needs to match the friction force # We can then translate that force into torque achieved_speed = np.array([]) for j in range(num_pnts): achieved_speed = np.append(achieved_speed, 3.6 * np.interp(mass_rvr[j] * g * c * wheel_radius / 4, torque_nm, rpm) * wheel_circumference / (60 * reductions[i] * 1.608)) ax.plot(mass_rvr, achieved_speed, linewidth = 2.0, label = 'Rdc: ' + str(reductions[i])) ax.legend(loc = 'upper right', prop={'size': 8}) ax.set(xlim = (2, 8), ylim = (1.5, 11)) fig.savefig('PredictedMaxSpeedSimulationmih.png', dpi = 200) plt.show()
7. Summary
I've started a bit late with a lot of work ahead. I will try tackling first some of the harder aspects of the design like the small gearboxes and the suspension for the rover, before going into the sensor boxes and the GUI. It will be a fun 2 months full of work, but I can't wait to see the rover moving in the end! Thanks for reading the blog, hope you liked it!
Milos
Relevant links for the competition:
- Just Encase Design Challenge
- Just Encase Design Challenge - About
- Just Encase Design Challenge - Challengers
Link to my GitHub where you can find all of the files used for this project (codes, 3D models,...):
Link to my Project Collection:
NEXT BLOG -----> Orb-weaver Rover - Blog #2