Categories
Projects

DIY Hot Wheels Speedometer

My nephew’s birthday was coming up in a couple days and I wanted to build or (3D) print him something. He loves Hot Wheels, so I thought I’d make him a speedometer with a digital speed readout. I envisioned two LEDs illuminating two photoresistors. When the car speeds by it would block one photoresistor and then the other. Simple! Well, suddenly it was a year or two later and his birthday was coming up again. I don’t remember why I didn’t finish the project the first time, but now I was determined. (I think for that previous birthday I printed him a rocket, so I wasn’t a complete failure of an uncle.)

Speedometer schematic.
Speedometer circuit schematic. Each photoresistor forms a voltage divider so that the voltage the analog pin sees varies with light level.

The figure above shows the circuit schematic for the speedometer. I used an Arduino Nano, a 9 V battery, two cadmium sulfide photoresistors, two 220 Ω resistors for the voltage divider, two 5 mm white LEDs, two current limiting 450 Ω resistors, an 8×32 LED display with MAX7219, and a 4 cm x 6 cm perfboard. A switch was added later. Most of the connections were made with 2- and 3-pin JST connectors as opposed to direct soldering, since this is still pretty much a prototype. You can see the assembled board in the figure below. Sorry, I didn’t take any underside photos.

Pre-enclosure photo of the assembled speedometer. I added a switch later, after an embarrassing “duh” moment.

I designed a very simple enclosure for the electronics. The important part is the separation of the light/photoresistor pairs. In my model this distance is 100 mm. I didn’t have time to come up with a secure latching mechanism for the lid (because I only had time to print it once). My choice of small posts, each with a bump, seemed OK in theory, but were much to small. Within the first week my nephew dropped the device onto carpet and all the posts broke. While I’m working on an updated model, the lid is being held in place with clear tape. Also, the part that holds the perfboard in the original model was made slightly longer than it should have been. The board still fits, but barely. You can download the STL here. Hopefully, I’ll update this post when I fix the design.

The assembled speedometer. I didn’t do a great job organizing the inside, but everything fits. The switch and Dupont header on the display touch, so the switch should really be moved.
The speedometer in action!

The Arduino code for the speedometer is below. It is adapted from an example in the MD_MAX72XX Arduino library (github), which you will need to compile the sketch. It is very basic. It simply finds a moving average for the ADC values associated with each photoresistor voltage divider, and triggers the next state when the instantaneous ADC value goes above two times the average. The speed in inches per second is found by dividing the length (3.9 inches) by the time between triggering the two photoresistors. This is sent to the display using the logic from the MD_MAX72XX example.

To summarize this project: it’s ugly, but it works!

// Code modified from the MD_MAX72XX example to display text from serial.
// Hot Wheels Speedometer
// kylelarsen.com

#include <MD_MAX72xx.h>
#include <SPI.h>

#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }

// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4

#define CLK_PIN   13  // or SCK
#define DATA_PIN  11  // or MOSI
#define CS_PIN    10  // or SS

// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// Text parameters
#define CHAR_SPACING  1 // pixels between characters

// Global message buffers shared by Serial and Scrolling functions
#define BUF_SIZE  75
char message[BUF_SIZE] = "Ready!";
bool newMessageAvailable = true;

// Distance between sensors
// LDR = Light dependent resistor

// Distance between LDRs
float ldr_dist = 3.937; // inches

int ldrA_pin = A3;
int ldrB_pin = A0;

// ADC values
unsigned int ldrA = 0;
unsigned int ldrB = 0;

// Last time LDR was triggered
unsigned long ldrA_time = 0;
unsigned long ldrB_time = 0;

// Number of points to average for baseline. Cannot go too high because of limited memory
const int     ldr_avg_num = 128;
// TO DO: Am I going to use this array for anything besides average?
unsigned long ldrA_avg_arr[ldr_avg_num];
unsigned long ldrB_avg_arr[ldr_avg_num];
unsigned long ldrA_avg    = 0;
unsigned long ldrB_avg    = 0;
unsigned int  ldr_avg_idx = 0;

// Simple state machine.
//  WAIT_A - waiting for the car to pass the first LDR
//  WAIT_B - waiting for the car to pass the second LDR
enum Status {WAIT_A, WAIT_B};
Status status = WAIT_A;

void printText(uint8_t modStart, uint8_t modEnd, char *pMsg);

void setup()
{
  mx.begin();
  mx.control(MD_MAX72XX::INTENSITY, 1);

  Serial.begin(57600);  
}

