Thursday, December 30, 2010

Fun with Processing + Future project (IR thermal scanner)

I anticipation for my next project I've been looking at different ways to process and represent feedback from a digital IR sensor. I found this very nice tool called Processing. The tool allows me to write a piece of code that ca take multiple inputs (such as serial) and display it in anyway I want. As a sample test I decided to make a binary clock. A lot of this will be translated into my project such as the live updates and ellipse drawings. It's too bad I can't attach a java app to this blog so here's the code:

Processing page (http://www.processing.org/)

void setup() {
size(300,160);
frameRate(1);
}

void draw() {
//hh:mm:ss
//24:69:69
//23:34:34
background(50,50,50);
String s = ""+second();  // Values from 0 - 59
String m = ""+minute();  // Values from 0 - 59
String h = ""+hour();    // Values from 0 - 23
String time = ""+h+m+s;

println (time);

boolean [][] hs = digit(h);
boolean [][] ms = digit(m);
boolean [][] ss = digit(s);
digit_draw(2,3,33,0,hs);
digit_draw(3,4,33,3,ms);
digit_draw(3,4,33,6,ss);
}

void digit_draw(int d0, int d1, int sz, int xoffset, boolean [][]vs) {  
for(int i=0; i < d0; i++) {
        if(vs[0][i]) fill(255,0,0);
        else fill(0,0,0);
        ellipse(sz*xoffset+sz, sz*i+sz, sz, sz);
    }
    for(int i=0; i < d1; i++) {
        if(vs[1][i]) fill(255,0,0);
        else fill(0,0,0);
        ellipse(sz*(xoffset+1)+sz, sz*i+sz, sz, sz);
    }
}

boolean [][] digit(String v) {
  int h0,h1;
  h1=-1;
  h0=v.charAt(0)-48;
  if(v.length() > 1) h1=v.charAt(1)-48;
int ht=h1;
if(h1==-1) { h1=h0; h0=0; };
int sz=4;
boolean [][] d = new boolean[sz][sz];
String b0 = binary(h0);
String b1 = binary(h1);
//println (h0 + "" + h1 + " " + b0 + " " + b1);
for(int i=0; i < sz; i++) {
d[0][i] = d[1][i] = false;
}

for(int i=0; i < b0.length(); i++) {
if(b0.charAt(b0.length()-1-i) == '1') d[0][i]=true;
else d[0][i]=false;
}
for(int i=0; i < b1.length(); i++) {
if(b1.charAt(b1.length()-1-i) == '1') d[1][i]=true;
else d[1][i]=false;
}
return d;
}

Sunday, December 19, 2010

Custom Racing seat - impressions

So after many hours behind the wheel in my seat I decided that I did a very good job! Haha, yeah I'm gloating. Unlike some other make-shift setups that I have done this one was very comfortable and my back and legs don't hurt after getting up from a 4+ hour run.

My friend came over to test it out and before hand was skeptical of the placement of the shifter, that it would get in the way. After some adjustments (wheel hight/pull-in) he found out it worked really well. There is a lot less stress on the arms when using the H pattern shifter which is a benefit when doing competition racing. Unlike a "normal" setup where the shifter ball is higher than the center of the wheel, this one is much further down resulting in a relaxed arm position while shifting and adding quicker hand movement from shifter to wheel.

I was originally planning on attaching the seat to the box for those panic brakes (thought was that the box would slide away) but it turns out that the 70something pounds that is the box + controller stays put on carpet even under "stressful" conditions. This is a benefit to me since my cat likes to interact with the chairs soft fabric and I need to hide it :)

Some additions that were added are an extended top plank for a mouse just behind the shifter and side tray for keyboard for those PC simulators (LFS, iRacing....). I would imagine that if I wanted to extend this to a full-on simulator I could easily make a flight stick holder that would bolt right in, but that's to be seen in the future.

Monday, December 13, 2010

Custom Racing Seat

