Sunday, March 20, 2011

Magic 8 Thing

For about a week now I have been staring at a cellphone charger my friend Caitlin gave me to repair after she had loaned it to some chimpanzees for apparent durability testing.  I was debating on whether I had put the repair off long enough, when, with no humans in earshot to ask, I figured I would build a Magic 8 Thing and ask it if I could procrastinate any longer.  Was this a lack of motivation to repair a cellphone charger or was it motivation to make a Magic 8 Thing.  Irrelevant really as I got both things done in about 10 hours this weekend.

For those who don't know what a Magic 8 Thing is, it is a Magic 8 Ball, made with an ATMega328, an LCD, a surplus mercury tilt switch from a retired thermostat and some code.  I call it a "Thing" instead of a "Ball" because my version is both non-spherical and bears no functional resemblance to an actual "Magic 8 Ball".  It does, however, have equal effectiveness in helping you make important life decisions.

I wanted to be able to physically shake the thing to illicit a response to my questions.  And, since I wanted this project to be done quickly i.e. no ordering parts  I was thinking about making a "shake" switch out of a couple of quarters and a pachinko ball.  But, instead, while rooting through one of my many "stuff" bins I found an old thermostat with a mercury tilt switch.  This has an unanticipated side effect of being able to hear the mercury sloshing around when you shake it.  It is cool, it almost sounds like the original Magic 8 Ball that has the answer icosahedron floating in it.  If I had my druthers, however, I would have used an accelerometer like a normal human to detect the shakes, but I have none at the moment.

You can see the mercury switch in the picture below.  It is hot glued to the lower left of the circuit board.  The header at the top connects the LCD which is mounted in the lid of this enclosure.  Space and time were limiting factors on this  build so, component layout was adjusted accordingly.  E.g. jamb the bits in where they fit.

Here is a schematic.

And the code.

Program Tile: Magic 8 Thing
Filename: ATMega_328_Magic_8_Thing_v1.0_main.c

Date: 2011.03.20

Author: Pete Mills

Ext. Crystal Osc. 8.0-    MHz; Start-up time PWRDWN/RESET: 258 CK/14 CK + 4.1 ms

Program description

This program uses an ATMega328 to interface an LCD and a Hg "shake" switch.
This is actually a mercury tilt switch from a retired thermostat.
rand() is called to generate a random value for a switch statement.  There are 
20 possible answers based on the original "Magic 8 Ball" toy.  The responses were 
found on wikipedia and modified very little.

The LCD library is written by Peter Fleury and can be found at
You will need to modify lcd.h to fit your port connections.

The user is to ask the device a question then shake the unit.  The device then
responds with one of 20 answers ranging from affirmative to negative and somewhere
in between.  Don't shake it too hard or it will tell you to "Stop It! before giving
you an answer.

// Includes

#include <stdlib.h>
#include <avr/io.h>     // defines things like "PORTB" and "TCCR0" etc
#include <util/delay.h>    // delay functions
#include "lcd.h"     // LCD Library
#include <avr/interrupt.h>

// Definitions

#define LED PD1      // LED pin PORTD Pin 2

// function prototypes

void setup(void);
int is_shakin(void);

// Global 

volatile uint8_t shake_count = 0; // counts up the mercury switch contacts, overflows for entropy
volatile uint16_t seconds = 0;  // seconds counter

int main(void)

