Adventures with Arduino, PWM, Servos, and Tones

[video width="272" height="480" m4v="http://www.nickwallace.us/blog/wp-content/uploads/2017/10/IMG_0102.m4v"][/video] While reviewing Pcomp concepts this week, I tried mocking up a small scale version of an automated dog feeder that I want to build in the future. I built a cardboard prototype dog feeder that plays a noise and rotates a servo when a button is pressed--the servo is attached to a wheel which dumps the food into a ramp that outputs out of the box it is inside.

This is the initial design sketchup:

Besides learning more about servos and how to create sounds with an Arduino, this was a good lesson in prototyping--my design changed 3-4 times, and I now have a better idea of the tolerances I will need to build into my final design. I also used fritzing for the first time to build a circuit diagram, to decent success.

I build the box and wheel out of cardboard that I laser cut:

Breadboarding the servo setup (fritzing diagram at the bottom):

Testing the servo action:

[video width="272" height="480" m4v="http://www.nickwallace.us/blog/wp-content/uploads/2017/10/IMG_0073.m4v"][/video]

I mounted the servo to the back of the cardboard box, and glued the wheel to the servo horns. Testing:

[video width="272" height="480" m4v="http://www.nickwallace.us/blog/wp-content/uploads/2017/10/IMG_0074.m4v"][/video]

I built out more of the box, and realized I would need some additional "stopped" guards on the inside of the ramps to prevent spillage:

Attempted fritzing diagram:

 

Arduino code:

// Add libraries
#include <Servo.h>
#include <time.h>
#include <TimeAlarms.h>
#include <pitches.h>

// Setup manual feed button
#define MANUAL_FEED_PIN 3
#define SPEAKER_PIN 8
boolean manual;
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

// Setup Servo
Servo dogFeeder;
int potPin = 0; // Pin used to control potentiometer for servo
int potVal; // Value of potentiometer

// tone/music code taken from tone sketch by Tom Igoe
// zelda "secret unlock" melody (ish) created by ear
int melody[] = {
  NOTE_G4, NOTE_FS4, NOTE_DS5, NOTE_A4, NOTE_GS3, NOTE_E5, NOTE_GS5, NOTE_C6
};

// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  8, 8, 8, 8, 8, 8, 8, 8
};

void setup() {
  Serial.begin(9600);
  Serial.println("Doggo-matic Online!");
  Serial.println("------------------------------------------------");
  Serial.println();

  dogFeeder.attach(2); // Servo on pin 2

  // Setup alarms
  Alarm.alarmRepeat(9, 00, 0, feed); // Morning feed: 9am
  Alarm.alarmRepeat(19, 00, 0, feed); // Evening feed: 7pm
}

void loop() {
  buttonState = digitalRead(MANUAL_FEED_PIN); // Read the manual feed button input pin

  // Check if button has been pressed by comparing to previous state
  if (buttonState != lastButtonState) {

    // If the state has changed, feed the dog
    if (buttonState == HIGH) {
      manual = true;

      for (int thisNote = 0; thisNote < 8; thisNote++) {
        // to calculate the note duration, take one second
        // divided by the note type.
        //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
        int noteDuration = 1000 / noteDurations[thisNote];
        tone(8, melody[thisNote], noteDuration);

        // to distinguish the notes, set a minimum time between them.
        // the note's duration + 30% seems to work well:
        int pauseBetweenNotes = noteDuration * 1.30;
        delay(pauseBetweenNotes);
        // stop the tone playing:
        noTone(8);
      }

      feed();
    }

    // Delay a little bit to avoid bouncing
    delay(50);
  }

  // Save the current state as the last state, for next time through the loop
  lastButtonState = buttonState;

}

void feed() {

  // Play food sound
  // CODE HERE

  // Read in pot value and turn servo accordingly
  potVal = analogRead(potPin);
  potVal = map(potVal, 0, 1023, 500, 2000); // Map value to 0.5-2 sec delay

  dogFeeder.write(170); // Move the servo to dispense food
  delay(potVal);
  dogFeeder.write(0); // Reset the servo

  time_t lastFed = now();

  // Play sit command sound
  // CODE HERE

  if (manual == true) {
    Serial.println("Doggo fed at: [TIME GOES HERE]! (manual)");
    Serial.println();
    manual = false;
  } else {
    Serial.println("Doggo fed at: [TIME GOES HERE]!");
    Serial.println();
  }
}