
.jpg)
INTRODUN
Our final project is a musical water fountain loosely based on the fountain. The basic idea of the project is to take an input from an iPod (or any sound source), sample the sound and break it down to different “sequencies” by Walsh Transform , then use the output to turn on various solenoid valves. We first used a two-stage low pass filter, and then performed Fast Walsh Transforms on the sound source to split the sounds into different frequency ranges. Each different range will correspond to a different valve which comprises a musical water fountain operating to the beats and rhythms of the song.
Currently, we have split the sound source into eight frequency ranges. The reasons for eight fountain heads are: 1- aesthetically appealing and 2- there are 8 pins for a standard I/O port on the Atmel Microcontroller. Since the purpose of this final project is to create a water fountain, we needed a pressurized water source. Originally, we had thought about using fish pumps; but then realized they did not have enough pressure. The pressure coming from the sink is between 30 and 40 psi, and therefore we decided to attach our input source to the sink.
SOFTWARE
One of the most difficult parts of our program was determining which transform we should use. Originally, we had decided to use the Fourier Transform, because we felt we understood it the best. However, the Fourier transform took too many cycles, and was not fast enough. We then had to determine whether to use the Fast Fourier transform, which splits the sound waves into different frequency ranges, or the Walsh transform. The Walsh transform is faster, and much easier to code; in fact Professor Land had already written a basic Walsh transform which we could modify for our purposes. Unfortunately, the Walsh transform does not directly break up the sound waves into frequency ranges. Instead, it breaks up the sound waves into sequency ranges, which has a linear relationship to frequency ranges. Rather than sine and cosine, the ranges are broken into cal and sal equations. The result is often thought of as "a poor-man's fast Fourier transform (FFT)" representing the conversion of a time-sampled signal into an equivalent frequency-sampled form. Every range has roughly 150 Hz frequency range. In the end, we chose the Walsh transform, due to its speed.
CIRCUIT
The sound source or music can be fed from any standard player (mp3, cd, walkman, pc) with the 3.5mm stereo phone jack. We used a simple 2-to-1 splitter so the music can be played through a speaker and inputted into the Analog to Digital Converter (ADC) port of the microcontroller at the same time. The sound source from a standard player (ex. mp3) can not be directly used for ADC because the output signal maximum amplitude is usually +/- 1.5 V. The input signal therefore needs to have a DC bias which eliminates negative voltages and still stay within the range of the ADC reference voltage of 5V. Even though the audible range of a human ear is 20Hz-20kHz, we decided to sample the music at 2kHz to allow for fast ADC conversion and simple breakdown of frequency ranges. At this sampling rate, we can detect tones at up to 1kHz which will cut off high frequency treble sounds but still be useful for decoding typical music. As a result, we low pass filtered the input signal through two stages for sharper cutoff at 1kHz, removed the DC component of the sound source, and then biased the signal at 2.5V.


