Antweights


I was contacted by an ex-student about getting involved with robot-wars. I had thought about this in the past, but always came to the same conclusions: we don't have the the mechanical engineering resources e.g. metal bashing, welding etc. However, he introduced me to the world of Antweights. The max weight of these robots is 150g (5.3oz) and must fit inside a 10cm (4 inch) cube. These limitations make this class of robot a lot more achievable given the mechanical workshop and equipment available. A full list of rules are available here: (Link), (Link). Looking into these robots there is also more opportunity to embed some electronics, making it a nice teaching platform i.e. interfacing, actuator control and embedded software. Therefore, i thought i would give it a go.

Table of Contents

Cannon-fodder-v1
Cannon-fodder-v2
Cannon-fodder-v3 Competition Kit
Cannon-fodder-v3 Mechanical Construction Guide
Cannon-fodder-v3 Electronic Construction Guide
Cannon-fodder-v4
Cannon-fodder-v4a
Cannon-fodder-v5
Cannon-fodder-v6

Cannon-fodder-v1

To start off i wanted to make a very cheap robot to test out some ideas, mainly the remote control, electronics etc. The aim was to try and make a robot for approximately £10, such that we can afford the possibility that they may be destroyed in battle. The long term goal is to develop a design that could be made in house, so that we can run a competition with approximately ten teams, allowing students to build and customise their own robots, and keep their creations at the end (another reason for a budget, put a limit on those recurring costs). For prototype number 1 i went with what we had in stock, the chassis was made from 3D printed PLA plastic elements and acrylic sheet, not the strongest materials, but what i had to hand. The original 3D models are shown in figure 1.


Figure 1: 3D model

This robot does not have any weapons, circular in shape to deflect attacks and can run both sides-up i.e. can be flipped. The design thought was to develop a robot that could survive a battle, a defensive design, following the wise words of KISS. The initial proto-type is shown in figure 2






Figure 2: Prototype 1

As the aim was to keep costs low, went for the same motors used in the Zergling robots. These are a little on the big side, heavy and slow, but, they are cheap and do give quite a bit of torque with a 1:200 gearbox i.e. perfect for pushing type attacks, robot sumo :). Costs seems to have increased a bit since i last bought these, but can be found for about £1.50, you can also get version with lower ration gearboxes e.g. 1:120, if we need to increase max speed. Unfortunately, the robot is a little over weight and thats before we even consider the electronics :(

Total = 174g

I could reduce weight by switching to micro-DC motors. These came onto the market a few year ago, nice motor's with a wide range of metal gearboxes, unfortunately a little more expensive, the cheapest costing £4.00 each, therefore, £8.00 the pair makes these a little too expensive given the £10 budget. Could increase the budget, but for the moment going to stick with the £10 limit as costs always have a habit of spiralling up.

Therefore, sticking with the tried and tested motors, we have 86g for the rest of the robot. Using the power of guessing, i estimate i will need 5g - 15g for the battery depending on capacity, and 15g - 20g for the electronics, so have 51g - 66g for the chassis. The chassis's current weight is 80g, but definitely some room for improvement here, maybe, ideally looking to half the weight to 40g. Polycarbonate would be the obvious choice to replace the acrylic sheet, a lot stronger and less brittle. However, its difficult to laser cut, which rules it out, as we need to build these in house i.e. don't have the time to Mill & Drill. Plan B would be 2mm ABS sheet, combined with some selective material removal i.e. drilling holes, i think this will work. The 3D printed walls can also be reduced in thickness to reduce weight. Again, can move to ABS, allowing a thinner wall thickness (stronger plastic), can also remove some excess material on the four uprights. If needed could then acetone vapour weld/smooth the surface to re-enforce the layers. With these mods i think i'm in the ball park of 150g, so will continue development.

In the past i've normally gone for nickel metal hydride (NiMH) batteries, simple for safety i.e. these batteries don't tend to catch fire, but with the size and weight constraints i think i need a battery technology with a better power to size performance, therefore, going to have to go with lithium polymer batteries (LiPo). With the space available looking at a 3.7V 200mA/h (or 300mA/h) batteries as shown in figure 3.The good thing about these types of LiPos is that they come with short circuit and under-voltage protection built in, down side is they are only 3.7V, so will need a boost converter. From the electronics point of view this isn't a big problem, rather its the impact on space and cost, both of which are limited resources i.e. if you look at figure 1 there are three areas were components can be placed: at the back and next to drive motors. Ideally most of the weight needs to go at the back i.e. over the drive wheels to improve traction. I've used off the self Boost/Buck converters before, very handy little modules. However, they do tend to be relatively thick (inductor, pot and caps), there are lower profile ones out there as shown in figure 4, but i'm guessing these smaller versions may need extra external smoothing capacitors, time will tell. For the purposes of testing used a slightly larger module as shown in figure 5.


Figure 3: battery pack


Figure 4: boost converter


Figure 5: boost converter test circuit

The initial design of this robot was based on this project: (Link). I wanted a system that could be programmed in C, so an Arduino based robot and controller were ideal. Wireless communications between these micro-controllers was implemented using nRF24L01 modules. These worked out of the box once i noticed that i had connected them to the wrong pins :). The initial test system is shown in figure 6. The handset was configured such that each joystick controlled the speed and direction of one motor e.g. middle/resting position was stop, pushing a joystick forwards increased the speed of that motor in the forwards direction, pulling it back increased the speed of that motor in the backwards direction. Therefore, pushing both joysticks forwards caused the robot to move forwards, then by varying the amount each joystick was moved you can cause the robot to move in an arc/curve, or move backwards.


Figure 6: Robot ver 1.0


Figure 7: handset v1.0

Cost was in budget-ish, with the most expensive item being the Arduino:

This gives a total electronics cost for the robot of £5.64, and for the handset of £7.99. This pushes the total budget over £10, but if everyone uses a standard handset we can keep these and only give away the robot, so we are back on budget :). The proto-type system worked perfectly for about 2 minutes then the robot blow up :(. You may of spotted the PB6 battery, it seems that when the motors are under load they cause the switch mode boost converter to go a little mad, as shown in figure 8. With just a motor connected, the boost converter's output is 8V, then if you add a little load to the motor the DC level increases to 13V with transient spikes of 20V+. This "slight" increase in voltage popped all the ICs in the robot :(.


Figure 8: Boost converter output voltage

After replacing all the boards and switching to a +9V battery, all worked again, some videos of the robot working are shown below:

As you can see from the videos, i need to practice driving, or adjust the control interface, maybe some preset movements, change the control to one joystick direction, the other speed .... Also, quickly switching the motors to full power caused the robot's Arduino to trip out. Therefore, need to do a little work on the power supply. However, for a proto-type it seemed functional, apart from being a little over weight: 209g, but 49g isn't too much of a diet for the robot. From a robot pushing point of view the heavier the robot is the better i.e. to generate more friction between the wheels of the ground. Therefore, as the robot will need to be reduced in weight i may need to redesign the wheels to increase their surface area i.e. when the robot tried to push the container full of water in the above video (650g) the motors did not stall, rather they lost traction and skidded across the ground.


Figure 9: a little over wieght

The 9V battery worked ok, but was a little over voltage for the 3.3V regulator so needed a 7V solution. Decided to use two 3.7V Li-Pos in series to give 7.4V, then to help reduce brown-outs on the CPU input voltage used a diode and reservoir capacitor (1000uF) e.g. if the drive motor current switched suddenly (full power standing start) the battery voltage may dip, but the diode will prevent the capacitor discharging, maintaining input voltage during these transitory high current periods. Basic battery circuit shown in figure 10. Note, battery packs can be removed and charged separately to avoid over-voltage conditions.


Figure 10: Power supply

The remote control handset is again Arduino based. From a hardware point of view the same as the original source. Software was modified to use hardware timers and a different control interface, but, with Arduino's that was nice and simple to implement, the joy of a flexible software layer. A picture of the final handset is below, not shown is the 7.2V NiMH battery pack that is bolted to the back, surprisingly cheap, AA cell NiMH based only £3.70.




Figure 11: handset

Watching some antweight battles on Youtube this robot will definitely be cannon fodder for the more professional robots, especially the ones with front spinners, also the speed is a little slow. Thinking tactics, i imagine this robot will be spending most of its time trying to run away from trouble, waiting for its opponent to run out of power or develop a hardware fault :), so need to increase the robot's top speed. The motors used do come in a 1:120 version, rather than the 1:200 used, these are a slightly different size but should fit into the existing chassis with a little modification to the mounting bracket. Therefore, decided to try these ones out, time for version 2.

