Intro: Ultrasonic Obstacle-avoiding Robot
This is my attempt at designing and building an obstacle avoiding robot! RekaBot (named after a fairy (: ) can detect obstacles with an ultrasonic sensor that can move around with help from a servo. Based on the measurements she takes, the tracks are driven towards the direction with the biggest distance - avoiding obstacles.
Read in and try to build one yourself!
Step 1: Concept, Drawings and Preliminary BOM
In this project I will tell the story of how RekaBot was built. The target is to build a robot which is capable of avoiding walls and obstacles in an infinite loop.
What do we need in order to build this bot:
- Zumo Robot chassis - ebay
- Pololu micro gearmotors 1:100 - ebay
- HCSR04 ultrasonic sensor - ebay
- SG90 servo motor - ebay
- DRV8801 carrier boards - pololu
- MC34063 DC-DC converter IC - ebay
- PIC18F4550 microcontroller - ebay
- USB socket - ebay
- DIP40, DIP8 IC holders - ebay, ebay
- Lithium-Ion batteries - ebay
- inductors, capacitors, resistors - local electronics shop
- diodes, transistors, LEDs - local electronics shop
- PCBs, ferric chloride, laser printer, acetone
- drills, bits, saw, screwdriver, hammer, pliers, tweezers, sand paper
- soldering iron, solder wik, solder, flux
- PicKit2, PicKit3, for programming
- multimeters for voltage and current measurements
- a laptop with useful software on it, printer
- serial to USB converter - ebay
What I had extra:
- heat gun to salvage components from old boards
- third hand for soldering components
- power supply for tests
- digital storage oscilloscope
- lots of components in stock for quick tests
Long story short
I started with ordering components off E-Bay and local sites, starting with the robot chassis. Nothing special here, just the fact that they shipped without the motors, this hurt a bit - I wasn't expecting that. I found the warning on their website eventually: "no motors included!". Of course, it had a link to a quite expensive motor, so I ended up purchasing them separately. After I got those, I assembled the chassis, and drew some initial schematics. With the schematics done, I designed the PCBs with AD14, made the artwork prints on shiny paper, etched and drilled some PCB's, and soldered the components. Looking back, I can say that all the stuff mentioned in the two lists were really helpful, the oscilloscope in particular. It was extremely useful for debugging the PSU board, all four converters were in trouble at first power-up, but thanks to my RIGOL DZ1104, the errors were easy to locate, and I eventually succeeded powering everything up.
After I got rid of the obvious hardware problems, I used a PicKit3 to write a bootloader into the PIC. This is an important step to fast program update, not to mention, that it only needs a free USB port from the laptop, there's no need to take your programmer everywhere. Once that was done, I ended up calculating timers, setting up time bases, and writing the C routines for the servo motor, ultrasonic range sensor, LEDs, and other stuff that needed to be handled.
When I got to a point where I was able to turn the servo at any given angle, and read an approximate distance to the closest obstacle with the HC-SR04, I developed a super-simple algorithm which leads the robot away from the obstacles. Once the testing was done, I gathered all the photos I took, all the memo-s I wrote to myself, and compiled it into this instructable for you to read and re-make. I hope you'll find it interesting, and will try to make one yourself!
Step 2: DC-DC Step-Up and Step-Down Converters
This step will be all about the four converters used in this project, so for start I will link in the datasheet for everyone to see and analyze. In terms of efficiency, switching power supplies have more to offer than linear power supplies. They can boost, step-down, or invert an input voltage, and maintain regulation even when the input changes. Some designs can even isolate the output from the input. In this project I will stick to the two most common not isolated setups, the buck and boost topology. The manufacturer provides sample schematics that you can follow, if you intend to build a converter.
Benefits of using SMPS
An LDO uses a resistive voltage drop - and is basically wasting power (P = I * U) in the form of heat - to maintain its output voltage. In contrast the inductor of a SMPS-s will store energy in the ON state, this energy will be guided to the output in the OFF state, and won't get lost. Less energy loss means better efficiency, and this is where the SMPS-s have the obvious advantage over LDO-s. Because less heat is produced, we don't need to add heatsinks to our design, which often are quite expensive. Another great advantage of the switch mode power supplies is that they can provide us with voltages that are greater than the input voltage.
Drawbacks of using SMPS
In some cases however, it's not worth to use a buck or boost converter, many times the current capabilities of an LDO will suffice, and we get away easy and cheap with an LDO IC and a few capacitors. Because of high current switching, switched mode power supplies can be too noisy for some applications. This is another place where the LDO wins hands down, no noises are injected into nearby traces or planes.
A buck or step-down converter is a DC-DC power converter which steps down the voltage it gets on its input. It is a good alternative for an LDO regulator in cases when you need bigger currents but have no room or budget for heatsink. The base schematic contains a power switch, a rectifier diode, an inductor and two capacitors, one on the input and one on the output. Newer designs replace the diode with another power switch, making the circuit more efficient - these are called synchronous converters.
A boost or step-up converter is another frequently used DC-DC power converter, which steps up the voltage it gets on its input. Similarly to a buck, the base schematic contains a power switch, a rectifier diode, an inductor and two capacitors, on the input and output. I used two of these boosters to step up the 4.8 [V] input to approximately 10 [V] for the DRV8801 boards - one booster for each H-Bridge driver.
Step 3: Drawing the Schematics
Boost converter left and right for VMM rails
These are no different one from the other, I just copied the suggested boost schematic from the MC34063 datasheet, and put the schematic together. I also assembled this small schematic in LTSpice, I wanted to see if the rails and the schematic checks out OK, they did.
Buck converters for 5V and 3.3V rails
Not much to say here either, the buck story is told in a separate step. The only difference between the two schematics is the change in the upper feedback resistor, which is 3.6k for the 5 [V] output, and 2k for the 3.3 [V] output. There's an LTSpice schematic for these too, for the same reason as there was for the boosters.
Upper board interface connectors
The servo motor and the ultrasonic range detector connect to these pins. Warning! For my own convenience, I swapped the order of the pins on the servo header! This means that the routing and ultimately the connection will be affected too. No changes were made on the HC-SR04 connection, that will be a simple straight female - female wire.
This is the main control unit of the project, all nets - be it feedback, communication signal, pulses, PWM - end up here. All power pins of the PIC18F4550 must be connected, and decoupled with 100 [nF] capacitors. The VUSB pin must be decoupled with 470 [nF], a value recommended in datasheet.
The main clock source of the microcontroller and the two capacitors to ground, as recommended in the datasheet.
The motor drivers are actually factory made boards, I drew symbol and footprint for the board, instead of putting the H-bridge IC separately on my lower board. They are made by Pololu, and can be supplied with a motor voltage between 8 [V] and 36 [V] (hence the need for boosters). These little boards can deliver 1 [A] continuous current and 2.8 [A] peak current. Every I/O pin this board has is in some way connected to the PIC for control, and eventual future development. Such a development would be to read back the current that flows through the motors, so one can shut them off in case of stall.
This is the connector through which the firmware updates happen. The USB power pin is connected through a resistor to an MCU I/O. If no resistor is added in series to limit the current, the PIC will be powered through its I/O pin, and unexpected things can occur - this resistor is needed.
They give status about the bootloader and the state of the firmware update when programming, and can be used for debug when in application mode. I sent out a 1 [Hz] signal to the red LED, so I know when the PIC is alive. This is a heartbeat signal that makes you notice a badly or not running MCU by simply looking at it.
Guide the signals from the PSU board to the control board. Motor control signals and power rails are routed to these pins.
Step 4: Preparation and Routing of the PCB
Unlike with many board I made before, this project was a bit constrained from the PCB size point of view. I had to make the PCB so it fits and can be mounted to the Zumo chassis. Since I wasn't able to locate any mechanical drawing for the holes on the chassis top, I took a caliper, and measured everything by hand. After having these measurements done, I drew the board outline, added holes for screws, and carried on putting the components.
When routing boards, you should try and keep the tracks as short as possible, as wide as possible, and away from noisy stuff. Sometimes you can respect these constraints, sometimes you don't. You'll have to weigh which constraints are the most important, and which are those that can be disregarded to a certain level. I wanted two boards to make the width and length as short as possible - I had to stack them. This meant, that some control lines from the MCU went exactly above the inductors, not good at all. I decided to give it a try even so, and lucky enough, there was no interference. After all, the current consumption isn't that high.
Special care must be taken when routing the power converters, layout guidelines can be found on the web, but the best way to go is to read through the datasheet and find the optimal component placement based on the given IC's manufacturer recommendations. I found a good description at Linear Technology on the web. They have explanations, guidelines, images, tips and tricks that can help. Long story short, you will have to minimize the length of the nets of the pulsating current. This can be achieved by placing the components as close as possible to each other, with their respective pins facing each other. Polygon pours are also recommended, but I couldn't go that way, I wanted this to be a single layer board. A single layer board means that all PCB copper traces go on one layer, as you see on one of the images, this is the bottom layer, with the artwork with red.
Exporting layout artwork to PDF
To etch a PCB, we need something we can iron to the copper clad board. To get the prints, follow the instructions from one of my previous instructables. Mirror is OFF on bottom layer, ON on top layer!! Don't forget this, or you'll get a mirrored board, and you will only realize this after you drilled it, cleaned it, and soldered some of the components. You know what Murphy said: if something CAN happen, it WILL happen. Double check everything and keep in mind that the paper will be flipped when you iron it!
Step 5: Etching the PCBs
Toner transfer for the win
As most of the times, I used the toner transfer method to make these PCBs, again because of speed considerations. I used UV technique to make a PCB for a current project, I will give details about the UV method as well later when I churn that into an instructable.
Get an iron, get the artwork, and the nice, clean PCB, stack them and start ironing. For me it usually takes around a minute to get the paper to stick properly to the board. Get the toner to stick to the PCB copper side, then cool it down. Go to the sink, put the paper+board under water, wait until the paper gets soaked, then gently remove it.
A newly found useful trick
Recently I found that this is very very easy to do if the water you use is a bit warm. When making these PCBs I didn't pay any attention to this detail, just poured some water of random temperature and waited for the paper to get soaked, then rubbed it off. You can see on the pictures how the rubbing removed some of the toner that should have been left intact, I had to go over the board with an OHP marker and make some corrections - extra time and work. By using warm water, the paper comes down very easily, in one piece, no need for rubbing (which can potentially damage the toner layer). Dry the board, and put it into Ferric Chloride. Wait until the copper is eaten away, leaving the board with the tracks and pads only. Use acetone to wipe off the toner, then drill the holes, cut off the excess FR4 and polish the board shiny with fine sand paper under water. If you have flux spray at hand, spray the board, this will help when you start soldering.
Step 6: Populating and Testing the Power Board
Populating the power board
If you have your lower board ready, take the components, and begin soldering, For this to go even easier, you could always print the silkscreen artwork as well, and iron it to the board one you're done with the etching. I know, it's black, but it does help, and this isn't done for beauty after all, but for easier assembly. I skipped this part and just looked at the laptop monitor to figure out which comes where.
As a general practice, it's always better to start with the components with the lowest profile. I started with the jumper wires, resistors, diodes, LEDs, capacitors and IC holders, and ended the soldering by putting the connectors in place. The tall bulk capacitor was added later, this was the last component to be soldered - by chance, it is also the tallest component of this board. I used my third hand stand to keep the board fixed while soldering.
Testing and troubleshooting the power board
Just to be on the safe side, I decided to power this up from my lab PSU, with a current limit set to 200mA. I turned it on, and it went instantly into current limit. At this point I got my buzzer and started testing against shorts on the input, there weren't any.
Simplest thing to do (not if you soldered the IC's directly without DIP sockets...) is to snap the MC34063s out and test again. Did it, and everything was fine, no shorts this time. I concluded, that one or more of these are creating the short condition, so I started placing them back one by one.
1. put one booster back
After putting an MC34063 into a boost socket, I turned on the PSU and didn't see any shorts. What do we do now? We check the output voltage, which I wanted to be around 10V. I measured a completely insane 29V, so I turned everything off to think what could be wrong. First thought: feedback resistors! Are they of the correct value? After a brief check, I realized that I somehow mixed the feedback resistors with other components I had lying around on my desk. Consulted the datasheet again (page 11), and recalculated the feedback resistors - they weren't even close to what I've had installed. By changing those to the right values, the booster came around and gave me a nice 9.90V output - close enough!
2. put the second booster back
This one was tricky, because I was able to measure the output voltage with the scope, but not with the multimeter. After a couple of minutes, I realized, that the GND clip of the scope probe was indeed connected to the circuits GND net, but the pad I pressed against with the black probe of my multimeter wasn't. A copper island of GND was completely disconnected because of a missing jumper wire! Soldered that in and the problem went away. Booster 2 output was repaired.
3. put one of the buck converters back
I removed the boosters to make sure I only see eventual errors because of the buck converters, and powered the circuit up. Short was detected, and my PSU went into current limit. I double checked against at least a dozen buck schematics from the web, looked at the feedback resistors, looked for shorts between traces, measured the feedback with scope, but found nothing. After an hour of stress it finally occurred to me: if you reverse the diode by accident, you will get a short circuit at every ON cycle of the MC34063's internal transistor. I checked, and yes, that was it, the diodes were soldered in backwards..the black silkscreen I was too lazy to add would have helped here! By changing the diode polarity to normal, the buck powered up and delivered me a steady 5V.
4. put both buck converters back
After its diode got back to normal polarity, this 3.3V buck started working as well.
With this debug session successfully completed, I put back every IC and powered the board up. No shorts, every rail present, the lower board was working properly, I could move on to the upper hull.
Batteries and charging
Power supply is also something that belongs to this step, and since I tested with Li-Ion batteries as well, I will write a little about how I charged mine. I attached a charging profile picture to this step, taken from the web. I began charging the cells when they were at about 3.4 [V], with 1/10 of their capacity, 180 [mA] @ 8.2 [V]. After a minute or two, I turned the pot up to supply 500 [mA], and kept it that way until the PSU came out of short-circuit. With the current slowly decreasing I waited until it went down to 180 [mA], and stopped charging.
The Ni-MH were new batteries, that came with a charger, I didn't had to bother with charging them from my lab PSU.
Step 7: Populating and Testing the Control Board
Populating the control board
The control board consists of the board carrying the PIC18F4550, CD4050, USB plug, crystal and some passives. It also keeps the servo and ON/OFF switch firmly secured.
Assembly goes in the exact same manner as it did with the power board, just load the PCB with components in height order. The shortest come first, the tallest come last. Once you have everything soldered, take the PIC and burn in the USB bootloader so you never have to think again of the PicKit, and the extra pin header it requires. Once you have that, just try to program it with a test firmware. A good test firmware is a simple LED blinker which blinks an LED at the rate of the instruction clock: 12MHz. If you see it blink, you know the firmware update works, and you can also measure it with a scope to be sure that you clock your PIC at the speed you think you do. This measurement can also be done with a logic analyzer. The USB bootloader, and the benefits of using it are explained in the next step.
Step 8: PIC Programming, Blinking an LED, Bootloader
Why do we need a bootloader in the first place?
Having a PicKit3 around is necessary, however having it lying around our workspace with no good reason is not. Initially I didn't wanted to complicate my life with bootloaders, but later I came to realize the truth: this project is better without PicKit. Having one single cable for programming is nice, especially if it's an extremely common USB mini cable, which every hobbyist has a bunch of. The five pin ICSP header was left out of the design intentionally, I used my ZIF connector board to flash the bootloader into the PIC.
USB bootloader, simple and good
Looking at the datasheet of the processor I decided to use, one can see that there is UART and USB available. If I pick UART, I still need a USB-UART converter and a bunch of wires - defeats the purpose. So the best, simplest, nicest choice is USB. Microchip offers all kind of code samples for the PICs, bootloaders also. If you download and install "Microchip Libraries for Applications", there will be a whole folder about all sort of USB applications, bootloaders, tools and other useful stuff. In the sub-folder "Bootloaders" I picked "USB HID Bootloader" and opened it in MPLAB X. After reading through it a couple of times to understand its logic, I implemented modifications to make it perfect for my RekaBot.
A few words about bootloaders
A bootloader is a special section of code that is written into microcontrollers once, and provides a way to upload firmware without the need of a special (high-voltage) programmer, like the PicKit3 in our case. Unfortunately, there's no way to completely avoid the use of the PicKit, the bootloader has to get into the PIC somehow. The good part: you can borrow a PicKit, ICD2 or any PIC flasher from a friend, and give it back once the bootloader is written into the microcontroller. After that - depending on what type of bootloader got flashed, you can upload firmware to program memory through UART, USB, CAN or whatever. Using bootloaders is simple, but it comes with the disadvantage of losing precious program memory. Some bootloaders eat up less space, some eat up more - this one eats 1K of memory. At startup every PIC powers up at address 0 and starts executing whatever code the programmer has written there. If it's the bootloader = the PIC runs the bootloader. If it's the user code = the PIC runs the user code. Fairly simple!
In order to use them both, we have to switch somehow between these two programs. This switchover can be done based on a condition, event or whatever - it will be very much like a huge "if" statement. I chose this condition to be the voltage level on an input pin, named USB SENSE in the schematics. This pin was connected to the 5V pin of the USB connector through a resistor. If the pin is at 5 [V] at startup, the PIC has to execute the bootloader branch of the huge "if", if it is at 0 [V], the user application code had to be run. I placed the user code right after the bootloader code, at address 1000h. The PIC18F4550 checks the sense pin pin repeatedly, so when the USB cable is removed and the 5 [V] disappears from RD3, the PIC will jump to the code placed at 1000h, the user application code. In the user application code I check again the voltage level on the sense pin, if the program sees 5 [V] on it, the USB cable has been plugged in again and the PIC has to jump to the bootloader. I make this jump to bootloader mode with an inline assembly "RESET" instruction. Since the PIC starts executing from address 0, the simplest and most common thing to do is to place the bootloader code there, and the user code right after the bootloader.
The bootloader code can be modified to check whether or not there's a valid firmware written in the PIC. This is done by checking the memory content on address 1006h-1007h. After each successful programming, the bootloader inserts "600D" (leet for "good") at this location, meaning that the code has been written into the PIC successfully - this is what it gets checked for validity. We must specify in the user application project settings that this location should not be overwritten, see the images to see how this is done (ROM ranges are specified to skip this location when the user application code is compiled). Failing to do so will result in errors really hard to detect later. The section where the bootloader lies must be protected as well: this is done by offsetting the whole user application code by 1000h (Code offset parameter at linker options). Bootloaders are usually located at the beginning or at the end of the program memory - care must be taken not to overwrite it! I wanted to add some parametrization for this device, that's why the 3000-3100 range is protected as well. You don't need that, just the other one!
Problems I hit when working with the bootloader
Compiling the project will result in a hex file being generated - this has to be flashed into the PIC with a PicKit or whatever. In an older project I ran into a strange problem, the USB bootloader tool couldn't detect anything. I decided to make measurements on the VUSB pin of the PIC with my scope, and the moment I touched the pin with the scope probe, the well known USB sound played and the board got recognized! I thought the problem went away, but as soon as I removed the probe, the thing stopped working again. I realized that everything works when the scope probe is on, even writes, resets - the problem had to be something that the probe fixes. Then I thought: what do scope probes have? Parasitic capacitance. The capacitor on VUSB pin was huge in value, and couldn't filter out high frequency noises. A 100nF capacitor was added in parallel, and the board was recognized immediately.
Knowing this upfront, I added a small value capacitor on the top of the original one, the USB came on without a problem at the first try! Lesson learned!
If your bootloader works, flash your LED blinker program, debug it if needed, and proceed to write some more serious stuff, described in the following steps.
Step 9: Firmware Development, Interesting Aspects, Inspiration
As a general thought, I suggest everyone to keep the code as modular as possible. This way - if you need some part of it for a future project - you can simply drop c and h files into your other projects. After 20+ projects you will have dozens of these files, and project development will be super quick thanks to this driver collection. Put some effort in modularity, it's worth it.
When you got over your LED blinking successfully, it's time to build up a time base, so you can easily schedule what you do and when you do it. I usually use Timer0 for generating the time base. I set up this timer module to give me an interrupt every milliseconds, and I count the interrupts in my main loop. I set up some code branches that execute at each 1 [ms], 10 [ms], 100 [ms] or a 1 [s]. These were implemented using simple if-else statements.
I took inspiration from projects from work, internet, and gave special attention to forum posts and books that deal with good project structure. After looking at the last line that has text of each source or header file, I count ~1840 lines of code. If I knew that, I might have left this project for an easier time..
Step 10: Control and Problems of the H-Bridge
For the speed and direction control of the robot, I used Pololu made DRV8801 boards. I have some L298 drivers are well, but they are huge compared to these nice little SMT boards.This DRV8801 can deliver a continuous 1 A on its output, and works with input voltages between 8 [V] to 36 [V]. I had absolutely no problems with these boards, just the fact that I needed to build a booster circuit for each one. The DRV8801 board is controlled through two pins, speed and direction. A current-sense feedback pin is also there (connected to my PIC, but not used for now).
The working principles of the DRV8801 component are described in its Texas Instruments datasheet, I cropped out an image which shows the path of the current, based on the direction selected.
Direction and speed control with a single PIC pin
A sweet trick can be applied with all of these direction + speed interfaced bridges. One can control the speed AND direction with one single pin, if connected smart enough. I accidentally discovered this while working with one of TI's products, but later on found it described in one of their tips and tricks documents. If you connect steady ON to the speed input pin, and route your PWM on the phase / direction pin, you will control two things with one PWM output! By outputting 50% PWM you will drive the motor forward and back for the same amount of time, so you won't see the shaft turning anywhere. However, by decreasing the duty cycle, you will tell the motor to move more forward, than back. Similarly, by increasing the duty cycle, you will move the motor more in the back than you move it forward. This will result in full speed forward at 0% duty, full speed backward at 100%, and stop at 50%.
Step 11: Control and Problems of the Servo Motor
Control theory and my implementation
How do we know how to control something? We look up its datasheet. That's exactly what I did (a long time ago) with this servo, and found out that I needed a 50Hz signal of variable duty cycle. That's a bit slow for a hardware PWM module which we're on short with anyway (we need them for the track-driving motors), this servo signal must be bit banged - software generated.
Knowing this, I calculated a timer to generate an interrupt every 20ms. For this task i used the free Timer3 module of the microcontroller. Knowing that:
Fosc = 48 [MHz], we find that Fosc / 4 = 12 [MHz], and from here we calculate Tinstruction = 83.33 [ns]
This is the time needed for one instruction to complete in the PIC, and the time-base for all internal timer modules. We also know, that Timer3 is a 16 bit timer/counter, so it can count to 65535, and overflow + give an interrupt at the next increment. We have to configure it so it gives this interrupt every 20 [ms]. For this, I calculated the number of timer increments that are needed for 20 [ms] to elapse:
20 [ms] / 83.33 [ns] = 240096
This is a value that won't fit into the 16 bit counter chain, so we will have to slow the increment speed down somehow: this is easy to do by using prescalers. By assigning a 1:4 prescaler to this timer, we tell the PIC to increment the timer register value at a rate four times slower than this internal clock of 83.33 [ns].
240096 / 4 = 60024
This translates to: with the count speed decreased to 332.33 [ns], the timer will have to take 60024 steps until our 20 [ms] elapses. We have to trigger an interrupt, when this happens, and when do interrupts occur? When the timer overflows. That being said, we subtract this value from the overflow value of 65536:
65536 - 60024 = 5512 = 0x1588
If we put this into the TMR3 counter chain every time the interrupt flag gets set by the overflow event, we will get a nice 20 [ms] interrupt rate which can be used for this servo algorithm.
Next, we have to deal with changing the duty cycle. This would have been very easy to do if I had a compare module available, but unfortunately I didn't. To overcome this, I defined a variable which I toggled every time an interrupt came. Knowing the value of this variable allowed me to distinguish between two different interrrupts: the one that triggered a rising edge at the beginning of the 20 [ms] period, and that triggered the falling edge based on the required duty cycle. Because the rising edge is mandatory, each time the interrupt is triggered, an our variable is '0', we send the IO pin to high state. Based on the required on time (let's take 1 [ms] for now), we quickly calculate a new timer counter init value, so the timer overflows again after this time period. For 1 [ms] this calculation is as follows:
1 [ms] / 333.3 [ns] = 3000
So we set TMR3's counter register to:
TMR3 = 65536 - 3000 = 62536
and wait for the next interrupt. When it occurs, we send the IO pin to low state, and configure the TMR3 counter chain again with the number for 20 [ms]. We have to take into consideration, that 1 [ms] of the 20 [ms] already passed, so the math will look something like this for the second TMR3 update:
TMR3 = 5512 + 3000
After we're done with this, we toggle our variable again and wait for an interrupt. This will trigger a rising edge and the 20 [ms] period begins. Then again we calculate the new TMR3, and this goes on and on and on, in an endless loop. By looking in the datasheet I told you about earlier, we can find the maximum length of this duty cycle, software limits can be written to prevent the servo from hitting the ends and struggling to go further (and consume 650mA...).
The servo's feedback isn't that great at all, so the servo will shake even when it keeps getting the same duty forever - this must be solved somehow. I put a software counter that increments when the 20 [ms] period begins, and added some code that only allows a signal with a certain duty to be sent a limited amount of times. After 20 cycles, I assume that the shaft is at the desired position, and I stop sending signals = the motor will not shake or buzz.
Several problems occurred until I finally got to move the servo in a repeatable manner. I used servomotors before with no problems, knew what kind of control signals must be provided, but was only able to turn the servo every now and then, randomly. At one power-up it worked, at the next five it didn't. I suspected input voltage droop, 5 [V] buck output voltage droop, and made all kind of tests to eliminate them one by one. Finally, after trying various battery types, I ended up putting a bulky 3300 [uF] capacitor very close to the servo pin header - and voilá, it finally became more obedient.
At this point it's good to switch to a lab PSU, if available
What can you do to troubleshoot in situations like this? If you suspect, that there is some kind of power surge in your circuit, there are some tricks you can use to track down the error. First, it helps a lot if you have a laboratory power supply with current limit feature, this will prevent blowing things up. Simply using the batteries (I tried with Li-Ion as well) is a bit dangerous since those will give as much as they can, in case of a short, and will probably burn everything in their way.
This one was the obvious test series: continuity tests, tests against shorts, standard stuff. The LEDs were blinking, so the 5V buck rail had to be OK, still I did these to be sure. I also double checked that the control signal is indeed getting to the yellow pin of the servo - it did. Bad connections theory was busted.
There was a moment when I believed that my batteries simply can't give enough juice to move the servo. To confirm/bust this, I ended up looking at the SG90's parameter list, and found that the stall current is 650 [mA]! I thought, that this will be the problem, and the batteries can't assure the same current as my PSU - this needed a test.
I took a 1.2 ohms resistor, and put it in series with the robots negative input wire. The measurement had to be low-side, because the scope ground probe and the PSU ground were connected through earth. By measuring voltage across a series resistor of known value, one can calculate the current that is flowing through it, the current consumption of the robot in this case. I took two measurements, one with the Li-Ion battery pack, and one with the PSU, you can see these attached to this step. The one with the voltage / division at 1 [V] is the test with the battery, the other one with 500 [mV] per division is the PSU. Because the battery had lower voltage than the PSU setting, and the DC-DC converters had to draw extra current to maintain their outputs regulated: you can see a 4 [A] spike!
This means, that the battery can provide quite enough for the whole circuit to start up, including the initial overload on the servo when it starts turning the shaft. So this theory was busted as well.
After thinking of how long the 5 [V] traces are and how high the stall current of this servo is, I realized, that a local bulky capacitor would be a good idea. This would help with local spontaneous current draws. Since this involved drilling holes and mounting a new cap, I was a bit against it. However, when a smart friend came with this very suggestion without me even mentioning this theory, I decided to bite the bullet and give this a try. I took a high quality (salvaged from a motherboard) 3300 [uF]/6.3 [V] capacitor, drilled some holes for it very close to the servo output pin header, and soldered the thing in. I turned the power on and it worked!!! This theory is confirmed to be true, and going through with it proved to work like a charm.
Conclusion and finishing this test series
After I saw this working with the Li-Ion battery, and the test was repeatable, I started thinking about how to mount the huge batteries on the robot. I tried putting them on the back side, but that resulted in the robot getting unbalanced - so I decided to give another shot to the Ni-MH batteries, and use the robots 4 x AA cell holder for power source. I made the necessary modifications, plugged the batteries, and again: it worked! Problem solved, and the look of the bot is seriously better!
Step 12: Control and Problems of the HCSR-04 Module
GND - common ground
This pin connects to the ground net of the schematic
VCC - power rail
This pin must be tied to the power line of our circuit, which - in my case is provided by the 5V buck converter.
TRIG - trigger pin
The user has to provide a microsecond long impulse on this pin, then wait for the echo to arrive on the echo pin. Note, that echo is high in the first tens of milliseconds, make sure you wait that out at initialization. This pin is asserted, then program has to wait for a rising edge and start a timer, TMR1. The WASTE_10_US(); is a macro I wrote, it consists of enough "NOP" assembly instructions to waste a total time of 10 microseconds. Counting is stopped when the echo pulse goes low again. From the number in the counter register we know the time needed for the sound to get back from an eventual obstacle. We know the speed of sound - we can calculate the distance!
ECHO - echo pin
This pin is used to detect the reflected sound waves. I connected it to a pin that has interrupt, just in case - but it is used in simple input mode. A rising edge stops the previously mentioned counter, and saves it into a variable, which gets processed shortly after.
A little trick to make the PIC-s life easier
The timing of the pulses is shown in the datasheet of this module. As mentioned before, you need to wait out the initial ~100 ms, then give 10 microsecond trigger pulses on the TRIG pin. Giving such an impulse results in an 8 cycle burst coming from the transmitter 'speaker'. Then, we need to wait for an echo to come. The echo is captured by the receiver, and transformed to an impulse of variable length on the ECHO pin. The length of this echo impulse is directly proportional with the distance to the obstacle. The datasheet gives a simple formula to calculate this:
distance [cm] = t measured [microseconds] / 58
this will return the distance in centimeters. I only divide by 5.8, so the distance gets returned in millimeters. Since I don't necessarily need extremely precise readings, I will add a small twist here. I only want to avoid obstacles, I don't care how far they are, so there is no use of calculating distance in double variable types. Those kind of computations take a long time, which in this case is completely unnecessary. So let's simplify the calculations a little. We know, that the received impulse's length in microseconds is needed to figure out the distance. However, our time base isn't a very round one, 83.33 [ns]. So, instead of taking the TMR1 count, multiplying it by the time base to get the length in microseconds, then dividing it by 5.8 to get the distance in millimeters, we will do something simpler and dumber. Let's establish what one single TMR1 count means in millimeters:
1 count = 83.33 [ns] = 0.08333 [us], which, divided by 5.8 results in 0.014 [mm] for every TMR1 [count]
Having said that, we can write the following:
1 [count] .......................... 0.014 [mm]
x [count] .......................... y [mm]
and carry on writing the following formula:
y [mm] = (x * 0.014) / 1 = x * 0.014 = x / 71.42
So we know that we can get the distance to an obstacle by dividing TMR1 count with 71.42. This, however, is still a precision calculus that takes a long time, so we take a look and realize that this is not that far from 64, a nice 'round' number. Dividing with the powers of 2 in software is very easy, can be done via shifting right six times. Byte shifting is a native instruction of the PIC, and it is very very quick in comparison with a double division. So, let's round 71.42 to 64 and just shift TMR1 left 6 times to get an idea on how close an obstacle is!
Let's see the difference this trick gives at a 8000 (for example) count:
original: 8000 / 71.42 = 112[mm]
tricky: 8000 / 64 = (8000 >> 6) = 125[mm]
So the error is around 10%, not much compared to the huge amount of time we just saved!
I haven't hit any problems while working with this module, the one thing that happened was that the wire between the PIC and the ECHO pin was left disconnected, and the PIC waited endlessly for edges on that pin. This was easy to catch, since the red LED that toggles at every second, stopped. I immediately knew that the program froze, and the one place it can freeze is this wait for edge routine. Problem solved after one minute!
One more thing: these things work with 5 [V], so having just 4.8 [V] at full charge means, that we're stretching it too far. If the voltage isn't enough to catch the echo, the MCU will hang, waiting for the ECHO to arrive!
Step 13: Debugging the Firmware With Everything Added
The best part in writing a firmware is the moment when everything works at the first try. I tend to write a lot of code before actually go out and test it, so if it works at first power-on, it's even better. Unfortunately and naturally this wasn't the case, I had to do quite a lot of debugging to get the software to function the way I imagined it.
Since debugging can and will take longer than typing the code itself - prepare yourself for the worst. There is a pretty funny and realistic picture on the web, that shows the stages of debugging.
How to debug
To figure out where your program counter is at a given moment, one must think of ways to get some sort of feedback from the PIC. The easiest way to go is to use some LEDs, and light them up at given points in the code - this way you will know for sure, that the code written there was executed. Since I had an unused 2 x 4 female header on-board, I designed a small board just for debugging purposes, a board that plugs into this header. This way I was able to identify whether or not the PIC is taking the right decisions in the algorithm. That was relatively easy to put in place. After a while the LEDs simply weren't enough, and I began thinking of some sort of debugging port, maybe an SPI display that displays the recorded distances. It didn't take long to realize that - by chance - I brought out two very important PIC pins to this 2 x 4 header: the RX and TX pins of the serial port! I pulled out a serial-USB converter I ordered from e-bay a while ago (link), and connected to these pins.
The hardware was ready, I just needed some firmware to initialize the port with the required parameters, and to set up communication speed. This can be done by reading the datasheet section, I have done this by hand many-many times, so this time I decided to use a calculator for faster development. Check out the pictures, one of them has this neat little baud-rate (bits/second) generator - good stuff! I used 48 [MHz] oscillator speed, and made the calculations for 115200 [bits/second].
After I finished the initial setup, I wrote some common functions I knew I will need, and put it to a test. The serial port worked at first trial, my better debug port was ready to use! I just needed a terminal program to be able to visualize the data the PIC sent. Easy: I downloaded Hercules from here: link.
Advantages of having the serial port
After I got the serial port up and running, my life became ten times easier. Every variable, every measurement, everything that was written inside the PIC became accessible through this debug port. I quickly found that the variable that stored the maximum of the 11 distance measurements was "unsigned char", but some readings were bigger than this type can fit in, I switched to unsigned int with that.
I also realized that the distance measured is precise enough, but I didn't take into account the fact that the robot needs some space to be able to turn left or right. I subtracted 8 [cm] from the final "run forward" command, and solved this problem.
As soon as the robot started going the right distances compared to the distances it measured, I removed the serial connection and put the LED board back inside.
Step 14: Mechanics, Tools, Instruments
Some pictures of the tools I used!
Step 15: Writing the PC Software - Future Development Possibility!
I wrote a simple PC software for his project to simulate distance measurements, and the way the motors react, here it is. A future development would be to connect this to the bot (preferably without any wires)! There are plenty of examples of C# serial port handling programs on the web, I might just build a mapping robot out of this. I just have to figure out a way to add some mechanical encoders to the upper board, so I can read back the distance the chains travel.
Step 16: Testing and Final Words
To test the bot, I created all sorts of labyrinths made of foam core boards, cardboard, boxes and post-it stacks, the corridor on the video is of a simplistic S shape. The bot is pretty close to the edges, but makes the turns without problems!
That being said, this instructable was a long, but nice trip - I hope some of the readers will end up making one for themselves. If someone does, post a picture of your bot!