Thursday, June 2, 2016

First Post, also a lesson in what not to do

I was going to save the first post for a project that I could document from the beginning. Also, the project I have been working on isn't going so well and I thought that I didn't want to start this blog with a failure. On the other hand, I have learned a lot in doing this and so I decided that others may learn from this. So here it is.

A friend of a friend runs a tutorial center. She uses this suitcase game show buzzer for her program. It has eight buttons and eight light bulbs. First to press the button gets their light turned on and the buzzer sounds. Pretty simple, the circuit board is older than I am. The problem with it is that the light bulbs kept burning out. No problem, I can convert it to run off a computer chip, LEDs instead of light bulbs and make it play a tune instead of a boring old buzzer.

I committed to a project on someone else's property without really knowing what I was doing. I had messed around with Arduino before. It's a micro controller board with a simplified programming language to create an easier entry into the world of electronics. Up to this point I had only really fudged my way through the examples and had a basic understanding of the capabilities of the board. I was way in over my head.

I worked through the design of the circuit in my head. I also worked through the programming logic and kludged my way through the code. Circuit and code went through several revisions until I had a reasonable confidence that it would work.

So I ordered the stuff from Tayda Electronics. They are a great company with low prices and quick shipping. You can also order in low quantities. Their inventory isn't as extensive as some other supply houses but they have the most common items. I ordered:
  • Atmega 328P - micro controller chip in an Arduino Uno board
  • 74HC595 - Shift register to allow me to control 8 LEDs with only 4 wires
  • 75HC165 - Shift register to allow me to take 8 buttons with only 4 wires
  • 8 LEDs and LED holders
  • Strip board - fiberglass board with a grid of holes connected in rows by copper
I also would need a few capacitors, resistors, wires, a transistor and a voltage regulator but I had those salvaged from other garbage electronics.

To design the circuit I used KiCad EDA. It's free (libre) software available for Windows, Mac, and Linux. Also there are many tutorials on how to use it on YouTube. There are others and even free available like Eagle but it's what I picked.

I followed this tutorial/guide: ArduinoToBreadboard and followed the branch eliminating the external crystal oscillator. Since this project doesn't require precise timing (fractions of a second over several hours), it seemed an easy thing to be able to drop from the shopping list.

This I followed ArduinoISP so I could put my code for the game show buzzer on my chip. I also consulted many YouTube videos for help.

I tested the different sections of the code on the breadboard before soldering it together. Each element worked great individually so I was ready to put it on the strip board.

So I soldered everything together on the strip board and plugged it in. Everything worked beautifully and I did a happy dance. Except that it didn't. And as of now still doesn't.

There is a steep learning curve to be able to use KiCad. It's not that KiCad is bad software, because I think it does what it was designed to do very well. It's just a complicated thing to design a circuit board with nearly no electronics knowledge.

Creating a stand-alone Atmega 328P using the Arduino software isn't difficult. If you follow the tutorial correctly and read all the instructions first before beginning.

Soldering isn't difficult. It takes some practice but it's a skill that can be learned in a couple hours. Mastery takes more time. More time than I have invested so far. Also, pay attention to your schematic. It's the map that shows you how everything connects. Before you start getting your parts laid out, make sure you trace every connection on the schematic. Make sure it makes sense electronically. I had the buttons not connected to any power. I had a few pins on the Atmega chip connected to power that should have been grounded.

After making those corrections, it still doesn't work. I think I may have to go back to the breadboard and make a more through breadboard mock-up. See if the problem lies in the hardware, my soldering job, or the code.

The lessons here to be learned from all this: Don't commit to a project that someone else is relying on before you are completely confident in your skills. Take baby steps on little projects. There is a lot between an idea and the finished product. There is a reason why companies have different teams involved in the circuit design, the programming and the assembly. I tried to learn all of them at once from scratch.

Some helpful links that may help you in your projects:

Arduino - ShiftOut - a tutorial in how to use the 74HC595 chip to control many outputs with only using 4 pins of the Arduino

Arduino PinMapping - the pins of the Atmega 328P don't correspond to the pin naming of the Arduino Uno. This helps translate between the two.