sei();        // enable global interrupts
lcd_init(LCD_DISP_ON);    // initialize display and turn off cursor

 uint8_t rand_num = 0;   // random number from prng seeded w/ shake_count
 lcd_clrscr();     // clear display and put cursor @ home
 lcd_puts("Magic 8 Thing\n"); // line 1
 lcd_puts("Ask and Shake");  // line 2
 while (is_shakin() == 0);  // while nothing is happening
         // now it detected a shake
 _delay_ms(500);     // wait a second er, half
 lcd_clrscr();     // clear display and put cursor @ home
 while (is_shakin() == 1)  // wait for the shaking to stop
  lcd_puts("Stop It!");  // line 1
  lcd_clrscr();    // clear display and put cursor @ home
 srand(shake_count);    // seed the prng with shake_count
 rand_num = (uint8_t) rand(); // get a pseudo random number from 0 to 255
 lcd_puts("Magic 8 Thing\n"); // line 1
 lcd_puts("Says...");   // line 2
 // the responses...
 lcd_clrscr();     // clear display and put cursor @ home
 switch (rand_num % 20)   // pick an answer
  case 0:
   lcd_puts("As I see it, yes\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 1:
   lcd_puts("It is certain!\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 2:
   lcd_puts("It is decidedly\n"); // line 1
   lcd_puts("so.");    // line 2
  case 3:
   lcd_puts("Most likely.\n");  // line 1
   //lcd_puts("blank");   // line 2
  case 4:
   lcd_puts("Outlook good!\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 5:
   lcd_puts("Signs point to\n"); // line 1
   lcd_puts("yes!");    // line 2
  case 6:
   lcd_puts("Without a doubt!\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 7:
   lcd_puts("Yes.\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 8:
   lcd_puts("Yes, definitely\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 9:
   lcd_puts("You may rely\n");  // line 1
   lcd_puts("on it!");    // line 2
  case 10:
   lcd_puts("Reply hazy,\n");  // line 1
   lcd_puts("try again.");   // line 2
  case 11:
   lcd_puts("Ask again later.\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 12:
   lcd_puts("Better not tell\n"); // line 1
   lcd_puts("you now!");   // line 2
  case 13:
   lcd_puts("Cannot predict\n"); // line 1
   lcd_puts("now...");    // line 2
  case 14:
   lcd_puts("Concentrate and\n"); // line 1
   lcd_puts("ask again.");   // line 2
  case 15:
   lcd_puts("Don't count on\n"); // line 1
   lcd_puts("it!");    // line 2
  case 16:
   lcd_puts("My reply is no.\n"); // line 1
   //lcd_puts("blank");   // line 2
  case 17:
   lcd_puts("My sources say\n"); // line 1
   lcd_puts("no!");    // line 2
  case 18:
   lcd_puts("Outlook not so\n"); // line 1
   lcd_puts("good.");    // line 2
  case 19:
   lcd_puts("Very doubtful!\n"); // line 1
   lcd_puts("LOL");    // line 2
  default: // you should never be here
   lcd_puts("IDK\n");    // line 1
  _delay_ms(5000); // display the answer for this long

// functions

void setup(void)

// port config

DDRC |= ((1<<1) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6)); // set PC0::6 to 1 for LCD OUTPUT

DDRD &= ~(1<<2);    // set PD0 to "0" for mercury switch input
PORTD |= (1<<2);    // enable internal pullup
DDRD |= ((1<<0) | (1<<1)); // set PD1 to "1" for output - LED PD0 for LCD
PORTD &= ~(1<<1);    // set the output low

// interrupt config
// external interrupts

EICRA |= ((1<<ISC00) | (1<<ISC01)); // external rising edge interrupt on INT0
EIMSK |= (1<<INT0);     // enable external interrupt



int is_shakin(void)
// this function determines if the unit is being shaken based on the number
// of pin state changes in 100mS

 uint8_t shake_count_first = shake_count; // initial shake count
 if (shake_count - shake_count_first > 3) // can adjust the shake "sensitivity" here
  LED_PORT |= (1<<LED);
  return 1;
 LED_PORT &= ~(1<<LED);

return 0;

Here is a video of the Magic 8 Thing in action.  As you can see, you ask it a question (which I failed to do in the video), shake it and it replies with one of its 20 answers.  If you shake it to hard though, it tells you to "Stop It!" then gives you an answer anyway. The sound at the end of the video is my cellphone getting a text message.  It has nothing to do with the Magic 8 Thing.

So, as I stated in the beginning, this Magic 8 Thing was made to help me decide if I have goofed off enough and should start on the cell phone charger repair.  When I posed the the question "Magic 8 thing, have I procrastinated long enough?" it replied with the text in the picture below. Apropos.

Friday, March 11, 2011

Cigar Box Laser Light Show

It was spring break last week so I had some spare time to kill.  I wanted to do a project that would be done fairly quickly and still have some time to study for classes resuming.  I do have several other projects going that I could have worked on, but I figured a laser light show would be appropriate for the occasion, being spring break and all.  Actually, I have no idea if spring break party goers are the least bit interested in seeing laser light shows, but I am interested in getting some motors spinning programmatically.  And to seal the deal there is a new cat in the mix over here so I have buckets full of laser pointers.  

A cigar box made a really nice case for this project due to the nature of mounting the motors.   Initially I was going to use a Fossil watch tin but it proved too small for everything to fit nicely.  Cigar shops sell empty boxes for a few dollars each.

Here is an overview of the theory of operation.  When a mirror is mounted on a motor shaft so that the mirrors' face is less than perpendicular and less than parallel to the motors axis of rotation and a laser is reflected off of this rotating mirror, the reflected laser light will trace a circle on a projection surface and dependent on the speed of the motor being high enough, the laser path traced will appear to be a solid circle on the projection surface (a wall in my case) due to the persistence of vision.  You can think about it like a laser cone being reflected off of the mirror.  If you stopped there, with one motor and mirror, you would have a laser light show that can draw circles.  If instead, you aim this reflected "laser cone" onto another one or more motor/mirror pairs, very interesting shapes begin to appear and change too, depending on the relative speed of all the motors. 

I decided on 3 motors/mirrors for my laser light show.  I got the motors with nice metal gears on their shafts from Ratshack.  The gear was helpful in mounting the mirrors which are just hobby mirrors from some craft store.  They are not first surface mirrors as you would have expected in an optics related project, but I did not witness any ghosting as I was anticipating.  I think this is due to the relative brightness of the laser beam in a dark room.  

I first mounted (epoxied) the mirrors onto the motor shaft at too large of an angle.  I had carefully setup a jig that would hold a mirror at an angle and centered on the motor shaft to be glued.  With the deviation of motor rotation axis and mirror surface too far from perpendicular the reflected laser cone just shot off the edges of the final mirror.  In the end, I used a sharpie to mark the center of the mirror, squirted some hot glue on the back of the mirror and stuck the motor with gear on the center mark, holding it as near to perpendicular as my eyes could tell until the glue dried.  This worked great and was much simpler than I had tried to make it.  

After the project was complete, I projected circles onto a wall, one at a time with each mirror.  I measured the diameter of the circle and the distance to the wall to empirically derive the angle of the mirror face to the motor's axis of rotation to be 87.55 degrees, 87.74 degrees and 86.12 degrees, for the first, second and third mirrors respectively.

Here you can see a laser being reflected through the three mirrors.  And in the next photo how I set up the 3 motors/mirrors to be 45 degrees from each other.  The laser beam "enters" the lower left mirror, reflected to the top left mirror then, on to the final mirror on the right and out, straight down, 90  degrees from where it started.

And here it is all set up ready to use.  You can select manual mode where one of three potentiometers is assigned to one of the three motors to control its speed via PWM.  The scale potentiometer scales the PWM output so that you can run the whole system at say 50% or 82% etc of full speed.  In automatic mode a PRN generates the motors PWM values, it too is scaled by the scale pot.  Also, in automatic mode the third motor potentiometer now controls the fade delay, setting how long it takes the motor to get to its new PWM value.

Here are a couple of videos.  The first one shows the output while running in automatic mode.  The second video is a slide show of photos of the output.  There are more videos on my YouTube channel of the laser light show if you would like to have a look; these are just two.

A couple things I noticed about my setup are that one of the three motors whines at lower speeds.  I fully expected the whole thing to be a little noisy, and to be fair in person it is not loud at all.  I suspect the one motor has looser windings than the rest allowing them to vibrate more than the others.  The PWM frequency I had to drive the motors at is well within the human audible range.  I had initally tried a higher PWM frequency but could not get the motors to spin.  I think this is an issue with motor inductance.

As you can imagine video and pictures of the laser light show have framerate issues that produce interesting effects but aren't that great to look at on youtube.  In person, the laser is much brighter and it does produce some pretty interesting images.  Some almost appear three dimensional.  It was a fun project, but I doubt I will get that much use out of it.  I certainly wont be sitting there staring at it for hours on end, but on the upside to that, if I look in a mirror I probably won't see my face melting off and flowing into a river that a unicorn is drinking from.  Tune in, turn on, and drop out.

Code and circuit.  On the circuit side of things there really isnt much to talk about.  I included a description of the hardware in the comments in the program that has a bit more detail, but basically the ATMega328 reads a pot and outputs a PWM signal that triggers the gate of an IRF510 mosfet for each motor.  The motors needed 3vDC so I whipped up a quick power supply with an LM317 and a heatsink.  thats about it really.  

And here is the code.

Program Tile: Laser Light Show v1.0
Filename: ATMega_328_LLS_v1.0_main.c

Date: 2011.03.10

Author: Pete Mills

Int. RC Osc. 8 MHz; Start-up time PWRDWN/RESET: 6 CK/14 CK + 65 ms


Program Description

This program has two modes of operation.  In the manual mode, four potentiometers voltages
are measured. One for "scale" and 3 are assigned to 3 motors PWM channels. That is to say
an 8-bit pot value is read, it is scaled by the scale pot value and it is put into an 8-bit PWM 
register such that a potentiometer is controlling the speed of the motor via open loop PWM. 

The scale potentiometer sets the upper range for the PWM output. eg. if the scale pot is
at 50% the PWM output is scaled full range (0::255) to 0::127.

So, in manual mode, a pot is read, the value scaled based on the scale pot, the value
is output to a motor channel. This is done 3 times, once for each motor.

In automatic mode, the scale pot works in just the same way but, the 3 "motor pots" no
longer control the speed of the motors.  Instead rand() is called to generate an 8-bit
PRN that is then scaled by the scale pot and put into the PWM register for a motor.

In automatic mode the third motor pot (motor_pot_2 ADC CH2) is now used to set the fade delay 
between random speed generations.  Larger ADC values = slower fade times.

There is an LED connected to PD0 to indicate which mode you are in, though it is fairly
obvious when the laser light show is running itself using automatic mode.

Circuit description

Microcontroller: ATMega328

Regulated 5v power is supplied via an LM7805 and requisite caps
No external crystal as the internal R/C oscillator is used (dont for get to set the fuses...)
See above for internal R/C oscillator settings.

There are 4 potientiometers with their wipers connected to PC0::3 and legs to 5v and gnd
An led is connected to PD2 with a current limiting resistor
PD0 has a toggle switch to ground for selecting the mode
PD3, PD5 and PD6 connect to the gate of 3 separate IRF510 mosfets
The 3 IRF510 mosfets each have a 1k0 pulldown resistor attached to their gates.
The source of said mosfets are each connected to gnd and their drains to one leg of a motor,
the other leg of the motor connects to +3v motor power.
Across each of the motor leads is attached a reverse biased 1N4001 diode to protect against back EMF

The 3v motor power comes from an LM317 adjustable regulator with heatsink attached. 

// Includes

#include <avr/io.h>     // defines things like "PORTB" and "TCCR0" etc
#include <util/delay.h>    // delay functions
#include <avr/interrupt.h>   // interrupt service
//#include <avr/sleep.h>

// Definitions

#define LED PD2      // LED pin PORTD Pin 2
#define MODE PIND0     // mode switch

#define FIRST_ADC_INPUT 0   // lowest ADC channel to sample
#define ADC_CHANNELS 4    // number of ADC channels to sample
#define ADC_VREF_TYPE 0    // AREF, Internal Vref turned off

#define MOTOR_POT_0 0    // ADC channels
#define MOTOR_POT_1 1    
#define MOTOR_POT_2 2
#define SCALE_POT 3

// Global Constants

uint8_t FADE_DELAY_MIN = UINT8_C (10);  // mS
uint8_t IN_LOW_SCALE = UINT8_C (0);   // output scaling
uint8_t IN_HI_SCALE = UINT8_C (255);  // max val for motor pot ADC
uint8_t OUT_LOW_SCALE = UINT8_C (0);  // min val for motor pot ADC

volatile uint8_t adc_raw[ADC_CHANNELS];

// function prototypes

void setup(void);
int get_adc(int a);
int scale_outp(int input, int inp_low, int inp_hi, int outp_low, int outp_hi);

int main(void)

setup(); // do port configs etc
sei();  // global interrupt enable

  // Variables
  uint8_t cur_motor_speed_0 = 0;  // current speed of motor 0::255
  uint8_t cur_motor_speed_1 = 0;
  uint8_t cur_motor_speed_2 = 0;
  uint8_t new_motor_speed_0 = 0;  // random value to fade to 0::255
  uint8_t new_motor_speed_1 = 0;
  uint8_t new_motor_speed_2 = 0;
  uint8_t adc_val_motor_0 = 0;  // pot on adc0
  uint8_t adc_val_motor_1 = 0;  // pot on adc1
  uint8_t adc_val_motor_2 = 0;  // pot on adc2
  uint8_t adc_val_scale = 0;   // pot on adc3
  PORTD &= ~(1<<LED);  // turn off led to show where you are - manual mode
  // get the output scale value
  adc_val_scale = adc_raw[SCALE_POT];  
  // get the motor pot value, scale it and output to PWM channel - thrice
  adc_val_motor_0 = adc_raw[MOTOR_POT_0];  
  cur_motor_speed_0 = scale_outp(adc_val_motor_0, IN_LOW_SCALE, IN_HI_SCALE, OUT_LOW_SCALE, adc_val_scale);
  OCR2B = cur_motor_speed_0;
  adc_val_motor_1 = adc_raw[MOTOR_POT_1];
  cur_motor_speed_1 = scale_outp(adc_val_motor_1, IN_LOW_SCALE, IN_HI_SCALE, OUT_LOW_SCALE, adc_val_scale);
  OCR0B = cur_motor_speed_1;
  adc_val_motor_2 = adc_raw[MOTOR_POT_2];
  cur_motor_speed_2 = scale_outp(adc_val_motor_2, IN_LOW_SCALE, IN_HI_SCALE, OUT_LOW_SCALE, adc_val_scale);
  OCR0A = cur_motor_speed_2;
  // effective end of manual mode code
  while (PIND & (1<<MODE))     // if mode switch is on automatic mode (val 1/true)
   PORTD |= (1<<LED);      // turn on led to show mode is automatic mode
   new_motor_speed_0 = (uint8_t) rand(); // get a pseudo random number from 0 to 255 for each of the motors
   new_motor_speed_1 = (uint8_t) rand();
   new_motor_speed_2 = (uint8_t) rand();
   // while the current motor speed != motor speed set point
   while((cur_motor_speed_0 + cur_motor_speed_1 + cur_motor_speed_2) != (new_motor_speed_0 + new_motor_speed_1 + new_motor_speed_2))
    if(cur_motor_speed_0 > new_motor_speed_0){   // take a step towards the new speed for motor 0
    else if(cur_motor_speed_0 < new_motor_speed_0){
    if(cur_motor_speed_1 > new_motor_speed_1){  // and for motor 1
    else if(cur_motor_speed_1 < new_motor_speed_1){
    if(cur_motor_speed_2 > new_motor_speed_2){  // and motor 2
    else if(cur_motor_speed_2 < new_motor_speed_2){
    adc_val_scale = adc_raw[SCALE_POT];  // get the scale pot value
    // scale values first then output to PWM registers
    OCR2B = scale_outp(cur_motor_speed_0, IN_LOW_SCALE, IN_HI_SCALE, OUT_LOW_SCALE, adc_val_scale);
    OCR0B = scale_outp(cur_motor_speed_1, IN_LOW_SCALE, IN_HI_SCALE, OUT_LOW_SCALE, adc_val_scale);
    OCR0A = scale_outp(cur_motor_speed_2, IN_LOW_SCALE, IN_HI_SCALE, OUT_LOW_SCALE, adc_val_scale);
    // MOTOR_POT_2 now controls the fade delay within automatic mode
    adc_val_motor_2 = adc_raw[MOTOR_POT_2];  
    _delay_ms(FADE_DELAY_MIN + adc_val_motor_2); 
   } // end fade while
  } // end if(MODE)
 } // end while(1)
} // end int main()

// functions

void setup(void)

// port config

DDRC &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3));  // set PC0::3 to "0" for adc input

DDRD &= ~(1<<0);          // set PD0 to "0" for switch input
PORTD |= (1<<0);          // enable internal pullup
DDRD |= ((1<<2) | (1<<3) | (1<<5) | (1<<6));   // set PD2::3 and PD5::6 to "1" for output - PWM & LED
PORTD &= ~((1<<2) | (1<<3) | (1<<5) | (1<<6));  // set the outputs low

// ADC Config

// adc enable, cdiv 8Mhz/64 = 125kHz, interrupt enable, auto trigger enable

ADCSRA |= ((1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADIE) | (1<<ADATE));  
ADCSRB |= (1<<ADTS2); // set to be anything other than free running mode 
DIDR0 |=((1<<ADC0D) | (1<<ADC1D) | (1<<ADC2D) | (1<<ADC3D)); // disable digital input buffers to save power

// PWM config

// Timer/Counter0: channel:A/B clear on compare match, Fast PWM, TOP = OC0A,0C0B

TCCR0A |= ((1<<COM0A1) | (1<<COM0B1) | (1<<WGM01) | (1<<WGM00));
TCCR0B |= ((1<<CS00) | (1<<CS02));         // internal clock as source, div 1024 prescale

// Timer/Counter1: Channel:A clear on compare match, Fast PWM, TOP = OC2B

TCCR2A |= ((1<<COM2B1) | (1<<WGM21) | (1<<WGM20));
TCCR2B |= ((1<<CS20) | (1<<CS22));         // internal clock as source, div 1024 prescale


int scale_outp(int input, int inp_low, int inp_hi, int outp_low, int outp_hi)
return ((input - inp_low) * (outp_hi - outp_low) / (inp_hi - inp_low) + outp_low);

// ADC interrupt service routine
static unsigned char input_index=0;

// Read the AD conversion result
// Select next ADC input
   if (++input_index >= ADC_CHANNELS)

   ADMUX=(FIRST_ADC_INPUT | ADC_VREF_TYPE | (1<<ADLAR))+input_index; //and left adjust

// Start the AD conversion
   ADCSRA |= (1<<ADSC);



A reader asked if I would make a schematic available.  I didn't have one so I just drew this one up from the text description in the program, of the circuit, that I wrote when I did the project..  This schematic was drawn after I built the circuit.  I did not use this schematic while I was building this project, but I believe it to be correct.