Cannon-fodder-v2

Modifying the motor mounting bracket was simple, as shown in figure 12, also added a front pull bar to keep the motors aligned / parallel, this is currently a push fit, but may drill and pin with a 1mm rod. As these motors are slightly thinner (less gears) they can be moved inwards towards the centre of the robot. This frees some extra space for wider tyres. As identified from the previous push tests the motors have quite a bit of torque, where they fail is the wheels/tyres lose traction/skid, so in theory a wider tyre should give more grip. As the robot will be operating on a flat surface moving to a wider tyre is a problem e.g. adding an additional O-ring will not work unless the motors are mounted perfectly parallel to the ground. This is due to the robot balancing on the three high spots i.e. left/right wheels and front skid. Therefore, need a soft tyre that can deform i.e. auto self-level, also a soft material should also produce more friction, improving traction. To make these tyres my first thoughts were to 3D print them using a flexible plastic e.g. TPE or TPU, however, did not have any in stock, there was also a question regarding the print heads we have i.e. can they print this type of plastic, or would it end in a pool of molten plastic? Therefore, for a first attempt decided to go for a 3D printed mold and silicon rubber, as shown in figure 13.


Figure 12: mounting bracket




Figure 13: mold

Went for a "toothed" profile as it was easy to generate using a FOR loop in OpenScad, rather than any technical reasons. The idea was that this thinner contact area would allow the tyre to deform, compress a little, levelling the robot, maintaining maximum contact area with the ground. Well that was the thought. To reduce weight made the new tyre rims are hollow, contact width is increased from 2-3mm to 8mm, which should improve traction. Also modified wall thickness and pillar size reduce weight. Another thought was that moving to the lower ratio gearbox was that they were smaller and therefore should be lighter, however, i guess most the weight is in the motor, so a few less plastic gears didn't make much difference, as shown by the new weights below:

Total = 200g

So need to loose 50g, to keep things simple that will need to come from the top, base and walls, which is perhaps a big ask, currently these combined is 71g, so this needs to be reduced to 21g. Maybe a bit could also be saved from the wheels, for the moment setting the weight limit to 200g, and will try to loose the 50g later. Definitely need to print the walls etc in ABS, the PLA used has a lot of cracks, i suspect it will quickly de-laminate if hit, will focus of the chassis later. Therefore, for the moment i present Cannon-fodder-v2, as shown in figure 14, a good start, lucky 13 :). A short video of the robot in action is available here: (Link).






Figure 14: robot v2

Watching Youtube videos of antweight fights i think speed/mobility is a key element for success, it makes the robot harder to hit and the robot itself can hit harder as it has increased kinetic energy on impact. The max speed of the motor (drive shaft) is given by its construction (permanent magnets, windings etc) and its gearbox. Speed can be increased to a point by increasing the applied voltage i.e. the torque produced by the motor is mainly determined by V/L (ignoring R), however, you need to make sure you don't burn out the winding or brushes/commutator, so need to limit the maximum current. The motors used are rated for 3V - 12V, but this can be increased. Therefore, decided to go back to the boost converter and run the motors at 25V :), just need to ensure that the maximum winding current is not exceeded. Obviously even with a current trip this will shorten the life of the motor, but at £1.50 each this is an affordable optimisation e.g. the same approach is taken in F1, engines are designed to last a specific number of races. However, after the last blowup need to ensure the sensitive electronics are protected. The obvious solution is to rule two battery packs, full isolation, or some type of diode/inductor/capacitor smoothing circuit. To judge the effectiveness of this idea need to benchmark the motors, plot the relationship between voltage and speed to identify what are the fundamental mechanical limitations (which there will be). To do this 3D printer a encoder disk, added a reflective square and blu-tacked an infra-red sensor module: OPB608 (Link), to time the period on a scope, as shown in figure 15. To cleanup the signal i used the motor controller board from the robo-cockroach project: (Link), this has operational amplifier comparators and filters to square-up the small signal from the infra-red sensor. The two test motors were connect to a variable power supply and the voltage varied from 3V to 15V, the average sensor signal period was then recorded, as shown in figure 16.






Figure 15: Infra-red sensor


Figure 16: motor voltage vs speed (RPM) plots

I then increased the voltage exceeding the normal max voltage limit, intended to go to 25V, but these higher voltages produced a lot of electrical noise, swamping the infra-red sensor, making it impossible to read the period. This amount of radiated noise was quite surprising i.e. the motor and sensor were powered from separate bench power supplies, the electrical pickup must of come from the proximity of the sensor to the commutator in the motor. Unsurprisingly this test damaged the motor, lots or arcing, it didn't quite run as smoothly as it once did, but its speed did increase with voltage. I did start to see some saturation, but i think this was mostly due to over-voltage damage, so if current limiting was added there may be some mileage in this approach. Note, one thing to test later is the affect of this radiated electrical noise from the motors and the RF range/reliability. The other joy to consider is that the standatd H-bridge motor controllers tend to be limited to a supply voltage of 9V - 12V, so would need to roll my own which isn't a problem, but it does add weight and complexity, therefore, going to put this on on hold until ive got a basic robot working.


Figure 17: over voltage: motor voltage vs speed (RPM) plot

Cannon-fodder-v3


Figure 18: Cannon-fodder-v3

Each year i normally put together a kit of bits so that students can build a robot during the summer term i.e. after the exams have finished e.g. (Zerglings). This year i've decided to go for a robot sumo competition, therefore, work on the antweight robot has switched focus a little into developing a sumo robot kit for the student teams to use, Cannon-fodder-v3, as shown in figures 18 and 19. This robot is based on the previous designs. The main time element in constructing these previous robots was the wiring/connectors etc. To simplify construction i therefore decided to go for a PCB "motherboard" approach, removing most of the wiring joys. The downside of this approach is that the PCB shape does limit chassis design choices i.e. you can no longer position the electronics around the motors or battery etc. However, teams can still use the dead-bug construction approach if they desire. Also found a cheaper motor, slightly lower gearbox ratio, so does increase speed a little and reduces weight. It does have a double drive shaft which is not really required in this design, but can easily be cut off with snipes. The new motor and assembled robot are shown in figure 19. A short movie of the robot in action is available here: (Video)