DigiKey Resistor Color Codes - Translate the color bands of resistors to their numerical value.

Below is the code I used in this project. I'm not sure if it works but perhaps someone smarter than I will read this and see an error. If you are that person, please let me know!
[code]
//Code by Taylor Blake
//Created for Edna Runner Center

//--Libraries--
#include "pitches.h"


//bytes link LED values to 595 pins
const byte red1Pin = 0x01;  //binary code for LED 1
const byte red2Pin = 0x02;  //binary code for LED 2
const byte red3Pin = 0x04;
const byte red4Pin = 0x08;
const byte green1Pin = 0x10;
const byte green2Pin = 0x20;
const byte green3Pin = 0x40;
const byte green4Pin = 0x80;

//'595 pin values
int latchPin = 2;   //328 pin 4 connected to 595 ST_CP 12
int clockPin = 4;   //328 pin 6 connected to 595 SH_CP 11
int dataPin = 3;    //328 pin 5 connected to 595 DS 14

//--Variables--
byte currentAnimation = 0xAA;  //randomly generated sequence of LEDs to light
boolean hold;       //variable to determine if reset button has been pressed

int startup[7] = {
  NOTE_B4, NOTE_GS4, NOTE_GS4, NOTE_FS4, NOTE_GS4, NOTE_B4, NOTE_B4
};                   //MELODY TO PLAY DURING STARTUP
int startupDurations [] = {
  4, 4, 4, 4, 4, 4, 4
};                  //note durations 4=quarter, 8=eighth, etc.

int answerTone [] = {
  NOTE_C4, NOTE_E4, NOTE_G4
};                  //tone to play upon answer
int answerDur [] = {
  8, 8, 8
};



//--Pin Values--
int resetPin = A1;        //DP4 (6) connected to game reset button
int tonePin = 7;      //connect speaker to DP7 (13)


//--LED status--
boolean red1State;
boolean red2State;
boolean red3State;
boolean red4State;
boolean green1State;
boolean green2State;
boolean green3State;
boolean green4State;

//--LED durations--
unsigned long red1dur = 0;
unsigned long red2dur = 0;
unsigned long red3dur = 0;
unsigned long red4dur = 0;
unsigned long green1dur = 0;
unsigned long green2dur = 0;
unsigned long green3dur = 0;
unsigned long green4dur = 0;
unsigned long previousMillis[8] = {0, 0, 0, 0, 0, 0, 0, 0};

//--LED previous millis
unsigned long red1millis = 0;
unsigned long red2millis = 0;
unsigned long red3millis = 0;
unsigned long red4millis = 0;
unsigned long green1millis = 0;
unsigned long green2millis = 0;
unsigned long green3millis = 0;
unsigned long green4millis = 0;

//blink limits
int lowblink = 50;
int highblink = 125;

//74HC165 variables
#define NUMBER_OF_SHIFT_CHIPS 1
#define DATA_WIDTH NUMBER_OF_SHIFT_CHIPS * 8
#define PULSE_WIDTH_USEC 5
#define BYTES_VAL_T unsigned int

int ploadPin = 8;       //Connect DP8 (14) to 165 PL (1)
int clockEnablePin = 9; //Connect DP9 (15) to 165 CE (15)
int dataPin165 = 11;    //Connect DP11 (17) to 165 Q7 (9)
int clockPin165 = 12;      //Connect DP12 (18) to 165 CP (2)

