Wednesday, January 23, 2008

The Parable of the Parallel Parabola

OK, so....man, I wish I'd been detailing every little step so I wouldn't have to regurgitate it all up in a huge mass. I'll try to make this short.

First of all, I used my calculation to make a simple parabolic reflector. I just plotted it out on graph paper and then set a few nails as guides to hold the mirror in place. This actually worked really well. (Even more surprising in light of how poorly the (first!) oven-formed one came out. More about which below.)

The one on the right has a black dot where I pre-calculated the focus to be, the one on the left is just a different focal length.

Now then. Having a single strip mounted with nails isn't that useful to me, so I want to mass produce these. Can't use the nail thing as a form since it'll just bend unevenly. I spent quite a few days trying to figure out how to make a jig that would cut a perfect parabola, but it was too hard (I still have some ideas on that, though, but that's another 2 or 3 posts). (And before you tell me, I know all about the T-square and string method of drawing one.) I eventually decided to just freehand follow a line.

So I had my shop assistant cut a parabola for me and I sandwiched the mirror in there. (My shop assistant is my father-in-law down the street who actually owns a bandsaw.)

(Other item of note: I originally wanted to have the mirror soften and sink down into shape, but that creates alignment problems. Instead I clamped the bendy strip cold. But that means it's hard to tell when I've reached temperature. So I put a probe down into the coldest part of the thing. The tip of the temperature probe is resting right on the mirror, so when that gets up to ~210°F, I can stick a fork in it. This takes like 2 hours--wood is a really great insulator, unfortunately.)

(Oh also: You can't see it, but there's a little alignment peg sticking out of the convex part of the form. There's a corresponding hole in the concave part so it can stick through. There's also a hole in the middle of each mirror. If I put each mirror on the peg, then after I'm done with all of them, I can line them up perfectly. So clev.)

The result: Not so great.

How could that possibly be? How could a few nails hastily thrown together at a few points make a better parabola than a careful, full-contact form?

Then a phrase floated up out of the darkness1. The curve parallel to a parabola is not another parabola. Just think about that for a minute. If you have a parabola and you want to make a curve parallel to it, you can't just take the same parabola and shift it up. Nor can you use some other parabola. (Read the gories yourself, it's pretty cool. If you like that sort of thing.)

So if you cut a parabolic form and sandwich it around a mirror, FOR EXAMPLE, then you are probably going to get the wrong shape because the two halves want to be parallel (i.e. separated by the thickness of the mirror) but can't. Wellity wellity wellity.

I took the equations in that paper and made a little program2 that would generate an SVG file of the shapes I wanted. Now I can take those back to my shop assistant and have him cut it out again.

(Note to anyone who actually reads this far, runs the program, examines the output and starts wondering: The curves aren't really all that different. I think the issue isn't so much that the curve is wrong, but that the poor alignment doesn't provide even pressure across the entire mirror. So it ends up wibbly-wobbly rather than smooth. Then again, the freehand wood parabola isn't all that smooth either, so maybe THAT'S the source of the error. The nail method at least creates a smooth curve, even if it isn't mathematically perfect.)

1I think it came from Practical Conic Sections, a really rip-roaring tale that I've been reading to the kids at bedtime. But seriously, it's very clear and pretty practical.

2

#!/usr/bin/python

# p1 and p2 are parallel to the parabola, i.e. a constant distance
# away *along the normal to the parabola*.

# For a curve C with generated by the function y = f(x), the parallel
# curve C' is given parametrically by:

#                 y'
# X = x - k -------------
#           sqrt(1+(y')^2)
#
#                 1
# Y = y + k -------------
#           sqrt(1+(y')^2)

# where k is the distance of the parallel from the curve.

# For derivation, see "The Curve Parallel to a Parabola is not a
# Parabola" by F. Max Stein.

import math

print '<?xml version="1.0" standalone="no"?>'
print '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"'
print '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
print '<svg xmlns="http://www.w3.org/2000/svg"'
print '    width="8.5in" height="14in">'

focallength = 2.5
a = 1/(4.0 * focallength)
mirrorwidth = .125