Figure 19: Cannon-fodder-v3, motors (top), assembled robot (below)

To make health-and-safety a little easier for the competition i'm going to modify the antwieght rules a little for the robot sumo competition:

The circuit diagram and PCB layout of the motherboard are shown in figures 20 - 23. This version is a slight variation on the one shown in figure 19, i added a couple more connectors, messed around with the power circuit a little, but basically the same. This PCB mounts the three main modules: Arduino Nano, Wifi and motor controller, did use SIL sockets just in case of accidents, but these module could be soldered directly into the PCB to save weight. It also has the required voltage regulators and power supply smoothing circuits.


Figure 20: Cannon-fodder-v3 Schematic

Note, an interesting side affect of moving from Lithium to a standard PB3 Alkaline battery is that the higher internal resistance of these batteries causes the Arduino to trip out (reset) if the robot switches both drive motors fully on from a standing start. Therefore, you need to override the remote control signals to more "slowly" feed in power when changing the PWM signals when accelerating these motors. From one point of view not an ideal characteristic, however, i think it adds another element to the software and hardware design :).


Figure 21: Cannon-fodder-v3 PCB bottom layer


Figure 22: Cannon-fodder-v3 PCB top layer


Figure 23: Cannon-fodder-v3 PCB combined

The chassis is a skeleton of the previous version, all linked around the main motor mounting block. This removes the need for a base plate saving a lot of weight. Alternatively, individual motor mounts are available if students want to position the PCB between the motors or in an alternative position. The 3D printed kit of bits is shown in figure 24. The STL file can be downloaded here: (Link).


Figure 24: 3D printed parts

If required each team will be given up to two small servo motors, as shown in figure 25, to implement flipping, hammer or jaw type weapons. However, all robots must still meet the 225g weight limit. The weight of the current test robot (Cannon-fodder-v3) is 175g and each servo motor weights 11g, as shown in figure 26.


Figure 25: Servo motor




Figure 26: Weight

This leaves approximately 40g for chassis elements. These can be 3D printed, however, as spinner and blade weapons are not permitted laminate card based bodies are more than adequate. This also simplifies construction i.e. scissors and glue :). If you were to replace the PP3 with a LiPo you do get below the original antweight 150g weight limit, 131g as shown in figure 26, so it could be possible to enter a robot based on this design into an antweight competition. With a little more weight reduction e.g. removing metal screws, sockets for modules and other connectors, i'm sure this could be pruned down to around 110g. The leaves 19g - 40g for body and weapons.

For the remote control i took the same motherboard approach, basically the same design as the previous remote control, but with a few more LEDs and digital inputs to allow some "mode" switches i.e. change the behaviour of joystick controls etc. The circuit diagram for the remote is shown in figure 27, the PCB in figures 28 - 30.


Figure 27: Remote control schematic


Figure 28: Remote control PCB (bottom)


Figure 29: Remote control PCB (top)


Figure 30: Remote control PCB (all)

The battery pack is a 7.2V NiCad pack, mounted under the PCB, as shown in figures 31 and 32. Have not added the mode switches at present, will add brackets later. Was a bit concerned the 7.2V wouldn't quite be large enough for the Arduino regulator, but seems ok.


Figure 31: Battery clip




Figure 32: Remote control

Cannon-fodder-v3 Mechanical Construction Guide


Figure 33: Wheel blanks, bottom (left), top (right)

Wheels come in two section as shown in figure 33. The bottom section is slightly thicker than the top. This was done to avoid difficult overhangs when 3D printing. Using a craft knife first bevel the outer and inner edges as shown in figure 34. This allows the drive shaft to be inserted, producing a friction fit. Don't remove too much material, but you do need to remove enough to guide the drive shaft into the hole. Too little the drive shaft will not fit, too much and the wheel will wobble, so do take your time. Next, sand the two inner surfaces flat, you always get a few lumps and bumps when 3D printing. If the surfaces are not flat the super-glue will not work. Be careful to apply even pressure when sanding, otherwise you may take too much material off one side of the wheel. This will result in the wheel not being perpendicular to the drive shaft, which will again cause a wobble when rotated. Hold the two halves between thumb and finger to feel if they are flat.




Figure 34: Wheel construction - step 1

Test the fit of the drive shaft into the top and bottom sections. Push the bottom wheel section onto the drive shaft, apply a small ring of super-glue on its surface and push the top section on. Make sure the bottom section is not pushed down too far, you need the drive shaft to locate into the hole on the top section. When assembled clamp together using pegs, as shown in figure 35. Allow the glue to dry for 2 minutes then using a screwdriver gently lever the wheel off the drive shaft, leave to full harden for at least 10 minute. When the wheel is removed check that the two holes/section are correctly aligned. Then repeat the process for the other wheel.




Figure 35: Wheel construction - step 2

Next, bevel both sides of the spacer such that it can slide onto the drive shaft, as show in figure 36. Apply a layer of super-glue and then push on the wheel onto the drive shaft. Allow to dry for 2 minutes, then lever off using a screw driver. Finally snap in the rubber tyre.






Figure 36: Wheel construction - step 3

Both motors are mounted in the chassis block. Using a needle file remove any first layer bulging i.e. overhanging lips, on the outer edge. Both the inner edges and outer edges. Then file off any general lumps and bumps on the inner surface, as shown in figure 37. These will prevent the motor from fitting into the chassis. After adjustments test fit the motor to see how tight it is, but do not push it all the way in (it may get jammed). Again looking for a friction fit, so remove material carefully.




Figure 37: Chassis construction - step 1

When you are happy with the fit, push the motor into the chassis such that the locating pin on the motor is pushed into the cutout on the side of the chassis. You can still remove the motor, but it is a bit of a tight fit. Next, using a pair of side cutters reduce the size of the inner drive shaft, so that when the other motor is inserted the two drive shafts do not touch.

MAKE SURE THE MOTOR IS THE RIGHT WAY ROUND BEFORE YOU CUT I.E. THE LOCATING PEG IS IN THE CUTOUT.

Do not cut off too much, you can always reduce its length, or remove any sharp edges using a needle file or sand paper. Repeat for the the other motor. Finally, insert both motors and attach the wheels as shown in figure 38.




Figure 38: Chassis construction - step 2

The battery box and back wheels are shown in figure 39. Using a needle file remove any overhangs on the inner and outer edges on the battery box. The connecting beams for the rear wheels and the battery box should be a push fit into the chassis, no glue needed. To strengthen the rear wheel mounting holes super-glue on a 4mm washer. This also gives a nice flat running surface for the wheel. Clear the holes through each wheel using a 3mm drill, then mount using a 12mm x 3mm bolt, self-tapped into the plastic.




Figure 39: Chassis construction - step 3