BYTES_VAL_T pinValues;
BYTES_VAL_T oldPinValues;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  randomSeed (analogRead(0));
 

  //set pinMode
  pinMode (resetPin, INPUT);
  //595 pins
  pinMode (latchPin, OUTPUT);
  pinMode (clockPin, OUTPUT);
  pinMode (dataPin, INPUT);
  //165 pins
  pinMode (ploadPin, OUTPUT);
  pinMode (clockEnablePin, OUTPUT);
  pinMode (clockPin165, OUTPUT);
  pinMode (dataPin165, INPUT);
 
  //Required initial states
  //595 pins
  digitalWrite (clockPin, LOW);
  //165 pins
  digitalWrite (clockPin165, HIGH);
  digitalWrite (ploadPin, HIGH);

  //test lights
  digitalWrite (latchPin, 0);
  shiftOut (dataPin, clockPin, currentAnimation);
  digitalWrite (latchPin, 1);
  delay(250);

  digitalWrite (latchPin, 0);
  shiftOut (dataPin, clockPin, 0x55);
  digitalWrite (latchPin, 1);
  delay(250);
 
  //test speaker tone functions
  for (int thisNote = 0; thisNote < 7; thisNote++) {
    //calculate note duration 1000/length
    int noteDuration = 500 / startupDurations[thisNote];
    tone(tonePin, startup[thisNote], noteDuration);
    int pauseBetweenNotes = noteDuration *1.30;
    delay (pauseBetweenNotes);
    noTone(tonePin);
  }

}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned long currentMillis = millis();
 
  //red1
  if (currentMillis - previousMillis[0] >= red1dur) {
    if (red1State == LOW) {
      red1State = HIGH;
      currentAnimation = currentAnimation + red1Pin;
      previousMillis[0] = currentMillis;
      red1dur = random(lowblink, highblink);
    } else {
      red1State = LOW;
      currentAnimation = currentAnimation - red1Pin;
      previousMillis[0] = currentMillis;
      red1dur = random(lowblink, highblink);
    }
  }

  //red2
  if (currentMillis - previousMillis[1] >= red2dur) {
    if (red2State == LOW) {
      red2State = HIGH;
      currentAnimation = currentAnimation + red2Pin;
      previousMillis[1] = currentMillis;
      red2dur = random(lowblink, highblink);
    } else {
      red2State = LOW;
      currentAnimation = currentAnimation - red2Pin;
      previousMillis[1] = currentMillis;
      red2dur = random(lowblink, highblink);
    }
  }

  //red3
  if (currentMillis - previousMillis[2] >= red3dur) {
    if (red3State == LOW) {
      red3State = HIGH;
      currentAnimation = currentAnimation + red3Pin;
      previousMillis[2] = currentMillis;
      red3dur = random(lowblink, highblink);
    } else {
      red3State = LOW;
      currentAnimation = currentAnimation - red3Pin;
      previousMillis[2] = currentMillis;
      red3dur = random (lowblink, highblink);
    }
  }

  //red 4
  if (currentMillis - previousMillis[3] >= red4dur) {
    if (red4State == LOW) {
      red4State = HIGH;
      currentAnimation = currentAnimation + red4Pin;
      previousMillis[3] = currentMillis;
      red4dur = random(lowblink, highblink);
    } else {
      red4State = LOW;
      currentAnimation = currentAnimation - red4Pin;
      previousMillis[3] = currentMillis;
      red4dur = random (lowblink, highblink);
    }
  }

  //green1
  if (currentMillis - previousMillis[4] >= green1dur) {
    if (green1State == LOW) {
      green1State = HIGH;
      currentAnimation = currentAnimation + green1Pin;
      previousMillis[4] = currentMillis;
      green1dur = random(lowblink, highblink);
    } else {
      green1State = LOW;
      currentAnimation = currentAnimation - green1Pin;
      previousMillis[4] = currentMillis;
      green1dur = random(lowblink, highblink);
    }
  }

  //green2
  if (currentMillis - previousMillis[5] >= green2dur) {
    if (green2State == LOW) {
      green2State = HIGH;
      currentAnimation = currentAnimation + green2Pin;
      previousMillis[5] = currentMillis;
      green2dur = random(lowblink, highblink);
    } else {
      green2State = LOW;
      currentAnimation = currentAnimation - green2Pin;
      previousMillis[5] = currentMillis;
      green2dur = random(lowblink, highblink);
    }
  }

  //green3
  if (currentMillis - previousMillis[6] >= green3dur) {
    if (green3State == LOW) {
      green3State = HIGH;
      currentAnimation = currentAnimation + green3Pin;
      previousMillis[6] = currentMillis;
      green3dur = random(lowblink, highblink);
    } else {
      green3State = LOW;
      currentAnimation = currentAnimation - green3Pin;
      previousMillis[6] = currentMillis;
      green3dur = random (lowblink, highblink);
    }
  }

  //green 4
  if (currentMillis - previousMillis[7] >= green4dur) {
    if (green4State == LOW) {
      green4State = HIGH;
      currentAnimation = currentAnimation + green4Pin;
      previousMillis[7] = currentMillis;
      green4dur = random(lowblink, highblink);
    } else {
      green4State = LOW;
      currentAnimation = currentAnimation - green4Pin;
      previousMillis[7] = currentMillis;
      green4dur = random (lowblink, highblink);
    }
  }

  //shift out latest animation frame
  //ground latchPin and hold low for as long as your are transmitting
  digitalWrite (latchPin, 0);
  //move 'em out
  shiftOut (dataPin, clockPin, currentAnimation);
  //return the latch pin high to signal chip that it
  //no longer needs to listen for information
  digitalWrite (latchPin, 1);

 
  //Check for inputs!
  pinValues = read_shift_regs();
  Serial.println (pinValues);
  if (pinValues != 0 ) {
    //play answer tone
    /*
    for (int thisNote = 0; thisNote < 6; thisNote++) {
      //calculate note duration 1000/length
      int noteDuration = 1000 / startupDurations[thisNote];
      tone(tonePin, startup[thisNote], noteDuration);
      int pauseBetweenNotes = noteDuration *1.30;
      delay (pauseBetweenNotes);
      noTone(tonePin);
    }
    Serial.println ("Win tone played");
*/
    //turn on winning light
    digitalWrite (latchPin, 0);
    shiftOut (dataPin, clockPin, pinValues);
    digitalWrite (latchPin, 1);
    hold = digitalRead (resetPin);
    Serial.println ("Winning light lit.");

    //wait for reset
    while (hold == 0) {
     
      hold = digitalRead (resetPin);
      Serial.print ("Waiting on reset");
      Serial.println (hold);
    }
  }
}



