Friday, December 24, 2010

Fanwing simulation in OpenFOAM (a free CFD program)



This is one of my many Fanwing simulation (and other) cases run in OpenFoam, which is an open-source CFD code. The flow field patterns match well with the few research papers that have been done on the Fanwing (which used CFX/Fluent, and compared to physical experimental observations). The colors represent pressure values and the white lines are streamlines.

As you can see, the primary lift mechanism is the low-pressure eccentric vortex formed by the fan. Also, if you look at videos of the fanwing, you will notice the sound created by it can be explained by the pressure oscillation at the front of the wing (~400 Hz). The pressures within the rotor cannot be assessed by Bernoulli's equation for incompressible flow, because of vorticity. Vorticity is causing the stagnation pressure of the rotor interior to change, specifically to lower greatly. Inside the rotor the static pressure takes on the value of this lowered stagnation pressure, as the velocity inside the rotor at the center of the eccentric vortex is near zero. This simulation uses a transient solver. The turbulence model used is k-Omega SST. The blades are turning at 1480 RPM. The freestream is 6.8 m/s. The whole simulation takes place over a quarter of a second.

This case uses GGI (generalized grid interface), where the rotor mesh (blades) rotates with respect to the casing. The interface is resolved by using weighting factors on the overlapping and interfacing cells, without the mesh actually deforming, unlike sliding interface meshes (Google it for more info).
You may have noticed the weird streamline behavior at the interface of the rotor and outside air (that they disappear and appear). I do not think it is an indicator of something wrong with the simulation, but probably merely that the streamline integrator cannot handle the tiny gaps at the rotor-stator interface.

The mesh was created using gmsh. A great tutorial for setting up 2D cases for OpenFOAM can be found here. The whole simulation was run in Ubuntu (necessarily, OpenFoam doesnt work on Windows.....yet). The post-processing was done in ParaView.

CFD-online.com is a great resource for beginning with OpenFOAM. I have had important questions of mine answered there; it is a pretty active forum. I could not have done this simulation without it.

So everything needed for this was FREE! And a talented high-schooler could set up and use this.

Wireless PWM with ADC: Xbee with No Microcontroller (Direct I/O)



Here we look at two xbees communicating, with no microcontroller. In this example one takes acts as an ADC (Analog to Digital Converter), and sends the data to the recieving xbee, which outputs a pwm signal proportional to the ADC reading.

The Xbee pins have oneto-one correspondence; Pin AD3 on one Xbee only communicates with Pin AD3 on the other, etc.

The pwm signal is at 15625 Hz, which is ideal for PWM (pulse-width modulation) motor control if I am not mistaken (there will soon be another page up with microcontroller-less xbees controlling a 24V DC, 10A, 135W motor).

The xbees in this example are using Lady Ada's adapter. With this adapter, the setup is quite simple, as it comes with a voltage regulator and a regulated 3V output. The configuration is made simple as well by its FTDI-cable-ready pinouts. However, to get to the VREF (for ADC), AD2 (for analog signal input), and PWM2 (PWM output) pins, extra jumpers were needed to be soldered. The adapter leaves these pins accesible, but as holes.

Connecting and configuring the xbee is quite simple. There are many tutorials online. For configuration, this example utilized the Lady Ada's tutorial. For mcu-less communication, the configuration code Rob Faludi's website was referenced (my circuit was quite different), as well as the xbee product manual.

I have read that only the AD1 pin will communicate with PWM1, and only AD2 will only communicate to PWM2. I chose PWM2 (for no important reason).

On the ADC xbee, I jumpered the 3V regulated output to VREF and also used the 3V output on the pot. To power the xbees, I used a ~4-4.5 V source on the 5V input on the adapter. 3.30V was measured at 100% duty cycle on the PWM output.

The configuration code is below. Good luck!

For potentiometer reader (controller):

//pot (potentiometer) reader configuration
//initialization: type "+++", WAIT few seconds for ok,
//then type "AT" and press enter, get ok, then issue commands.
//(numbers are in hexadecimal format)

ATID 4064
ATMY 40
ATDL 64
ATD1 2
ATIR A //(A=10 in hexadecimal format)
ATIT 1
ATWR

For PWM outputter (reciever):


//pwm outputter
//initialization: type "+++", WAIT few seconds for ok,
//then type "AT" and press enter, get ok, then issue commands.
//(numbers are in hexadecimal format)

ATID 4064
ATMY 64
ATDL 40
ATP1 2
ATIU 1
ATIA 40
ATWR

Arduino Xbee Wireless Full-Range Joystick FPS Servo Turret



Here we look at a full-range servo turret controlled wirelessly by Arduino via Xbee modules. The title says "fps" because if you were to have the point of view of the pencil tip, the joystick would control like an fps video game.

It works by moving the turret whenever the joystick is moved outside the neutral center position, and stays at whatever position its at when the joystick is moved back to the center position. Just like an fps video game.