To assemble push fit the rear wheel and battery box mounting bars into the chassis as show in figure 40.






Figure 40: Chassis construction - step 4

Cannon-fodder-v3 Electronic Construction Guide


Figure 41: New PCB

To simplify the connection of the Arduino, motor controller and the wifi module i had the circuit shown in figure 20 made into a PCB, as shown in figure 41. The first items to solder are the pins on the Arduino and motor controller boards. As shown in figure 42. When soldering these pin strps make sure they are mount square the to board.


Figure 42: Arduino

To mount the various boards onto the PCB i would strongly recommend using SIL sockets. These come in lengths of 20 sockets, therefore, they need to be cut to length. You can bend/snap these to length, but i find using a pair of side cutters to snip through an unused socket more reliable, as shown in figure 43. The rough edges can then be filed down to size. You will need 15x2, 6x2 and 4x2.






Figure 43: Arduino sockets

With the modules mounted in their sockets insert these pins into the PCB and solder in place. This ensures that the correct pin and socket alignment, as shown in figure 44. Allowing the modules to be easily remove and inserted.




Figure 44: mounting sockets on PCB

To help reduce electrical noise produced by the motors and the digital electronic systems a number of 100nF capacitors (yellow components in figure 45) are placed around the PCB, across the different supply rails. To solder these into the PCB, place the component legs through the board and bent them a little to stop them falling out when the board is turned over. Tip, solder one leg of each capacitor, then turn the board back over and check if any of the components have dropped/moved. If they have simply reflow/heat the required solder joint and re-position. Solder the remaining legs and trim with side cutters.


Figure 45: ceramic capacitors

To provide a low impedance current source for the different components when they need a larger surge/pulse of current, two 10uF capacitors are used. These components need to be placed in the correct way round, the white band indicates the 0V leg, as shown in figure 46.

** MAKE SURE THESE COMPONENTS ARE THE CORRECT WAY ROUND **




Figure 46: electrolytic capacitors (do make sure they are the correct way round)

Larger current surges from the motors can cause the battery voltage to drop (these batteries has a relatively high ESR). If the battery voltage falls below the cut-off voltage of the regulators on the PCB or Arduino the processor and associated hardware will brown-out/crash. To help prevent these voltage drops propagating to the digital logic circuits two diodes are used i.e. when the voltage on the 10uF capacitors are larger than the battery voltage the diodes are reversed biased. Note, i am pushing these diodes shown in figure 47 a little, but given the current characteristics of the battery we are using it should be fine :). Also shown in the bottom photo are the link wire and jumper used to implement the on/off switch. Make sure that the diodes are the correct way round i.e. Anode/Cathode, as indicated by the black bar on the diode. Tip, when bending the diode leg's use a pair of long nose pliers, to produce a clean 90 degree bend. Also, when soldering in place make sure there is an air gap between the diode and the PCB to prevent hot spots.

** MAKE SURE THESE COMPONENTS ARE THE CORRECT WAY ROUND **






Figure 47: diodes, link and jumper

The first deliberate mistake :). Could not get the same 3.3V regulators used in the original design, so the silk screen image on the PCB is the ** WRONG WAY ROUND **, make sure the new 3.3V regulator is orientated as shown in figure 48.

** MAKE SURE THIS COMPONENT IS THE CORRECT WAY ROUND **




Figure 48: 3.3V regulator (do make sure its the correct way round)

The resistors R1,R2,R3 and R4 are not necessarily needed. They are intended to drop some of the battery/input voltage so that you dissipate less power in the regulators i.e. dissipate some of the power in the resistors. They are also intended to snub out some of the noise generated by the motors i.e. reduce some of the transient peaks by forming an RC low pass filter network. Finally they limit the max current the motor can take, helping to reduce battery voltage drop-outs. The values chosen to start with are 10, 4.7 and 2.7 ohms. However, these can be changed to match your system's requirements. Note, the idea of these resistors is to reduce the voltage across the regulator e.g. if the Arduino is taking 100mA the voltage dropped across a 10 ohm resistor will be 1V.






Figure 47: resistors

Final step is wiring, orange +9V, black 0V. This can was soldered on for easy connection to a bench power supply during initial testing. Note, its always a good idea to test the test before assembly, just in case you need to re-solder/replace components. The green and black wires are for each motor, these will be switched/reversed by the H-bridge so no positive/negative leads (even through one is black). A short movie of the PCB in action is available here: (Video). When your happy all is working replace the Orange/Black wires with the PP3 battery connector and de-solder the motors leads before final assembly. Note, you may need to trim down the solder joints so that the PCB can slide into the chassis mounts.






Figure 48: completed PCB

Cannon-fodder-v4 : "The Goat"




Figure 49: competition robot 1 : The Goat

Named after the king of sumo, i've 3D printed out a new base and a wall. Owing to the robot being rear wheel drive the base is tilted/angled, therefore, i printed out the wall separately, such that it could be aligned with the ground, reducing the ground clearance to wedge/flippers, as shown in figure 49. Note, not sure if i should keep the wall free floating or glue it to the base, maybe need a hinge. Also completed the controllers, added some PS3 like arms, as shown in figure 50, all seems to work so fine. You may notice 'coils'/inductors at the top, these are me just having fun, on the PCB these are where the power dump resistors would be soldered if the board was powered from a larger battery e.g. 12V, the coils don't really serve any purpose other than a wire link, but they look impressive, design to confuse your opponents, I guess they may serve some RF suppression i.e. 2.4GHz from the wifi module, but i suspect not :).


Figure 50: Controller handset.

The controller's code is based on examples from the Arduino nRF24L01 library: (Link), initial test code for the robot and controller are shown below. For the first prototype just transmitted all the raw values i.e. dumb controller, all processing done on the robot. For later version switched this around a little to reduce the size of the RX/TX packet.

// **************
// * CONTROLLER *
// **************

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

// DEBUG
// -----

#define DEBUG_SW       1
#define DEBUG_JOYSTICK 1

// INPUTS
// ------

//right joystick 
#define JOYSTICK_0_VX A0   
#define JOYSTICK_0_VY A1
#define JOYSTICK_0_SW 3

//left joystick
#define JOYSTICK_1_VX A3    
#define JOYSTICK_1_VY A2
#define JOYSTICK_1_SW 2

#define SIDE_SW_0 4 
#define SIDE_SW_1 5
#define SIDE_SW_2 6
#define SIDE_SW_3 9

// OUTPUTS
// -------

#define RED_LED    10
#define YELLOW_LED A4
#define GREEN_LED  A5

// VARIABLES
// ---------

RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00001";
uint8_t msg[5];

byte control_0_vx = 0;
byte control_0_vy = 0;
byte control_1_vx = 0;
byte control_1_vy = 0;

byte control_0_sw  = 0;
byte control_1_sw  = 0;

byte pushButton_0 = 0;
byte pushButton_1 = 0;
byte pushButton_2 = 0;
byte pushButton_3 = 0;

// INITIALISATION
// --------------

