Showing posts with label uas sensors. Show all posts
Showing posts with label uas sensors. Show all posts

Wednesday, November 11, 2009

If you were an UGV, what kind of truck would you be?

This week I have been digging into the 2d plane again. Yeah, that sounds kind of odd, but I think it is pretty accurate. I am looking for a platform that will let me test my own avionics and code in a way that will not have $10k at 300' and then in a heartbeat have it smashed into dust . The ground does rush up pretty quickly at the wrong times.

One of my colleagues at my real job has egged me on, so I have been evaluating what it takes to get my software to work in a constant altitude kind of setup. Ok for all non-geeks, it means on a truck. I think that the premise has been tossed around several times.

I found that several things during this evaluation. One, electric 1/5th scale trucks are not common. There are lots of 25cc gasser trucks. The three that I was reviewing were:

HPI Baha 5T






NuTech Mega Monster






Redcat Racing







After reading a bunch of information about all of these chassis, and 10's or 100's of reviews and projects. I even found 1 from Sony on the HPI... The Nutech seems to be ahead in the research. It does not weigh as much as the Redcat. The Nutech is Chinese, try not to just mail money to them these days. The HPI has great reviews but is significantly more expensive. I have not found anything to say that one is better than the others. So it may come down to weight and cost.

I will not be using the 23-26cc engine. It will be replaced with some pretty beefy NEU motors and a lot of LiPo. My Mini9 will probably be its brain for the foreseeable future. I do need to work on a COM to handle the code.

Kind of cool to watch the movies of these guys flipping around. I know mine won't do it, but will get its fair share of curb crashing I am sure.

Friday, October 23, 2009

Phidgets UltraSonic RangeFinder

The rangefinder is not a Phidgets. They are reselling a LV Max Sonar EZ1 from MaxBotix. You get in the bag the sensor (transceiver) and a small wire bundle. The wires need to be soldered into the proper pins. Just for reference from the pin closest to the mounting hole,

black
red
open
green
white
open
open

wire meanings

black - ground
red +5V
green signal to trip the ultrasonic ping, +5V I think, but I do not use it
white 5V signal

This will cable your phidgets three wire signal cable correctly. Remember this is not a normal sensor, it is one of their analog sensors. So you will need one of their a/d boards or interface boards in their terminology. The event that you will want to hook, the SensorChange event. Everything the system reads a new voltage on the port it fires sensor change. See my post on the 888 interface kit to see how to set up the interface kit.

Here is my way of switching the data so you only need one method for any port change.

protected void SensorChange(object Sender, SensorChangeEventArgs Args)
{
try
{
InstrumentType _type = InstrumentType.notset;
int _index = 0;

switch (Args.Index)
{
case 7:
_type = InstrumentType.ultrasonicRangeFinder;
_index = 4;
break;
case 6:
_type = InstrumentType.ultrasonicRangeFinder;
_index = 3;
break;
case 5:
_type = InstrumentType.ultrasonicRangeFinder;
_index = 2;
break;
case 4:
_type = InstrumentType.ultrasonicRangeFinder;
_index = 1;
break;
case 3:
_type = InstrumentType.ultrasonicRangeFinder;
_index = 0;
break;
case 2:
_type = InstrumentType.pressureTotal;
totalPressure = convertVoltageToUnit(_type, Args.Value, _index);
speed = getPressureSpeed(totalPressure, staticPressure, staticTemperature);
break;
case 1:
_type = InstrumentType.temperature;
staticTemperature = convertVoltageToUnit(_type, Args.Value, _index);
speed = getPressureSpeed(totalPressure, staticPressure, staticTemperature);
break;
case 0:
_type = InstrumentType.pressureStatic;
staticPressure = convertVoltageToUnit(_type, Args.Value, _index);
speed = getPressureSpeed(totalPressure, staticPressure, staticTemperature);
pressureAltitude = getGeoPotentialAltitude(staticPressure / units.kPaToPsf);
break;
}
if (_type == InstrumentType.ultrasonicRangeFinder)
{
rangeFinders[_index] = convertVoltageToUnit(InstrumentType.ultrasonicRangeFinder, Args.Value, _index);
}

}
catch (Exception _exc)
{

throw new Exception(className + " protected void SensorChange( Sender, Args) :: " + _exc.Message + "\n");

}
}

There are some other details in this code. Since I use a bunch of helper methods to calibrate the data coming out. Remember the values (Args.Value) come out as a double so you can write a simple linear interpolation for you that will get your data back into usable units from Voltage. For simplicity's sake, I have the interpolation points in an xml config file that is read in at start time to handle each of the instruments. That way the values can easily be changed for recalibration.

