Posted by Pete Burnight on November 28, 2010 at 6:30pm
I've been building both AutoPilots, and QuadraCopters, and I wanted to drive more servos and ESCs, with less jitter, so I used some ideas from the Paparazzi gang and developed a way to drive up to 10 servos with just 2 output pins from an Arduino or ATMega8.
The design uses just one chip, a Johnson style Decade Counter, and requires only two output pins from the Arduino. The Arduino uses one PWM pin (pin9), and one general IO pin (pin8). All the pulse are generated in sequence on the PWM pin, then spread out to the individual servos via the decade counter. The decade counter costs 63 cents.
Now I can build Hexa-Copters without having to use I2C ESCs, and still have servo outputs available for camera controls.
Posted by Pete Burnight on October 8, 2009 at 12:00pm
I wanted two way communication with my airplane, and had moved the serial communication stuff to a 2nd arduino (see my prev blog) but I didn't like the way the code looked, so I reconfigured the system to have the 2nd Arduino handle the GPS and Gyro, and then use I2c to send the data to the main processor. Essentially making the GPS into an I2c device, and freeing up the main serial port for 2-way com with the ground.
This works out great. The GPS code is very static (doesn't change) so the code within the 2nd Arduino doesn't have to be updated all the time. (at all) Having the Gyro and GPS together works great - the 2nd arduino becomes an I2c GPS / Heading device that the host simply polls 5 times a second to get the current heading and location. This works better with the I2c protocol ... we're sending small blocks of data instead of the huge text based data I was trying to send around.5 times a second, the host asks the slave for 32 bytes of data. The slave returns the following data:float Gyro_Heading;float GPS_Lat;float GPS_Lon;float GPS_Altitude;float GPS_CMG;float GPS_Speed;long GPS_TimeStamp;int Gyro_Rate;int Flags;The slave takes care of reading the Gyro every 20ms, and updating the Gyro_Heading value.Every time it gets new GPS data, it updates the Gyro_Heading, which takes care of any gyro drift.This has turned out to be a much better way to organize the system. The code in the Arduino slave is static, it is essentially an I2c front-end to the GPS code. The changes to ArduPilot are now minimal. It still calls decode_gps() - which simply requests, and returns the I2c data, and now all the debugging and Serial.println stuff to and from the ground station is back in the main code and much easier to change and much more straight forward.Here's a link to the slave source code: ArduinoSlave.zipAnd another thing...The slave Arduino board started out so pretty... and then things happen.
I used the Spark-Fun XBee carrier board 'cause it said it would hook directly into 5 volt systems, and would actually tolerate up to 12volts of input. The Arduino Mini will also regulate it's own power - so I could use an external 7.4v lipo and not have to do a power supply. Yea !! The gyro gets it's +5 volt power from the Arduino Mini's regulated +5 volt pin. It draws nothing. Great - I could hook up the XBee and Arduino directly to the lipo. Very tiddy. But NO. I had all the code working on the bench, installed everything in the plane. Ready for a test flight, but ... no data coming to the ground station ?? all the lights are blinking - everybody's sending and receiving something ???Turns out the Spark Fun XBee board doesn't like talking directly to my Arduinos. Tx Signal lines are too strong. If I put a voltage divider inline, it mostly works, but with noise garbage here and there. Yuck. I hate flaky com lines. I even tried their level converter chip, but still garbage. (strange - 'cause I've used these before with great success)So I switched back to the Ladyada XBee board, but it doesn't like the 7 volt lipo. (its on board regulator gets too hot) It wants +5volts - but I don't want to pull thru the Arduino Mini's regulator - too much draw - 250 mah. So I bite the bullet and put a 5 volt regulator where the SparkFun XBee board was, and went back to the external XBee board. What a hassle.
But the connections to the system are now very tiddy. Mini Slave board has it's own power from the 7.4v lipo. GPS plugs into the Mini Slave board (and gets it's power from my 5 volt regulator) Power lines from XBee board plug into my Slave board, and also use my 5 volt regulated power. Arduino mini regulates it's own power from the lipo, and provides power for gyro. Only 3 connections to the main system. Two wires plug into Analog 4&5 for the I2c line. 2 wires plug into the TxRx pins (where the ftdi plug goes) and 1 wire plugs into a servo ground.
Read more…
Posted by Pete Burnight on October 6, 2009 at 10:46am
I wanted two way communication with my aircraft, but when the GPS is running, the Arduino board only has 1 side of its single serial port available, so I added another Arduino Mini to provide 2-way communication between my ground station and the aircraft.The additional board consists of a 5 volt Arduino Mini Pro, with a ATMega 328 chip running at 16 mHz, and a Spark Fun 5 volt carrier board for the XBee. That's it. I had to move my Gyro to this new board, cause I used I2c to link the main ArduPilot board with my new "com" board, and I2c uses the Analog Pins 4 & 5 for communication. (I had my gyro on analog 4&5)
It takes three wires to connect to the ArduPilot board - SDA, SCL and Ground. The two I2c wires go to Analog pins 4 and 5 on the ArduPilot board. Ground plugs into an open servo socket. I used a separate 7.4v lipo battery to power the new Arduino and the XBee. (the XBee 900 XSC draws 250 mah during transmission)
The code changes within ArduPilot were very minimal. I used the Wire Library for I2c. It's not efficient, but it's easy to use. It uses blocking reads, which means your code is sitting around waiting for the data to arrive back, instead of being able to go about your business until an interrupt tells you your data is ready. I'll improve this later.All of my downlink data (data going to the ground station) is isolated within the print_data() function - so making the changes was easy. I used the PString library to mimic the Serial.print calls, so code that used to look like:Serial.print(",CRS:");Serial.print(ground_course);Serial.print (",SPD:");Serial.print(ground_speed);Serial.print(",CRT:");Serial.print(climb_rate);Now looks like:theStr.print(",CRS:");theStr.print(ground_course);theStr.print (",SPD:");theStr.print(ground_speed);theStr.print(",CRT:");theStr.print(climb_rate);Wire.beginTransmission(4); // transmit to our I2c slave Arduino (ID=4)Wire.send(theStr);Wire.endTransmission();The slave Arduino takes all I2c data it receives from the master and transmits it out it's serial port to the XBee where it continues thru space to the ground station.// This is the interrupt routine in the slave Arduino that gets called every time// the master sends out some data.void master_has_sent_us_data (int howMuch){byte slen=0;while (Wire.available() > 0){RxBuffer[slen] = Wire.receive();if (slen < MAX_BUF_INDEX) slen++;}RxBuffer[slen] = 0; // terminating zero for c-stringSerial.println(RxBuffer); // Send to Ground Station via XBee}// -------------------Reading the Gyro is easy. It's actually an analog gyro, but now the main software reads it like it's an I2c gyro. Every 20ms the main loop calls:Wire.requestFrom(4, 4); // request 4 bytes from our slave device #4if (Wire.available() >= 4){gyro = (Wire.receive() << 8) + Wire.receive(); // read 2 bytes as an IntvRef = (Wire.receive() << 8) + Wire.receive();}// -----So for about $40, and a couple hours wiring, I get another serial port, 4 more analog ports (2 of the 6 are used for I2c) 13 more digital lines, and a bunch of horsepower (that pretty much goes un-used) But it's cool - and I really wanted the two way com between the aircraft and the ground. Now I have it. Cheers.
Read more…
Posted by Pete Burnight on October 4, 2009 at 12:30pm
While flight testing my Magpie and ArduPilot, I was having a problem with over-shoot during Way Point Navigation. After hitting the way point, the aircraft would start turning towards the next way point, and then continue turning until way past the bearing to the next way point, requiring the aircraft to make a sweeping s-turn to get back on track towards the next way point.
I suspected a number of things... the directional bearing data the aircraft uses comes from the GPS "Ground Course" data. It has no idea which direction you're actually pointing, it can only tell you the bearing between where you are now, and where you were a little while ago. I'm using the EM406 GPS, and there's a little lag in what it tells you your course is, as it tries to integrate, and smooth the data it's receiving. In addition, while my aircraft is in a high banking turn, it's actually pointing "ahead" of where the direction vector says it is, and, my GPS only puts out new data once per second, so the current course data available to the Nav functions is old, and the aircraft easily gets pointed way ahead of where I want it to.So... I did a couple things. I added code to calculate my own CMG (course made good) that didn't filter or smooth the data so I'd have better data quicker, I added a Gyro to update my current bearing based on rate of rotation during a turn, and I added some code to compensate for high bank turns, that essentially add some offset to the current heading based on how much the aircraft is banking.Works Great. The Gyro is sampled every 20 ms, and updates the variable gyroHeading based on rate of rotation. Every 200 ms I set the update flag so the nav routines think there's new data, and re-evaluate the roll set point based on the updated heading provided by gyroHeading. Every time I get new GPS data (once per second) I re-calc my own version of CMG by calc'ing the bearing BACK to where we were 1 second ago, and setting gyroHeading to this new course. That way, gyro drift is minimized (it only has 1 second to get in trouble) and I really only need the gyro during high bank turns, where the aircraft's course is changing quickly, and I'm only getting GPS data once per second. This gives the nav function 5 updates per second on the "true" nature of the quickly changing course of the aircraft, and the PID functions then do a much better job of calculating the roll set point needed to "round the mark" smoothly.
// --------------------------------------------------------------------------------------------------// adjust the heading if we're banking hardint heading_roll_compensation(){int rollAmt = get_roll();if (abs(rollAmt) < 16) return 0; // don't adjust unless banking hardreturn (rollAmt/2);}// --------------------------------------------------------------------------------------------------// Calc our own Course Made Good - use as Gyro Referencevoid calc_our_CMG(){static float prevLat=0;static float prevLon=0;if (calc_dist(prevLat, prevLon, lat, lon) > 1){ourCMG = calc_bearing(prevLat, prevLon, lat, lon) + heading_roll_compensation();if (ourCMG > 360.0) ourCMG -= 360.0;if (ourCMG < 0) ourCMG += 360.0;prevLat = lat;prevLon = lon;gyroHeading = ourCMG;}}// --------------------------------------------------------------------------------------------------The code is then used within the function navigation():wp_bearing = calc_bearing(lat, lon, wp_current_lat, wp_current_lon);calc_our_CMG();roll_set_point = calc_roll(heading_error(wp_bearing, gyroHeading), dt_t);Note that calc_our_CMG() will NOT update gyroHeading every time thru, only when it gets new GPS data that puts us at least 1 meter away from our previous position (usually every second) In between, the gyro will have been updating gyroHeading 5 times per second so the nav routines can respond quick during the turn.The code below supports the Gyro. Init_Gyro_Bias() is called during init - NO MOTION.The main routine sample_gyro_data() is called each time thru the main loop.// ---------------------------------------------------------------------------------------------------------------------------// Called from Main Loop to sample gyro data & update gyroHeading.// Sample the analog data from the Gyro about 2,000 times per second.// call the Update Heading routine only every 20ms// smooth the over-sampled analog lines.void sample_gyro_data(){static unsigned long gyroTimer=0;static unsigned int gyroCounter = 0;unsigned long dt;analog4 = (analog4 + analogRead(4)) >> 1; // sample Analog inputs every time thruanalog5 = (analog5 + analogRead(5)) >> 1; // analog 4 & 5 are for the Gyrodt = millis() - gyroTimer;if (dt > 50) dt = 50;if (dt > 20) {update_gyro_heading(dt);gyroTimer = millis();gyroCounter++;if (gyroCounter > 10) {data_update_event |= 1; // update rudder info 5 times a secondgyroCounter = 0;}}}// -----------------------------------------------------------------------------------------------------------------------------// rate of gyro at full swing = 150 degrees per second// this should be called every 20ms -> 50 times per second// dt is in milli-secondsvoid update_gyro_heading(int dt){int gData;float gyroDelta;gData = (analog4 - analog5) - gyroBias; // range is now -512 ... 0 ... 512gyroDelta = (float)(gData) / 512.0; // range is now -1.0 ... 0 ... 1.0gyroDelta = gyroDelta * 150.0 * (float)(dt / 1000.0);gyroHeading += gyroDelta;if (gyroHeading > 360.0) gyroHeading -= 360.0;if (gyroHeading < 0.0) gyroHeading += 360.0;}// ---------------------------------------------------------------------------------------------------------------------------// gyroBias = reading of gyro at rest. So we know the zero point.// called at Init time gyroBias = init_gyro_bias();int init_gyro_bias(){int n;int theBias;analog4 = analogRead(4); // sample Analog input for gyro rateanalog5 = analogRead(5); // 2.5v voltage reference = 512theBias = analog4 - analog5;for (n=0; n<50; n++){analog4 = (analog4 + analogRead(4)) >> 1; // range 0..1023analog5 = (analog5 + analogRead(5)) >> 1;theBias = (theBias + (analog4-analog5)) / 2; // signed value -512..0..511}return theBias;}// ---------------------------------------------------------------------------------------------------------------------------
Read more…
Posted by Pete Burnight on October 3, 2009 at 2:04pm
I've been tuning the GPS portion of my ArduPilot / Magpie airframe combination, and put together this flight data recorder to save the results of the flights without having to take a laptop out to the field.The data recorder consists of a Spark Fun Data Recorder, an XBee radio modem, and a battery. The Data Recorder takes a serial stream at 9600 baud, and records it to a SD data card as a text file.The Spark Fun card takes un-regulated power in (my 6v battery), regulates it, an provides the regulated 3.2v power required for the XBee. (very simple) Just 3 wires between the XBee and the Data Recorder, V+, Gnd, and DataOut (XBee->Recorder)
I used a 4 cell battery case from Radio Shack for power - it sits on the back of a bread board used to hold everything together. Very easy. Thanks Spark Fun for making such amazing little products.The aircraft has a XBee Pro 900 modem sending out 1 line of flight data every 300 ms. I capture it with my portable download link to an SD data card that I copy to my computer at home after the flight. The Spark Fun Data Recorder saves the data as a simple text file, that I post-process into a KML file for Google Earth.|-- AP Mode:1,GPS:0,Mir:189,CH1:-2,CH2:-1,IMU:321.74,CMG:306,GHD:301.77,CRS:319.61,***|-1 LAT:36956188,LON:-122063904,ASL:34.76,ALT:34,ALH:29,***|-2 WPN:2,BER:257,DST:54,CRS:317.06,SPD:23.57,CRT:0.30,WPa:125,***|-3 ASP:0,RLL:-20,PCH:-5,THH:30,rSv:7,pSv:10,rSp:-31,pSp:5,tSp:85,***|-4 GYRO:4,Rate:428,vRef:513,Bias:-89,***|-- AP Mode:1,GPS:0,Mir:189,CH1:1,CH2:0,IMU:318.86,CMG:305,GHD:300.63,CRS:310.88,***|-1 LAT:36956340,LON:-122064112,ASL:35.76,ALT:35,ALH:29,***|-2 WPN:2,BER:230,DST:45,CRS:305.32,SPD:24.04,CRT:0.00,WPa:125,***|-3 ASP:0,RLL:-44,PCH:-7,THH:30,rSv:10,pSv:11,rSp:-40,pSp:5,tSp:85,***|-4 GYRO:54,Rate:478,vRef:513,Bias:-89,***|-- AP Mode:1,GPS:0,Mir:192,CH1:0,CH2:-1,IMU:310.09,CMG:279,GHD:278.19,CRS:297.76,***|-1 LAT:36956444,LON:-122064368,ASL:36.46,ALT:36,ALH:29,***|-2 WPN:2,BER:196,DST:42,CRS:291.87,SPD:26.01,CRT:0.40,WPa:125,***|-3 ASP:0,RLL:-44,PCH:0,THH:30,rSv:11,pSv:2,rSp:-36,pSp:5,tSp:85,***|-4 GYRO:82,Rate:507,vRef:514,Bias:-89,***|-- AP Mode:1,GPS:0,Mir:192,CH1:0,CH2:0,IMU:297.90,CMG:257,GHD:259.03,CRS:284.54,***|-1 LAT:36956468,LON:-122064512,ASL:36.96,ALT:36,ALH:29,***|-2 WPN:2,BER:164,DST:45,CRS:280.27,SPD:24.76,CRT:0.20,WPa:125,***|-3 ASP:0,RLL:-45,PCH:-10,THH:30,rSv:8,pSv:13,rSp:-45,pSp:5,tSp:85,***|-4 GYRO:9,Rate:433,vRef:513,Bias:-89,***|-- AP Mode:1,GPS:0,Mir:192,CH1:0,CH2:0,IMU:285.74,CMG:239,GHD:238.06,CRS:275.35,***|-1 LAT:36956460,LON:-122064768,ASL:37.16,ALT:37,ALH:29,***|-2 WPN:2,BER:137,DST:46,CRS:264.31,SPD:18.75,CRT:0.70,WPa:125,***|-3 ASP:0,RLL:-37,PCH:-8,THH:30,rSv:10,pSv:12,rSp:-34,pSp:5,tSp:85,***|-4 GYRO:46,Rate:471,vRef:514,Bias:-89,***// ---------------I put what-ever I want in the print_data() function of ArduPilot, and it gets copied to the output stream.After the flight, I post-process the data file into a KML for Google Earth, so I can evaluate the results of the latest code changes.
Posted by Pete Burnight on September 19, 2009 at 11:16am
I just finished setting up my Magpie AP airplane to use the ArduPilot UAV controller, and used a fast and easy way to setup the PID and trim values.I made a ground station that can send new trim values up to the plane while it's in flight. That made trimming easy - only took two flights to get it straight, level, and stable.
The ground station consists of an ATMega168 running the code, an XBee pro for the radio transceiver, a box full of buttons, a rotary pot to adjust values, a serial LCD for the display and a 1700 mah NiMh battery.The ArduPilot code was changed to put many of the adjust and trim constants into EEPROM settings and code variables so they could be changed, and stored on the fly as tuning progressed.The ground station allows me to send up new PID values (Kp, Ki, Kd, Min/Max) and trim settings (roll_trim, pitch_trim) as well as any other variables I'm adjusting (Fly_by_Wire_Roll_Gain, Fly_by_Wire_Throttle_Set_Point, etc)
The ArduPilot code only required a small change to add the code required to receive the messages from the ground. (The GPS was removed, and the XBee on the plane was connected to both Rx & Tx for send/receive to/from the ground station during the trimming process)
The commands from the ground are very simple. I only send one value at a time. ( Roll_Kp = 0.3 ) The ground station controller uses a Menu system to scroll thru the various commands available. Each screen shows the variable being adjusted, it's current value (sent from the plane via the std ArduPilot Print_Data sequence) The rotary pot is used to dial in a new value. Once the value is set via the pot, one of the red buttons is pressed to actually transmit the command to the airplane. The code in the plane sends back an "ack" and the ground station beeps 3 times to signal a successful transmission of the data to the plane. That way I KNOW the new value is in place. I can also confirm the new value via the display - the aircraft is always transmitting the current values - and these show up in the ground station display in real time.Using this ground station to up-link data to the aircraft made setup and trimming a breeze.In general, here is the sequence I followed:1. Do ground testing of the UAV control stuff - test the sensors to confirm control surfaces move in the proper direction when tilting the aircraft.2. Get the aircraft flying under manual control. Adjust and trim for straight, level flight.3. Switch to "Fly by Wire" mode and watch what happens.(Be ready to quickly switch back to manual control)Does the aircraft dive?, climb?, Roll?, wobble?, Stall?, all of the above?4. My aircraft oscillated about the roll axis, and wanted to dive.I knew this meant the roll PID was set too high, and the pitch trim was off.5. I decided to work on the roll values first.- I turned off the pitch PID (by setting pKp and pKi to zero.)- I turned off the roll Ki (set it to very low: .04), so I could first tune Kp.Oscillations mean Kp is set too high. The PID is overshooting it's mark.There are some good articles on tuning PIDs.The current EasyStar Kp value was 0.7. I started dropping by 0.1 to 0.6, 0.5, and at 0.4 the oscillations stopped. I took manual control of the airplane, placed it in a steep roll, and turned on the AutoPilot. The plane immediately righted itself, and returned to almost straight flight. Increasing the roll Ki to 0.1 brought the aircraft to a more stable level position. (adjust Kp for quick, gross response without oscillations. Adjust Ki to fine tune the setting)6. I needed to adjust the pitch trim. The aircraft wanted to dive.I turned the pitch PID values back on (Kp = 1.3, Ki = 0.7)The current trim setting was zero. I sent up the value pitch_trim = 10. From manual straight and level flight, I switched into Fly by Wire mode, aircraft immediately dove. Back to manual control. Sent up the value pitch_trim = -10. Entered Fly by Wire, and the aircraft was flying straight and level.I had been hoping I could read the pitch & roll data real time during flight via the ground station, but I kept getting radio interference during flight, and unreliable radio connection while the aircraft was overhead. We had to glide the aircraft low and slow (no motor) near the ground station to send up commands.
Read more…