Here is a little JavaScript program I wrote that allows one to play with potential flow by inserting sources in a wind tunnel. Potential flow is an idealized flow characterized by irrotationality. The particular form used here has the added characterizations of being inviscid and incompressible, and satisfies Laplace's Equation for a velocity 'potential':
The velocities are recovered by:
Second-order-accurate finite differencing schemes are used to recover the velocities.
I will continuously be working on this, so this is by no means the final version. Currently I aim to bring the following:
-Doublets
-Vortices
-Making it into a game
It is colored by potential. Click on it!
Discrete Resolution:
Pixel Dimensions:
Showing posts with label Software. Show all posts
Showing posts with label Software. Show all posts
Saturday, May 10, 2014
Saturday, December 21, 2013
Github Code
I just wanted to share and link some code that I had uploaded to Github a while ago: https://github.com/lordvon?tab=repositories
Mainly there are two things:
-OpenFOAM Code.
-Incompressible flow solver, and related utilities.
The incompressible flow solver is based on J.B. Perot's Exact Fractional Step Method. I wanted to use this instead of OpenFOAM's PISO for transient flows. Theoretically and practically in my opinion the Exact Fractional Step Method is superior; you can find more information from Perot directly with a simple Google search. From scratch, I coded it in C (with the awesome tool VALGRIND) and got to the point of being able to simulate a 2D box in a wind tunnel using an unstructured-multiblock grid. However, the additional features I required (3D, sliding mesh interface) proved too much in terms of time and effort (especially debugging!), so I reverted back to OpenFOAM.
The OpenFOAM modules are very useful and I use them heavily today. They include mesh motion functions for the AMI moving region and turbulence models. Take a look at them if you are interested.
-Nested rotating interfaces: regions rotating and translating inside of another rotating region, i.e. cyclorotor simulations.
-Ramped rotating motion: linear speedup of rotational velocity, to allow for Courant numbers not affected too much by high-velocity transients. This is supposed to allow for faster resolution of low-frequency starting phenomenon in transient simulations.
-Proper and Official Spalart-Allmaras Turbulence Model: From a research publication written by Spalart himself in 2013. He acknowledges the confusion and variation of in the popular RANS model and establishes the correct and most up-to-date way to implement it. It differs quite a bit from the OpenFOAM standard S-A model.
-Spalart-Allmaras with ROTATION/CURVATURE CORRECTION: Detailed in Zhang and Yang (2013), this model utilizes the Richardson number to sensitize the S-A to rotation and curvature (R/C), while bypassing the significant additional computation required by standard S-A R/C correction models. The more efficient model is shown in the paper to provide almost identical results to the traditional model in several test cases.
-And others... take a look!
Mainly there are two things:
-OpenFOAM Code.
-Incompressible flow solver, and related utilities.
The incompressible flow solver is based on J.B. Perot's Exact Fractional Step Method. I wanted to use this instead of OpenFOAM's PISO for transient flows. Theoretically and practically in my opinion the Exact Fractional Step Method is superior; you can find more information from Perot directly with a simple Google search. From scratch, I coded it in C (with the awesome tool VALGRIND) and got to the point of being able to simulate a 2D box in a wind tunnel using an unstructured-multiblock grid. However, the additional features I required (3D, sliding mesh interface) proved too much in terms of time and effort (especially debugging!), so I reverted back to OpenFOAM.
The OpenFOAM modules are very useful and I use them heavily today. They include mesh motion functions for the AMI moving region and turbulence models. Take a look at them if you are interested.
-Nested rotating interfaces: regions rotating and translating inside of another rotating region, i.e. cyclorotor simulations.
-Ramped rotating motion: linear speedup of rotational velocity, to allow for Courant numbers not affected too much by high-velocity transients. This is supposed to allow for faster resolution of low-frequency starting phenomenon in transient simulations.
-Proper and Official Spalart-Allmaras Turbulence Model: From a research publication written by Spalart himself in 2013. He acknowledges the confusion and variation of in the popular RANS model and establishes the correct and most up-to-date way to implement it. It differs quite a bit from the OpenFOAM standard S-A model.
-Spalart-Allmaras with ROTATION/CURVATURE CORRECTION: Detailed in Zhang and Yang (2013), this model utilizes the Richardson number to sensitize the S-A to rotation and curvature (R/C), while bypassing the significant additional computation required by standard S-A R/C correction models. The more efficient model is shown in the paper to provide almost identical results to the traditional model in several test cases.
-And others... take a look!
Tuesday, August 13, 2013
C Programming Memory Debugging: Valgrind
I wanted to spread the word about Valgrind, a memory management debugger for C programs. I just found out about it and in the last hour or so has already caused me to identify numerous bugs in my CFD program. My CFD program (which is an unsteady, multi-block, structured grid, incompressible Navier-Stokes solver) is the largest program I have ever written and bugs occasionally pop up when inputs change (in my case, larger grids and thus larger memory allocations), and in my search for solutions I came across Valgrind. It is also very easy to use and well documented. Go to the Valgrind website and check out the 'Quick Start' section to try it out yourself.
Sunday, November 11, 2012
Getting Started with GPU Coding: CUDA 5.0
Hello everyone, this is a quick post detailing how to get started with harnessing the potential of your GPU via CUDA. Of course, I am planning to use it for OpenFOAM; I want to translate the pimpleDyMFoam solver for CUDA.
Especially awesome about CUDA 5.0 is the new Nsight IDE for Eclipse. If you have ever programmed with Eclipse (I have in Java), you know how spoiled you can get with all of the real-time automated correction and prediction.
Anyways, here are the steps I took:
I have Ubuntu 12.04, and even though the CUDA toolkit web page has only downloads for Ubuntu 11.10 and 10.04, it really does not matter. I used 11.10 without a hitch.
Download the CUDA toolkit at:
https://developer.nvidia.com/cuda-downloads
Before proceeding make sure you have all required packages:
Then go to the directory of the download in a terminal. Then type (without the angle brackets):
chmod +x <whatever the name of the download is>
sudo ./<whatever the name of the download is>
If you get an error of some sort like I did, type:
And try the 'sudo ...' command again.
The 'sudo ./...' command actually installs three packages: driver, toolkit, and samples. I actually installed the nvidia accelerated graphics driver separately via:
But if the included driver installation works for you, then by all means continue.
Now, add to ~/.bashrc by:
gedit ~/.bashrc
Scroll down to bottom of file and add:
export PATH=/usr/local/cuda-5.0/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-5.0/lib64:/usr/local/cuda-5.0/lib:$LD_LIBRARY_PATH
Now to test everything out, go to your home directory:
cd ~/
Then go to the cuda samples folder (type 'ls' to view contents).
Once in the samples folder type 'make'. This will compile the sample codes, which may take a while. If you get an error (something like 'cannot find -lcuda', like I did), then type:
Especially awesome about CUDA 5.0 is the new Nsight IDE for Eclipse. If you have ever programmed with Eclipse (I have in Java), you know how spoiled you can get with all of the real-time automated correction and prediction.
Anyways, here are the steps I took:
I have Ubuntu 12.04, and even though the CUDA toolkit web page has only downloads for Ubuntu 11.10 and 10.04, it really does not matter. I used 11.10 without a hitch.
Download the CUDA toolkit at:
https://developer.nvidia.com/cuda-downloads
Before proceeding make sure you have all required packages:
sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev Then go to the directory of the download in a terminal. Then type (without the angle brackets):
chmod +x <whatever the name of the download is>
sudo ./<whatever the name of the download is>
If you get an error of some sort like I did, type:
sudo ln -s /usr/lib/x86_64-linux-gnu/libglut.so /usr/lib/libglut.s And try the 'sudo ...' command again.
The 'sudo ./...' command actually installs three packages: driver, toolkit, and samples. I actually installed the nvidia accelerated graphics driver separately via:
sudo apt-add-repository ppa:ubuntu-x-swat/x-updates
sudo apt-get update
sudo apt-get install nvidia-current
But if the included driver installation works for you, then by all means continue.
Now, add to ~/.bashrc by:
gedit ~/.bashrc
Scroll down to bottom of file and add:
export PATH=/usr/local/cuda-5.0/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-5.0/lib64:/usr/local/cuda-5.0/lib:$LD_LIBRARY_PATH
Now to test everything out, go to your home directory:
cd ~/
Then go to the cuda samples folder (type 'ls' to view contents).
Once in the samples folder type 'make'. This will compile the sample codes, which may take a while. If you get an error (something like 'cannot find -lcuda', like I did), then type:
sudo ln -s /usr/lib/nvidia-current/libcuda.so /usr/lib/libcuda.so
Then go into the sample files and run the executables (whatever catches your fancy)! Executables are colored green if you view the file contents via 'ls', and you run the executables by typing './' before the name of the file.
Tuesday, August 14, 2012
OpenFOAM 2.1.1 GGI / AMI Parallel Efficiency Test, Part II
These figures are pretty self-explanatory. Again, these are on single computers. My i7-2600 only has four cores, and the other data set represents a computer with 8 cores (2 x Opteron 2378). It is clear there is a significant parallelizability bottleneck (serial component) in the AMI. However, it seems this bottleneck may be controllable by minimizing AMI interface path face count, if the interface patch interpolation is a serial component.
Tuesday, April 17, 2012
A note about GMSH scripting
This is about a frustrating detail that took me a few hours to discover. Scripting in GMSH allows one flexibility in design. The parameterization of the mesh allows for quick redesign. As objects can become complex rather quickly, when scripting in GMSH it may be helpful to make 'functions'. I say 'functions' because these are parameter-less and act as though you copy and pasted the function where you called it. So it is very primitive.
This caveat that took me some time to discover was that you have to be sure that calling a function is the last thing you do on a line, and do not have multiple calls in one line. Only after a line break does the program refresh the variables and such.
Monday, February 27, 2012
A note about xbee serial communication accuracy
I found a neat fact about the xbee communication modules. Having too high a send rate can botch the accuracy of your transmission (although you should test it yourself, may be just the way my software works). I had two xbees transmitting strings at 57600 baud and quite a bit of noise was present. Turning it down to 4800 resulted in perfect transmission. This is a bit slow, but I will be determining the max transmission rate that doesn't give me noise.
Monday, January 9, 2012
Simple Arduino double-to-string function
Because Arduino language is rather low-level (C-based), such a function is not so straightforward or available as one might think.
Just input the double and the number of decimal places desired. The function:
//Rounds down (via intermediary integer conversion truncation)String doubleToString(double input,int decimalPlaces){if(decimalPlaces!=0){String string = String((int)(input*pow(10,decimalPlaces)));if(abs(input)<1){if(input>0)string = "0"+string;else if(input<0)string = string.substring(0,1)+"0"+string.substring(1);}return string.substring(0,string.length()-decimalPlaces)+"."+string.substring(string.length()-decimalPlaces);}else {return String((int)input);}}
More omniwheel-drive control considerations
Here are some plots and MATLAB code that shows the relationship between the net contribution of the motors versus direction, for different numbers of motors/wheels on the vehicle. The 'net contribution' of the motors was calculated by adding the absolute value of the motor wheel direction (contribution direction) dotted with the direction of the vehicle's movement. The motor usage 'efficiency' is the same figure as described, but divided by the number of motors. An efficiency of 1 would be where all the motors are lined up and going the same direction, which will not happen with an omniwheel vehicle. The efficiency consistently floats around the .6-.7 range, which is pretty good. Meaning if I use 5 of these 135-watt DC motors, I would get about 440 W of rated DC power driving around. Not bad.




