So I wanted o upgrade my tail light so that inattentive drivers know I'm slowing down. The biggest problem I noticed on the Ninja 250 is that the standard light (single pod) doesn't have good contrast. Even though the bulb is powerful, I don't feel it suits safety very well.
It was dumb luck that I discovered a perfect MR16 LED bulb on Amazon for $13.
It's a 5W, 250lm bulb which after removing some plastic, it fits perfectly in the extra unused pods on the tail light.
Originally I was planning on having a dimmed light because the controller inside the bulb supports some dimming. Turned out I would have needed to develop more than what I wanted to. The single LED driver wouldn't have been able to power 6 LEDs (from 2 bulbs) on the 12V bike power.
You can see the 2 MR16 bulbs on the external pods. They fit (with removed lenses and rings) right where a bulb socket would have gone.
I decided to just have it completely off and then on when braking only.
Normal operation just has the standard running light.
With the brakes on it's a LOT better than just the center bulb only. The 2 pictures are taken a the same speed/aperture for good comparison.
Overall I'm very happy with the upgrade and feel a bit safer. The LEDs turn on almost .25s (or more) faster than the standard bulb. The LEDs also allow me to brake flash better to warn people following too close or just when I may be stopping/slowing "unexpectedly"
Saturday, August 6, 2011
Friday, February 11, 2011
Arduino automatic pan/tilt Thermal scanner
I've always been interested in FLIR technology and how to apply it. While I've never owned or used a FLIR directly I know the basic principles.
Basically, I'm sort of making a first generation version that uses a single point scanner (IR temperature) and panning across in a gridded fashion. While this is crude and only suited for slow changes in temperature over an area it works for what I have in mind - scanning a wall in cold weather.
This is how I'm planning on how to do things:
Arduino - The interface between the hardware and software. It will read from the serial command to either pan, tilt or read from the temperature sensor. The temperature sensor has a serial port built into it so I don't need to worry about that.
Processing (on the computer - actual program called "Processing") - I already have the grid plotted and functioning as well as the program controlling the pan and tilt function properly. The next thing is to get the code to read the temperature an apply to the grid. The grid is a color-coded grid, where blue is cold and red is hot (relative)
After spending a bit on the code and the hardware with my new soldering iron it works quite well!
The first image shows a crude setup of the sensor on a pan/tilt mount. I used an old DSL filter for the RJ45 connector to the sensor which makes quick disconnects easy. An old IDE connector on a pcb board for quick plugin to the arduino.
The next image shows what the sensor was pointing at. My cat like the laser dot as well where the sensor is point near :) For proof that it detect heat sources the supply is for my laptop and runs hot - about 95F. The picture is just about the area being scanned. Unfortunately the thermal sensor is very slow so the framerate in processing is 0.8 frames/second... YIKES!
This last picture is the dynamic map of the readings. You can clearly see the power brick light up in comparison to the surrounding area which is 70ish or less. I need to make a PDF export of the image when it reaches the corners!
Next is to make this a bit more... portable. I need to get the pan/tilt mount to be better built and have some type of system to manage the cables into a unit that requires a usb cable.
Updates:
I used a busted HDD case with spindle still on it as my turntable. With a few modifications with a drill-press it suits my needs. I also attached my tripod to the casing so that it can measure in an elevated position.
Here are some more close up shots of the micro processor with connections.


And a larger scan of a "real" surface (chimney and wall)

Update: Made instructable: http://www.instructables.com/id/Build-an-Arduino-based-thermal-scanner/
After spending a bit on the code and the hardware with my new soldering iron it works quite well!
The first image shows a crude setup of the sensor on a pan/tilt mount. I used an old DSL filter for the RJ45 connector to the sensor which makes quick disconnects easy. An old IDE connector on a pcb board for quick plugin to the arduino.
The next image shows what the sensor was pointing at. My cat like the laser dot as well where the sensor is point near :) For proof that it detect heat sources the supply is for my laptop and runs hot - about 95F. The picture is just about the area being scanned. Unfortunately the thermal sensor is very slow so the framerate in processing is 0.8 frames/second... YIKES!
This last picture is the dynamic map of the readings. You can clearly see the power brick light up in comparison to the surrounding area which is 70ish or less. I need to make a PDF export of the image when it reaches the corners!
Next is to make this a bit more... portable. I need to get the pan/tilt mount to be better built and have some type of system to manage the cables into a unit that requires a usb cable.
Updates:
I used a busted HDD case with spindle still on it as my turntable. With a few modifications with a drill-press it suits my needs. I also attached my tripod to the casing so that it can measure in an elevated position.
Here are some more close up shots of the micro processor with connections.
And a larger scan of a "real" surface (chimney and wall)
Update: Made instructable: http://www.instructables.com/id/Build-an-Arduino-based-thermal-scanner/
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;
}
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.
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
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();
}
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();
}
Subscribe to:
Posts (Atom)