vertoffset = 7
horizoffset = 2
phorizoffset = 2
prevx = 0
prevy = 0
pprevx = 0
pprevy = 0
first = True
x = -5.5
while x <= 5.5:
    y = a*x*x
    px = x - (mirrorwidth * 2 * a * x)/(math.sqrt(1 + (2*a*x)**2))
    py = y + (mirrorwidth * 1)/(math.sqrt(1 + (2*a*x)**2))
    if not first:
        print '<line x1="%.2fin" y1="%.2fin" x2="%.2fin" y2="%.2fin" style="stroke:black;stroke-width:2"/>' \
              % (prevy+horizoffset, prevx+vertoffset, y+horizoffset,x+vertoffset)
        print '<line x1="%.2fin" y1="%.2fin" x2="%.2fin" y2="%.2fin" style="stroke:red;stroke-width:2"/>' \
              % (pprevy+phorizoffset, pprevx+vertoffset, py+phorizoffset,px+vertoffset)
    prevx = x
    prevy = y
    pprevx = px
    pprevy = py
    x += .125
    first = False

print '</svg>'

Friday, January 11, 2008

Parabolae Foci

I keep having to solve this problem. It's not hard, but to "save time" I usually google it and find too much information and not enough explanation. Then I end up solving it myself. And it's so easy that each time I figure it out I'm like "there's no need to write this down--it's obvious BY INSPECTION". And then I forget it. So here it is:

Where is the focus of a parabola? Alternatively, what formula should I use to create a parabola with a given focus?

Take the equation y = ax2. Obviously the focus will be on the y-axis, but how far up? We know all the incoming rays will meet there, so let's choose a convenient one. A great choice is a ray that is turned by 90°. That is, it comes in vertically, hits the parabola, and heads towards the focus horizontally.

Since the angle of incidence equals the angle of reflection, the slope of the parabola at the point the ray hits is going to be 45°. (I spent like 20 minutes using gnuplot and the gimp trying to illustrate this before giving up. Just visualize it.) Where on a parabola is the slope 45°? Slope is also rise/run, so the the slope in the y = mx + b sense is 1. Where is the slope 1?

The equation was y = ax2. The instantaneous slope is the derivative, y' = 2ax. We want that to be 1.

2ax = 1
x = 1/2a

Substitute in to find out where on the y-axis this is.

y = ax2
y = a(1/2a)2
y = 1/4a

Now let's say I want to make a parabola with a focus that is 6 inches from the bottom of the curve.

1/4a = 6 inches
a = 1/24 inches

Therefore my equation in inches should be: y = .0416x2. (I think. Contradicting my claim about how easy this is is the fact that I actually got this wrong on paper, TWICE, before posting this.)

Monday, January 7, 2008

SUCCESS!

A non-blurry, curved mirror.