void setup()
{  
  Serial.begin(115200);  
  while (!Serial) {}; 
  Serial.println("Robot Sumo Controller"); 

  pinMode( JOYSTICK_0_SW, INPUT );
  pinMode( JOYSTICK_1_SW, INPUT );  
  
  pinMode( SIDE_SW_0, INPUT );
  pinMode( SIDE_SW_1, INPUT );
  pinMode( SIDE_SW_2, INPUT );
  pinMode( SIDE_SW_3, INPUT );
  
  pinMode (RED_LED, OUTPUT );
  pinMode (YELLOW_LED, OUTPUT );  
  pinMode (GREEN_LED, OUTPUT );
  
  digitalWrite( RED_LED, HIGH );
  digitalWrite( YELLOW_LED, HIGH ); 
  digitalWrite( GREEN_LED, HIGH );
  
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

// MAIN LOOP
// ---------

void loop()
{  
  control_0_sw = digitalRead( JOYSTICK_0_SW );
  control_1_sw = digitalRead( JOYSTICK_1_SW );

  pushButton_0 = digitalRead( SIDE_SW_0 ); //bottom left 
  pushButton_1 = digitalRead( SIDE_SW_1 ); //top left 
  pushButton_2 = digitalRead( SIDE_SW_2 ); //bottom right 
  pushButton_3 = digitalRead( SIDE_SW_3 ); //top right 
   
  #if DEBUG_SW
    Serial.print( control_0_sw );
    Serial.print( " " ); 
    Serial.print( control_1_sw );
    Serial.print( " " ); 
    Serial.print( pushButton_0 );
    Serial.print( " " ); 
    Serial.print( pushButton_1 );
    Serial.print( " " ); 
    Serial.print( pushButton_2 );
    Serial.print( " " ); 
    Serial.print( pushButton_3 );  
    Serial.println( "." );
  #endif
  
  control_0_vx = map( analogRead(JOYSTICK_0_VX), 0, 1023, 0, 255);
  control_0_vy = map( analogRead(JOYSTICK_0_VY), 0, 1023, 0, 255);
  control_1_vx = map( analogRead(JOYSTICK_1_VX), 0, 1023, 0, 255);
  control_1_vy = map( analogRead(JOYSTICK_1_VY), 0, 1023, 0, 255);
  
  #if DEBUG_JOYSTICK
    Serial.print( control_0_vx );
    Serial.print( " " );   
    Serial.print( control_0_vy );
    Serial.print( " " ); 
    Serial.print( control_1_vx );
    Serial.print( " " ); 
    Serial.print( control_1_vy );
    Serial.println( "." );
  #endif

  msg[0] = control_0_vx;
  msg[1] = control_0_vy;  
  msg[2] = control_1_vx;  
  msg[3] = control_1_vy; 
  msg[4] = (pushButton_3<<5) | (pushButton_2<<4) | (pushButton_1<<3) | (pushButton_0<<2) | (control_1_sw<<1) | (control_0_sw); 
 
  radio.write(msg,5); 
  delay(100); 
}
// *********
// * ROBOT *
// *********

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

//
// DEBUG
// -----

#define DEBUG_PACKET    1

// INPUTS
// ------

// OUTPUTS
// -------

#define MOTOR_A_PWM 9 
#define MOTOR_B_PWM 10
#define MOTOR_A_CTL 5
#define MOTOR_B_CTL 6

#define SERVO       2

#define SPARE0      3
#define SPARE1      A4
#define SPARE2      A5

// CONSTANTS
// ---------

#define OFF 0
#define ON  255

// VARIABLES
// ---------

RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00001";
uint8_t msg[5];

// INITIALISATION
// --------------

void setup() { 
  Serial.begin(115200);
  
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();
  
  // Setup control bits for motors
  pinMode(MOTOR_A_CTL, OUTPUT);
  pinMode(MOTOR_B_CTL, OUTPUT);  
  pinMode(MOTOR_A_PWM, OUTPUT);
  pinMode(MOTOR_B_PWM, OUTPUT); 
  
  // Configure motors to free-wheel  
  digitalWrite(MOTOR_A_CTL, LOW);
  digitalWrite(MOTOR_B_CTL, LOW); 
  analogWrite(MOTOR_A_PWM, OFF);
  analogWrite(MOTOR_B_PWM, OFF);
}

// MAIN LOOP
// ---------

void loop() {
  if (radio.available()) {
    radio.read(&msg, sizeof(msg));

    #if DEBUG_PACKET
      Serial.print( msg[0] );
      Serial.print( " " ); 
      Serial.print( msg[1] );
      Serial.print( " " ); 
      Serial.print( msg[2] ); 
      Serial.print( " " );   
      Serial.print( msg[3] );
      Serial.print( " " ); 
      Serial.print( msg[4] );     
      Serial.println( "." );
    #endif
    
    delay(100);
  }
}

To test the core controller and robot software i constructed the test board shown in figure 51 to view the various outputs on LEDs, easier then chasing the robot across the room.


Figure 51: test board

One of the challenges for this robot is power management. The problem is that the PP3 battery has a relative high effective series resistance (ESR) limiting the battery's output current. Therefore, large current surges will cause significant voltage drops to that well below the minimum voltage needed to run the Arduino i.e. resetting the processor. To try and produce a low impedance current source i added two parallel capacitors, just across the battery, as shown in figure 52. These are low ESR caps, producing a total of 2000uF at 35V. Also as these motors can produce a significant amount of torque a resistor can be placed in series with these motors to help reduce the maximum current surge, but this will come at the cost of reduce maximum torque.




Figure 52: power supply

This does seem to solve the resetting issue, a short video of the first robot in action is available here: (Video). Previously even with a new battery you would see processor resets when changing from stop to full power forwards, or backwards. Now this seems to be fixed. Probably will have some leakage current through the capacitors so will need to disconnect battery for storage.

In the past we have had similar staff-vs-student robot competitions and i've always taken the same approach, which is: cheat, cheat and cheat some more :), well when i say cheat, push the rules to their limit, and develop a very over engineered solution, as more hardware always makes things better. Therefore, going back to my original idea of an over voltage motor driver. However, there is a slight flaw to this plan, the motor drive board is limited to 10V and i'm looking to use 30V. Also, no space for a boost converter. So, plan B, a daughter board mounted on top of the robot. To keep this in the spirit of home built, rather than bought in, i designed a new motor driver board from stuff available in the lab, as shown in figure 53. Note, could argue this driver is flawed as if both control inputs are set to a logic '1' (under software control) you can damage the drive transistors i.e. both sides of the H bridge would be turned on at the same time.


Figure 53: new motor driver board

Knocked up a quick prototype, shown in figure 54. Had a few teething problems, as shown in figure 55, can you spot the problems with these transistors? Yes the magic smoke escaped through the holes and cracks :).


Figure 54: prototype


Figure 55: oops moments

I am running these transistors a little hot, but thats all i had in the lab. Also pushing the max voltages a little on VCE and capacitors, but, should be ok, not running them for hours etc. A short video of the motor in action is available here: (Video). When watching you can hear the relay click on, dumping about 30V across the motor for about a second. Not looking to run the motors at this voltage continuously, rather in bursts, to break the traction / stiction of the opponent to start to push them backwards. Also, this circuit does not have enough power to maintain a constant 30V across the motors, but it does give a boost. A prototype PCB layout is shown in figure 56.


Figure 56: Boost PCB