The code I have presented is good simple code that is quick and should work for almost any purpose and uses solid error trapping techniques.

Tuesday, October 20, 2009

The Girls Love the Long Ball, But the Boys Love the Spin

Someone once told me that it is not the boom, it is all about the shock wave. I guess that is true, you need to feel the motion of the ocean.

You ask, what does this have to do with gyros? Well, if you cannot figure out how fast it is twisting, there is no way to know how far your ball will go. Yeah, actually a lot, since driving a golf ball is not a momentum problem alone. The dimples hold the boundary layer on and make it go much farther.

Gyroscopes are cool, I have the neat little 300 deg/s board from SparkFun, built on a 3DOF IMU. I soldered on some pins so that it is easier to connect to. Connected the ground to the system ground and connected each of the signal pins to a separate "connector". Really, easy and I am a bit electrically declined.

I then set up an easy pseudo code system to watch the ISensor.DataUpdated method. That lets me see the phidgets.datachanged event in my little API. The delegate that I use for ISensor.DataUpdated feeds my smoothing algorithm. I use the smoothing algorithm to keep out shorts and wonky voltage changes that happen. This would feed the Kalmann filter which I still do not have.

I then feed these data updated events into an ArrayList and do my Runge-Kutta routines on them to integrate the unknown functions. This running integration is how I get an approximation of the position of the gyro. Remember gyros return the rate of change of the gyration, an angular velocity so to speak.

We can do a simple integration to get back to a position.

xi = xdot*dt+xi-1

for constant timestep dt and the previous position, xi-1.

I will put some code together for the next post on this.

Thursday, October 15, 2009

How to Connect and Communicate With a Phidgets Interface Kit in C#

I have a bunch of there 8/8/8 kits. They are great. First, I would suggest getting their newest API package for your favorite platform.

I use visual studio, so I will have biased examples. I could regurgitate their examples, but mine hopefully don't suck.

From a simple command line application. You may get into some static thread issues, but they are generally avoidable.
in your main()...

try
{
InterfaceKit ik1 = new InterfaceKit();
ik1.open();
}
catch (Exception _exc)
{
Console.WriteLine("interface kit error : " + _exc.Message + "\n",);
}

This is pretty simple, and just opens the IK for management. You have to set up some events. In a constructor method, I dump the following code after you execute the Open() method.