You may have noticed, one joystick is superfluous in this demo. That will be used when (or if) I create an "fps bot", which would have a live wireless camera, omni wheels, and other things to allow for an fps control of a physical robot that has all of the movement freedom of a typical fps video game.

This project utilized Xbee modules with the FTDI-ready adapter that can be found at www.adafruit.com, Arduino Duemilanove, HS-311 Standard servos, and these adafruit.com joysticks.

Here's an Arduino Reference, if you need it. For the Xbees, plenty of resources online can help you configure and set it up. Good luck!

Below are two codes, one for the controller and one for the reciever. If you guys need help, comment on my corresponding Youtube video and I will try to get to it, as I am on Youtube quite often.

Controller code:

//CONTROLLER CODE
//Feel free to use this code however you like, but please credit lordvon and this site!
//'ud' and 'lr' are the pot signals for the up/down and left/right pots, respectively.

//(Joysticks and their pot values are not perfect, so some things have to be empirically
//determined. The serial read function is a great tool for collecting the values.)
//range of ud: 29-1014 (middle: around 479-540; avg:~510, center:+/-35)
//range of lr: 0-960 (middle: around 419-471; avg:445, center:+/-30)
//minimum range extreme (from avg) magnitude: 445
//maximum tolerance value: 35

//Print values as 'topic:value', where 'topic' denotes either 'ud' or 'lr',
//and 'value' is the associated pot value.
//Reciever is prompted to record by ':', and ended by ','.

//"" gives characters, '' gives characters in integer form.

//Empirically determined and/or arbitrary constants.
#define ud1 0
#define lr1 1
#define highpulse 2400
#define lowpulse 600
#define lr1avg 445
#define ud1avg 510
#define tolerance1 40
#define range1 445
#define maxadd 35

int ud1val;
int lr1val;
int lr1pulse;
int ud1pulse;
int lr1previous;
int ud1previous;
boolean update;

void setup()
{
Serial.begin(9600);
//initialize servo to its center position.
lr1previous = lr1avg;
ud1previous = ud1avg;
Serial.print(':');
Serial.print((highpulse-lowpulse)/2+lowpulse);
Serial.print(',');
Serial.print((highpulse-lowpulse)/2+lowpulse);
}

void loop()
{
ud1val = analogRead(ud1);
lr1val = analogRead(lr1);
update = (abs(lr1avg-lr1val) > tolerance1) || (abs(ud1avg-ud1val) > tolerance1);
if (update)
{
lr1val = newval(lr1val,(lr1avg-range1),(lr1avg+range1),lr1avg,lr1previous);
ud1val = newval(ud1val,(ud1avg-range1),(ud1avg+range1),ud1avg,ud1previous);
lr1previous = lr1val;
ud1previous = ud1val;
lr1pulse = map(lr1val,(lr1avg-range1),(lr1avg+range1),lowpulse,highpulse);
ud1pulse = map(ud1val,(ud1avg-range1),(ud1avg+range1),lowpulse,highpulse);
//':' is packet delimiter, ',' is UD/LR delimiter.
//Each cycle of delimiters denote a packet.
//Packet: UD value first, then LR value. (:UD,LR:)
Serial.print(':');
Serial.print(ud1pulse);
Serial.print(',');
Serial.print(lr1pulse);
delay(25);
update = false;
}
}

int newval(int val, int minval, int maxval, int avgval, int previousval)
{
int increment;
increment = val-avgval;
if (increment > 0)
{
increment = increment - tolerance1;
}
else if (increment < 0) { increment = increment + tolerance1; } increment = map(increment,0,range1-tolerance1,0,maxadd); val = increment + previousval; if (!(val <= maxval) || !(val >= minval))
{
if (val > maxval)
{
val = maxval;
}
else
{
val = minval;
}
}
return val;
}

Reciever code:

//RECIEVER CODE
//Feel free to use this code however you like, but please credit lordvon and this site!
#include
<Servo.h>

Servo elevation;
Servo azimuth;

#define highpulse 2400
#define lowpulse 600

char received;
char pulsechar[4];
int counter=0;
int pulse;
boolean delimiter;

void setup()
{
Serial.begin(9600);
while (!Serial.available()){}
received = Serial.read();
elevation.attach(12);
azimuth.attach(13);
}

void loop()
{
// if (Serial.available())
// {
// Serial.println(char(Serial.read()));
// }
delimiter = (received == ',') || (received == ':');
if (!delimiter)
{
counter = 0;
while (!delimiter)
{
while (!Serial.available()){}
pulsechar[counter] = received;
counter += 1;
received = Serial.read();
delimiter = (received == ',') || (received == ':');
}
pulsechar[counter] = '\0';
pulse = atoi(pulsechar);
switch (received)
{
case ',':
azimuth.writeMicroseconds(pulse);
break;
case ':':
elevation.writeMicroseconds(pulse);
break;
}
}
else
{
while (!Serial.available()){}
received = Serial.read();
}
}

