Showing posts with label hc-sr04. Show all posts
Showing posts with label hc-sr04. Show all posts

Wednesday, November 9, 2016

Halloween Candy Cauldron V3.1

V3.1 in the enclosure. Ignore the external speaker--it's a vestige. Ready to tidy up for V4.
As I said in my last post on this project, all that was left to do was tidy up the connections and enclose the electronics. I decided that the enclosure I was using was too big. The reason it was too big was that I had the project on a breadboard instead of soldering the components to a PCB. I have always wondered why places like Radio Shack that sell enclosures and PCBs don't sell PCBs that fit in the enclosures--with mounting holes and screws.  So, I went looking and found this enclosure with matching PCB and a battery compartment. A little pricey, but good quality and they have a variety of sizes and features. (I'm using the term PCB to include proto boards here.) Follow-up note: I found that the soldering pads on the pcb were insufficient.  I'll accept blame for my soldering technique, but I had more trouble with this than any other I've worked with.

Since the new enclosure was a little smaller, I decided to use an  +Arduino Pro Mini (actually an Arducam clone) instead of the +Adafruit Industries Pro Trinket.  It's a little smaller, and I had a few in inventory. It needs an FTDI to serial module to program, but I was using one anyway for the Trinket to be able to use the serial monitor.

The code compiled with no changes.  (See link to last post in opening sentence to see code.) In order to upload to the board, the IDE required some changes (to point to the right board--Nano, not mini), and the USB Port needed a tweak in Windows Device Manager (I'm on Windows 10). Arduino.cc has a clear and concise getting started article describing this better than I can.

Interrupt

Note that the interrupt code also works as-is.  I need to work on the program to make the sleep more useful. As you can see in the video (the blue LED is on when it's sleeping), it doesn't sleep much, I'm using the Echo pin on the HC-SR04 sensor to wake it up. I put it to sleep at the top of the loop, then wait for Echo to go LOW or HIGH. It works the same either way, at least to the naked eye.

I used LOW because IDLE is the only mode that can be woken from anything other than LOW, and I wanted to be able to play with other modes. I'm thinking about using an inverter so I can set the interrupt on LOW, but have the pin go LOW when the Echo pin goes HIGH. +Home DIY Electronics has a good article on interrupts and the HC-SR04.

Since the sensor is ranging constantly, it wakes up pretty quickly. If I try to put it in power down mode, it does not wake up (maybe because the sensor shuts down?):  The LED indicating sleep stays on and no gesture will wake it up. There are 5 sleep modes and these are only the 2 extremes. I will play with some of the intermediate modes--next year, or the next time I'm playing with interrupts on an AVR board.

Construction
  1. Test the mini
    Since I changed boards, I needed to upload the code, wire the circuit on a breadboard, and test. As described above, all was well.
  2. Prep the enclosure
    In this design, the speaker and the battery are now inside the enclosure, I only need holes to bring in wires from the switch and from the HC-SR04. I also drilled 7 holes to let the sound out, places where I planned to put the speaker.
  3. Design the board layout
    I needed to mount the Pro Mini, then find room for the amplifier and Micro SD Reader.  This required some planning, so I simulated with header pins on the PCB and marked where I would place the components.  All the components had male headers attached, because I originally set set this up for a breadboard, so I soldered the headers to the printed side of the board.
  4. Wire the busses
    I created power and ground busses on the inside edge of the board, next to the battery, using tinned copper bus wire. The board has pairs of adjacent rows of connected holes, so I used two of these pairs (one row for each bus, each adjacent row for connections to the circuit).  The power rail is supplied from the voltage regulator circuit 5V output, and the ground from the 9V battery's negative lead. The positive lead from the battery goes to a 2-wire JST connector, which mates to another connector with leads going to a toggle switch. The other lead from the case (with the + from the battery) is the 9V input to the voltage regulator.
  5. Test the Voltage Regulator Circuit
    Since I have habit of messing up connections, and power is a key part of this, I wanted to be sure that I was getting 5V out of this part of the circuit before completing the soldering. When I got it right (after some stupid soldering tricks), I was ready to move on.
  6. Mount the components
    All the components had male headers attached, because I originally set set this up for a breadboard, so I soldered the headers to the printed side of the board. I also added an LED and resistor for visual representation of the sleep mode (the code for this was already included, so what the heck).
  7. Test the circuit
    Before fastening the circuit board to the enclosure, make sure everything works.  Here's where I ran into trouble.  I worked the continuity function of my multi-meter extensively. I thought I had everything in order, but it failed. I went back to a version of the sketch that had serial commands for testing in it, and saw that the SD board was not initializing.  I rechecked the connections, checked the SD card to see if it could be read, and then got drastic:  clipped the jumpers from the mini to the SD reader, and connected them via alligator leads to another reader.  Still not initializing, so I was pretty sure it was my connections.  One more continuity test revealed a problem with one of the jumpers. I re-soldered, put it back together, and it worked. Stupid soldering tricks, indeed!
    Working circuit, mounted. Clockwise from upper left: Adafruit Class D Mono Amp (wires to speaker), voltage regulator circuit, indicator LED and resistor, Pro Mini, Micro SD Card Reader. Yellow and green wires go to HC-SR04, red and black wires on right are power to HC-SR04 (red and black) and from 9V battery and switch (black and gray). 
  8. Put it in the box
    See photo at the top of this post.  Enclosing was a matter of adding screws in the prepared holes to secure the PCB, then passing the 4 wires to the HC-SR04 through one hole and the battery connection through another, position the speaker under the holes, and closing the box.
  9. Video
    Available on Youtube (my channel)



Wednesday, October 19, 2016

Halloween Candy Cauldron V3

Cauldron V3 in-process.
The large speaker will be removed--replaced by the small green on in the enclosure.
The red FTDI Friend and USB cable are there for programming.
We'll shorten the wires, neaten it up, and put the cover on the enclosure.
The red LED is for testing--turned on before sleeping, turned off on waking.

When last we looked a this (last Halloween), I was having trouble making the electronics small. To recap, I started out with an +Arduino Uno, an SD card shield, +Adafruit Industries class D mono amp, and an HC-SR04 Ultrasonic Sensor.  Then someone reaches in to grab a piece of candy, the sensor reacts, code on the arduino then chooses one of nine sounds to play, via the amp and an 8 Ohm speaker.

Objective

The big issue is that the electronics are loose in the bottom of the cauldron, so I wanted to use a smaller board and smaller SD breakout (like a MicroSD), and put the whole thing in an enclosure. I tried the Adafruit Pro Trinket 5V last year and ran out of time before I could get it to work.

Turns out this was a stupid wiring trick. The circuit is powered by a 9V battery, run through a 7805 5V Voltage Regulator circuit.  The dumb thing I did last year was connect the BAT pin on the Trinket to the battery.  I'm not sure what, if anything, else I did wrong (I may have also connected that same pin to the 5V rail), but this year I connected the BAT pin to the 5V rail. Last year I got only static, this year it works. In both cases I powered the peripherals from the 5V rail (output of the VR circuit), to provide enough current, since the Trinket's 5V pin provides 150mA max.

Further, I wanted to put the Trinket to sleep while waiting for trick-or-treaters to save battery.

Video

Interrupts

Sleep mode turned out to be simple, but required some learning.  As described in this post on interrupts on the M0, I just completed my first successful Arduino project using interrupts.  So, I started with a __WFI(); instruction to wait for an interrupt. It turns out that different processors use different instructions, since interrupts are so hardware dependent, so this did not compile.

My main source of information was here. The avr\sleep.h library (which does not work with the M0), was the key, since the main differences between the 2 processor families is how you put the processor to sleep, and the sleep modes. On the M0, the top of loop tested to see if one of our interrupts had been fired, and if not issued a wfi--back to sleep.  If so, go on to process In the case of the Trinket, also at the top of loop, we attached the interrupt, enabled sleep, went to sleep, and on waking, disabled sleep and detached the interrupt. The code:
//*****sleep**************************
    digitalWrite(ledPin, HIGH);  //turn on the LED (testing)
    attachInterrupt(digitalPinToInterrupt(echoPin), echoISR, HIGH); //attach interrupt
    sleep_enable();          // enables the sleep bit in the mcucr register
    sleep_mode();            // here the device is actually put to sleep!!
//***********************************/

//wait here for echoPin interrupt--ignore all others
//*****wake up************************
    sleep_disable();         // first thing after waking from sleep: disable sleep...
    detachInterrupt(digitalPinToInterrupt(echoPin));      // disables interrupt because pin stays high
//***********************************/

echoPin is Trinket pin 3, the only one that sees interrupts
echoISR just sets a volatile boolean, handIn
The next code tests handIn to see if it was set, meaning that we woke up because of the interrupt we care about, then goes on to reset the boolean and process.

In set up, we enabled interrupts ("interrupts();") and set the sleep mode ("set_sleep_mode(SLEEP_MODE_IDLE);"). According to the article cited above, IDLE is the only mode that can be awakened by a mode other than LOW, and I thought since we want to be interrupted when echoPin goes HIGH, we're stuck with it, even though it saves the least power of all modes.

However, some experimentation yielded that LOW works similarly to HIGH on the attachInterrupt.  I tried SLEEP_MODE_PWR_DOWN, AND it seemed to sleep a little more, but the board hung with regularity, so I went back to IDLE.

I'm sure there are more and better ways to do this, but this is working for me.

Other Enhancements

I was never happy with how the code looked, There's a lot of function-specific processing that made the loop long. So, I broke out the code to calculate the distance based on the sensor pin values and the code to choose which sound to play and put them in functions.  Not rocket science, but good programming practice.

I replaced the 3.5" speaker with a 1.5". That allowed me to enclosed the speaker in the box, reducing wires. I also made it a little quieter--young trick-or-treaters don't like the loud version. Yes I know I can control the volume on the amp, but this works well.

Fritzing Diagram


Code

/*2014-10-05 Virgil Machine Halloween Candy Dish: Play random sound when kid (or greedy adult) reaches into bowl

2016-10-18 V3 VM  never got Trinket to work last year, this week I did (stupid wiring trick)
 also replaced external speaker with a 1.5" that dit in the enclosure
 Added interrupt  handling to put it to sleep to save battery (thanks to http://playground.arduino.cc/Learning/ArduinoSleepCode)
 had  to change echoPin to #3--only pin on Trinket that is interruptable; changed trigPin to 4 to be
side-by-side for wiring, added ledPin for testing
Also, put the song selection/playing and distance calculation code in functions to make loop more readable.

2015-10-14 V2 VM change to Pro Trinket from Uno to put the circuit in an enclosure
 Echo to 6 (Pro Trinket does not have pin 7)

2014-10-05 VM HC_SR04 Distance sensor code added
Neeed to modify pins for Halloween (13&12 used by SPI)

2014-04-26 VM Downloaded from Instructables
 HC-SR04 Ping distance sensor]
 VCC to arduino 5v GND to arduino GND
 Echo to Arduino pin 13 Trig to Arduino pin 12 (used 8&7 instead--need SPI pins for SD reader)
*/

/******* see virgilmachine.blogspot.com*****/

/*includes*/
#include    //SPI library
#include     //SD card library
#include //library for playing sound
#include  //sleep functions
#include  //interrupt functions

/*constants*/
#define SD_ChipSelectPin 10
#define echoPin 3 //so we can have an interrupt (D3 is the only one)
#define trigPin 4 //to be next to echo for wiring
#define ledPin  5

/*variables*/
int song = 0;   // song number for random function
volatile boolean handIn = false; //variables in ISR need to be volatile
/*objects*/
TMRpcm speaker;   // create an object for use in this sketch


void echoISR() //ISR for distrance sensor
{
    handIn = true;  //someone put his or her hand in
}

void setup(){
  randomSeed(analogRead(0));  //initialize random (A0 unconected)
  pinMode(trigPin, OUTPUT);   //pins for distance sensor
  pinMode(echoPin, INPUT);
  pinMode(ledPin, OUTPUT);   //LED fot testing
  digitalWrite(ledPin, LOW);  //default to off
  speaker.speakerPin = 9; //output to amp
  speaker.loop(0); //2014-10-05 do not play repeatedly
//  Serial.begin(9600); //Serial is for testing--comment to reduce time/power consumption
  if (!SD.begin(SD_ChipSelectPin))
     {  // see if the card is present and can be initialized:
//Serial.println("SD not initialized");
      return;   // don't do anything more if not
      }
//   else
//      {
//       Serial.println("SD initialized");
     //
//     }  
  speaker.volume(1);
//  speaker.setVolume(7);   //attempt to increase volume

interrupts(); //enable interrupts (should not need to do this, but just for drill...)
set_sleep_mode(SLEEP_MODE_IDLE);   // sleep mode is set here
//set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // PWR_DOWN hangs periodically
}

void loop() {

//*****sleep**************************
    digitalWrite(ledPin, HIGH);  //turn on the LED (testing)
    attachInterrupt(digitalPinToInterrupt(echoPin), echoISR, LOW); //attach interrupt
    sleep_enable();          // enables the sleep bit in the mcucr register
    sleep_mode();            // here the device is actually put to sleep!!
//***********************************/

//wait here for echoPin interrupt--ignore all others
//*****wake up************************
    sleep_disable();         // first thing after waking from sleep: disable sleep...
    detachInterrupt(digitalPinToInterrupt(echoPin));      // disables interrupt because pin stays high
//***********************************/
  while (handIn) {   //someone wants candy, hcsr04 interrupted
    digitalWrite(ledPin, LOW);  //turn off LED (testing)
    handIn=(!handIn); //reset the boolean
//2014-10-05 If distance is <8in cm="" hand="" his="" in="" nbsp="" p="" put="" someone="">//2016-10-18 (the getDistance function calculates that)
    if (getDistance() < 20)
    {
      playSong(); //function to select and play a random song
    } //distance
      delay(2500);  //give the song a chance to play
  } //while

} //loop

void playSong() {
        song = random(1,10); //get random number from 1 to 9
//      Serial.print("song: "); //for testing
//      Serial.println(song); //for testing
      switch (song) {
        case 1:
          speaker.play("1.wav");
          break;
        case 2:
          speaker.play("2.wav");
          break;
        case 3:
          speaker.play("3.wav");
          break;
        case 4:
          speaker.play("4.wav");
          break;
        case 5:
          speaker.play("5.wav");
          break;
        case 6:
          speaker.play("6.wav");
          break;
        case 7:
          speaker.play("7.wav");
          break;
        case 8:
          speaker.play("8.wav");
          break;
        case 9:
          speaker.play("9.wav");
          break;
      } //switch/case
} //playSong

long getDistance() {
  long duration, distance;
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
//trigPin must me high for 10 microsecs to do ranging
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
//duration is the time between pings of the sensor  in microseconds as returned on echoPin
  duration = pulseIn(echoPin, HIGH);
//duration is a round-trip, so divide by 2; speed of sound is 29.1 cm/microsec, so distance is in cm
  distance = (duration/2) / 29.1;
  return distance;
} //getDistance