Week 4: Microcontroller Programming

This week, we learned about how to use C++ to program an Arduino Uno, and how to form circuits with both inputs and outputs to work with the Uno and interact with a user.

Project Goal: Memory Game

I decided to try making a memory game where a random sequence would be played for the user, and then the user would have to press buttons to try to repeat that sequence.

The Plan

I wanted to try out using an RGB light rather than a simple on/off one, so I planned on using three colors in the pattern that the user would have to match. I decided to use a button layout similar to the one in lab, but with three different buttons side by side. I followed an online tutorial on how to use an RGB light with an Arduino, and included that in the plan. I've included sketches of the circuit setups I used below:


The Process

My goal for the start was to simply get the User's turn working. This meant I would have to set up three buttons, detect when the buttons are pressed, and switch the lights in that case. Once everything was set up physically, I went to program this. I made separate classes for the light and the buttons, which was especially helpful for the buttons since there were three of them and it helped me not repeat code!

Button Code:

Button :: Button(int pin_number)
{
  pin = pin_number;
  previousState = false;
}

// detects switches from pressed to unpressed or vice versa
// returns true if now it's pressed, false otherwise
bool Button :: detectStart(){
  bool buttonState = digitalRead(pin) == 1;
  if (buttonState != previousState) {
    previousState = buttonState;
    return buttonState;
  }
  return false;
}
            

Light Code:

RGBLight :: RGBLight(int redPin1, long bluePin1, long greenPin1)
{
  redPin = redPin1;
  bluePin = bluePin1;
  greenPin = greenPin1;
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

// Switches color of light display
void RGBLight :: switchColor(int redValue, int greenValue, int blueValue)
{
  analogWrite(redPin, redValue);
  analogWrite(greenPin, greenValue);
  analogWrite(bluePin, blueValue);
}

void RGBLight :: off()
{
  switchColor(0, 0, 0);
}
            

At this point, after some debugging, the buttons started working!

Next, I decided the light wouldn't be enough to make the game fun and interesting, and also that it could be difficult for colorblind people, so I decided to add some sound. I got a buzzer, connected it to the arduino as shown in the sketch below, and wrote another class for the buzzer.

buzzer circuit
Buzzer :: Buzzer(int buzzerPinIn)
{
  buzzerPin = buzzerPinIn;
  pinMode(buzzerPin, OUTPUT);
}

// Plays note at specified frrequency for specified length
void Buzzer :: playNote(float newFrequency, long newDuration)
{
  duration = newDuration;
  frequency = newFrequency;
  lastTime = millis();
  tone(buzzerPin, frequency);
}

// Hard resets the buzzer to off
void Buzzer :: off() {
  noTone(buzzerPin);
  duration = 0;
}

void Buzzer :: update()
{
  if (millis() - lastTime > duration) {
    noTone(buzzerPin);
  }
}
                  
I then used a chart matching musical notes to frequencies to build out a file with the notes I wanted to play and their frequencies:
buzzer circuit
#define B2 123.47
#define C3 130.81
#define CSHARP3 138.59
#define D3 146.83
#define C4 261.63
#define E4 329.63
#define G4 392.00
#define D5 587.33
#define A5 880.00
                

This got working after some debugging too!

Finally, I had to set up the actual game part. I tried to avoid it for a while, but I ended up using <delay> for sections where the user should not do anything. This was probably the part where I struggled most with the coding part, both at first just trying to make it work, and afterword when I went through and split my code up into different files and created functions to avoid redundancy.

#include "notes.h"
#include "RGBLight.h"
#include "Buzzer.h"
#include "Button.h"

RGBLight light = RGBLight(5, 7, 6);
Buzzer buzz = Buzzer(9);
Button redButton = Button(12);
Button yellowButton = Button(11);
Button blueButton = Button(10);

bool userTurn;
int trueSequence[50];
int sequenceLength;
int userLength;

// Resets game to length of 1
void reset() {
  sequenceLength = 0;
  buzz.off();
  light.off();
  nextRound();
}

// Creates new sequence of greater length
void makeSequence() {
  sequenceLength ++;
  userLength = 0;
  trueSequence[0] = random(3);
  for (int i = 1; i < sequenceLength; i++) {
    int newRand = random(3);
    while (newRand == trueSequence[i - 1]) {
      newRand = random(3);
    }
    trueSequence[i] = newRand;
  }
}

// Plays the new sequence for the user to memorize
void playSequence() {
  for (int i = 0; i < sequenceLength; i++) {
    int value = trueSequence[i];
    if (value == 0) {
      noteLightDelay(C4, 255, 0, 0, 1000);
    }
    if (value == 1) {
      noteLightDelay(E4, 255, 100, 0, 1000);
    }
    if (value == 2) {
      noteLightDelay(G4, 0, 0, 255, 1000);
    }
  }
  buzz.off();
  light.off();
  userTurn = true;
}

// Moves to the next round
void nextRound() {
  makeSequence();
  delay(1000);
  buzz.off();
  light.off();
  delay(1000);
}

// Plays a note and displays a light for a specified amount of time
void noteLightDelay(int note, int red, int blue, int green, long duration) {
  buzz.playNote(note, duration);
  light.switchColor(red, blue, green);
  delay(duration);
}

// Play notes and reset 
void endGame() {
  noteLightDelay(D3, 150, 0, 200, 1000);
  noteLightDelay(CSHARP3, 112, 0, 150, 1000);
  noteLightDelay(C3, 50, 0, 100, 1000);
  noteLightDelay(B2, 20, 0, 50, 1500);
  reset();
}

// Play sound to indicate success
void playSuccess() {
  delay(300);
  buzz.off();
  delay(1000);
  noteLightDelay(D5, 0, 150, 0, 150);
  noteLightDelay(A5, 0, 255, 0, 400);
  buzz.off();
  light.off();
  nextRound();
}

// Records that a user has pressed a button to check against sequence
void userMove(int buttonChoice) {
  if (buttonChoice != trueSequence[userLength]) {
    userTurn = false;
    endGame();
    return;
  }
  userLength++;
  if (userLength == sequenceLength) {
    userTurn = false;
    playSuccess();
  }
}

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(A0));
  reset();
}

void loop() {
  buzz.update();
  if (!userTurn) {
    playSequence();
  }
  else {
    if (redButton.detectStart()){
      light.switchColor(255, 0, 0);
      buzz.playNote(C4, 300);
      userMove(0);
    }
    if (yellowButton.detectStart()){
      light.switchColor(255, 100, 0);
      buzz.playNote(E4, 300);
      userMove(1);
    }
    if (blueButton.detectStart()){
      light.switchColor(0, 0, 255); 
      buzz.playNote(G4, 300); 
      userMove(2);
    }
  }
}

The Product

The game ended up turning out pretty well! I added some success and failure sounds, as you can hear in the video!