Nintendo DS Touchscreen Arduino Servo Turret



Here we describe how to set up a full-range 3-servo turret controlled by a Nintendo DS touchscreen and Arduino Duemilanove with an ATMega328 microcontroller. The magnitude of the distance from the center of the touchscreen to the touched position will control the angle of the servo on top for elevation, while the azimuth, or rotation of the ground plane, will be controlled by basically polar coordinates on the DS touchscreen.

It's just a demo; I did this to become familiar with Arduino microcontrollers. You can mount a bb gun controlled by a simple switch. Making it wireless may be one of my next projects.

One of the things that make the Arduino really easy to use is the libraries. These are basically sets of functions that achieve a certain category of tasks. In this project we use the "Servo" library. Information on this library can be found here.

Below is the code. The ranges of movement corresponding to the ranges of touch can be easily adjusted by changing the constants defined at the beginning, so you can make the turret easier to use.

Circuit details:
--See code comments for connections to the touchscreen (X1,Y2,X2,Y1,Xin,Yin).
--Connect the servos (HT-311) to an appropriate power source (red to positive, black to negative).
--Group the signal wires (yellow) of the azimuth (ground-plane) servos together and connect them to digital pin 11.
--Connect the elevation servo signal wire to digital pin 12.
--Be sure to connect the ground of the arduino to the ground of the servo power supply. I do not know why this is necessary, haha, and it took me a while to figure out.
--That's it! Here's an Arduino Reference, if you need it.

//Feel free to use this code however you like, but please credit lordvon and this site!
//Code was adapted from that found on (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243499684/0).
#include <Servo.h>

Servo azimuth;
Servo elevation;

// Empirically determined; Voltage between an output high and output low pin.
#define Vref 4.46
#define highpulse 2400
#define lowpulse 600
#define quarter (highpulse-lowpulse)/4
#define s1high lowpulse+quarter/2
#define s2high s1high+quarter
#define s3high s2high+quarter
#define s4high s3high+quarter
#define sdivide .7071
#define maxmag 420

//When looking at DS screen with connector bottom left,
// the connections are from left to right:
//TOP, X1
//LEFT, Y2
//BOTTOM, X2
//RIGHT, Y1

// Digital pin connections (used to drive power)
#define X1 2 // TOP to Digital output 5
#define Y2 3 // LEFT to digital output 2
#define X2 4 // BOTTOM to digital output 3
#define Y1 5 // RIGHT to digital output 4

// Analog inputs
#define Xin 3 // From X1
#define Yin 4 // From Y1

// set initial touched position
int touchX;
int touchY;
int xpos;
int ypos;
float cosine;
float sine;
float mag;
int apulse;
int epulse;

void setup()
{
azimuth.attach(11);
elevation.attach(12);
}

void loop()
{
if (touched())
{
xpos = touchX - 511;
ypos = touchY - 511;
if(!(xpos == 0 and ypos == 0))
{
mag = sqrt(pow(ypos,2) + pow(xpos,2));
sine = ypos / mag;
if(abs(sine)>=sdivide)
{
cosine = xpos / mag;
if(ypos>0)
{
apulse = map(cosine*10000,-7071,7071,s2high,s3high);
}
else
{
if(xpos >= 0)
{
apulse = map(cosine*10000,7071,0,s4high,highpulse);
}
else
{
apulse = map(cosine*10000,0,-7071,lowpulse,s1high);
}
}
}
else
{
if(xpos>0)
{
apulse = map(sine*10000,7071,-7071,s3high,s4high);
}
else
{
apulse = map(sine*10000,-7071,7071,s1high,s2high);
}
}
}
azimuth.writeMicroseconds(apulse);
if(mag>maxmag or mag<0)
{
mag=maxmag/2;
}
epulse = map(mag,0,maxmag,highpulse,lowpulse);
elevation.writeMicroseconds(epulse);
}
}

boolean touched()
{
boolean touch = false;
// Horizontal routine Y1:Y2::Vcc:Gnd
pinMode(Y2, OUTPUT);
digitalWrite(Y2, LOW);
pinMode(Y1, OUTPUT);
digitalWrite(Y1, HIGH);
// X1,X2 high impedance (input mode)
pinMode(X1, INPUT);
pinMode(X2, INPUT);
// wait a bit, then read from X1
delay(10);
touchX = analogRead(Xin);
// Vertical routine X1:X2::Vcc:Gnd
pinMode(X2, OUTPUT);
digitalWrite(X2, LOW);
pinMode(X1, OUTPUT);
digitalWrite(X1, HIGH);
// Y1,Y2 high impedance (input mode)
pinMode(Y1, INPUT);
pinMode(Y2, INPUT);
// wait a bit, then read from Y1
delay(10);
touchY = analogRead(Yin);

// Only read touch if coords are below 1000 and above 0
// (stops errors with certain screens)
if(touchX < 1023 and touchX > 0 and touchY < 1023 and touchY > 0)
{
touch = true;
}

return touch;
}