void loop()
{
  if (newMessageAvailable)
  {
    // Send message to display
    printText(0, MAX_DEVICES-1, message);
    newMessageAvailable = false;
  }

  // Read the values for the LDRs
  ldrA = analogRead(ldrA_pin);
  ldrB = analogRead(ldrB_pin);

  // Add to rolling average array
  ldrA_avg_arr[ldr_avg_idx] = ldrA;
  ldrB_avg_arr[ldr_avg_idx] = ldrB;

  ldr_avg_idx++;

  if (ldr_avg_idx >= ldr_avg_num)
  {
    ldr_avg_idx = 0;
  }

  // Do the rolling average
  int i;
  ldrA_avg = 0;
  ldrB_avg = 0;
  for (i = 0; i < ldr_avg_num; i++)
  {
    // If we reach a value that is exactly 0 then the ldrN_avg_arr hasn't filled up yet.
    if (ldrA_avg_arr[i] == 0 || ldrB_avg_arr[i] == 0)
    {
      break;
    }
    
    ldrA_avg += ldrA_avg_arr[i];
    ldrB_avg += ldrB_avg_arr[i];
  }

  ldrA_avg /= i; // ldr_avg_num;
  ldrB_avg /= i; // ldr_avg_num;

  // The trigger threshold is two times the average
  if (status == WAIT_A && ldrA > 2*ldrA_avg) {
    ldrA_time = millis();
    status = WAIT_B;
  }

  // Again, the trigger threshold is two times the average,
  // but the car must have already passed the first LDR.
  if (status == WAIT_B && ldrB > 2*ldrB_avg) {
    ldrB_time = millis();

    float dt = ((float)(ldrB_time - ldrA_time) / 1000 ); // seconds

    // Speed = Distance / time    
    sprintf(message, "%d in/s", (int)(ldr_dist / dt));
    newMessageAvailable = true;
    
    status = WAIT_A;
  }

  // Ten second timeout
  if (status == WAIT_B && (millis() - ldrA_time) > 10000)
  {
    status = WAIT_A;
    ldrA_time = 0;
  }

  // View with arduino or other plotter
  Serial.print(ldrA);
  Serial.print(",");
  Serial.print(ldrB);
  Serial.print(",");
  Serial.print(ldrA_avg);
  Serial.print(",");
  Serial.print(ldrB_avg);
  Serial.print("\n");
  
  
  delay(5);
  
}