CONCLUSION
Our project evolved numerous ways over the course of the planning, execution, construction, and testing phases. However, the main goals and concept of our musical fountain (or water fountain) stayed the same and we were able to successfully achieve the desired results. While our end result did not come any close to the famed Bellagio water fountains in Vegas, we did have great satisfaction in building a water fountain (or fall) that can play to the beats and rhythms of our music source. We realized early on that some aspects of our project were too ambitious and we made the right changes to continue progress and ultimately accomplish the main goals we set in the beginning. For example, the solenoid valves we need in this project turned out to be very difficult to get because of their cost and the lack of free samples from valve manufacturers. Fortunately we were able to secure a lot of overstocked, outdated valves on Ebay for an extremely bargain price. Overall, this project turned out to be much more challenging and time consuming than we originally planned. A large amount of time was spent in mechanical engineering and construction on top of the circuit design, testing, and programming.
#include <mega32.h>
//#include <stdio.h>
#include <math.h>
//I like these definitions
#define begin {
#define end }
#define t1 200 //Valve cycle time
#define t2 100
#define t3 400
//State machine state names - Debouncer
#define NoPush 1
#define MaybePush 2
#define Pushed 3
#define MaybeNoPush 4
#define int2fix(a) (((int)(a))<<8)
#define fix2int(a) ((signed char)((a)>>8))
#define float2fix(a) ((int)((a)*256.0))
#define fix2float(a) ((float)(a)/256.0)
#define N_WAVE 16 /* size of FWT */
#define LOG2_N_WAVE 4 /* log2(N_WAVE) */
int fwt_t1, fwt_t2;
//FWT State
#define Single 1 //Find max sequency - 1 valve on per cycle
#define Multi 2 //Valve on if pass threshold
int ain1[N_WAVE], ain0[N_WAVE] ; //double buffer inputs
char countISR, Nbuffer; //Nbuffer keeps track of which buffer is in use
unsigned char State;
unsigned int timer, debtime, time3;
unsigned char PushFlag1, PushFlag2, PushFlag3; //message indicating a button push
unsigned char PushState1, PushState2, PushState3; //state machine
//reordering tables
flash char reorder16[16] = {0,15,7,8,3,12,4,11,1,14,6,9,2,13,5,10};
//========================================================
void FWTfix(int x[])
// This routine does foward transform only
//output is in dyadic order -- see web links
//use FWTreorder() to put the transform in sequency order
begin
unsigned char ii, k, j, i, bb, st;
bb = N_WAVE/2; st = N_WAVE;
//
for (i=1; i<LOG2_N_WAVE+1; i++)
begin
for (j=0; j<N_WAVE; j+=st)
begin
for (k=0; k<bb; k++)
begin
ii=j+k;
fwt_t1 = x[ii] >>1 ; //scale to keep fixed pt in range
fwt_t2 = x[ii+bb] >>1 ;
x[ii] = fwt_t1 + fwt_t2 ; //
x[ii+bb] = fwt_t1 - fwt_t2 ;
end
end
bb = bb>>1 ; st = st>>1 ;
end
end
//========================================================
void FWTreorder(int x[], flash char r[])
//converts from dyadic order to sequency order -- see references
begin
char i,j;
int xp[N_WAVE];
for (i=0; i<N_WAVE; i++)
begin
j = r[i];
xp[j] = x[i];
end
for (i=0; i<N_WAVE; i++)
begin
x[i] = xp[i];
end
end
//========================================================
interrupt [TIM0_COMP] void getAD(void)
begin
//read a/d converter and update appropriate buffer
if(timer>0) timer--;
if(debtime>0) debtime--;
if(time3>0) time3--;
if (countISR < N_WAVE)
begin
if (Nbuffer) ain1[countISR] = (ADCH *2);
else ain0[countISR] = (int)(ADCH *2) ;
//start a/d converter
ADCSR.6 = 1;
//update ISR counter
countISR++;
//PORTB = ~PORTB;
end
end
//========================================================
void debounce1(void)
//button for switching modes
begin
debtime=t2; //reset the task timer
switch (PushState1)
begin
case NoPush:
if (~PINA.1 == 0x01) PushState1=MaybePush;
else PushState1=NoPush;
break;
case MaybePush:
if (~PINA.1 == 0x01)
begin
PushState1=Pushed;
PushFlag1=1;
end
else PushState1=NoPush;
break;
case Pushed:
if (~PINA.1 == 0x01) PushState1=Pushed;
else PushState1=MaybeNoPush;
break;
case MaybeNoPush:
if (~PINA.1 == 0x01) PushState1=Pushed;
else
begin
PushState1=NoPush;
PushFlag1=0;
end
break;
end
end
//========================================================
void debounce2(void)
//button for increasing threshold
begin
switch (PushState2)
begin
case NoPush:
if (~PINA.2 == 0x01) PushState2=MaybePush;
else PushState2=NoPush;
break;
case MaybePush:
if (~PINA.2 == 0x01)
begin
PushState2=Pushed;
PushFlag2=1;
end
else PushState2=NoPush;
break;
case Pushed:
if (~PINA.2 == 0x01) PushState2=Pushed;
else PushState2=MaybeNoPush;
break;
case MaybeNoPush:
if (~PINA.2 == 0x01) PushState2=Pushed;
else
begin
PushState2=NoPush;
PushFlag2=0;
end
break;
end
end
//========================================================
void debounce3(void)
//button for decreasing threshold
begin
switch (PushState3)
begin
case NoPush:
if (~PINA.3 == 0x01) PushState3=MaybePush;
else PushState3=NoPush;
break;
case MaybePush:
if (~PINA.3 == 0x01)
begin
PushState3=Pushed;
PushFlag3=1;
end
else PushState3=NoPush;
break;
case Pushed:
if (~PINA.3 == 0x01) PushState3=Pushed;
else PushState3=MaybeNoPush;
break;
case MaybeNoPush:
if (~PINA.3 == 0x01) PushState3=Pushed;
else
begin
PushState3=NoPush;
PushFlag3=0;
end
break;
end
end
//========================================================
//read A/D converter
// transform
// PWM LEDs to make 8 channel analyser
void main(void)
begin
unsigned char CurBuf, i, m ;
int compare[8], th;
//serial setup for debugging using printf, etc.
UCSRB = 0x18 ;
UBRRL = 103 ;
//putsf("\r\nStarting...\r\n");
//set up timer0 to sample a/d at about 7800 Hz
//turn on timer with period= 256*8 cycles = 2048 cycles
//which implies 7812 samples/sec
TCCR0 = 0b00001011;
TIMSK = 2 ;
OCR0 = 125;
timer = t1;
debtime = t2;
time3 = t3;
//set up a/d for external Vref, channel 0
//channel zero/ left adj /EXTERNAL Aref
//!!!CONNECT Aref jumper!!!!
ADMUX = 0b01100000;
//enable ADC and set prescaler to 1/128*16MHz=125,000
//and clear interupt enable
//and start a conversion
ADCSR = 0b11000111;
//initialize state to single valve
State = Single;
//set up PORTB/D for output
DDRB=0xff;
DDRD=0xff;
PORTD.7=0;
//initialize PortB for off (Valve Normally Open)
PORTB=0xff;
//initialize debounce variables
PushFlag1=0;
PushFlag2=0;
PushFlag3=0;
PushState1=NoPush;
PushState2=NoPush;
PushState3=NoPush;
//and start the show
#asm("sei")
//start with buffer 0
Nbuffer = 0;
//led threshold **************************************************
th=40; //initial value
while(1)
begin
if(debtime==0)
begin
debtime=t2;
debounce1();
debounce2();
debounce3();
end
if(time3==0)
begin
time3=t3;
if(PushFlag1)
begin
if(State==Single) State=Multi;
else if(State==Multi) State=Single;
PushFlag1=0;
end
//threshold can be set from 10-80
if(PushFlag2 && th<80)
begin
th = th + 10;
PushFlag2=0;
end
if(PushFlag3 && th>10)
begin
th = th-10;
PushFlag3=0;
end
end
//when there are 16 points in buffer
if (countISR == N_WAVE)
begin
//critical section
#asm("cli")
CurBuf = Nbuffer;
//switch input buffers
Nbuffer ^= 0x01;
//reset the counter
countISR = 0;
#asm("sei")
//end critical section
//update the FWT
if (CurBuf) FWTfix(ain1);
else FWTfix(ain0);
// sequency order
if (CurBuf) FWTreorder(ain1,reorder16);
else FWTreorder(ain0,reorder16);
// combine sal and cal
if (CurBuf)
begin
//omit DC in ain1[0]
compare[0] = (abs(ain1[1])+abs(ain1[2])) ;//adds the cal and sal values for each sequency band
compare[1] = (abs(ain1[3])+abs(ain1[4])) ;
compare[2] = (abs(ain1[5])+abs(ain1[6])) ;
compare[3] = (abs(ain1[7])+abs(ain1[8])) ;
compare[4] = (abs(ain1[9])+abs(ain1[10])) ;
compare[5] = (abs(ain1[11])+abs(ain1[12])) ;
compare[6] = (abs(ain1[13])+abs(ain1[14])) ;
compare[7] = (abs(ain1[15])); //highest sequency - no sal value
//printf("%u\r\n",compare[7]);
end
else
begin
//omit DC
compare[0] = (abs(ain0[1])+abs(ain0[2])) ;//adds the cal and sal values for each sequency band
compare[1] = (abs(ain0[3])+abs(ain0[4])) ;
compare[2] = (abs(ain0[5])+abs(ain0[6])) ;
compare[3] = (abs(ain0[7])+abs(ain0[8])) ;
compare[4] = (abs(ain0[9])+abs(ain0[10])) ;
compare[5] = (abs(ain0[11])+abs(ain0[12])) ;
compare[6] = (abs(ain0[13])+abs(ain0[14])) ;
compare[7] = (abs(ain0[15])); //highest sequency - no sal value
end
if(timer == 0)
begin
//printf("********************************\n\r");
/*printf("compare0 %i \n\r", compare[0]);
printf("compare1 %i \n\r", compare[1]);
printf("compare2 %i \n\r", compare[2]);
printf("compare3 %i \n\r", compare[3]);
printf("compare4 %i \n\r", compare[4]);
printf("compare5 %i \n\r", compare[5]);
printf("compare6 %i \n\r", compare[6]);
printf("compare7 %i \n\r", compare[7]); */
//printf("state %i\n\r", State);
//printf("th %i\n\r", th);
//printf("m %i \n\r", m);
//combine threshold and max and light leds
//find the maximum element
m = 0;
for (i=1; i<8; i++)
begin
if (compare[i]>compare[m]) m = i;
end
if (State == Single)
begin
if((m == 7) && (compare[7]>th)) PORTB.0 = 0; else PORTB.0 = 1;
if((m == 6) && (compare[6]>th)) PORTB.1 = 0; else PORTB.1 = 1;
if((m == 5) && (compare[5]>th)) PORTB.2 = 0; else PORTB.2 = 1;
if((m == 4) && (compare[4]>th)) PORTB.3 = 0; else PORTB.3 = 1;
if((m == 3) && (compare[3]>th)) PORTB.4 = 0; else PORTB.4 = 1;
if((m == 2) && (compare[2]>th)) PORTB.5 = 0; else PORTB.5 = 1;
if((m == 1) && (compare[1]>th)) PORTB.6 = 0; else PORTB.6 = 1;
if((m == 0) && (compare[0]>th)) PORTB.7 = 0; else PORTB.7 = 1;
end
if (State == Multi)
begin
if((compare[7]>th)) PORTB.0 = 0; else PORTB.0 = 1;
if((compare[6]>th)) PORTB.1 = 0; else PORTB.1 = 1;
if((compare[5]>th)) PORTB.2 = 0; else PORTB.2 = 1;
if((compare[4]>th)) PORTB.3 = 0; else PORTB.3 = 1;
if((compare[3]>th)) PORTB.4 = 0; else PORTB.4 = 1;
if((compare[2]>th)) PORTB.5 = 0; else PORTB.5 = 1;
if((compare[1]>th)) PORTB.6 = 0; else PORTB.6 = 1;
if((compare[0]>th)) PORTB.7 = 0; else PORTB.7 = 1;
end
timer = t1;
end
end
end
end