![]() |
|
By Jeff Spencer, jeffsp@nwlink.com
First of all Ill give some history of how I came to build a line following robot. Recently I became interested in robotics and attended my first SRS meeting. Fortunately the meeting I picked happened to coincide with the Fire Fighting Robot contest. As a result of the competition I became even more enthused about robots and various methods of determining the exact position of a robot within a home. And since CCD cameras are getting cheaper I spent a lot of time looking into very high resolution robot vision solutions. Incidentally I hope to publish what I learned in the Encoder in the future. Anyhow, after a month of reading and web based research I attended my second SRS meeting. After the meeting I joined a lot of the SRS members at the local Pizza joint. During the ensuing conversation some of the SRS members convinced me that starting with a simple robot made sense. In fact, they specifically suggested that I target the upcoming line maze contest using a Bot Board Plus for the CPU (available from http://www.nwlink.com/~kevinro/products.html ) , a Marvin Slyder Base (available from http://www.sinerobotics.com/ ), and write the program in either Sbasic (available from Karl Lunts home page www.seanet.com/~karllunt ) or assembly language. At the time, I thought I would do the programming in C or assembly language (Im a software engineer by day) which is probably why they suggested the Bot Board Plus, since the 2K EEPROM could get cramped for doing an Sbasic line maze robot.
In the end, I ordered the components above and decided to use Sbasic since it came with the Bot Board Plus and was clearly the path of least resistance. Also, writing assembly language is a very slow process because the instructions are so primitive compared to a high level language.
So once I finally decided what I was going to buy it was time to order everything.
- I ordered the Bot Board Plus starter kit, but if I had to do it again Id order the full assembled version for my first bot (reasons below).
- I ordered the Futuba S148 servos because I found multiple references to converting these to continuous rotation and Karls book provides a description of how to add an encoder to this (Ive yet to add the encoders).
- And since Im really serious about this, I also ordered all of the Kevin Rosss favorite parts from Digi-Key. To find his favorite parts see the July 1999 Encoder. If youre just getting started youll need a Molex Universal Crimp Tool to attach wires to the connectors that come with the Bot Board Plus Starter Kit. This is Digi-Key part number WM9999-ND ($32.59). Initially I had trouble finding the correct Molex KK connectors. But some quick e-mail to Kevin solved that problem. The 4 circuit terminal housing is WM2002-ND, the tin plated version of the 4 circuit terminals is WM4202-ND and the crimp on connectors (that go in the housing) with gold contacts is WM2312-ND. (I actually intended to buy all gold plated connectors but accidentally bought tin headers). Tin is probably just fine, but since Im just getting started and didnt want any extra complications due to a bad connection I spent the extra money.
- I also order some photo detectors, Digi-Key part #L14G1 which are QT optoelectronics part #L14G1 more about them later.
- What I didnt order but should have is some 10 pin ribbon cable sockets and 10 pin ribbon cable to connect to the digital and analog I/O headers of the Bot Board Plus
Ive purchased several other items since then, but I believe this covers the bulk of the items I needed.
The assembling of the BotBoard Plus and the serial cable for the Bot Board Plus went smoothly. However, when I was done the serial cable didnt work. And since I havent taken the time to get it working I cant tell you why. Since I know there arent any cold solder joints (I re-flowed everything) I suspect either a solder bridge or I overheated a part. Anyhow, I was stuck cold at this point. Primarily due to lack of tools. I couldnt find my multi-meter, I didnt have an oscilloscope and didnt have an extra Bot-Board or download cable to isolate the problem. Out of frustration I solved the problem with the shotgun approach. In other words I resolved all of those situations at the same time. I ordered an oscilloscope, a much better multi-meter than the radio shack model I couldnt find and ordered a fully assembled Bot Board Plus and cable. While it was very frustrating waiting for all of this to arrive.
You can imagine my glee once all those orders arrived. Using the new serial/signal cable I quickly determined that I had two completely functional Bot Board Pluss to work with. Needless to say, programming my new bot controller was much more interesting than debugging a hardware problem in a serial cable. Which explains why I havent fixed the original serial cable yet.
Since serial output looked very simple using Sbasic. I decided to write a "Hello There" program first to test my board. Unfortunately after several hours (late at night) it wasnt working. So I gave in and did something simpler. I wired a LED through a 150 ohm resistor to an output on Port-C. Since at that point I simply wanted to get ANYTHING working I wrote a quick loop to blink all the outputs on Port C. It worked like a charm. So I was back to trying to get serial output working.
I decided it was time to work through the serial I/O one step at a time. So the first step was to read the 68HC11 manual and figure out what the correct initialization sequence was, even thought every example I found used the same initialization sequence I was. Which was:
pokeb baud, $30
pokeb sccr2, $0c
In the end the problem was that my HC11 also required that a zero be written to sccr1. So once I changed the initialization sequence to the following code I could finally print to the serial port and use Hyperterm to see what was happening within my code. I cant help but wonder if everyone hits this issue and has to work through it.
pokeb baud, $30
pokeb sccr1, $00
pokeb sccr2, $0c
OK, now that I had a working microcontroller it was time to learn how to read the analog ports. Using some example code of Karls I very quickly had the first four channels working. However, I couldnt read the 2nd set of four channels until Kevin Ross helped me out after I sent a plea to the SRS mailing list (thanks Kevin).
I decided to use Karl Lunts line sensor array design from his book Build Your Own Robot! Which if you havent bought yet, you really need to. Page 180 has the schematic that I used for the sensors. Unfortunately, I couldnt find the surplus photo transistors that he used so I had to modify the resistor values to match the photo transistors Im using. With Opto Electronics L14G1 (Digi-Key part number L14G1) I found that a 1.2K resistor gives a good range of values for measuring a black line on white paper. Note that per Karls advice Im using RED super-brite LEDs driven by a 5+ V power supply through a 150 ohm resister. Karl recommends not using Infrared Light because some paper is transparent to infrared light and the sensor is likely to detect features underneath the paper. And while following an expansion crack in a concrete floor through paper is pretty cool, its not a good way to win a line following or maze solving contest.
Click here to watch a Real Player movie of this robot in action (127kb for 56k modem)
I determined the best resistor value for use with the photo transistor by experimentally driving it through a 5K potentiometer taken from the Futuba S148 servos. While 1.2K seemed like the best value to use. I found that I had more 1K resistors than 1.2K, so I went with the 1K resistors instead. And they seem to be working fine. While I havent tested this, if you like you can use Opto Electronics L14G2 instead of L14G1. The G2 model simply gates half as much current (3 milli-amps instead of 6 milli-amps) as the L14G1 part I used. So in theory a 2.2K resistor used with the L14G2 will work just as well, but with half the current draw.
While Id like to go on to describe the rest of my experience in detail. I dont have anymore time to dedicate to this article. So at this point Ill simply describe my current program and include the Sbasic code. I would like to mention that the BotBoard Plus schematic I have is wrong. And if youre working off of it be aware that the port labeled Servo 1 is in fact PA6 and not PA3 as the schematic states. Likewise Servo 2 is in fact PA5. Presumably, Servo 3 is PA4 and Servo 4 is PA3 but I havent verified this. Also the schematic incorrectly shows pin 1 on the servos as the signal line. In fact pin 1 (indicated by the square pad on the circuit board) is the ground line and pin 3 is the signal line. If you look at the traces on the circuit board it should be fairly obvious as pin 1 clearly has a trace running to pin 1 on all the servo headers. I lost a lot of sleep figuring all that out. Hopefully I can save some people some time by mentioning it here. As far as I can tell Port As 10 pin header is correct on the schematic.
This robot is intended to become a line-maze robot. But at this point its simply a line following robot. And a very simple one at that. The robot starts off by initializing the serial port, the D to A converter and the pulse width modulation control for the servos. It then spins in place multiple times calibrating the sensors to the current environment. Then it turns until it finds the line and starts following it by alternately turning the left or right wheel. Ive put lots of comments in the code to help, so Ill let the code explain the rest.
Editors note: To download the code as a file, click here.
include "regs11.lib"
declare i declare n declare t declare x declare z declare y declare n2
declare Fob declare FobAdjust declare Tmp
' Ground sensor values declare LeftCenterG declare RightG declare LeftG declare RightCenterG declare TimeIt declare wait declare RollIt declare Number2 declare TimeCount declare TimeCountH
declare iTmp
declare Analog(8) declare SenseMax(8) declare SenseMin(8) declare SenseAverage(8) declare SenseTotalL(8) declare SenseTotalH(8) declare SenseThreshold(8) declare SenseDiff(8)
const LeftStop = $9AA const LeftFF = $2000 const LeftFR = $200
const RightStop =$9EB const RightFF =$200 const RightFR =$2000
const LeftSpeed = TOC2 const RightSpeed = TOC3
const L = 2 const LC = 6 const RC = 3 const R = 7
wait = 0
TimeIt = 0 RollIt = 0 y = 0 n = 14 z = 1 n2 = 0 Number2 = 50
' ' The RTI interrupt service routine '
interrupt $fff0 'RTI Interrupt if peekb(tflg2) and %01000000 <> 0 if wait <> 0 wait = wait - 1 endif TimeIt = TimeIt + 1 pokeb tflg2, %01000000 endif
' keep track of PWM timer roll over if peekb(tflg2) and %10000000 <> 0 RollIt = RollIt + 1 pokeb tflg2, %10000000 endif
' I don't think this is used at the moment ' but in case it is I'll leave it in for the Encoder article if peekb(tflg1) and %01000000 <> 0 Number2 = Number2 + 1 pokeb tflg1, %01000000 endif
end
'This subroutine blinks an LED attached to any of the 'output lines on port c. It will be removed at some time in the future 'currently it's never called. To call it simply add the line 'call Alive Alive: y = 0 for n2 = 0 to 10 for n = 0 to 1 for x = 0 to 6300 next next
' Alternate between all on and all off if y = 0 y = $ff else y = 0 endif pokeb portc, y 'Change port C to either all low or all high next pokeb portc, 0 return
'This subroutine reads all of the Analog To Digital channels when it's called 'It stores the results in the array "Analog". 'It also stores the difference from the average value which was calculated 'earlier. This difference value is meaningless until after the Calibrate 'subroutine is called. sense: pokeb adctl, $10 ' multi-chnl, 1-4 waituntil adctl, $80 ' loop until conversion complete Analog(0) = peekb(adr1) Analog(1) = peekb(adr2) Analog(2) = peekb(adr3) Analog(3) = peekb(adr4) ' Analog 4
pokeb adctl, $14 ' multi-chnl, 5-8 waituntil adctl, $80 Analog(4) = peekb(adr1) Analog(5) = peekb(adr2) Analog(6) = peekb(adr3) ' Analog 7 Analog(7) = peekb(adr4) ' Analog 8
for iTmp = 0 to 7 SenseDiff(iTmp) = Analog(iTmp) - SenseAverage(iTmp) next
Fob = Analog(0) return
'Calibrate 'Does software calibration of the analog channels. The code 'runs for all the analog channels even though currently only '4 of them have line sensors attatched. This keeps the code simple 'but if memory constraints become a problem it will need to be 'optimized to only read the line sensors.
'It works by spinning the robot around for awhile and 'tracking the largest and smallest readings for each sensor 'It also averages all of the readings for each sensor. 'Since SBasic doesn't have 32 bit integers it's necessary 'to calculate an average on 128 samples at a time. Then average 'each of those averages at the end of the calibration time. 'While this is inconvient, it works just fine. It is possible to 'loose up to 127 samples at the end, but on average we'll only 'loose half that many (64) and compared to the total samples 'I measured of approximately 3500 that's an insignifigant 'number of samples.
'Since the majority of the paper doesn't have any lines on it 'the average value is very close to the value of the paper 'without a line. Providing a very good basis for determining 'if a line is present or not.
'Lastly this function calculates a sense threshold. This threshold is 'used to determin if a sensor is over a black line. It's calculated by 'picking a value exactly half way between the average and the maximum 'value for that sensor. The goal is to privide a threshold with lots 'of margin for error.
Calibrate:
'Spin in place counter clockwise poke RightSpeed, RightFF poke LeftSpeed, LeftFR
' Initialize with values to be replaced for i = 0 to 7 ' Loop through all 8 analog channels SenseMin(i) = 255 SenseMax(i) = 0 SenseTotalL(i) = 0 SenseTotalH(i) = 0 next
' Calculate min and max and average values while spinning in place RollIt = 0 TimeCount = 0 TimeCountH = 0 while RollIt < 400 'arbitrarily wait until the PWM generator has rolled over 400 times gosub Sense TimeCount = TimeCount + 1 for i = 0 to 7 SenseMin(i) = Min(SenseMin(i), Analog(i)) SenseMax(i) = Max(SenseMax(i), Analog(i)) SenseTotalL(i) = SenseTotalL(i) + Analog(i) 'Total the readings for the Low average next
'If it's time to calculate the low average, then do it if TimeCount = 128 TimeCount = 0 'Prepare to add up another 128 samples TimeCountH = TimeCountH + 1 'Count the number of low averages we're adding to High average total for i = 0 to 7 x = SenseTotalL(i) / 128 SenseTotalL(i) = 0 SenseTotalH(i) = SenseTotalH(i) + x next endif wend
' print the results for debugging for i = 0 to 7 SenseAverage(i) = SenseTotalH(i) / TimeCountH x = SenseAverage(i) + SenseMax(i) SenseThreshold(i) = x / 2 printx "Index:"; i;" Min:"; SenseMin(i); " Max:"; SenseMax(i); " Average:"; SenseAverage(i); " Thresh:"; SenseThreshold(i) next printx "TimeCount:"; TimeCount printx "TimeCountH:"; TimeCountH
'Time to stop spinning in place poke RightSpeed, RightStop poke LeftSpeed, LeftStop
return
main: 'Initialize the serial port pokeb baud, $30 pokeb sccr1, $00 'critical line that was missing from the examples I used pokeb sccr2, $0c
'Set Port C is all outputs and set all the pins low pokeb ddrc, $ff pokeb portc, $00
'Overflow and RTI pokeb tflg2, %11000000 pokeb tmsk2, %11000000
'Initialize A/D pokeb option, $80 ' turn on A/D system
' Set up for PWM pokeb OC1M, %11111000 ' Out Cmp 1 affects PA7-PA3 pokeb OC1D, %11111000 ' Sending them high pokeb TCTL1, %10100000 ' OC2 turns off PA6 and OC3 turns off PA5
pokeb TMSK1, %00000000 'no interrupt pokeb TFLG1, %00000000
'Stop the servos by sending the correct PWM as determined through experimentation poke TOC2, LeftStop 'Stop for left servo poke TOC3, RightStop
interrupts on
'Gosub Alive 'don't call the subroutine alive anymore print "Hello There" print
'Z never equals 35 so this code isn't used anymore. I used it while figuring out the 'PWM values to use for stop. Fob is an analog channel that I hung a potentiometer off of. 'The potentiometer was wired as a voltage divider to provide a number between 0-255 depending 'on it's position. if z = 35 gosub Sense Tmp = peek(TOC2) FobAdjust = Fob - 8 if FobAdjust < 0 FobAdjust = 0 endif FobAdjust = $950 + FobAdjust Tmp = FobAdjust printx "Fob: "; Fob; " NewVal:"; Tmp
poke TOC3, Tmp for x = 0 to 2000 next endif
'Calibrate the analog line sensors GoSub Calibrate
' Spin counter clockwise poke RightSpeed, RightFF poke LeftSpeed, LeftFR
' Keep spinning until we find the line by watching the Right Center (RC) ' Analog Sensor. When we find it the line should be between the left center and ' right center sensors. At which point it's time to simply follow the line. n = 0 while n = 0 Gosub Sense if Analog(RC) > SenseThreshold(RC) n = 1 endif wend
'Follow the line by adjusting the pulse width modulation (PWM) 'each time the PWM clock generator rolls over (starting the pulse) RollIt = 0 while z<> 0 ' Loop forever if RollIt > 0 ' Wait for the PWM counter to roll over RollIt = 0 ' Reset PWM rollover flag for next time. gosub Sense 'Read the line sensors x = SenseDiff(RC) - SenseDiff(LC) 'Get the difference in the readings
'Convert the difference to an absolute value if x < 0 x = 0 - x endif
'If the difference is small enough run both wheels forward 'Note: This isn't working well and needs work. x is rarely, if ever under 8 if x < 8 poke RightSpeed, RightFF poke LeftSpeed, LeftFF else 'Turn which ever way is necessary to center the line. if SenseDiff(RC) > SenseDiff(LC) 'too far right poke RightSpeed, RightStop poke LeftSpeed, LeftFF else poke RightSpeed, RightFF poke LeftSpeed, LeftStop endif endif endif wend