The code also includes last post's stuff (the speed consistency vs. direction plots). Again, even/odd wheel numbers are in their own trend groups, and as the number of motors go up the standard deviation of the efficiency with respect to direction goes down.
The efficiency plots:





The code:
%FPS bot testingclose all;offsetAngles = [0 0 0 0 0];%Just the difference in angle between frontmost motor and forward direction.motorCounts = [3 4 5 6 7];motorAngles = 2*pi./motorCounts;for j = 1:numel(motorCounts)mvec = zeros(motorCounts(j),2);for k = 1:motorCounts(j)mvec(k,:) = [cos(offsetAngles(j)+(k-1)*motorAngles(j)) -sin(offsetAngles(j)+(k-1)*motorAngles(j))];endresolution = 10000;theta = linspace(0,2*pi,resolution)';directionVector = zeros(resolution,2);directionVector(:,1) = cos(theta);directionVector(:,2) = sin(theta);%Speedmag = zeros(resolution,1);currentMag = zeros(motorCounts(j),1);for k=1:resolutionfor i=1:motorCounts(j)currentMag(i) = abs(mvec(i,:)*directionVector(k,:)');endmag(k) = max(currentMag);endfigure;plot(theta,mag);ylabel('Normalized speed magnitude');xlabel('Direction (radians)');title(['Normalized speed magnitude vs. direction, ' num2str(motorCounts(j)) ' motors']);%TorquemotorUse = zeros(resolution,1);for k=1:resolutionfor m=1:motorCounts(j)motorUse(k) = motorUse(k)+abs(mvec(m,:)*directionVector(k,:)');endendfigure;plot(theta,motorUse);ylabel('Equivalent motor usage (# of motors)');xlabel('Direction (radians)');title(['Motor usage vs. direction, ' num2str(motorCounts(j)) ' motors']);figure;plot(theta,motorUse/motorCounts(j));ylabel('Motor usage efficiency');xlabel('Direction (radians)');title(['Motor usage efficiency vs. direction, ' num2str(motorCounts(j)) ' motors']);end
Sunday, January 1, 2012
Changing Arduino Output PWM frequency
Here is a very useful post about changing the output PWM frequency on the Arduino (post #5). It was useful for me because I wanted a frequency over 20 kHz for DC motor control (>20 kHz is above the hearing range).
Getting PWM frequency and duty cycle with Arduino
Here is a simple code that reads a PWM signal and calculates its frequency and duty cycle in real time. The lack of an oscilloscope motivated this code.
The star of the show is the built-in 'pulseIn()' function, which gives the length of a low or high pulse. With these two measurements the calculation of duty cycle and frequency of the PWM signal is straightforward.
I have two different versions, simple and slightly longer.
The slightly longer code takes a user-specified time delay over which the maximum length is taken to help prevent truncation of a signal. For example, if an input pulse of 10 ms has already been running for 2 ms and the pulseIn function is called, it may return 8 ms. I am actually not sure if this is the way pulseIn works, but I tried both. With the default Arduino PWM frequency of approximately 490 Hz (http://arduino.cc/en/Reference/analogWrite), the slightly longer code returns 490 +/- 3 Hz, while the simple code returns 500 +/- 3 Hz. I tested this over low and high duty cycles. So it appears the slightly longer code has slightly better accuracy.
Simple code:
//Reads a PWM signal's duty cycle and frequency.#define READ_PIN 13#define PWM_OUTPUT 10static double duty;static double freq;static long highTime = 0;static long lowTime = 0;static long tempPulse;void setup(){pinMode(READ_PIN,INPUT);Serial.begin(9600);analogWrite(PWM_OUTPUT,230);}void loop(){readPWM(READ_PIN);Serial.println(freq);Serial.println(duty);}//Takes in reading pins and outputs pwm frequency and duty cycle.void readPWM(int readPin){highTime = 0;lowTime = 0;tempPulse = pulseIn(readPin,HIGH);if(tempPulse>highTime){highTime = tempPulse;}tempPulse = pulseIn(readPin,LOW);if(tempPulse>lowTime){lowTime = tempPulse;}freq = ((double) 1000000)/(double (lowTime+highTime));duty = (100*(highTime/(double (lowTime+highTime))));}
Slightly longer code:
//Reads a PWM signal's duty cycle and frequency.#define READ_PIN 13#define READ_DELAY 100#define PWM_OUTPUT 10static double duty;static double freq;static long highTime = 0;static long lowTime = 0;static long tempPulse;static long lastSeen;void setup(){pinMode(READ_PIN,INPUT);Serial.begin(9600);analogWrite(PWM_OUTPUT,230);}void loop(){readPWM(READ_PIN);Serial.println(freq);Serial.println(duty);}//Takes in reading pins and outputs pwm frequency and duty cycle.void readPWM(int readPin){highTime = 0;lowTime = 0;lastSeen = millis();while((millis()-lastSeen)<read_delay){< div="">tempPulse = pulseIn(readPin,HIGH);if(tempPulse>highTime){highTime = tempPulse;}}lastSeen = millis();while((millis()-lastSeen)<read_delay){< div="">tempPulse = pulseIn(readPin,LOW);if(tempPulse>lowTime){lowTime = tempPulse;}}freq = ((double) 1000000)/(double (lowTime+highTime));duty = (100*(highTime/(double (lowTime+highTime))));}
Monday, December 26, 2011
A note about Arduino serial
When reading strings through Arduino Serial, I usually make a string variable and concatenate onto it as each Serial.read() character comes in.
Suppose Serial communication occurs one string at a time. If you collect each string with a while(Serial.available()) loop, a problem arises. The first three characters, if the string is longer than three characters, is truncated. Why?
I fixed this problem by adding a small delay (~5 ms, could be less, I did not play with it and it depends on the amount of code between the delay and character reception) after every three characters. It appears Serial communication is done in packets of three, with a small delay in between, making Serial.available() false every three characters for a brief period of time.
Simple LCD Character Display and Arduino Uno
It is easy to hook up and use a character display to an Arduino. Turns out, a specific driver that comes with LCD displays that is very common is the HD44780 driver and there is an Arduino library specifically for it: LiquidCrystal.
It is very easy to use in software and in hardware. The following picture is from the documentation for my screen that I bought on eBay (JHD-204A), showing the pin configuration and power characteristics.
The official Arduino documentation on LiquidCrystal is all you need and it can be found at: http://www.arduino.cc/en/Tutorial/LiquidCrystal. Actually no other parts than connecting wires are required. The potentiometer mentioned in the above reference is just for contrast adjustment.

I personally prefer wire wrapping to soldering when I can, mainly because I am not good at soldering and I do not like toxic fumes. Here I had to solder the header pins onto the LCD display pins, but after that wire wrapping all the way. It was fun. Wire wrapping wire and header pins are an awesome combination.
One inevitably runs into errors when installing the driver for the Arduino Uno on my Windows 7 PC. I thought it was just an error on my computer specifically but apparently it is the norm for now. The solution is found on the official Arduino website here. They acknowledge that the error occurs no matter what, "despite [Window's] best efforts".
It is very easy to use in software and in hardware. The following picture is from the documentation for my screen that I bought on eBay (JHD-204A), showing the pin configuration and power characteristics.
The official Arduino documentation on LiquidCrystal is all you need and it can be found at: http://www.arduino.cc/en/Tutorial/LiquidCrystal. Actually no other parts than connecting wires are required. The potentiometer mentioned in the above reference is just for contrast adjustment.

I personally prefer wire wrapping to soldering when I can, mainly because I am not good at soldering and I do not like toxic fumes. Here I had to solder the header pins onto the LCD display pins, but after that wire wrapping all the way. It was fun. Wire wrapping wire and header pins are an awesome combination.
One inevitably runs into errors when installing the driver for the Arduino Uno on my Windows 7 PC. I thought it was just an error on my computer specifically but apparently it is the norm for now. The solution is found on the official Arduino website here. They acknowledge that the error occurs no matter what, "despite [Window's] best efforts".
Sunday, December 11, 2011
Omniwheel vehicle speed consistency
I am currently working on an omniwheel drive vehicle with a rather... unique control system. I am estimating it will be done by January. It involves wireless Arduino serial communication via XBEE, water-jet cut custom aluminum parts, h-bridge PWM DC motor controllers, and mutilated IKEA furniture, haha.
This post is about some speed control considerations on a vector-based control algorithm. Depending on what direction you going in, the speed will differ, because the wheel orientations change. Obviously, for a good human-operated vehicle this speed difference must not be too great. If the direction is aligned with the wheel's normal rolling direction, the speed will be at a maximum. The wheel most aligned with the direction of movement will dictate the top speed of the vehicle. All other wheels will provide moment balance and extra torque.
Below are some plots showing the normalized (meaning min is 0, max is 1) speed vs. direction, in radians. The different plots are for different numbers of motors/wheels installed on the vehicle.


As you can see, for 3 and 4 motors the speed difference is quite significant. For three motors, the lowest speed depending on the direction of vehicle movement can be about 14% lower than the normal speed (in the graphs it would be 1). Four motors fares even worse, which can be surprising at first glance. Four motors give a minimum speed around 30% lower than the normal speed.
A few interesting patterns can be seen here. The number of dips equals the number of wheels/motors if the number of wheels/motors is odd, and twice the number of wheels/motors if the number is even. Also, locally the speed difference is minimized if the number of wheels/motors is odd. 5 wheels/motors is a reasonable number that gives an acceptable speed difference, <5%. Of course, all of this can be fixed to give virtually no speed difference consistent speed in the controller's software.
This was a very interesting investigation! The code to generate the plots above was written in MATLAB and is included below (MATLAB is not free, but SCILAB is! And it is very similar! It will probably run the code with little or no modification!):
%FPS bot testing
offsetAngles = [0 0 0 0 0];%Just the difference in angle between frontmost motor and forward direction.
motorCounts = [3 4 5 6 7];
motorAngles = 2*pi./motorCounts;
for j = 1:numel(motorCounts)
mvec = zeros(motorCounts(j),2);
for k = 1:motorCounts(j)
mvec(k,:) = [cos(offsetAngles(j)+(k-1)*motorAngles(j)) -sin(offsetAngles(j)+(k-1)*motorAngles(j))];
end
resolution = 10000;
mag = zeros(resolution,1);
theta = linspace(0,2*pi,resolution)';
directionVector = zeros(resolution,2);
directionVector(:,1) = cos(theta);
directionVector(:,2) = sin(theta);
currentMag = zeros(motorCounts(j),1);
for k=1:resolution
for i=1:motorCounts(j)
currentMag(i) = abs(mvec(i,:)*directionVector(k,:)');
end
mag(k) = max(currentMag);
end
figure;
plot(theta,mag);
ylabel('Normalized speed magnitude');
xlabel('Direction (radians)');
title(['Normalized speed magnitude vs. direction, ' num2str(motorCounts(j)) ' motors']);
end
Friday, February 18, 2011
Easy pulse width modulation (PWM) with PICs to control brushless motor ESCs, servos, and other things
Today I present a brushless motor controlled by an electronic speed controller (ESC) and a PIC microcontroller. The PIC microcontroller performs the function of a R/C reciever by taking in a signal (here generated by a pot) and outputting the appropriate signal (here a PWM square wave) for the ESC to use to determine the rotation speed of the brushless motor.
Simple background: this brushless motor was bought at Hobbyking for less than $20 and can put out something like 600W. Although that is pretty high, it is not to be used for wheeled vehicles, which have highly dynamic loads and high current spikes. These brushless motors are intended for R/C planes' propellers, which have very steady and predictable loads. I have it connected to an electronic speed controller (ESC), which I bought, that dishes out the current at the right times to the brushless motor based on a simple pulse-width modulated signal it receives. The ESC is necessary because brushless motors operate in a totally different way from DC motors. Brushless motors are 3-phase, and as a result have three wires that need to be connected to a power source. Where you could just apply a steady voltage to DC motor and the commutator takes care of current alternation, brushless motors require more deliberate control. This control is not the subject of this post. I use a PIC to easily create and control a PWM signal using PIC's onboard timers for the ESC to interpret. So this post is basically about programming PICs specifically, to generate a PWM signal, of any flavor or kind if you like.
The signal generated here is a standard servo signal of 50 Hz (period 20 ms) with a square wave pulse that ranges in width from 1 to 2 ms.
What the software does: The software uses PIC's Timer1 and Timer0 and interrupts to make the signal, whose length varies (of the signal that can vary from 1 to 2 ms, the 50 Hz cycle is fixed) based on an analog input from a potentiometer. The PIC is constantly polling and converting the signal from a potentiometer to a value from 0 to 255, while in the background the Timers are counting down. Timer1 has a fixed time length (of course defined by me) of 20 ms, and Timer0 has a length from 1 ms to 2 ms, determined by the ADC. Timer1 dictates when the pulse, whose length is determined by Timer0, occurs. Notice that every cycle there is at least a period of 18 ms where the signal is low. The timers counting down and the potentiometer signal polling occurs simultaneously. When the Timers countdown, interrupts stop the PIC from whatever it was doing, executes a specified procedure, and then the PIC resumes whatever it was doing. In this case, the interrupts would be used to stop and start signals.
The c code:
The c code is turned into a hex file via MPLAB and I program the microcontroller with the PICKit 2. The microcontroller is a PIC16F690. Analog-to-digital conversion signal (from potentiometer) input is at A0 and ouput PWM signal is at C0. There is an input at C1 for a momentary pushbutton, which needs to be held in order for an input at C1 to be high. This is for safety, as I want it to be easy to turn the motor off by just letting go of the pushbutton. So no matter how high a speed the potentiometer is set to, nothing will happen unless the pushbutton is held.
Since the ADC gives values from 0 to 255, the resolution, or how many speed levels, you have is limited to 256. So it does not matter whether you use Timer0 or Timer1 for determining the signal length (Timer1 has a much higher temporal resolution). I use Timer0 because it probably takes less time for the PIC to do the arithmetic of converting the ADC value to the appropriate number. Timer0 uses 8 bits to define counter number (up to 255 in decimal), while Timer1 uses 16 bits (up to 65535 in decimal).
Remember, when using PICs as standalone portable devices, two things should be done: there should be a capacitor across the power supply pins (here, .1 microFarad, and yes this is absolutely necessary, else the PIC just keeps turning off and on) and a resistor tying MCLR pin to the power supply (here 10 kOhm). Also, I tied the input C1 pin to ground, so that electrical noise wont make it go high and low unintentionally.
Simple background: this brushless motor was bought at Hobbyking for less than $20 and can put out something like 600W. Although that is pretty high, it is not to be used for wheeled vehicles, which have highly dynamic loads and high current spikes. These brushless motors are intended for R/C planes' propellers, which have very steady and predictable loads. I have it connected to an electronic speed controller (ESC), which I bought, that dishes out the current at the right times to the brushless motor based on a simple pulse-width modulated signal it receives. The ESC is necessary because brushless motors operate in a totally different way from DC motors. Brushless motors are 3-phase, and as a result have three wires that need to be connected to a power source. Where you could just apply a steady voltage to DC motor and the commutator takes care of current alternation, brushless motors require more deliberate control. This control is not the subject of this post. I use a PIC to easily create and control a PWM signal using PIC's onboard timers for the ESC to interpret. So this post is basically about programming PICs specifically, to generate a PWM signal, of any flavor or kind if you like.
The signal generated here is a standard servo signal of 50 Hz (period 20 ms) with a square wave pulse that ranges in width from 1 to 2 ms.
What the software does: The software uses PIC's Timer1 and Timer0 and interrupts to make the signal, whose length varies (of the signal that can vary from 1 to 2 ms, the 50 Hz cycle is fixed) based on an analog input from a potentiometer. The PIC is constantly polling and converting the signal from a potentiometer to a value from 0 to 255, while in the background the Timers are counting down. Timer1 has a fixed time length (of course defined by me) of 20 ms, and Timer0 has a length from 1 ms to 2 ms, determined by the ADC. Timer1 dictates when the pulse, whose length is determined by Timer0, occurs. Notice that every cycle there is at least a period of 18 ms where the signal is low. The timers counting down and the potentiometer signal polling occurs simultaneously. When the Timers countdown, interrupts stop the PIC from whatever it was doing, executes a specified procedure, and then the PIC resumes whatever it was doing. In this case, the interrupts would be used to stop and start signals.
The c code:
//Using default Internal Clock of 4 Mhz
//To use TMR1 at 50 Hz and 1:1 prescaler, set offset to 45535.
//TMR0 can vary from 7 to 132, or 2 ms to 1 ms, at 1:1 prescaler.
//Use TMR0 to control pulse width, even though TMR1 has much higher resolution, because TMR1 offset is 16 bits and may require lots of time to compute proper TMR1 offset from ADC value.
#include
__CONFIG(INTIO & WDTDIS & PWRTEN & MCLRDIS & UNPROTECT \
& UNPROTECT & BORDIS & IESODIS & FCMDIS);
unsigned int pulse = 0;
//Interrupt function
static void interrupt isr(void)
{
if(T0IF)
{
RC0 = 0;
}
if(TMR1IF)
{
TMR1IF = 0;
//Start 50 Hz cycle period.
TMR1L = 0b11011111;
TMR1H = 0b10110001;
//Turn on variable pulse.
RC0 = 1;
if(RC1 == 1) //safety switch (must be pressed).
{
TMR0 = pulse;
T0IF = 0;
}
else //off.
{
TMR0 = 132;
T0IF = 0;
}
}
}
void main(void)
{
//Setting ports.
TRISA = 0x00001001; // Set A0 (for ADC) and A3 (MCLR) to input, others output
PORTA = 0x00;
TRISC = 0b00000010; //Set C1 to input for safety on switch.
PORTC = 0x00;
//ADC configuration.
ADCON1 = 0x00;//So that ADC conversion clock is set to Fosc/2.
ADCON0 = 0b00000001;//ADC enabled.
ANSEL = 0x01; // Set A0 to analog, others digital
//Timer configuration
OPTION = 0b00000010; // Timer0 configuration, 1:8 prescaler.
TMR1L = 0b11011111;
TMR1H = 0b10110001;
T1CON = 0b00000001; // Timer1 configuration, including 1:1 Prescaler and TMR1ON = 1.
//Timer interrupt enabling.
T0IE = 1;
TMR1IE = 1; // Timer1 interrupt enable, needed with PEIE and GIE bits set.
PEIE = 1; // Peripheral interrupt enable, for Timer1.
//General interrupt enabling.
GIE = 1; // Global interrupt enable, for Timer1 and Timer0.
while(1)
{
GODONE=1; // initiate conversion.
while(GODONE) continue; // Wait for conversion to finish.
pulse = 7+ADRESH*125/255;
}
}
The c code is turned into a hex file via MPLAB and I program the microcontroller with the PICKit 2. The microcontroller is a PIC16F690. Analog-to-digital conversion signal (from potentiometer) input is at A0 and ouput PWM signal is at C0. There is an input at C1 for a momentary pushbutton, which needs to be held in order for an input at C1 to be high. This is for safety, as I want it to be easy to turn the motor off by just letting go of the pushbutton. So no matter how high a speed the potentiometer is set to, nothing will happen unless the pushbutton is held.
Since the ADC gives values from 0 to 255, the resolution, or how many speed levels, you have is limited to 256. So it does not matter whether you use Timer0 or Timer1 for determining the signal length (Timer1 has a much higher temporal resolution). I use Timer0 because it probably takes less time for the PIC to do the arithmetic of converting the ADC value to the appropriate number. Timer0 uses 8 bits to define counter number (up to 255 in decimal), while Timer1 uses 16 bits (up to 65535 in decimal).
Remember, when using PICs as standalone portable devices, two things should be done: there should be a capacitor across the power supply pins (here, .1 microFarad, and yes this is absolutely necessary, else the PIC just keeps turning off and on) and a resistor tying MCLR pin to the power supply (here 10 kOhm). Also, I tied the input C1 pin to ground, so that electrical noise wont make it go high and low unintentionally.
Thursday, January 20, 2011
PIC servo control with ADC Input
I have not used PIC in a long while, as it has been replaced by the Arduino. The reasons are that I have to program the PIC at a lower level of abstraction, the PIC does not have all of the built-in convenient functionality of the Arduino, and the PIC is harder to debug.
Even so, I have programmed a servo controller with a PIC that takes in an analog value from the Arduino (via PWM, a rectifier capacitor can be placed), and translates this to a 50 Hz PWM servo signal with pulse widths ranging from 0.6 ms to 2.4 ms for the control of a HS-311 servo. If your servo requires different pulse lengths it is easy to change in the code.
The reasons for making a PIC servo controller are two:
-The PIC is much cheaper. Ordering from Microchip I can get the PIC16F690 for less than $2 a piece.
-The Arduino servo library control gets messed up when you change the timer settings. On the same Arduino I wanted to control the servos, I changed the timers so that the PWM operates at above 30 kHz.
I programmed a PIC16F690 using PicKit 2 (there is now a newer PicKit 3) and the free MPLAB IDE using the also-free HITECH compiler.
The software utilizes Timer 0 for the 50 Hz signal refresh rate and Timer 1 for the pulse length. This was done because a higher resolution of pulse width was achieved with Timer 1. Several very useful calculators for finding the Timer offset value settings to correspond to the desired times can be found by Google searching 'PIC timer calculator'. I have one that I got a few years back, but I cannot seem to find it now; maybe it updated.
Friday, December 24, 2010
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;
}
Subscribe to:
Posts (Atom)