do
{

if (ik1.Attached)
{

Console.WriteLine("ik1 attached.");

//interface kit events
ik1.Attach += phidgets_Attach;
ik1.Detach += phidgets_Detach;
ik1.Error += phidgets_Error;
ik1.SensorChange += _ik1_SensorChange;
ik1.OutputChange += _ik1_OutputChange;
ik1.InputChange += _ik1_InputChange;
}
else
{
Console.WriteLine(("retry : " + retry + " waiting for ik1 attach");
}
retry++;
} while (retry < 10 && !ik1.Attached);


I like these do loops for the initialization setups. Why, because it retries and does not fail just because. It says which instrument is having an issue before it proceeds. I use the phidgets_xxx methods to have a standard system for handling the major Phidgets methods .

#region Phidgets event handlers
///
/// handle the phidget device discovery events
///

/// /// protected void phidgets_Attach(object Sender, AttachEventArgs Args)
{
try
{
Console.WriteLine(Args.Device.Type + " attached.");
}
catch (Exception _exc)
{
throw new Exception(className +
" protected void phidgets_Attach( Sender, Args) :: " + _exc.Message +"\n");
}
} //phidgets_Attach
///
/// handle the phidget device discovery events
///

/// /// protected void phidgets_Detach(object Sender, DetachEventArgs Args)
{
try
{
Console.WriteLine(Args.Device.Type + " detached.");
}
catch (Exception _exc)
{
throw new Exception(className + " protected void phidgets_Detach( Sender, Args) :: " + _exc.Message +"\n");
}
} //phidgets_Detach
protected void phidgets_Error(object Sender, ErrorEventArgs Args)
{
try
{
Console.WriteLine("phidgets error : " + Args.Code + " " + Args.Description);
}
catch (Exception _exc)
{
throw new Exception(className +
" protected void phidgets_Error( Sender, Args) :: " + _exc.Message +"\n");
}
} //phidgets_Error
Then some simple methods to catch changes per port. I connect different instruments to each port. I have to calibrate the readings for each port so that the readings make sense directly from the Read() methods.

protected void _ik1_SensorChange(object Sender, SensorChangeEventArgs Args)
{
try
{
InstrumentType _type = InstrumentType.notset;
int _index = 0;

switch (Args.Index)
{
case 7:
_type = InstrumentType.notset;
_index = 4;
break;
case 6:
_type = InstrumentType.notset;
_index = 3;
break;
case 5:
_type = InstrumentType.notset;
_index = 2;
break;
case 4:
_type = InstrumentType.notset;
_index = 1;
break;
case 3:
_type = InstrumentType.notset;
_index = 0;
break;
case 2:
_type = InstrumentType.notset;
break;
case 1:
_type = InstrumentType.gyro;
gyro.SetRollData(
convertVoltageToUnit(InstrumentType.gyro, Args.Value, 1));
break;
case 0:
_type = InstrumentType.gyro;
gyro.SetPitchData(
convertVoltageToUnit(InstrumentType.gyro, Args.Value, 0));
break;
}
if (_type == InstrumentType.ultrasonicRangeFinder)
{
rangeFinders[_index] = convertVoltageToUnit(
InstrumentType.ultrasonicRangeFinder, Args.Value, _index);
}
Console.WriteLine(className +
" instrument change " + _type + " value: " + Args.Value);
}
catch (Exception _exc)
{
Console.WriteLine(className +
" protected void _ik1_SensorChange( Sender, Args) :: " + _exc.Message + "\n");

}
} //_ik0_SensorChange
protected void _ik1_InputChange(object Sender, InputChangeEventArgs Args)
{
try
{
}
catch (Exception _exc)
{
Console.WriteLine(className +
" protected void _ik1_InputChange( Sender, Args) :: " + _exc.Message + "\n");
}
} //_ik0_InputChange
protected void _ik1_OutputChange(object Sender, OutputChangeEventArgs Args)
{
try
{
}
catch (Exception _exc)
{
Console.WriteLine(className +
" protected void _ik1_OutputChange( Sender, Args) :: " +
_exc.Message + "\n");
}
} //_ik1_OutputChange

///
/// converts the read value, probably in volts to the correct units
/// this will also apply a 6:4 smooth, old value*0.6 + new value*0.4 to help
/// smooth out the readings
///

/// type of instrument read, to get the correct coeefficients/// reading of the instrument/// some instruments are indexed/// the calibrated value
protected double convertVoltageToUnit(InstrumentType Type, double Reading, int Index)
{
double _value;
try
{
int _InstrumentTypeIndex = 0;
double _old = 0;
switch (Type)
{
case InstrumentType.accelerometer:
_InstrumentTypeIndex = 2;
_old = rawAcc[Index];
break;
case InstrumentType.gyro:
_InstrumentTypeIndex = 3;
_old = gyration[Index];
break;
case InstrumentType.pressureStatic:
_InstrumentTypeIndex = 0;
break;
case InstrumentType.temperature:
_InstrumentTypeIndex = 1;
break;
case InstrumentType.ultrasonicRangeFinder:
_InstrumentTypeIndex = 4;
_old = rangeFinders[Index];
break;
case InstrumentType.servo:
_InstrumentTypeIndex = 5;
_old = servos[Index];
break;
case InstrumentType.notset:
break;
}
double A = double.Parse(
AppSettings["instrumentCalibration" + _InstrumentTypeIndex + "A"]);
double B = double.Parse(
AppSettings["instrumentCalibration" + _InstrumentTypeIndex + "B"]);
double C = double.Parse(
AppSettings["instrumentCalibration" + _InstrumentTypeIndex + "C"]);
//apply basic filter to smooth the data
// value = Ax^2+Bx+C
if (_old != 0)
{
_value = 0.6*_old + 0.4*(A*Reading*Reading + B*Reading + C);
}
else
{
_value = A*Reading*Reading + B*Reading + C;
}
}
catch (Exception _exc)
{
throw new Exception(className +
" protected double convertVoltageToUnit( " + Type.ToString("0.000") +
" , " + Reading.ToString("0.000") + " ) :: " + _exc.Message + "\n");
}
return _value;
} //convertVoltageToUnit

It is pretty simple. I then just use events to manage the reading of the ports or inputs. It is really easy at this point. I put the whole session into a System.Timers.Timer loop or a Console.ReadLine system to control the starting and stopping of the application.

A Stability Manager is Good to Have, But You Could Wing It

More about the loathe and planning of your own autopilot. I used a hopefully simple system to handle the stability manager. I set up a system that uses a simple interface for strategies to get to keep the plane in the air. From this strategy interface, I developed several strategies that specialized in different aspects of managing the airplane's performance.

The strategies then calculate a solution and then pass it to the task manager. The servo manager pops the solution off the task manager's stack and gives. From the solution the servo manager converts the new inputs to a servo position map. It converts the map to servo motion and executes the changes. The Phidgets controllers that we use allow for several settings to be set as it moves the servos into position.

It may seem a bit "math nerd", but the cool thing about the Phidgets controller is that you can set the final position, the speed and the magnitude of the acceleration used to make the changes. In general, I leave it to figure out the acceleration. However, in coordinated systems it may be important to set the jerk of the servo arms so that you do not lock up linkages or other mechanical interfaces to the servos.

My strategies are simple. I tried to decorate a base object with virtualized methods, that way if I missed or the method's implementation did not just come to me the methods would have something by way of implementation. This seems to be a good way to handle this kind of "how the heck do I do this" programming. I am sure there are more professional ways of doing this, but here is how I did it.

///
/// used in the strategy design pattern, so that you can use different strategies and change
/// them by delegate
///

public interface IStrategy
{
///
/// execute the strategy but do not return any results
///

void Execute();
///
/// exectute the strategy, return the result
///

/// result of the strategy
object Execute(bool bResults);
///
/// exectute the strategy, return the result and a object
///

/// return a second resultant object /// result of the strategy
object Execute(out object Result);

}

public interface IStrategyResult
{
object ShowResult();
}

Now these are interfaces that I used to make a series of strategies to balance the different flight aspects and how to keep the system on course. These are run through at about 20Hz and the results are compared at 10Hz. That way decisions can be made at 5Hz and you can keep the whole thing from bumping into the data collection and servo motion phases. It happens, but if the frequency is high enough, we can miss a solution or two and still make it in time to decide which solution was best.

A simple markup model then decides which result is most successful and passes it down the chain. My biggest troubles are always how to correctly smooth the position and orientation data. They are effected by several different coupling and acceleration balances. I have heard about these Kalman filters that everyone loves so much, but have yet to be successful implementing one myself from scratch. I usually keep a simple integrator system to keep the changes small(er) and tend to stay in constant speed-level flight if at all possible. That is better for overall system performance anyway. You have to pay the piper for every energy change.

Tuesday, October 13, 2009

Business Opportunities in Central Europe or Brazil

Buzz Labs is looking for business contacts in Central Europe or Brazil. We are seeking interesting applications of UAS and UGV in most places or partners to help develop certain aspects of technologies for related projects. Our business is about developing technologies to support unmanned systems in general or how to achieve certain commercial or scientific requirements.

Maximizing our understanding of networking and aviation knowledge is allowing for fascinating new insights into what is possible even with the current states of technologies. We are most interested in multi-spectral image analysis, distributed pico-computing, alternatives to radio control, streaming technologies,power systems and system configuration.

Central Europe and Brazil are great stable markets that we would like to become a bigger player in. Please feel free to contact us with questions or ideas.

info@fatmanflying.com

Saturday, October 3, 2009

Of Directors and Other School Marms


In my model, there is one king pin class. It does all of the general start to stop management of the system. The director class is where all of the managers hang from. I do not really hang a lot of factories off of the director, the  decorations to the class are minimal.

At start up, the director sets all of its properties from the configuration files and starts the managers. The managers in my model are:

  • instrumentation manager, im
  • task manager, tm
  • ai manager, aim
  • stability manager, stab man
  • communication manager, comm
  • display manager, display

This can be relatively complicated since the observation system must be handled by the director. The data flows from the instrumentation manager to the stability manager (stab man observes im). Task managers observe the communication manager to queue jobs, they also watch the stability manager. The Instrumentation Manager also watches the task manager, but only because it also contains the servo manager. Tasks go from the stability manager to the task stack and are then sent to the servo manager.

I thought this was the best way because whether or not the communications manager has a command, the task queue will be populated. The servo manager will then grab tasks and handle them in a first come first served pattern. If a command expires by sitting in the queue too long it is dumped by the task manager when the servo manager pops it off of the queue. As the system becomes more complicated, there could be more queues to do more things. However, tasks that require moving a servo always go to the servo queue.

I have read the great MAV blog by Tom Pycke. He has lots of things to say on the topic of real time operations. However, my idea is that they are not really so important. I do agree with him that his way of dealing with garbled or miscommunicated commands to the MAV is pretty interesting. My way seems to be not so bad either. The system must be able to fly itself, you tell it a basic plan to fly along. Any command inputs from the ground station are "exceptions".

Exceptions are easier to manage because you do them as soon as possible. When they expire you stop doing them, the system still tries to make the best of the situation and go back to the plan. This guidance could make a complete mess that breaks everything, in the case of coordinates that are for a different city. Your system may start flying off in a crazy direction trying to get home. However, that is why there are a few directives that the director knows and is the only one that can execute.

I keep a few tasks in the clip for a special occasion. My "director-only" tasks are:
  • shutdown
  • go to safe altitude
  • land immediately
  • idle
Shutdown is a good example of a game ender. If you shut down the computer systems and the current. For a UGS it may not be so bad, a UAS just falls out of the sky. In my overly instrumented systems, we have an aircraft on ground sensor. It is nothing as cool as a weight on wheels, but it does check to see how far the fuselage is off of the ground. This is to enforce a flight floor system, only in certain situations will it not try to climb to the safe altitude. In general, the safe altitude should be above the tree line. Altitude is safe for a UAS, this is not as necessary for a UGS but it can be interesting to have. Land immediately works for either kind of system, it forces the system to plot the fastest route to the start area/landing strip. Once the system arrives in the pattern, it will begin normal landing/parking processes. Idle is similar to shutdown, but it does not cut the power. It however, means RC override in implementation. The computer lets the system freewheel, this can be useful if the computer keeps doing crazy crap and you have to bring it back.

The ai (artificial intelligence) manager is the basis for the flight state. It reads the preset," at least we have somewhere to go" map and determines which mission phase the system should be in. Mission state is important information because it will time the use of instrumentation or cameras, as well as when to hit the "panic" button. The stability manager uses this to set system configurations and movement regimes for the  system.

A director class is a operator, directing the data from one manager to the other. The AI manager is where the state manager exists.  More on the AIM later, check in tomorrow if I am feeling feisty and have not cut myself up or glued something good to the table.

Saturday, September 26, 2009

Gyroscopes Are Fun!

Piezo-electric gyroscopes are fun. However, I certainly had to learn a lot of things to get a usable signal from them. Remember that  gyros report a rate of change not just an absolute value. This rate presents an interesting issue, that we have to integrate the signal at each step. You may want to use a more advanced curve fitting integration scheme than the one that I will present here.

Integration for those not in the know is just finding the area under a curve. The catch is that, you will never be given a curve by the gyroscope. Not having a curve means that we must discretize the signal, from these step-wise readings we can write a formula that will help us back out what the absolute angle is in that direction. However, since we are integrating these formulae will not take into account small changes that happen to the signal between samples.






The simplest form of integration is, just calculating a rectangle. Your software will have to remember the last reading from the gyroscope and the last calculated  position.

angle change =dAngle* step length

so the basic formula would be,

Angle i = Angle i-1+ angle change

A better technique would be simply approximating the changes in the curve as linear segments and then calculating the differences as triangles or trapezoids.






This is a relatively bad way to do this, but is easy to understand. I would suggest using a multi-order approach to try and take into account some of the variations in these signals. The nuts and bolts of it is, is that the equation only works if the change in angle is relatively small compared to the current angle. If you are executing maneuvers and sampling relatively slowly this formula will lead your software astray. I would use a gyroscope sampling rate that is at half of the gyroscope's sample acquisition rate. Halving the sampling rate should keep the software getting into too much trouble from landing between readings and getting garbage.

Remember to read your manufacturer's guidance on this. It will tell you how quickly it can measure changes. We use several of these:



They are 500 deg/s sampling rate at 2mV/deg. That is usually too small for our analog to digital coverter boards (A/D boards), so we lower that to 150 deg/s and get a better signal rate of 9mV/deg. This means that we can get a nice clean signal that is several bits wide from our A/D board. Remember that your I/O system will determine a lot of things about your code.

If you sample to fast, you can have lock issues, too slow and you do not get a representative sample. I would suggest that you shoot around 200Hz for samples, and then apply your filtering to bring it down to a rate to allow your other software to make decision on the filtered values as if they were the just read values.

There are better boards available, but the ones from Phidgets.com have a great API for almost any platform. Their 8/8/8 boards are great all-around boards, but do not have the highest sampling precision. For most things, it is plenty.




I really like the .NET support and the usb interface. Even I could figure it out from their examples in only a few minutes. Next time, I will show you how easy it is to connect to the A/D board and begin acquiring signals. The code samples from Phidgets are effective and describe most of the general functions that you will need to implement a system with their instruments.