Got a prototype board etched in house, 3D printed a mounting for the inductor as shown in figures 57 and 58. Looks like i had a small moment of madness in the original design, as always corrected with the magic green bodging wire :), as seen in figure 58. A short video of the motor in action is available here: (Video). All seems to work fine, now need to add the original board on top. May need to play around with the frequency, inductor and capacitor values to see if i can make the boost voltage more stable. However, for the moment going to stick with what i have as it does seem to increase motor speed a bit. Note, a demonstrated from the previous tests, going to higher sustained voltages eventually burns the commutator out, so probably best to stick with what i have anyway. The next stage is to get a servo going, implement some sort of lifting, pushing arm.


Figure 57: Coil mount








Figure 58: Boost PCB

Cannon-fodder-v4a : "The New Goat"

It soon became clear that the robot was overweight, therefore, holes, lots of holes, as shown in figure 59. This tactic solved the problem, freeing weight for a front lifter. This rack and pinion was based on this design: (Link), the final design is shown in figure 60. To increase the lift height the servo was modified to allow continuous rotation, using the tutorial on: (Link),(Local). I used normal 1/8W resistors, rather than the surface mount versions shown in the article. I have not operated this type of servo before, so to be honest wasn't quite sure how these operate. Like a normal servo controlled using a pulse width signal, but, as feedback now comes from a fixed potential divider rather than a variable resistor, a 1ms pulse causes the motor to continuously turn clockwise, a 2ms causes the motor to turn anti-clockwise and no pulse stops the motor. There is a small element of speed control when you have a pulse around 1.5ms, but, these modified servos seem to be happier operated in an on/off mode. The servo's connector plugs into the +5V regulator and control pin 2 under the wifi transceiver. To generate the PWM signal i used the Arduino SoftPWM library: (Link), this work well, you don't get as wide a range of control as the hardware PWM, but as the servo operating in continuous rotation mode its fine i.e. only need to generate two pulse widths.


Figure 59: Thinned down parts


Figure 60: Lifter

The final assembled robot is shown in figure 61, assembled weight is 220g, so have 5g spare if needed. Had to remove R4, the 2.7 ohm resistor, used to limit the current on the old H-bridge driver with a wire link as the surge current from the boost converter and relay produced in a significant voltage drop, resulting in a brown-out on the 555 timer. Two short videos of the motor in action are available here: (Video1)(Video2). With a fully charged battery the boost voltage is a little on the large size, resulting in again blown drive transistors. Reducing the boost voltage to 28V seemed to work fine, although battery life is quite short, especially if you use the boost mode.







Figure 61: The new goat

From initial testing i thinks this one is now good to go, only a small but: over driving the motors does pop the motor drivers, had to replace some of the drive transistors three times now, cause is the lack of over-voltage control from the boost circuit i.e. its open loop, so if you have a freshly charged battery you get a bigger boost voltage, which can fry the transistors, solution de-tune the boost stage and use a smaller voltage. The only other thing i may try is to add an I2C gyro, finding it tricky to move the robot in a straight line, a little bit of control may be helpful.

Plan B: open loop calibration. A quick alternative to closing the loop is to calibrate the motor speeds i.e. identify what PWM mark-space ration is needed on each motor to obtain the same RPM on each motor. Therefore, first thing to do is apply different PWM values to each motor and measure their RPM. The active PWM values are 40 to 250, increments of 10, where 0 is off and 255 is fully on. The speed was measured using the same setup as shown in figure 16. The plot of this data is shown in figure 62 below. Note, below 40 the motor does not turn and above 250 there is no measurable speed difference. The drive wheel diameters varies a little owing to tyre used, manufacturing tolerances etc, so an average of 43mm was selected (somewhere in the middle). Robot velocity is then calculated using RPS*WD*PI.




Figure 62: motor speeds

These plots show the right motor (RM) is slightly slower, at max speed approximately 3 rpm, which is about 10 mm/s, therefore explaining its non optimal straight line performance i.e. very curvy. To correct this, the relationship between the RPM and PWM signal can be approximated as: RPM^2 is proportional to PWM, then the equation of the line can be obtained, this relationship can then be used in the Arduino to adjust the PWM signals to suit i.e. to slow down the faster motor. This also raised the question of acceleration. To avoid wheel spin at the start, which could introduce a significant orientation error and to help reduce large current surges from a standing start, which may lead to prevent burn-outs, its probably a good idea to implement a soft start in software i.e. consider a constant acceleration, slowly increasing the robot's velocity, to again help prevent wheel slippage whilst accelerating up to the desired velocity. Looking around the web there is a surprising lack of examples relating to these questions. This lead to Cannon-fodder-v5 : "Gyro".

Cannon-fodder-v5 : "Gyro"

Systematic errors (motor mountings, variations friction) and non-systematic errors (uneven surface, wheel spin) means that robots "never" go in a straight line. The previous version: "The New Goat" is quite tricky to control, therefore, wanted to look into closing the loop and use sensors to correct for these errors. I had a old MPU6050, a 6 axis sensor, XYZ accelerometers and gyroscopes. Mounted this sensor module on the remains of Cannon-fodder-v2, added a new battery clip and front scoop so its a working solution, as shown in figures 63 and 64, just in case the motor driver in "The New Goat" blows up again :). Ideally in these situation you would add wheel encoders to measure the movement of each wheel. However, these can be very expensive and tend to be sightly on the large/heavy side so not really a possibility for this robot. The joy of the 6 axis sensor is its cheap and easy to mount, fingers cross it will solve the control issues of the previous robot.


Figure 63: 3D parts


Figure 64: Gyro

The code to read and process these sensors is based on these two web-pages, both are very nice tutorials: (Link), (Link), final code shown below. Decided to keep in the roll and pitch calculation, even though they are not used in this version, did not significantly add to the processing delay, gave about a 15ms loop time. Perhaps will experiment with the next version, see if the accelerometers can be used.

// *********
// * ROBOT *
// *********

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#include <Wire.h>

// DEBUG
// -----

#define DEBUG_PACKET          0
#define DEBUG_NEW_SPEED       0
#define DEBUG_ROLL_PITCH_YAW  0

// INPUTS
// ------


// OUTPUTS
// -------

#define MOTOR_A_PWM 9 
#define MOTOR_B_PWM 10
#define MOTOR_A_CTL 5
#define MOTOR_B_CTL 6

#define LED0        A4
#define LED1        A5

#define BUZZER      2

// CONSTANTS
// ---------

#define MIN_SPEED 10
#define MAX_SPEED 180

#define STOP      0
#define FORWARDS  1
#define BACKWARDS 2
#define LEFT      3
#define RIGHT     4   

#define OFF 0
#define ON  255

// VARIABLES
// ---------

const int MPU = 0x68; // MPU6050 I2C address

float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY, gyroAngleZ;
float heading, roll, pitch, yaw;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY, GyroErrorZ;
float elapsedTime, currentTime, previousTime;

int c = 0;

RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00111";
uint8_t msg[4];

unsigned char motor_speed   = MIN_SPEED;
unsigned char motor_speed_A = MIN_SPEED;
unsigned char motor_speed_B = MIN_SPEED;
unsigned char motor_state   = STOP;