So after getting GT5 (and other racing sims) and not wanting to use the standard controller I decided to create a custom racing cockpit. The idea came to design & build quickly when a friend showed interest in having one as well. I plotted everything in sketchup (go google!) so that I could just cut and put together. What a time saver! I was able to determine that a single 4'x8' plywood sheet (oak covered) could fit the pieces. The nice thing about planning the way I did was I could determine where to cut with the hand circular saw and what can go on the table saw. In the end I did a single cut with the hand circular saw and with help did another on the table. After that all the pieces were very manageable! So after 7ish hours this is the product:

The wheel can slide up/down and in/out with the bolts. The seat is a real car seat with front/back sliding rails. When comparing to a pre-made simulation seat that's around $400 (seat + stand for controller and pedals) ... the cost of this is well worth the effort. The wood sheet was only $45 from Home Depot and the seat from Amazon. 

The touch up is to use the router and round all the edges so they are not 90 deg corners which are painful when hit! Maybe a touch of stain might do well?

Update: I used the router to smooth all the visible edges so there are no sharp corners. I also cut the bolts so that the threads don't stick out further than the hex-nut. These two modifications have made the appearance of the box much more appealing.

Saturday, November 13, 2010

I went to the salvage yard today to look for a car seat for a custom racing cockpit with a friend. Unfortunately I didn't see one that would work or is clean enough.. but I did see this which made my day. Make sure to read the text first :)

Posted by Picasa

Wednesday, July 21, 2010

Arduino project - Morse code (part 2)

In response to Greg who wrote a comment about my pet project and to keep my blog alive I have completed the morse code project. It's actually been done for a while but I've been busy with work and home stuff as of late.

The code stream uses 7478 byes so it's fairly compact. I have successfully 'input' a sample code by using the onboard supply and tapping the input pin. It worked great :) It does have limits though, say a user was transmitting and varied speed too much, this would cause some of the dashes to be dots if they decided to send slower. There are a few solutions to this, one is to attempt to detect spaces and flush the system to reset the timers (timers are used to identify dash and dots).

There's a few options built into the code:
rate -- rate at which the program transmits
recdelay -sample frequency for input
perr - Error recovery, increase this if a user is off on timing but can blur differences between the sequences.
rectimeout - expected time window to when a user transmits.
help - prints out commands one can set :)

There's a few parts to the code stream that are critical: the first one is priority. Basically I want to make sure that any incoming signals have priority over any outgoing. That is if I receive a signal it will stay on that until the time lapses and then send the signal I want to transmit. This is simply done in the loop() statement


void loop() {
//if there's data on the serial port go and read that
if(digitalRead(input)) {
int bits[5000];
buffer_signal(bits);
decode(bits);
}
if(Serial.available()) {
//will get one bit at a time to encode and transmit
char mybit = Serial.read();
if(!scancontrol(mybit)) {
flash(encode(mybit));
}
}
//if there's data on the input port read that
//potentailly make this an interrupt process?

}

Let's first skip to the user input part to create a baseline. The first argument is scanning for a control statement


