AVR ATmega328p Arduino Halloween Sound Effects Prototype
by CScientific in Circuits > Electronics
1173 Views, 3 Favorites, 0 Comments
AVR ATmega328p Arduino Halloween Sound Effects Prototype
Halloween sound effects prototype using Atmega328 microprocessor, Arduino IDE and SparkFun AVR programmer.
The prototype board used was developed in this Instructable:
https://www.instructables.com/Attiny85-Attiny84-an...
This version of the code randomly plays sixteen different 'scary' WAV files (from a SPI Micro SD card reader) when a PIR sensor is triggered.
Making the development cables would probably be challenging for a beginner.
Here is the crimping tool I used and the dupont crimp pin assortment:
https://www.amazon.com/gp/product/B01N4L8QMW/ref=p...
https://www.amazon.com/gp/product/B078RRPRQZ/ref=p...
The real utility in this Instructable, I think, is the code itself. It could easily be modified to play different WAV files in different ways. I have one version that plays only a dog barking when the PIR interrupt is triggered; this might be used as an alarm/deterrent. The code illustrates some advanced microprocessor coding techniques, some specific to the Atmega328.
WAV files are not supplied here, instructions for generating them in the required format are included.
In a later phase of the project, an Attiny84 was coded to drive a stepper motor upon receiving an interrupt trigger from the Atmega328.
Update: Added schematic on step 3 and step 6.
Components
The prototype board used, as mentioned, was developed in this Instructable:
https://www.instructables.com/Attiny85-Attiny84-an...
I'm not sure where I sourced the 16MHz resonator from, below is an Amazon source, you can probably find them cheaper:
https://www.amazon.com/gp/product/B00BJHVWT6/ref=p...
The SPI Micro SD card reader was sourced from Amazon (about $1.50 each at time of purchase):
https://www.amazon.com/gp/product/B07BJ2P6X6/ref=p...
I also got the Micro SD cards from Amazon (about $5.00 each at time of purchase), 8GB is way bigger than you need:
https://www.amazon.com/gp/product/B0858FBL8V/ref=p...
The PIR sensor was sourced from Amazon as well (about $2.50 each at time of purchase):
https://www.amazon.com/gp/product/B07NPKMH58/ref=p...
The ATmega328 microprocessor itself with a socket can currently be found on Amazon for about $4.00 each:
https://www.amazon.com/gp/product/B08WWW75B2/ref=p...
I don't remember where I got the 4 AA (6 volts) battery holder with switch from. These would do (they are about $3.50 each):
https://www.amazon.com/LAMPVPATH-Battery-Holder-Sw...
And last but not least, the 9v standalone mini-amplifier/speaker was purchased from Radio Shack (I couldn't tell you how long ago). You can still get them from Radio Shack but they are a bit pricey at $30.00 each now. You can probably find something cheaper. It's better if the speaker has its own power source. I tried powering one from the 6v battery pack and didn't get good results.
https://www.radioshack.com/products/radioshack-min...
Besides Amazon I also use Mouser and Sparkfun occasionally.
For those suppliers you usually have to pay shipping.
Sparkfun is worth supporting.
Reference and Inspiration
The SparkFun website has some excellent tutorials on getting the SparkFun AVR Programmer working with the Arduino IDE (among many other excellent tutorials). This link is specific to the attiny85, but is applicable to a lot of other boards.
https://learn.sparkfun.com/tutorials/tiny-avr-prog...
Another inspiration was 'Make: AVR Programming' by Elliot Williams. He primarily focuses on the atmega168 in the book. But all of the 28pin ATmega chips (48, 88, 168 and 328) have the same pin arrangement. So the same board should work for all of them.
Here is a link to the book on Amazon. I should note that I am not affiliated with any of the sources I reference.
https://www.amazon.com/AVR-Programming-Learning-So...
The ATmega328 datasheet is really helpful. Below is a link to it online. I made my own paper copy of it in a book form. Next time I make a book I'll take pictures for an Instructable.
https://ww1.microchip.com/downloads/en/DeviceDoc/A...
And last but not least, the Arduino website provides a lot of reference material.
The pinout cross reference for the ATmega168 (same pinout at the ATmega328) is essential to knowing how to reference the ATmega328 pins inside the Arduino IDE.
Programming and Testing
ATmega328 pinout reference for the code.
SPI pins used for the Micro SD Card Reader:
Arduino Atmega Atmega SPI
ref port physical
13 PB5 19 SCK (clock)
12 PB4 18 MISO
11 PB3 17 MOSI
10 PB2 16 SS/CS (master)
TMRpcm audio output:
Arduino Atmega Atmega
ref port physical
9 PB1 15
PIR interrupt pin:
Arduino Atmega Atmega Atmega
ref port pcint physical
4 PD4 PCINT20 6
Motor pin (high/on while audio is playing):
Arduino Atmega Atmega Atmega
ref port pcint physical
3 PD3 PCINT19 5
LED indicators:
Arduino Atmega Atmega
ref port physical
0 PD0 2 led0
1 PD1 3 led1
2 PD2 4 led2
16 MHz Ceramic Resonator:
Atmega Atmega
port physical
PB6 9
PB7 10
Random noise ADC:
Arduino Atmega Atmega
ref port physical
A0 PC0 23
I'm running the Arduino IDE on Windows 10. I also have cygwin install, giving linux tools for moving around the filesystem. 'find' and 'diff' are two of the most handy for a project like this. Always save an original copy of any third party library file that you modify. It beats having to reinstall the entire library if you loose your way. A diff of the new vs the original will show you if you did anything that you didn't intend to.
The ATmega328 board configuration used in the Arduino IDE is shown in the screen shot above. To upload a program through the SparkFun AVR Programmer to an ATmega328 through the SPI interface, you have to use the 'Upload Using Programmer' selection in the Sketch pulldown (screen shot also shown above). Just clicking the upload arrow from the screen doesn't work for the ATmega328.
I configured the chip to use a 16MHz external resonator. The 8MHz internal was too slow for good sound. I gave a short try to a 20MHz external resonator, but got nothing out. Didn't try to figure out why.
Besides standard (preinstalled) Arduino libraries, TMRpcm needed to be installed. It's already in the library list included with the Arduino install. Pull down from Sketch -> Include Libraries -> Manage Libraries and then search for it and install from there.
Online TMRpcm documentation includes instructions for configuring the WAV files. I used Audacity for the conversion from mp3 files, stereo merged into a single mono track and down converted to a HZ rate of 22,050.
https://github.com/TMRh20/TMRpcm/wiki
https://github.com/TMRh20/TMRpcm/wiki
The sound is good, but not suitable for very loud amplification.
I purchased this digital mp3 album from Amazon for $1.00 and sourced my sound effects from it:
https://github.com/TMRh20/TMRpcm/wiki
Also important to note, the file names must be in the old DOS format: 8.3
Use all caps, up to eight characters, a period and then a three character extension (recommend WAV).
I made a couple of changes in TMRpcm/pcmConfig.h worth noting. The library puts out complimentary sound on pins 9 and 10 with 9 being the default. I preferred using pin 10 for SPI select so the SPI pins would all be on the same side adjacent to each other, so I turned it off. Uncommenting this #define in the configuration will disable audio on pin 10: #define DISABLE_SPEAKER2. I also increased the buffer size to the max. These changes are noted in the source code.
One other specialized technique is using random noise from one of the ADC pins to set the seed for the random number generator. If your sequence of 'random' audio always starts from the same file, you didn't get this right.The rest of the details involved getting PIR pin interrupt and 'deep' sleep working. This is this is the part of the coding that I found most interesting and challenging. The ATmega328 has three dedicated pin interrupts, one for each of the 'port' banks` (PB, PC and PD on the ATmega328). Using Mr. Elliot's book and the datasheet I was able to sort out what pin identifiers I needed to use to configure an interrupt for the pin that I wanted to attach the PIR to. Just to note, the PIR is default LOW (good). So a HIGH on the pin triggers the interrupt. One other note, stand alone testing with the PIR showed that it remained HIGH initially for about eight seconds when power is first applied. A ten second delay was added to startup to keep the interrupt from triggering until after the PIR had stabilized.
It took several iterations of development to get the microprocessor to go into SLEEP_MODE_PWR_DOWN while waiting for an interrupt. My original code simply polled over the PIR pin in a loop with a small delay between each check, but I wanted to do better. I tried a couple of different add on sleep libraries, but neither of them worked well for me. I finally found that using just the simple built-in avr/sleep library it only took a few lines of code to accomplish what I needed.
Three led indicators are used, they are turned off after the first PIR interrupt is triggered to save power. led0: basic start/power on. led1: indicates once initialization delay is over (and interrupt is set to fire). led2: Micro SD card reader initialization has failed, the program exits.
During programming I was careful to unplug the SD card reader while the code was being uploaded through the SparkFun AVR programmer SPI interface. And likewise when testing the application I plugged the Micro SD card reader back in and disconnected the programmer from the USB port on the PC. The dual SPI port usage will step on the other process otherwise. The particular Mirco SD card reader used is very forgiving. I'd be embarrassed to say how many times I plugged it in backwards. In those cases led2 let me know right away that I had goofed.
If you have a multimeter that measures current in the milliamp range, you can get an idea of how much current the microprocessor is using when idle (aka quiescent period) while waiting for an interrupt. The multimeter should be placed inline from the power source, with the power flowing into the meter, then onto the board. Testing like this made me realize that I needed to disable the leds in the long run if I really wanted to get the power usage down. A picture of that testing is shown above. Running at 6V, 16MHz, I'm not expecting micro amp idle periods, but it's not doing too bad.
Source Code
Update: a couple tweaks have been made to the code since original posting. One, to make the interrupt firing and blocking more reliable. And two, to prevent the same WAV file from randomly playing again until three turns have past.
Update 2: Pin 3 was added as output high while audio is playing to trigger a stepper motor (via an attiny84 interrupt) and turn on an LED light strip (via a mosfet).
Here is a link to the source code:
http://www.cscientific.com/cgi-bin/displaySource.p...
And it is pasted below.
/* Arduino Based audio Player
www.arduino.cc/en/Hacking/PinMapping168
The circuit:
* Audio Out - pin 9
* SD card attached to SPI bus as follows:
** CS - pin 10 // must disable speaker2, see below
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
// wav file with 8 bit resolution, 16KHz or 22KHz sampling rate, mono and unsigned 8 but PCM format.
atmega328p 16 MHZ external
<a href="https://github.com/TMRh20/TMRpcm/wiki" rel="nofollow">
<a href="https://www.arduino.cc/en/Hacking/PinMapping168" rel="nofollow">
https://www.arduino.cc/en/Hacking/PinMapping168
</a>
</a>
<a href="https://github.com/TMRh20/TMRpcm/wiki/Advanced-Features" rel="nofollow">
https://www.arduino.cc/en/Hacking/PinMapping168
</a>
NOTE: Filename constrained to 8.3 naming format
TMRpcm audio;
audio.play("filename"); plays a file
audio.play("filename",30); plays a file starting at 30 seconds into the track
audio.speakerPin = 11; set to 5,6,11 or 46 for Mega, 9 for Uno, Nano, etc.
audio.disable(); disables the timer on output pin and stops the music
audio.stopPlayback(); stops the music, but leaves the timer running
audio.isPlaying(); returns 1 if music playing, 0 if not
audio.pause(); pauses/unpauses playback
audio.quality(1); Set 1 for 2x oversampling
audio.volume(0); 1(up) or 0(down) to control volume
audio.setVolume(0); 0 to 7. Set volume level
audio.loop(1); 0 or 1. Can be changed during playback for full control of looping.
*/
#include "avr/sleep.h"
#include "SD.h" //Lib to read SD card
#include "SPI.h" //SPI lib for SD card
#include "TMRpcm.h" //Lib to play auido
//#define SD_ChipSelectPin 4 //Chip select is pin number 4
#define SD_ChipSelectPin 10 //Chip select is pin number 10
// In: /cygdrive/e/atmega/328/libraries/TMRpcm/pcmConfig.h
// To use pin '10' as SS (CS) for SPI, speaker 2 must be disabled in
// uncommented
// #define DISABLE_SPEAKER2
// Also in: /cygdrive/e/atmega/328/libraries/TMRpcm/pcmConfig.h
// uncommented and set to max
// #define buffSize 254 //must be an even number
TMRpcm audio; //Lib object is named "audio"
// indicator LEDs
int pin0 = 0; // power on, turned off after first trigger
int pin1 = 1; // 1st trigger ready, turned off after first trigger
int pin2 = 2; // initial SD card read failed, code exits
// sensor
int pirPin = 4;
// motor on trigger pin
int motorPin = 3; // On while audio is playing to trigger a motor
// prevent same audio from randomly playing for three turns
char last1=-1;
char last2=-1;
char last3=-1;
// audio.isPlaying() does not seem to block in interrupt function call
// attempting to get better conrtol with this variable
char intPlaying=0;
void setup()
{
// initialize three pins as an led output.
pinMode(pin0, OUTPUT);
pinMode(pin1, OUTPUT);
pinMode(pin2, OUTPUT);
digitalWrite(pin0, LOW);
digitalWrite(pin1, LOW);
digitalWrite(pin2, LOW);
// configure PIR pin
pinMode(pirPin, INPUT);
// motor on pin, HIGH while audio is playing
pinMode(motorPin, OUTPUT);
digitalWrite(motorPin, LOW);
// set default value on unused pins
pinMode(5, INPUT_PULLUP); // PD5
pinMode(6, INPUT_PULLUP); // PD6
pinMode(7, INPUT_PULLUP); // PD7
pinMode(8, INPUT_PULLUP); // PB0
pinMode(A5, INPUT_PULLUP); // PC5
pinMode(A4, INPUT_PULLUP); // PC4
pinMode(A3, INPUT_PULLUP); // PC3
pinMode(A2, INPUT_PULLUP); // PC2
pinMode(A1, INPUT_PULLUP); // PC1
// pinMode(A0, INPUT_PULLUP); // PC0 // using for random seed
// power on LED
digitalWrite(pin0, HIGH);
if (!SD.begin(SD_ChipSelectPin)) {
// indicate SD failure
digitalWrite(pin2, HIGH); // turn the LED on (HIGH is the voltage level)
delay(5000); // wait for a bit
exit(1); // exit on SD initialization failure
} // end if
randomSeed(analogRead(A0));
audio.speakerPin = 9; // Audio out on pin 9
audio.setVolume(5); // 0 to 7. Set volume level
audio.quality(1); // Set 1 for 2x oversampling Set 0 for normal
} // end setup
// configure an interrupt on the PIR pir 4 PD4 (PCINT20)
// if PIR pin is changed, then PCINT20 must be changed to match
// if changed PIR pin is not in Port D, then PCIE2 must also change
void initPIRInterrupt(void)
{
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT20);
sei();
} // end initPIRInterrupt
// action performed on interrupt
// if PIR pin is changed and not in Port D, then PCIF2 must also change
ISR(PCINT2_vect)
{
if (digitalRead(pirPin)==HIGH && intPlaying==0)
{
intPlaying=1;
playScarySound();
} // end if
PCIFR |= (1 << PCIF2); // clear the interrupt
} // end ISR
void playScarySound(void)
{
char randomPlay = -1;
// prevent the same audio from randomly playing for three turns
while (randomPlay<0 || randomPlay>15 || randomPlay==last1 || randomPlay==last2 || randomPlay==last3)
randomPlay = random(0,16);
if (audio.isPlaying()==0)
{
digitalWrite(motorPin, HIGH);
switch (randomPlay) {
case 0 : audio.play("MADDOG.WAV"); break;
case 1 : audio.play("BLACKCAT.WAV"); break;
case 2 : audio.play("WITCH1.WAV"); break;
case 3 : audio.play("DRAGON.WAV"); break;
case 4 : audio.play("SCREAM1.WAV"); break;
case 5 : audio.play("GRIFFIN.WAV"); break;
case 6 : audio.play("KNOCKING.WAV"); break;
case 7 : audio.play("BATS.WAV"); break;
case 8 : audio.play("BOOGIEMN.WAV"); break;
case 9 : audio.play("EVLAUGH.WAV"); break;
case 10 : audio.play("WEREWOLF.WAV"); break;
case 11 : audio.play("WITCH2.WAV"); break;
case 12 : audio.play("DALEK.WAV"); break;
case 13 : audio.play("CREAKS.WAV"); break;
case 14 : audio.play("TORTURE.WAV"); break;
case 15 : audio.play("SCREAMS.WAV"); break;
default : audio.play("MADDOG.WAV"); break;
} // end switch
while (audio.isPlaying()==1) { delay(100); } // end while
} // end if
last3=last2;
last2=last1;
last1=randomPlay;
digitalWrite(motorPin, LOW);
delay(2000);
intPlaying=0;
} // end playScarySound
void loop()
{
char TURN_LEDS_OFF = 0;
delay(10000); // give the PIR time to stabilize
initPIRInterrupt(); // initialize the interrupt on the PIR pin
digitalWrite(pin1, HIGH); // indicate that PIR pin interrupt active
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
while (1)
{
// sleep mode will block sleeping until the PIR pin interrupt is triggered
// only put to sleep if audio is not playing
if (audio.isPlaying()==0)
{
sleep_bod_disable();
sleep_mode();
} // end if
delay(250);
while (audio.isPlaying()==1) { delay(100); } // end while
// after first trigger turn LEDs off to save power
if (TURN_LEDS_OFF==0)
{
digitalWrite(pin0, LOW);
digitalWrite(pin1, LOW);
digitalWrite(pin2, LOW);
TURN_LEDS_OFF=1;
} // end if
} // end while
intPlaying=0;
} // end loop
Hardening, Phase 1
Satisfied with the basic prototype, I've soldered a version onto an electrocookie pcb. The dupont sockets have been replaced with 2.54 mm JST-XH sockets. The JST sockets have polarity for safety (only plug in one way) and stay connected better than the dupont sockets. I dropped the LEDs, the logic is still in the code and the pin outputs can still be checked by touching with a test tool.
I encountered an interesting bug when testing the hardened version of the prototype. I used a new 4 AA battery holder, adding the JST connection and fresh batteries. The circuit was working but the PIR would not trigger an interrupt. I had four other PIR, none of them worked. The PIR had been one of the most reliable parts of the system. Independently testing the PIR on a breadboard, it showed that it was triggering an output of about 3.3 volts (using the new batteries). It turns out the batteries were "too new". They were outputting close to 6.5 volts. The PIR trigger voltage was not reaching a high enough voltage to trigger an interrupt in the Atmega328p. The interrupt trigger voltage probably needs to be greater than half of the supply voltage. Switching in older batteries fixed it. The long term solution will be to solder a diode inline with the positive power lead for the ~.6 volt voltage drop that it gives. 6.5 volts is pushing past the Atmega328p maximum voltage rating anyway.
I kludged a pin block to distribute the audio to different output. I'm using two speakers, each will have its own lm386 amplifier and 9volt battery. The lm386 breakout was purchased from Amazon, they were a bit over a buck each. As one reviewer said, you probably couldn't buy the components that cheap.
https://www.amazon.com/gp/product/B07P38H4P8/ref=p...
I 'fried' the one pictured by accidentally touching the positive lead from the battery to the ground on the breakout. Be careful about polarity on this one, they are not forgiving. I didn't understand the reason for the two ground pins at first, thought it was silly. Until I realized that I was the silly (ignorant) one. The two ground pins very conveniently allow you to connect the ground plains from the power source and the audio source. They are actually pretty much essential. There is a bright LED on each board, I carefully scraped it off to save power. With the LED the lm386 was drawing about 9.7 milliamps when idle. With it removed that dropped off to 1.2 milliamps. That should be significant for life of a 9 volt battery.
I prior had salvaged the speakers from an old itunes docking station. Recycling them here, they are working very well with the lm386. The output from the lm386 is just right as it is set and plenty loud (and still clear) from the speakers.
ElectroCookie board:
Added Stepper Motor (28BYJ-48) for Motion Controlled by an Attiny84 (and Lights)
Atmega328 code was updated to set pin 3 high while audio is playing.
Output from Atmega328 pin 3 high serves as input to an interrupt pin on an Attiny84 to control a stepper motor adding motion to the project.
Atmega328 pin3 high also switches a mosfet to turn on an LED light strip while audio is playing. The lights were put inside a glow in the dark skull to illuminate it from the inside out.
Both the stepper motor and the LED light strip are powered by their own 3 D cell battery pack.
Stepper motor 28BYJ-48 used for the project:
https://www.amazon.com/gp/product/B01CP18J4A/ref=p...
LED light strip used for the project:
https://www.amazon.com/gp/product/B01IN4ZH16/ref=p...
Here is a link to the Attiny84 stepper motor control source code:
http://www.cscientific.com/cgi-bin/displaySource.p...
And it is pasted below. Note that the arduino Stepper library was not used. Reviewing the library I saw that it uses digitalWrite() to change the pin status. digitalWrite() is demonstrably slower on the the attiny85 and attiny84 than direct register manipulation of the pin output. The eight step (half step?) motor sequence used in the code below was taken from code supplied by the seller on the Amazon product page. I did not attempt to use 'reverse' logic over the same switch statement to drive the motor in reverse. I found it more straight forward just to add a separate reverse function. Additionally the code may break some 'rules' of C coding style. But I find it much easier to understand the motor signal sequence visually with each step on the same line. This being my first time using stepper motors, steps per revolution was kind of confusing. The total number of 'steps' per revolution for this motor is 4096. The Arduino Stepper library uses a four motor signal sequence as a 'step'. 4096 / 4 = 1024 'steps' per revolution. The supplied 'half step' logic uses an eight signal motor sequence as a 'step'. 4096 / 8 = 512 'steps' per revolution.
/*
Stepper Motor Control
This program drives a unipolar stepper motor.
Code set to run on an attiny84 at 1 Mhz with internal clock.
Motor control output on:
Arduino Attiny84 Attiny84
ref port physical
0 PA0 13
1 PA1 12
2 PA2 11
3 PA3 10
Interrupt pin:
Arduino Attiny84 Attiny84
ref port physical
10 PB0 2
*/
#include "avr/sleep.h"
// interrupt pin
int trigPin = 10; // Attiny84 PB0 Physical pin 2
const int stepsPerRevolution = 512; // steps per revolution for motor
long microSecondPause=1600;
void setup() {
// Set PA0, PA1, PA2 & PA3 to be an output
DDRA = (1<<PA0) | (1<<PA1) | (1<<PA2) | (1<<PA3);
// ALL OFF
PORTA = (0<<PA0) | (0<<PA1) | (0<<PA2) | (0<<PA3);
} // end setup
// configure an interrupt on PB0 (PCINT8)
// if PIR pin is changed, then PCINT8 must be changed to match
// if changed PIR pin is not in Port B, then PCIE1 must also change
void initTrigPinInterrupt(void)
{
GIMSK |= (1 << PCIE1);
PCMSK1 |= (1 << PCINT8);
sei();
} // end initPIRInterrupt
// action performed on interrupt
// if PIR pin is changed and not in Port B, then PCIF1 must also change
ISR(PCINT1_vect)
{
if (digitalRead(trigPin)==HIGH)
{
openClose();
} // end if
GIFR |= (1 << PCIF1); // clear the interrupt
} // end ISR
void openClose(void)
{
int fractionOfTurn = (int)(stepsPerRevolution / 8);
while (digitalRead(trigPin)==HIGH)
{
for (int i=0;i<fractionOfTurn;i++) stepForward();
for (int i=0;i<fractionOfTurn;i++) stepReverse();
} // end if
// set all stepper pins to low after interrupt
// ALL OFF
PORTA = (0<<PA0) | (0<<PA1) | (0<<PA2) | (0<<PA3);
delay(1000);
} // end openClose
void stepForward(void)
{
PORTA = (0<<PA0) | (0<<PA1) | (0<<PA2) | (1<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (0<<PA1) | (1<<PA2) | (1<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (0<<PA1) | (1<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (1<<PA1) | (1<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (1<<PA1) | (0<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (1<<PA0) | (1<<PA1) | (0<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (1<<PA0) | (0<<PA1) | (0<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (1<<PA0) | (0<<PA1) | (0<<PA2) | (1<<PA3); delayMicroseconds(microSecondPause);
// ALL OFF
PORTA = (0<<PA0) | (0<<PA1) | (0<<PA2) | (0<<PA3);
} // end stepForward
void stepReverse(void)
{
PORTA = (1<<PA0) | (0<<PA1) | (0<<PA2) | (1<<PA3); delayMicroseconds(microSecondPause);
PORTA = (1<<PA0) | (0<<PA1) | (0<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (1<<PA0) | (1<<PA1) | (0<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (1<<PA1) | (0<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (1<<PA1) | (1<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (0<<PA1) | (1<<PA2) | (0<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (0<<PA1) | (1<<PA2) | (1<<PA3); delayMicroseconds(microSecondPause);
PORTA = (0<<PA0) | (0<<PA1) | (0<<PA2) | (1<<PA3); delayMicroseconds(microSecondPause);
// ALL OFF
PORTA = (0<<PA0) | (0<<PA1) | (0<<PA2) | (0<<PA3);
} // end stepReverse
void loop()
{
initTrigPinInterrupt(); // initialize the interrupt on the interrupt pin
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
while (1)
{
// sleep mode will block sleeping until the trigger pin interrupt is triggered
sleep_bod_disable();
sleep_mode();
while (digitalRead(trigPin)==HIGH) { delay(1000); } // end while
} // end while
delay(1000);
} // end loop
Below is condensed from the Amazon Stepper Motor product page.
'Practical Electronics For Inventors' Third Edition, includes three different stepper motor firing sequences in Chapter 14: Motors at the bottom of page 919. The half step sequence in the book is different from the one below. I didn't try it. The sequence below seems reliable, so I used it.
// from amazon product page
// forward
// case 0: (0); (0); (0); (1); break;
// case 1: (0); (0); (1); (1); break;
// case 2: (0); (0); (1); (0); break;
// case 3: (0); (1); (1); (0); break;
// case 4: (0); (1); (0); (0); break;
// case 5: (1); (1); (0); (0); break;
// case 6: (1); (0); (0); (0); break;
// case 7: (1); (0); (0); (1); break;
// from amazon product page
// reverse
// case 7: (1); (0); (0); (1); break;
// case 6: (1); (0); (0); (0); break;
// case 5: (1); (1); (0); (0); break;
// case 4: (0); (1); (0); (0); break;
// case 3: (0); (1); (1); (0); break;
// case 2: (0); (0); (1); (0); break;
// case 1: (0); (0); (1); (1); break;
// case 0: (0); (0); (0); (1); break;
Improved Pin Block
The first pin block was such a kludge that I redid it. This one isn't much prettier, but it's an improvement. It services two different signals: an audio side to drive two separate speakers and a logic high side to drive a motor and lights while the audio is playing.
I used markers to color code the signal and ground on each side.
Construction
General photos of construction of the skull and motor mount. A light coat of black spray paint hides most of the frame in the dark. A small paint stirrer was used as the lift arm on the stepper motor. The motor was coded to turn an1/8 revolution up and an 1/8 revolution down each sequence. This raises and lowers the jaw of the skull. This was as fast as the motor would reliably move. It could be coded to go slower, but not faster. Each up/down sequence is coded to be 'atomic' (will always complete start to finish). The lift arm should always end up in a known rest state: down.
Electronics Placement and Battery Usage
I used hot glue to place the electronic components where they would fit on the frame.
The number of batteries used is ridiculous. I thought of some ways to improve that, but not this go round. Most of the components draw very little current when idle. After Halloween the batteries should still be usable.
4 AA to power the Atmega328.
2 x 9v, one 9 volt battery for each speaker.
3 D cell to power the stepper motor.
3 D cell to power the LED light strip.
Demo
Attached is a video showing the stepper motor motion with the lights on (no sound).
And a video with the lights off, with motion and sound. The video without lights was pretty hard to capture.
Each audio sequence is triggered by the PIR (motion) sensor attached to the Atmega328.
The skull has a nice glow after an audio sequence.
My wife says it is too scary though.
Thanks for checking it out.
Any questions, feel free to inquire.