// MOTOR CONTROLLER
// ----------------

void motor_control( byte state, byte pwm_A, byte pwm_B ){   
  switch(state){
    case STOP:
      digitalWrite(MOTOR_A_CTL, LOW);
      digitalWrite(MOTOR_B_CTL, LOW);  
      analogWrite(MOTOR_A_PWM, OFF); 
      analogWrite(MOTOR_B_PWM, OFF);  
      heading = yaw;      
      break; 
    case FORWARDS:
      digitalWrite(MOTOR_A_CTL, LOW);
      digitalWrite(MOTOR_B_CTL, LOW);       
      analogWrite(MOTOR_A_PWM, pwm_A); 
      analogWrite(MOTOR_B_PWM, pwm_B); 
      break; 
    case BACKWARDS:
      digitalWrite(MOTOR_A_CTL, HIGH);
      digitalWrite(MOTOR_B_CTL, HIGH);        
      analogWrite(MOTOR_A_PWM, (255 - pwm_A)); 
      analogWrite(MOTOR_B_PWM, (255 - pwm_B)); 
      break;   
    case RIGHT:
      digitalWrite(MOTOR_A_CTL, LOW);
      digitalWrite(MOTOR_B_CTL, HIGH);     
      analogWrite(MOTOR_A_PWM, pwm_A); 
      analogWrite(MOTOR_B_PWM, (255 - pwm_B));         
      break; 
    case LEFT:
      digitalWrite(MOTOR_A_CTL, HIGH);
      digitalWrite(MOTOR_B_CTL, LOW);      
      analogWrite(MOTOR_A_PWM, (255 - pwm_A)); 
      analogWrite(MOTOR_B_PWM, pwm_B);         
      break;         
    default:        
      digitalWrite(MOTOR_A_CTL, LOW);
      digitalWrite(MOTOR_B_CTL, LOW);     
      analogWrite(MOTOR_A_PWM, OFF); 
      analogWrite(MOTOR_B_PWM, OFF);   
      break; 
  }
}

// MPU6050 STATIC ERROR
// --------------------

void calculate_IMU_error() {

  // Read accelerometer values 200 times
  while (c < 200) {
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU, 6, true);
    AccX = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
    AccY = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
    AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0 ;

    // Sum all readings
    AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * 180 / PI));
    AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * 180 / PI));
    c++;
  }

  //Divide the sum by 200 to get the error value
  AccErrorX = AccErrorX / 200;
  AccErrorY = AccErrorY / 200;
  c = 0;

  // Read gyro values 200 times
  while (c < 200) {
    Wire.beginTransmission(MPU);
    Wire.write(0x43);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU, 6, true);
    GyroX = Wire.read() << 8 | Wire.read();
    GyroY = Wire.read() << 8 | Wire.read();
    GyroZ = Wire.read() << 8 | Wire.read();

    // Sum all readings
    GyroErrorX = GyroErrorX + (GyroX / 131.0);
    GyroErrorY = GyroErrorY + (GyroY / 131.0);
    GyroErrorZ = GyroErrorZ + (GyroZ / 131.0);
    c++;
  }

  //Divide the sum by 200 to get the error value
  GyroErrorX = GyroErrorX / 200;
  GyroErrorY = GyroErrorY / 200;
  GyroErrorZ = GyroErrorZ / 200;

  // Print the error values on the Serial Monitor
  #if DEBUG_ROLL_PITCH_YAW
    Serial.print("AccErrorX: ");
    Serial.println(AccErrorX);
    Serial.print("AccErrorY: ");
    Serial.println(AccErrorY);
    Serial.print("GyroErrorX: ");
    Serial.println(GyroErrorX);
    Serial.print("GyroErrorY: ");
    Serial.println(GyroErrorY);
    Serial.print("GyroErrorZ: ");
    Serial.println(GyroErrorZ);
  #endif
}

// HEADING PID CONTROL
// -------------------

void PID(float Hdg, float HdgTgt, unsigned char motor_speed, unsigned char *motor_speed_A, unsigned char *motor_speed_B, 
         float kP, float kI, float kD, 
         unsigned char motor_state)                                                 
{
  static unsigned long lastTime; 
  static float output;  
  static float errSum, lastErr,error ; 
  static int new_speed;
  
  //IF not moving then 
  if( (motor_state != FORWARDS) && (motor_state != BACKWARDS) )
  {
    errSum = 0;
    lastErr = 0;
    return;
  }
    
  //How long since we last calculated
  unsigned long now = millis();    
  float timeChange = (float)(now - lastTime); 

  error = Hdg-HdgTgt;   

  errSum += (error * timeChange * 0.001);   
  LimitFloat(&errSum, -100, 100);
  float dErr = (error - lastErr) / (timeChange * 0.001);       

  /*Compute PID Output*/
  output = kP * error + kI * errSum + kD * dErr;  
     
  /*Remember some variables for next time*/
  lastErr = error;    
  lastTime = now; 

  LimitFloat(&output, -200, 200);
  if(output < 0){  
    new_speed = motor_speed - abs(output);
    
    //output NEG, B slower
    if(new_speed < 0){
      *motor_speed_B = 10; }
    else{
      *motor_speed_B = byte(new_speed); }
    
    //output NEG, A faster
    new_speed = motor_speed + abs(output);
  
    if(new_speed > 255){
      *motor_speed_A = 255; }
    else{
      *motor_speed_A = byte(new_speed); }    
  }
  else{
    new_speed = motor_speed - abs(output);
    
    //output POS, A slower
    if(new_speed < 0){
      *motor_speed_A = 10; }
    else{
      *motor_speed_A = byte(new_speed); }
    
    //output POS, B faster
    new_speed = motor_speed + abs(output);
  
    if(new_speed > 255){
      *motor_speed_B = 255; }
    else{
      *motor_speed_B = byte(new_speed); }  
  }
  
  if( motor_state == BACKWARDS ) {
    new_speed = *motor_speed_B;
    *motor_speed_B = *motor_speed_A;
    *motor_speed_A = new_speed;
  } 
  
  #if DEBUG_NEW_SPEED
    Serial.print(error);  
    Serial.print(":");
    Serial.print(new_speed);  
    Serial.print(":");
    Serial.print(*motor_speed_A);
    Serial.print(":");
    Serial.println(*motor_speed_B);
  #endif
}

void LimitFloat(float *x,float Min, float Max)
{
  if(*x > Max)
    *x = Max;
  if(*x < Min)
    *x = Min;
}

// INITIALISATION
// --------------

void setup() {
  Serial.begin(19200);
  
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();
  
  // Setup control bits for motros
  pinMode(MOTOR_A_CTL, OUTPUT);
  pinMode(MOTOR_B_CTL, OUTPUT);  
  pinMode(MOTOR_A_PWM, OUTPUT);
  pinMode(MOTOR_B_PWM, OUTPUT);
  
  // Configure motors to free-wheel  
  digitalWrite(MOTOR_A_CTL, LOW);
  digitalWrite(MOTOR_B_CTL, LOW);  
  analogWrite(MOTOR_A_PWM, OFF);
  analogWrite(MOTOR_B_PWM, OFF);
  
  // Reset MPU
  Wire.begin();                      
  Wire.beginTransmission(MPU);       
  Wire.write(0x6B);                  
  Wire.write(0x00);                  
  Wire.endTransmission(true);        
  
  // Calc MPU Error
  Serial.println("Calculating IMU offsets");
  calculate_IMU_error();
  delay(5000);  
}