void printText(uint8_t modStart, uint8_t modEnd, char *pMsg)
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
{
  uint8_t   state = 0;
  uint8_t   curLen;
  uint16_t  showLen;
  uint8_t   cBuf[8];
  int16_t   col = ((modEnd + 1) * COL_SIZE) - 1;

  mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  do     // finite state machine to print the characters in the space available
  {
    switch(state)
    {
      case 0: // Load the next character from the font table
        // if we reached end of message, reset the message pointer
        if (*pMsg == '\0')
        {
          showLen = col - (modEnd * COL_SIZE);  // padding characters
          state = 2;
          break;
        }

        // retrieve the next character form the font file
        showLen = mx.getChar(*pMsg++, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
        curLen = 0;
        state++;
        // !! deliberately fall through to next state to start displaying

      case 1: // display the next part of the character
        mx.setColumn(col--, cBuf[curLen++]);

        // done with font character, now display the space between chars
        if (curLen == showLen)
        {
          showLen = CHAR_SPACING;
          state = 2;
        }
        break;

      case 2: // initialize state for displaying empty columns
        curLen = 0;
        state++;
        // fall through

      case 3:  // display inter-character spacing or end of message padding (blank columns)
        mx.setColumn(col--, 0);
        curLen++;
        if (curLen == showLen)
          state = 0;
        break;

      default:
        col = -1;   // this definitely ends the do loop
    }
  } while (col >= (modStart * COL_SIZE));

  mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

Categories
Projects

Sine wave line shading

TL;DR: Convert a bitmap image into an SVG with sine wave line shading. See the code here: https://github.com/0not/laser_tools/

I recently built a laser engraver/cutter with a 30 W diode laser module and an OpenBuilds ACRO positioning system. I wanted a way to engrave moderately detailed images without having to deal with the slow speed of raster engraving. I ran across a video of an Inkscape plugin that could convert an image into a series of sine waves. (I can’t find that video or plugin now, but I do remember the plugin was associated with a Russian site). The plugin could change the frequency and amplitude (and a few more things) along each sine wave in order to realize a brighter or darker area of the image. Here is my attempt at a similar technique, but with only altering the frequency. I used Python and a Jupyter notebook for this project.

The algorithm is fairly simple: take an image then group and average adjacent rows to achieve some target output number of lines; convert the intensity at each pixel along each row into a frequency; accumulate the phase of each row/line to generate the output sine wave; and finally, display and save the waves as a vector image.

(Left) Grayscale version of input image. (Right) Representation of row-averaged image with 32 rows. The intensity across each row will be converted to frequency.
Here is astronaut Eileen Collins line shaded with 32 sine waves. This output was saved as a PNG (not SVG), so some of the fidelity was lost. It is possible to get even more detail with more than 32 rows.

The trickiest part about this project was properly accounting for phase as the frequency of the sine wave is changed. Here’s an example of what we don’t want:

x  = np.linspace(0, 50, 1000)
y1 = np.sin(1.1*x[0:len(x)//2])
y2 = np.sin(3.0*x[len(x)//2:])
plt.plot(x, np.hstack((y1, y2)))

In this example, the first half of the wave is at one frequency and the second half is at another. At the halfway point, these two waves have different phases, so we get a sharp transition.

To fix this problem, we need to accumulate the phase so that at the transition it is correct. Here is an example that accomplishes this:

x   = np.linspace(0, 50, 1000)
fs  = len(x) / x[-1]
f1  = 1.1*np.ones(len(x)//2)
f2  = 3.0*np.ones(len(x)//2)
f   = np.hstack((f1, f2))
# ex: np.cumsum([1, 2, 3, 4]) = [1, 3, 6, 10]
phi = np.cumsum(f) / fs
plt.plot(x, np.sin(phi))

I used the cumulative summation function from numpy to accumulate the phase from the lists of the two frequencies. The phase needs to be dimensionless and to account for having more than one sample per frequency, I divide by the sample frequency.

Once each sine wave is built up, I stack them vertically with (hopefully) the correct spacing and export the plot as an SVG. Using any number of tools, the SVG can be converted into g-code. I have mostly been using LaserWeb and cam.openbuilds.com.

Here is the first image I actually “engraved” on cardboard. The power was a bit too high, but the image is still visible:

In the darker areas the top layer of cardboard was burned through, but most of the image turned out well.

Give the Jupyter notebook a try using mybinder.com. The code is hosted at github.com. The code is useable, but hardly more than a proof-of-concept. Feel free to make something of it. Go wild!

A higher fidelity baboon than is in the example images.

Categories
Projects

Easy spectroscope

In 2018 I posted a design of a simple spectroscope to Thingiverse. I figure I’ll re-post it here to try and reach a wider audience. This project was inspired by the Nanomaker course on MIT OpenCourseWare.

Spectrum of fluorescent light
Spectrum of fluorescent light.

Summary

This project will teach you how to build a simple DIY spectroscope with an old CD, an empty paper towel roll, and (optionally) a 3D printer. Print the two different end caps, glue or tape the diffraction grating (part of an old CD with the metallic top layer removed) into the cap with the larger hole, and place both caps on the end of the paper towel roll. If you don’t have a 3D printer, you can use cardboard (e.g. from a cereal box) or thick cardstock. Cut slits similar to those of the 3D printed end caps.

Look through the end with the larger hole and the CD diffraction grating at different light sources to see their spectra.

Download the files from Thingiverse or here.

Materials

  • Empty paper towel roll.
  • Old, unwanted CD-ROM.
  • Glue or tape.
  • Scissors.
  • (Optional) 3D printer.
  • (Optional) If you can’t 3D print the end caps, use cardboard or dark and thick cardstock to create them.

Tips/Build steps

  • It might be tempting, but please don’t look directly at the sun, even through the spectroscope.
  • Pick a CD you don’t want anymore 😉
  • Use sharp scissors to cut out a portion of an old CD.
  • Tape (like duct tape) can help peel off the top metallic layer from the CD. Just stick the tape to the top surface and peel up.
  • The radial direction of the CD (the direction from the center of the CD to the edge) should be oriented parallel to the long dimension of the filter hole.
  • The two end caps should be rotated so that the two rectangles are perpendicular.
  • The previous two tips are to ensure that the diffraction pattern (the rainbow) is most visible. See the image of the diffraction pattern for an example of what the spectrum of a fluorescent light can look like.
  • Double-sided tape can be used to affix the CD to the end cap temporarily while adjustments are made.

Don’t have a 3D printer? Don’t worry — use cardboard for the end caps! The slit should be no more than about 0.5 mm wide. The other end cap doesn’t matter so much, just make a window for your diffraction grating/CD.

For bonus points, add a webcam and turn this into an easy spectrometer. (The difference between a -scope and a -meter is that a -scope just sees things whereas a -meter can measure things.)

Background information

With a spectroscope you can view the individual colors from a light source. The diffraction grating works like a prism in that it separates out these individual colors. The pattern caused by this separation is called a spectrum (plural spectra). The purpose of the slit is to help in distinguishing components of the spectrum. Try holding the diffraction grating up to your eye as you look at a room light. You’ll notice that without the slit, the diffraction patterns look like a rainbow version of the light source. What we want to see is just a series of lines corresponding to the colors in the light or a continuous rainbow if the light contains all colors. Different light sources have different spectra. The sun is considered white light — it contains all colors. Rainbows are the spectrum of the sun. Incandescent light bulbs also produce white light. You can see this continuous spectrum by looking through the spectroscope at an incandescent light bulb. Other light sources, like LEDs or fluorescent light bulbs, produce light that is missing some colors (or, only contains certain colors). The spectra of such light sources are called discrete, meaning you see distinct lines of colors in the spectrum. The image I attached to this project shows the spectrum of a fluorescent light. I hope you learn something new with this simple CD-ROM paper towel roll spectroscope!