//scan for control bits from console
boolean scancontrol(char mybit) {
if(mybit == '#') {
delay(1); //delay to allow serial to recover
//this is a control bit flag
char ctrl[20];
int i=0;
//buffer the command
while(Serial.available()) {
ctrl[i++] = Serial.read();
delay(1); //delay 1ms to allow serial to recover
}
ctrl[i] = '\0';

if(strcmp(ctrl, "rate", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set Tx rate=");
Serial.println(nr);
rate=atoi(nr);
}
else if(strcmp(ctrl, "recdelay", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set rec_delay=");
Serial.println(nr);
recdelay=atoi(nr);
}
else if(strcmp(ctrl, "perr", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set perr=");
Serial.println(nr);
recdelay=atoi(nr);
}
else if(strcmp(ctrl, "rectimeout", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set rec_timeout=");
Serial.println(nr);
rectimeout=atoi(nr);
}
else if(strcmp(ctrl, "help", 0)) {
Serial.println("#rate n - set dot rate (ms)");
Serial.println("#recdelay n - set rec_delay (ms)");
Serial.println("#perr n - set error for decoding");
Serial.println("#rectimeout n - set rec_timeout (ms)");
}
else {
Serial.print("Unknown command: ");
Serial.println(ctrl);
}
//Serial.flush();
return true;
}
return false; //false means it didnt find a control bit,
}


If there is no control statement found it will return false and allow flash() to execute. Flash, however, needs a string of encoded signals to transmit and that's where encode() is used.

Encode() is very straight forward. It takes in a single char and dumps out the equivalent string of dash and dot combination. This is nothing new and can be easily found on a wiki site.


//encode each character
char * encode(char chr) {
switch(chr) {
case 'a': return ".-";
case 'b': return "-...";
case 'c': return "-.-.";
case 'd': return "-..";
case 'e': return ".";
case 'f': return "..-.";
case 'g': return "--.";
case 'h': return "....";
case 'i': return "..";
case 'j': return ".---";
case 'k': return "-.-";
case 'l': return ".-..";
case 'm': return "--";
case 'n': return "-.";
case 'o': return "---";
case 'p': return ".--.";
case 'q': return "--.-";
case 'r': return ".-.";
case 's': return "...";
case 't': return "-";
case 'u': return "..-";
case 'v': return "...-";
case 'w': return ".--";
case 'x': return "-..-";
case 'y': return "-.--";
case 'z': return "--..";
case ' ': return " ";
case '0': return "-----";
case '1': return ".----";
case '2': return "..---";
case '3': return "...--";
case '4': return "....-";
case '5': return ".....";
case '6': return "-....";
case '7': return "--...";
case '8': return "---..";
case '9': return "----.";
case '.': return ".-.-.-";
case ',': return "--..--";
case '?': return "..--..";
case '\'': return ".----.";
case '!': return "-.-.--";
case '/': return "-..-.";
case '(': return "-.--.";
case ')': return "-.--.-";
case '&': return ".-...";
case ':': return "---...";
case ';': return "-.-.-.";
case '=': return "-...-";
case '+': return ".-.-.";
case '-': return "-....-";
case '_': return "..--.-";
case '"': return ".-..-.";
case '$': return "...-..-";
case '@': return ".--.-.";
default: return "";
}
}


The last bit that I have is the tougher one, decoding. I wanted to allow this to be somewhat dynamic in that a user doesn't need to know exactly what the rate should be.


void decode(int *bits) {
int i=0; //assume first bit is high
int max_on=0;
int max_off=0;
int min_off=-1;
int min_on=-1;
//first get the max cycle (dash)
while(bits[i]) {
if(max_on < bits[i]) max_on = bits[i];
if(max_off < bits[i+1]) max_off = bits[i+1];
if(min_on < 0 || min_on > bits[i]) min_on = bits[i];
if(min_off < 0 || min_off > bits[i]) min_off = bits[i];
for(int z=i; z <= i+1; z++) {
Serial.print(z);
Serial.print(">");
Serial.println(bits[z]);
}
i+=2; //i+1 = space/seperator
}
i=0;
char myset[100];
int z=0;
while(bits[i]) {
if(bits[i] <= (max_on*(1+perr/100.0)) && bits[i] >= (max_on*(1-perr/100.0))) myset[z] = '-';
else myset[z] = '.';

Serial.print(myset[z]);

z++;
//check the space, see if there is a space or new char
if(min_off <= (max_off*(1-perr/100.0)) &&
bits[i+1] <= (max_off*(1+perr/100.0)) && bits[i+1] >= (max_off*(1-perr/100.0))) {
myset[z++] = ' ';
Serial.println("");
}
i+=2;
}
myset[z]='\0';
Serial.println("");
}

The basic idea behind the decode is this: buffer the digital input and log the times the bit is high and low. By doing this I can simply search though the buffer to figure out the lengths of the longest 'on' period which will be signified as a dash. The dot is found by looking for the minimum time the bit is high. With a few error corrections I can find out the differences between the dashes and dots. At the same time this code also dumps the dash/dots to the terminal

With some of my tests I did the code seems to work just fine. I can tap a digital input, the classic S O S and it decodes it. Transmission seems to work as expected as well, too bad I don't have another, I could get them both talking to do a much better test!

I never got past the point of hooking it up to the radio and decoding something but I'm sure that getting some circuitry it can be done. All there really needs to be is something to smooth out the audio tone (bridge+caps?) and use an opamp. The opamp can be set up so it hits max gain when a tone comes in, at which point it would simulate a digital signal. Sending one would require an oscillator and set to a good frequency (probably somewhere around 400+ Hz). I suppose a lower tone would work just fine but too low and the oscillations wouldn't keep up with the transmissions.


int led = 13; //local LED pin
int sig = 9; //pin to write to to transmit
int input = 8; //pin to read from
int rate = 120; //basic rate (set to human readable)
int recdelay = 20;
int rectimeout = recdelay*20;
int perr = 10; //in percentage

void setup()
{
Serial.begin(9600);
establishContact();
pinMode(led, OUTPUT); //local LED indicator
pinMode(sig, OUTPUT); //output signal to transmit
pinMode(input, INPUT);
}

void loop() {
//if there's data on the serial port go and read that
if(digitalRead(input)) {
int bits[5000];
buffer_signal(bits);
decode(bits);
}
if(Serial.available()) {
//will get one bit at a time to encode and transmit
char mybit = Serial.read();
if(!scancontrol(mybit)) {
flash(encode(mybit));
}
}
//if there's data on the input port read that
//potentailly make this an interrupt process?

}

void decode(int *bits) {
int i=0; //assume first bit is high
int max_on=0;
int max_off=0;
int min_off=-1;
int min_on=-1;
//first get the max cycle (dash)
while(bits[i]) {
if(max_on < bits[i]) max_on = bits[i];
if(max_off < bits[i+1]) max_off = bits[i+1];
if(min_on < 0 || min_on > bits[i]) min_on = bits[i];
if(min_off < 0 || min_off > bits[i]) min_off = bits[i];
for(int z=i; z <= i+1; z++) {
Serial.print(z);
Serial.print(">");
Serial.println(bits[z]);
}
i+=2; //i+1 = space/seperator
}
i=0;
char myset[100];
int z=0;
while(bits[i]) {
if(bits[i] <= (max_on*(1+perr/100.0)) && bits[i] >= (max_on*(1-perr/100.0))) myset[z] = '-';
else myset[z] = '.';

Serial.print(myset[z]);

z++;
//check the space, see if there is a space or new char
if(min_off <= (max_off*(1-perr/100.0)) &&
bits[i+1] <= (max_off*(1+perr/100.0)) && bits[i+1] >= (max_off*(1-perr/100.0))) {
myset[z++] = ' ';
Serial.println("");
}
i+=2;
}
myset[z]='\0';
Serial.println("");
}

void buffer_signal(int *bits) {
boolean complete=false;
int cnt=0;
int i=0;
int valprev=1; //start out high
while(!complete) {
int val = digitalRead(input);
cnt++;
if(!val) digitalWrite(led, LOW);
else digitalWrite(led, HIGH);

if(val != valprev) {
bits[i++] = cnt;
cnt=0;
}
valprev = val;
delay(recdelay); //make this dymaic
if(!val && cnt >= rectimeout) complete=true; //make this dynamic
}
bits[i]=0;
bits[i+1]=0;
}

//scan for control bits from console
boolean scancontrol(char mybit) {
if(mybit == '#') {
delay(1); //delay to allow serial to recover
//this is a control bit flag
char ctrl[20];
int i=0;
//buffer the command
while(Serial.available()) {
ctrl[i++] = Serial.read();
delay(1); //delay 1ms to allow serial to recover
}
ctrl[i] = '\0';

if(strcmp(ctrl, "rate", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set Tx rate=");
Serial.println(nr);
rate=atoi(nr);
}
else if(strcmp(ctrl, "recdelay", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set rec_delay=");
Serial.println(nr);
recdelay=atoi(nr);
}
else if(strcmp(ctrl, "perr", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set perr=");
Serial.println(nr);
recdelay=atoi(nr);
}
else if(strcmp(ctrl, "rectimeout", 0)) {
char nr[20]; //make the same size as ctrl
substr(nr, ctrl, 4, -1);
Serial.print("Set rec_timeout=");
Serial.println(nr);
rectimeout=atoi(nr);
}
else if(strcmp(ctrl, "help", 0)) {
Serial.println("#rate n - set dot rate (ms)");
Serial.println("#recdelay n - set rec_delay (ms)");
Serial.println("#perr n - set error for decoding");
Serial.println("#rectimeout n - set rec_timeout (ms)");
}
else {
Serial.print("Unknown command: ");
Serial.println(ctrl);
}
//Serial.flush();
return true;
}
return false; //false means it didnt find a control bit,
}

//pull out a section of the string
void substr(char *ret, char *msg, int offset, int len) {
if(len<=0) len = strlen(msg);
for(int i=offset-1; i < len && i < strlen(msg); i++) {
ret[i-offset] = msg[i];
}
ret[len-offset]='\0';
}

//compare strings
boolean strcmp(char *msg, char*srch, int offset) {
for(int i=offset; i < strlen(srch) && i < strlen(msg); i++) {
if(srch[i] != msg[i]) return false;
}
return true;
}

//flash the morse code to output
void flash(char *msg) {
int i=0;
while(msg[i]) {
if(msg[i] == ' ') {
delay(rate); //space is a standard dot delay
}
else {
digitalWrite(led, HIGH);
digitalWrite(sig, HIGH);
if(msg[i] == '.') delay(rate); //default delay for a dot
else if(msg[i] == '-') delay(rate*2); //double delay for a dash
digitalWrite(led, LOW);
digitalWrite(sig, LOW);
}
Serial.print(msg[i]);
delay(rate); //delay slightly before next 'bit'
i++;
}
delay(rate*2);
Serial.println("");
}

//encode each character
char * encode(char chr) {
switch(chr) {
case 'a': return ".-";
case 'b': return "-...";
case 'c': return "-.-.";
case 'd': return "-..";
case 'e': return ".";
case 'f': return "..-.";
case 'g': return "--.";
case 'h': return "....";
case 'i': return "..";
case 'j': return ".---";
case 'k': return "-.-";
case 'l': return ".-..";
case 'm': return "--";
case 'n': return "-.";
case 'o': return "---";
case 'p': return ".--.";
case 'q': return "--.-";
case 'r': return ".-.";
case 's': return "...";
case 't': return "-";
case 'u': return "..-";
case 'v': return "...-";
case 'w': return ".--";
case 'x': return "-..-";
case 'y': return "-.--";
case 'z': return "--..";
case ' ': return " ";
case '0': return "-----";
case '1': return ".----";
case '2': return "..---";
case '3': return "...--";
case '4': return "....-";
case '5': return ".....";
case '6': return "-....";
case '7': return "--...";
case '8': return "---..";
case '9': return "----.";
case '.': return ".-.-.-";
case ',': return "--..--";
case '?': return "..--..";
case '\'': return ".----.";
case '!': return "-.-.--";
case '/': return "-..-.";
case '(': return "-.--.";
case ')': return "-.--.-";
case '&': return ".-...";
case ':': return "---...";
case ';': return "-.-.-.";
case '=': return "-...-";
case '+': return ".-.-.";
case '-': return "-....-";
case '_': return "..--.-";
case '"': return ".-..-.";
case '$': return "...-..-";
case '@': return ".--.-.";
default: return "";
}
}

void establishContact() {
while(Serial.available() <= 0) {
Serial.println("CN");
delay(1000);
}
Serial.flush();
}

Tuesday, February 9, 2010

Video Cards

I recently got Battlefield Bad Company 2 for the PC (beta at the moment) and wanted to make my system happy again. My older Nvidia GeForce 7950GT is starting to age under the stress of massive effects and elements in a game world. After hours of research and looking at performance charts I settled on an ATI HD 5770 1GB.

There were a few things about this card that caught my eye.
1. it was only $160
2. It's on par with the GTX260 (Nvidia) as far as specs/price
3. it supported DX11 which the GTX260 didn't

Normally I would go with the Nvidia cards since I always had good luck with them. ATI, well 13something years back the drivers sucked and caused crashes and that left a bad taste. I took a chance they changed.

After thoughts:
The card is a solid performer. It lives up to the specs (as expected) but the drivers are, well lacking. First thing that got me was the monitor. I still have a very nice CRT (1920x1680 @ 75) and the card didn't read the DDC correctly unlike my 7950 card. It was locked at 60hz for all ranges. I searched online (now that I know about this) and found this was a wider issue for the latest drivers. The easiest fix I found was to create a monitor driver, sigh.... Given that NEC doesnt give these out (and why should they when it's built into the monitor) i found a bunch of tools so that I can input the timings and create a driver. After a few tries I did manage to get the refresh back up to 85hz but have another issue (which is also known to all ati cards after searching) of the monitor not going into standby mode. The second one isn't much of an issue at the moment as a supposed patch is being developed and this computer is primarily my gaming/photo computer and power-on only when needed. A workaround is to instead of monitor off just put the entire computer into standby :)

In conclusion I probably would go with Nvidia again, after this card gets old, for the *insert 2-digit number* next years unless ATI has a MASSIVE lead over nvidia. It's possible that all I needed was an LCD and things would be peachy but we'll never know till my CRT goes out.

Friday, January 15, 2010

Arduino project - Morse code

Thanks to my brother for giving me an Arduino I've been playing around with it ever since Christmas. It took me a bit to figure out a first 'real' project to start with but I think I found an interesting one.

So having a GPRS radio (like an FRS but stronger and requires a license) I always hear quick blips of morse code coming across the channels. It probably has to do with being near a few air fields but I'm interested in what it is saying. So without becoming an expert in morse code I'm writing a program for the arduino to decode it :)

There's a few things that need to work for this:
1. Sample rate: the arduino needs to sample the analog signal fast enough to convert it into dots and dashes. This can be accomplished by counting the milliseconds that the tone is active and storing it for each 'bit'. A completed phrase will have a significant pause when done. I'll take this array and send it to the decoder for processing.
2. Decoding: Assuming this is a computer transmitting it makes it somewhat easier since the dot and dashes will be consistent. For instance I could assume a dash is 2x the time of a dot. If I wanted to decode a human operated control I would need to dynamically detect time segments between each beep as they can vary.
3. Text interface: Since I dont have a LCD display yet I'll just dump it to the serial port and require a computer to communicate.
4. Transmitter/receiver: I have a few GPRS radios around and will interface with the headphone port to get what I need for input/output. I will need to find a way to convert the tone into a semi-digital signal but that can be done with a bridge and a very small cap. For the output I will need to make a tone generator of around 700Hz (maybe faster for a quick transmit) which will be controlled by a pule form the arduino**.
5. Interface: I need to come up with some key words to control the transmission rate and other variables. I.E. typing "rate=60" in the com console to set the time on-off for a dot. That way if I'm talking to someone manually operating (highly unlikely) they can decode it.

**I don't plan on transmitting/responding to those signals I'm hearing since it could be the military base near by! HAHA, I wouldn't want an invasion in the middle of the night :)

EDIT: I've noticed a lot of hits for this project and thought I would link part 2:
http://davidgrundmann.blogspot.com/2010/06/arduino-project-morse-code-part-2.html

Friday, January 1, 2010

Camera Grip

I got a new toy for the camera :) It's a battery grip and hodler for my camera that allows me to take many, many more shots on a single charge. It's designed to not only contain 2 standard batteries in parallel (double the capacity) but it also contains an emergency AA cartarage and buttons on the grop for when the camera is rotated. I already am enjoying this grip with my T1i since it tends to chew battery fairly quick with 1080p movie and the IS lens I have!
Posted by Picasa