Friday, July 31, 2020

Hacked an Answering Machine

I found this old answering machine and originally thought it was a good find for just harvesting the 7-segment display. But as it sat around my work table it occurred to me it would be fun to use the display and buttons to insert a Simon game inside the case. I put it off because it seemed daunting but once I started on it yesterday it turned out to only take a day to put together.So what I have now is a Simon-type game where you have to guess the right sequence of the 9 buttons to succeed. It starts out with a sequence 1-9 from the CLOCK button and going counter-clockwise to VOLUME, but after you are successful and receive a crazy eight pattern (from my new NSegmentDisplay library!) the sequence is randomized for your next turn. Each time you hit a wrong button the display goes back to 0 and a vibration motor buzzes for some cool haptic feedback. So here itemized are the different aspects of the project.
Edit: I realize now this is not like a Simon game because it does not show you a sequence you have to copy. You just have to figure out the correct sequence by trial and error, and remember what you figured out so far! I actually like it that way.

The Circuit

First I wrote out the pins for the 7-segment:

Here is the original prototype circuit here on Tinkercad Circuits
The 7-segment display is common anode and the decimal point did not have a pin. I guess I didn't need to wire the anode to an IO pin but I did out of habit as that's what I would do with a multi-digit display. I used INPUT_PULLUP with the buttons to keep the wiring simple. Now my vibration motor circuit doesn't seem correct because the transistor collector is open, but wired this way is the only way the motor would turn on and off, otherwise it just stayed on. It could be pin 13 on the Adafruit Metro Mini I used in the final circuit was the issue as it was behaving buggy when I originally had a button connected to it.

Testing the Circuit

I tested the display and buttons with this code, note it uses my NSegmentDisplay library. So from this I could see all the buttons worked and the display showed numbers correctly. Because of INPUT_PULLUP the button registers as pressed when it is LOW.
#include <NSegmentDisplay.h>
//common anode display with 7 segments, one digit
const int NUM_SEG_PINS = 7;
const int NUM_DIGIT_PINS = 1;
const int segments[] = {3, 4, 5, 6, 7, 8, 9};
const int digits[] = {2};
const int buttons[] = {10, 11, 12, A0, A5, A4, A3, A2, A1};
NSegmentDisplay disp(true, NUM_SEG_PINS, segments, NUM_DIGIT_PINS, digits);
int current = 0;

void setup() {
  for (int i = 0; i < 9; i++) {
    pinMode(buttons[i], INPUT_PULLUP);
  }  
}

void loop() {
  disp.number(0, current);
  for (int i = 0; i < 9; i++) {
    if (digitalRead(buttons[i]) == LOW) {
      current = i;
    }
  }
}

The Build

I thought it was going to be really hard to line up the buttons (which I harvested from an old stereo panel) and the 7-seg display with their locations and depths under the machine face. But it turned out to work very easily as the perfboard I used was just the right size to fit in the plastic bottom which had supports in just the right places. Wow that was very good fortune. It did take some time to position the display on the board, taking the shell apart many times which involved releasing tight plastic clips. So power is supplied through the Metro Mini micro-USB sticking out the back. I could wire a battery to supply power to the Metro Mini inside but I don't want to have to keep cracking open the case to replace it, so I think I'm satisfied to have it powered by USB. 

Final Code

Here is the final code, which requires the NSegmentDisplay library. I may still add a long press option for one of the buttons to reset the game in case you get tired of solving one and want to restart. 
#include <NSegmentDisplay.h>
//common anode display with 7 segments, one digit
const int NUM_SEG_PINS = 7;
const int NUM_DIGIT_PINS = 1;
const int segments[] = {3, 4, 5, 6, 7, 8, 9};
const int digits[] = {2};
const int buttons[] = {10, 11, 12, A0, A5, A4, A3, A2, A1};
const int motor = 13;
int sequence[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
NSegmentDisplay disp(true, NUM_SEG_PINS, segments, NUM_DIGIT_PINS, digits);
int current = 0;

//I did not write this function, I got it here https://forum.arduino.cc/index.php?topic=66206.0
//I had no free analog pins to use for randomseed so needed some other way
#include <EEPROM.h>

void reseedRandom( void )
{
  static const uint32_t HappyPrime = 937;
  union
  {
    uint32_t i;
    uint8_t b[4];
  }
  raw;
  int8_t i;
  unsigned int seed;

  for ( i = 0; i < sizeof(raw.b); ++i )
  {
    raw.b[i] = EEPROM.read( i );
  }

  do
  {
    raw.i += HappyPrime;
    seed = raw.i & 0x7FFFFFFF;
  }
  while ( (seed < 1) || (seed > 2147483646) );

  randomSeed( seed );

  for ( i = 0; i < sizeof(raw.b); ++i )
  {
    EEPROM.write( i, raw.b[i] );
  }
}

void setup() {
  for (int i = 0; i < 9; i++) {
    pinMode(buttons[i], INPUT_PULLUP);
  }
  pinMode(motor, OUTPUT);
  digitalWrite(motor, HIGH); //turn off motor
  reseedRandom();
  Serial.begin(9600);
}

void loop() {
  disp.number(0, current);
  checkButtons();
  if(current > 8) {
    shuffle();
    for(int i = 0; i<5; i++) {
      disp.crazyEights(50);
    }
    reset();
  }
//  for(int i = 0; i< 9; i++) {
//    Serial.print(sequence[i]);
//  }
//  Serial.println();
}

void checkButtons() {
  for (int i = 0; i < 9; i++) {
    if (digitalRead(buttons[i]) == LOW) {
      eval(i);
    }
  }
}

void eval(int whichButton) {
  if (whichButton == sequence[current]) {
    current++;
    delay(500);  
  } else {
    buzz();
    reset();
  }
}

void reset() {
  current = 0;
}

void buzz() {
  digitalWrite(motor, LOW);
  delay(100);
  digitalWrite(motor, HIGH);
}
//found this here https://forum.arduino.cc/index.php?topic=345964.0
void shuffle() {
  const int count = sizeof sequence / sizeof sequence[0];
  for (int i = 0; i < count; i++) {
    int n = random(0, count); 
    int temp = sequence[n];
    sequence[n] =  sequence[i];
    sequence[i] = temp;
  }
}


No comments :