//shiftOut routine sends LED values to the 595 shift register

void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {
  //internal function setup
  int i = 0;
  int pinState;
  pinMode (myClockPin, OUTPUT);
  pinMode (myDataPin, OUTPUT);

  //clear everything out just in case to
  //prepare shift register for bit shifting
  digitalWrite (myDataPin, 0);
  digitalWrite (myClockPin, 0);

  //for each bit in the byte myDataOut
  for (i=7; i>=0; i--) {
    digitalWrite (myClockPin, 0);
    if (myDataOut & (1 << i) ) {
      pinState = 1;
    } else {
      pinState = 0;
    }

    //Sets the pin to HIGH or LOW depending on the pinState
    digitalWrite (myDataPin, pinState);

    //register shifts bits on upstroke of clock pin
    digitalWrite (myClockPin, 1);

    //zero the data pin after shift to prevent bleed through
    digitalWrite (myDataPin, 0);
  }

  //stop shifting
  digitalWrite (myClockPin, 0);
}

//read the shift register code
BYTES_VAL_T read_shift_regs () {
  long bitVal;
  BYTES_VAL_T bytesVal = 0;

  digitalWrite(clockEnablePin, HIGH);
  digitalWrite(ploadPin, LOW);
  delayMicroseconds(PULSE_WIDTH_USEC);
  digitalWrite(ploadPin, HIGH);
  digitalWrite(clockEnablePin, LOW);

  for (int i = 0; i < DATA_WIDTH; i++) {
    bitVal = digitalRead (dataPin165);
    bytesVal |= (bitVal << ((DATA_WIDTH-1) -i));

    digitalWrite (clockPin165, HIGH);
    delayMicroseconds (PULSE_WIDTH_USEC);
    digitalWrite (clockPin165, LOW);
  }

  return (bytesVal);
 
}

void display_pin_values () {
  for (int i = 0; i < DATA_WIDTH; i++) {
    Serial.print (" Pin-");
    Serial.print(i);
    Serial.print(": ");

    if ((pinValues >> i) & 1) {
      Serial.print ("HIGH");
    } else {
      Serial.print ("LOW");
    }
    Serial.print("\r\n");
  }
}

[/code]

 Here is the link to view the pdf of the "As-Built" schematic of my circuit. Again hopefully someone smarter than I will see an error and point it out to me.