In my last post, I hypothesized the problem might be too much time spent in the oven, causing some kind of degradation. That prompted epicanis to wonder if the acrylic was oxidizing. That in turn made me actually take a close look at the failed samples (why I didn't already do this is a mystery).

The acrylic is actually fine in those failures. It's the mirror backing that cheesed out on me. What is the backing made of? Who knows! What do I do about it? I dunno!

Then I was coincidentally reading a book about polymer clay and it had a tip for firing: Put it in at a sub-firing temperature to evenly heat the item, then turn it up to actually harden it. And I was all: whoa. I know that the softening point for acrylic is around 230°F, but I've had to turn the oven up to 250°F to get the samples to bend. I've been chalking it up to oven imprecision and chemical composition variation. But I think the real issue is that the core of the mirror doesn't get hot enough until the outside is too hot.

So this time I put the whole apparatus (which I should have taken a pic of, sorry) inside the oven at 200°F until a probe told me it was more or less up to temp. Then I just cranked it up to 215°F and voila, it worked.

Now I need to do a bunch of these at once (or one large sheet)...

Wednesday, January 2, 2008

Your Favorite Geek Desk Toy Sucks

I've seen these binary clocks around the office. They are fairly cool, but they fail to please in one important way: Each decimal digit is represented separately. 14 seconds is represented as a binary 1 and a binary 4, rather than a binary 14. Granted, reading 6 bits depicting 0-59 seconds is a little harder than the 4 bits required for 0-9. But is ease of use really the primary concern of a binary clock?

MISSION: Build this clock the right way. Namely, 6 bits for the seconds, 6 bits for the minutes and 5 bits for the hour (or maybe 4 bits for the hour with 1 bit for AM/PM). (Another idea would be a straight-up 17 bits for the 86400 seconds in a day, but seriously.)

Now then. I know there are clock chips out there. And it is probably possible to do this using hardware only, say with a 555. But I'm a dumb programmer, so everything looks like a Turing-complete problem to me. Plus I already have an Arduino. So that's the medium of choice. Using an Arduino, some LEDs and resistors and pure force of will, I'm going to make this work.

But there's a problem already. My design calls for 17 LEDs. The Arduino only has 14 output ports1, 2 of which I can't use because they are special. The solution to this problem is multiplexing. The basic idea is that you use X/Y coordinates to address each LED, Battleship-style. So for MxN LEDs, you only need M+N ports.

Let's say I want to light up the LED labeled 0,0. I need to apply positive voltage to A (the left column) and negative to 1 (the bottom row). B and C should be low while 1 and 2 should be high to "push the wrong way" against the remaining diodes.

But now there's another problem. Let's say I want to light up both 0,0 and 2,2. I apply positive to A and C and negative to 1 and 3...and I get all four corners lit up. Long story short, it is also necessary to employ a spot of subterfuge. If I want 0,0 and 2,2 lit up, I have to do them one at a time, but switch back and forth so fast nobody's the wiser.

And finally, there's the little matter of the clock function itself. There isn't an API call for that exactly, but the underlying chip supports interrupts. I basically just copyandpasted the timer code from elsewhere and then added a long comment explaining it to myself, probably incorrectly.

Grainy video (the ticking is an amazingly coincidental loud clock in the same room):

Somewhat less grainy still shot:

The code:

#include <avr/interrupt.h>
#include <avr/io.h>

#define NUMPOS 6
#define NUMNEG 3

int pos[NUMPOS] = {9,8,7,6,5,4};
int neg[NUMNEG] = {12,11,10};

int i = 0;
int j = 0;
int k = 0;
int lastpos = 0;
int lastneg = 0;

int isr_counter = 0;
int oldsecond = 0;
volatile int second = 0;
int seconds = 0;
int minutes = 31;
int hours = 13;

/*  
   Here's how I think this works.  The Atmega168 clock runs at 16MHz.  
   The "prescaler" divides that down.  In this case, it clicks at 2MHz.  
   Each time it clicks, it increments at 8 bit register by 1.  The register
   overflows after 256 clicks.  That overflow is the interrupt we receive.

   2000000 clicks/second divided by 256 clicks/overflow = 7812.5 overflows/second.
   So if I could count 7812.5 overflows, I know a second has elapsed.  I can't
   find .5 of an overflow, so I should really use the /4 prescaler.  But 
   a) that uses more power and b) I can't figure out what bits to set to do that.  
*/

ISR(TIMER2_OVF_vect) {
  isr_counter++;
  if (isr_counter > 7811) {
    second++;
    isr_counter = 0;
  }
};  

void SetupTimer2(){
  //Timer2 Settings: Timer Prescaler /8, mode 0
  //Timer clock = 16MHz/8 = 2Mhz or 0.5us
  //The /8 prescale gives us a good range to work with
  //so we just hard code this for now.
  TCCR2A = 0;
  TCCR2B = 0<<CS22 | 1<<CS21 | 0<<CS20;

  //Timer2 Overflow Interrupt Enable
  TIMSK2 = 1<<TOIE2;

  //load the timer
  TCNT2=0;
}

void setup() {
  for(i = 0; i<NUMPOS; i++) {
    pinMode(pos[i], OUTPUT);
  }
  for(i = 0; i<NUMNEG; i++) {
    pinMode(neg[i], OUTPUT);
  }

  for(i = 0; i<NUMPOS; i++) {
    digitalWrite(pos[i],LOW);
  }
  for(i = 0; i<NUMNEG; i++) {
    digitalWrite(neg[i],HIGH);
  }
  
  Serial.begin(9600);
  SetupTimer2();
}

void showXY(int col, int row) {
  digitalWrite(pos[lastpos],LOW);
  digitalWrite(neg[lastneg],HIGH);

  digitalWrite(pos[row],HIGH);
  digitalWrite(neg[col],LOW);
  
  lastpos = row;
  lastneg = col;
}

void loop() {
  // time changed, so readjust all the details
  if (oldsecond != second) {
    if (second > 59) {
      second = 0;
      minutes++;
      if (minutes > 59) {
        minutes = 0;
        hours++;
        if (hours > 23) {
         hours = 0;
        }
      }
    }          
    seconds = second;
    oldsecond = second;
  }

  // seconds is column 2 and has 6 bits
  k = 2;
  for(j=0; j<6; j++) {
    if (seconds >> j & 1) {
      showXY(k,j);
    }
  }

  // minutes is column 1 and has 6 bits
  k = 1;
  for(j=0; j<6; j++) {
    if (minutes >> j & 1) {
      showXY(k,j);
    }
  }

  // hours is column 0 and has 5 bits
  k = 0;
  for(j=0; j<5; j++) {
    if (hours >> j & 1) {
      showXY(k,j);
    }
  }

}
1Possibly not true. I found one post that said the 6 analog in ports could be used as digital out. But even if multiplexing isn't strictly necessary for this project, it would be for a larger one.