// MAIN LOOP
// ---------

void loop() {

  // === Read acceleromter data === //
  Wire.beginTransmission(MPU);
  Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 6, true); // Read 6 registers total, each axis value is stored in 2 registers
  
  //For a range of +-2g, we need to divide the raw values by 16384, according to the datasheet
  AccX = (Wire.read() << 8 | Wire.read()) / 16384.0; // X-axis value
  AccY = (Wire.read() << 8 | Wire.read()) / 16384.0; // Y-axis value
  AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0; // Z-axis value
  
  // Calculating Roll and Pitch from the accelerometer data
  accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI) - 0.58; // AccErrorX ~(0.58) See the calculate_IMU_error()custom function for more details
  accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI) + 1.58; // AccErrorY ~(-1.58)
  
  // === Read gyroscope data === //
  previousTime = currentTime;        
  currentTime = millis();           
  elapsedTime = (currentTime - previousTime) / 1000; 
  
  Wire.beginTransmission(MPU);
  Wire.write(0x43); 
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 6, true); 
  
  // For a 250deg/s range we have to divide first the raw value by 131.0, according to the datasheet
  GyroX = (Wire.read() << 8 | Wire.read()) / 131.0;
  GyroY = (Wire.read() << 8 | Wire.read()) / 131.0;
  GyroZ = (Wire.read() << 8 | Wire.read()) / 131.0;
  
  // Correct the outputs with the calculated error values  
  GyroX = GyroX + (GyroErrorZ * -1.0); 
  GyroY = GyroY + (GyroErrorY * -1.0); 
  GyroZ = GyroZ + (GyroErrorZ * -1.0); 
  
  // Currently the raw values are in degrees per seconds, deg/s, so we need to multiply by sendonds (s) to get the angle in degrees
  gyroAngleX = gyroAngleX + GyroX * elapsedTime; // deg/s * s = deg
  gyroAngleY = gyroAngleY + GyroY * elapsedTime;
  
  // Complementary filter - combine acceleromter and gyro angle values
  roll = 0.96 * gyroAngleX + 0.04 * accAngleX;
  pitch = 0.96 * gyroAngleY + 0.04 * accAngleY;
  yaw =  yaw + GyroZ * elapsedTime;
  
  // Print the values on the serial monitor
  #if DEBUG_ROLL_PITCH_YAW
    Serial.print(roll);
    Serial.print("/");
    Serial.print(pitch);
    Serial.print("/");
    Serial.println(yaw);
  #endif

  if (radio.available()) {
    radio.read(&msg, sizeof(msg));
   
    motor_state = msg[0];
    motor_speed = msg[1];       
  
    if (motor_speed > MAX_SPEED) motor_speed = MAX_SPEED;
    if (motor_speed < MIN_SPEED) motor_speed = MIN_SPEED; 
    
    #if DEBUG_PACKET
      Serial.print( motor_state );
      Serial.print( " " ); 
      Serial.print( motor_speed );   
      Serial.println( "." );
    #endif  
  }  
  
  if ( (motor_state == FORWARDS) || (motor_state == BACKWARDS) ){ 
    PID(yaw, heading, motor_speed, &motor_speed_A, &motor_speed_B, 10, 0, 0, motor_state);    
    motor_control( motor_state, motor_speed_A, motor_speed_B );    
  } 
  
  if ( (motor_state == LEFT) || (motor_state == RIGHT) || (motor_state == STOP) ){    
    motor_control( motor_state, motor_speed, motor_speed );    
  }  
  delay(1);    
}

The above code just uses proportional control, adjusted the KP gain to 10, seems fine, a balance between response time and stability. Also played around with the integral term KI, from observation a gain of 1 seemed to "something", but overall i would say it introduced a little more oscillation, i'm guessing there its got some integral windup issues. Have not used the differential term yet. Need to play around with these gains, go through some of standard tuning techniques, however, the problem is need a test rig so i can repeat/measure these tests, otherwise from experience you just keep going round in circles. Therefore for the moment KP=10, KI=0, KD=0, works, but could be better. The main loop processes all six sensors, but only the Z gyro (yaw) is used. This defines the robots heading (bearing). The robot's heading being updated in the "motor_control" function when the robot is at rest. This heading and the current Z-gyro reading are then feed into a PID controller which increases or decreases each drive motor's speed, maintaining a constant bearing: motor speed requests from the user are limited to 180 so that gives an adjustment range of -140 to +75. This works quite well, do see quite a bit of drift on the gyro readings, however, as the robot is only moving in a straight line for a few seconds this is not a significant error i.e. not looking for a constant repeatable bearing, the robot only needs maintain a bearing whilst moving FORWARDS or BACKWARDS. The LEFT and RIGHT movements are open loop as its not critical if the robot does not spin exactly on the spot. Two short videos of the motor in action are available here: (Video1)(Video2). The first shows the robot moving backwards and forwards maintaining a straight line movement. The second shows the pushing power of about 0.6KG. Interestingly, seemed to have more pushing power in reverse, it maybe be due to the front wedge getting compressed under the weight.

As this robot does do have a servo the spare GPIO line is used to drive a piezo-electric buzzer, super-glued to the acrylic base, under one of the drive motors, as shown in figure 64. Its actual quite loud, initial testing of the buzzer worked fine, but when integrated in strange things happen with the motor control. I think some of the new code/library elements i used conflict with some of the existing one, so skipped this one for now.

With all the robots so far i have not reached the stall torque of the motors owing to wheel slippage, therefore, the robot is not utilising its full pushing capabilities. Next, going back to the drive wheels, perhaps tank tracks?

Cannon-fodder-v6 : "KISS"

It took a little while, but finally common sense kicked in. An overly engineered solution may be fun to design and build i.e. Cannon-fodder-v4a : "The New Goat", but at the end of the day if you can't control the robot or get the power down i.e. you have so much power that its too fast and wheel spins most of the time, its not going to be a winning design. Rather its going to be something to perfect over time. Therefore, gone back to basics: keep it simple stupid (KISS), if the focus of the competition is push the other robot out of the ring then the robot needs to focus on traction. This means wheel grip and weight distribution, ideally all of the robots weight should be directly over the drive axis, maximising friction where you need it. Therefore, again i present the new and improved robot in figure 65.






Figure 65: KISS

Striped the robot back to basics, a very minimal chasiss, so that i could add a 40g metal bar directly below the drive axle, then added very big and grippy tyres. Ok, we have a robot, also added a slightly "better" sensor unit a MPU 9265, this is a 9 axis device, has an addition XYZ magnetometer, not sure these will be that useful give the magnets in the drive motors, but i thought worth a go. Time to go back the questions raised in figure 62, how should we control the velocity and acceleration of these drive motors in order to maximise our pushing power?

Work in progress

Creative Commons Licence

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

Contact details: email - mike.freeman@york.ac.uk, telephone - 01904 32(5473)

Back