#include /* Speech recorder Use a 16f84 with 2 push buttons andsome status LEDs to record ONE track of up to 8 minutes. The track can be subsequently replayed or overwritten with a singe button press. The iSD4004 is controlled via a bit-banged SPI bus. PJL, Aug. 2009, from an earlier version using an MP3 player. You may use or modify this code for your own non-commercial purposes. No warranty of correctness, usefulness or completeness is either given, or implied. There are definitely bugs in the software - which may affect your intended use. */ /***************** MAJOR BUG *********************************/ /***** The HiTech c compiler wrongly assumes a 16c84 *********/ /***** has 0x44 bytes of RAM. it doesn't. it has 0x24 ********/ /***** You need to go to the Project->Global_Options *********/ /***** and set the RAM range to 0c-2f, otherwise some ********/ /****** variables will mysteriously lose their values ********/ /****** or appear to be corrupted or reset to zero **********/ /*************************************************************/ __CONFIG(RC & WDTDIS & UNPROTECT); /* Frequency using RC oscillator. * 150pf + 10k ~ 660kHz */ #define _XTAL_FREQ 675000 // define some data structures for soe bit-mapped variables and // in/out values. #define PRESSED 1 #define RELEASED 2 typedef union { unsigned char ALL; struct { unsigned pressed:1; unsigned released:1; unsigned flash:1; }; } button_t; #define ON 1 #define PLAYING 2 #define RECORDING 4 #define WAITING 8 #define RUNNING 32 typedef union { unsigned char ALL; struct { unsigned on:1; unsigned playing:1; unsigned recording:1; unsigned waiting:1; unsigned intr:1; unsigned running:1; }; } isd_status_t; typedef union { unsigned char ALL; struct { unsigned addr:6; unsigned eom:1; unsigned ovr:1; }; } isd_response_t; // processor pins used for the SPI. if you choose other // pin assignments, be sure to update the TRIS assignments // accordingly #define INTR RB0 #define SDI RB1 #define SDO RB2 #define SS RB3 #define SCLK RB4 // declare prottypes of functions used in the code isd_response_t spi(unsigned char); void power_on(); void power_off(); void isd_stop(); void play_from_0(); void record_from_0(); void dly(unsigned char); // global variable declaration - when built, there are 2 bytes of RAM free volatile unsigned char timer; volatile unsigned char msec; volatile unsigned char tenths; volatile int seconds; volatile unsigned int flash_green; volatile unsigned int flash_yellow; volatile unsigned int flash_red; volatile int rac; isd_response_t isd_status; isd_status_t isd; // current state of ISD chip volatile button_t a, b; void main(void) { OPTION = 0xb00001000; /* pullups enabled - 0x80 */ /* prescaler to WDT (not used) */ INTCON = 0b00111000; // TMR0 interrupt // INT (RB0 1->0) // RB5 change TRISA = 0b00001100; // RA 2, 3 push-button inputs // RA0, 1 are LEDs TRISB = 0b00100011; // RB0 = INT, // RB1 = SPIn // RB5 = RAC /* initilaise all the variables here */ a.ALL = 0; b.ALL = 0; SS = 1; // quiescent state for SS is high SCLK = 0; SDO = 0; msec = 0; tenths = 0; seconds = 0; flash_green = 0; // off, to start with flash_yellow = 0; // always off flash_red = 0; GIE = 1; // let 'em in power_on(); isd.ALL = ON; // the status has // bits changed by inpterrupts // so wait until after they are // enabled before initialising // in case we get spurious // start-up intrs. rac = 0; // ISD row count from interrupt isd.waiting = 1; // will turn ISD off in 10 sec // if not used flash_green = 0b1000000000000000; // device ready /* main loop here. Check if any buttons have been pressed (and released) - set up actions if they have been Take action if the ISD raises an interrupt - interrogate it to clear See if the RAC bit is toggling, this indicates record / play - change the LED patterns accordingly */ loop: if(RA2 == 0) { // button A pressed a.ALL = PRESSED; a.flash = 1; // flash the red LED to acknowledge dly(10); // only for 10 mSec a.flash = 0; } if(RA3 == 0) { b.ALL = PRESSED; b.flash = 1; dly(10); b.flash = 0; } if((RA2 == 1) && a.pressed) { // button released a.ALL = RELEASED; // take action } if((RA3 == 1) && b.pressed) { b.ALL = RELEASED; } if(a.released) { a.ALL = 0; // clear outstanding action if(isd.playing) { // already playing, so stop isd_stop(); seconds = 0; // reset timer to indicate how // long the ISD has been off isd.ALL = ON; // reset status bits isd.waiting = 1; // on, but inactive flash_red = 0; flash_green = 0b1000000000000000; } else { if(!isd.on) power_on(); play_from_0(); isd.playing = 1; isd.waiting = 0; seconds = 0; flash_red = 0; flash_green = 0b1111000011110000; } } if(b.released) { b.ALL = 0; if(isd.recording) { // stop if already recording isd_stop(); seconds = 0; isd.ALL = ON; // reset all status bits isd.waiting = 1; flash_red = 0; flash_green = 0b1000000000000000; } else { // else start recording if(!isd.on) { power_on(); power_on(); } record_from_0(); isd.recording = 1; isd.waiting = 0; seconds = 0; flash_red = 0; flash_green = 0b1010101010101010; } } // use the yellow LED as a progress indictor // have the LED on for greater lengths of time // as the play / record time increases // The ISD plays for 8 minutes, the LED has 16 poitions // so each increment in flash time corresponds to 30 seconds // of playing / recording time if(rac == 0) flash_yellow = 0; else if(flash_yellow == 0) { // only update if last pattern // has finished flashing flash_yellow = 0b0000000000000001; if(rac > 300) flash_yellow = 0b0000000000000011; if(rac > 600) flash_yellow = 0b0000000000000111; if(rac > 900) flash_yellow = 0b0000000000001111; if(rac > 1200) flash_yellow = 0b0000000000011111; if(rac > 1500) flash_yellow = 0b0000000000111111; if(rac > 1800) flash_yellow = 0b0000000001111111; if(rac > 2100) flash_yellow = 0b0000000011111111; if(rac > 2400) flash_yellow = 0b0000000111111111; if(rac > 2700) flash_yellow = 0b0000001111111111; if(rac > 3000) flash_yellow = 0b0000011111111111; if(rac > 3300) flash_yellow = 0b0000111111111111; if(rac > 3600) flash_yellow = 0b0001111111111111; if(rac > 3900) flash_yellow = 0b0011111111111111; if(rac > 4200) flash_yellow = 0b0111111111111111; if(rac > 4600) flash_yellow = 0b1111111111111111; // Note: not 4500! } /* check if got an interrupt back from the ISD. If so, send a STOP message to interrogate it, Flash the red LED depending on the result */ if(isd.intr) { isd.intr = 0; isd_status.ALL = 0; isd_stop(); seconds = 0; isd.waiting = 1; if(isd_status.eom) flash_red = 0b1111111100000000; if(isd_status.ovr) flash_red = 0b1111111111111110; else flash_red = 0b0101001000100001; // (?) isd.playing = 0; isd.recording = 0; } dly(100); // give the lights time to flash // and switches time to debounce /* if the ISD has been on, but not doing anything for more than */ /* 10 seconds, switch it off - stops screeches if the power */ /* goes off. Might even prevent failures or corrupt data, too */ if(isd.waiting && (seconds > 10)) { power_off(); flash_red = 0; flash_green = 0b0101000000000000; // indicate ISD is powered down } goto loop; // end of actions, back to start } /*******************************************************/ /* Interrupt routine called ever 1mSec approximately */ /* Used mainly to provide time delays and to flash the */ /* LEDs which provide status information */ /*******************************************************/ void interrupt isr(void) { if(T0IF) { TMR0 = 183; // reload timer for next interrupt T0IF = 0; // clear interrupt timer--; // msec timer used in dealy loops msec++; if(msec > 99) { msec = 0; tenths++; /* every 1/10 seconds do something with the LEDs depending on what's in the "flash" variables. if the next rotated bit is a 1 light the LED, if 0, turn it off */ // want the yellow LED to run out after one cycle, so don't // set the LSB, whcih will always -> 0 after the "<< 1" if(flash_yellow & 0x8000) { RA0 = 1; TRISA0 = 0; // set to output flash_yellow = flash_yellow << 1; } else { flash_yellow = flash_yellow << 1; TRISA0 = 1; // turn off output } // want the green LED to repeat the programmed pattern until // a new one is entered, so reset the LSB if needed if(flash_green & 0x8000) { RA1 = 1; TRISA1 = 0; // set to output flash_green = flash_green << 1; flash_green |= 1; } else { TRISA1 = 1; // HI impedance state == OFF flash_green = flash_green << 1; } /* do the red LED last, so it takes priority over the green one */ if(flash_red & 0x8000) { RA1 = 0; TRISA1 = 0; // set to output flash_red = flash_red << 1; flash_red |= 1; } else { flash_red = flash_red << 1; if(flash_red) TRISA1 = 1; // turn off output if red is active } if(tenths > 9) { tenths = 0; seconds++; } } if(a.flash) { RA1 = 0; TRISA1 = 0; } // override pattern if(b.flash) { RA1 = 0; TRISA1 = 0; } // with (short) red flash } // interrupt when pin RB0 goes high if(INTF) { // interrupt on RB0 INTF = 0; // reset interupt isd.intr = 1; // flag device has interrupted // nust read SPI to clear it } // interrupt on pin change (either 0->1 or 1->0) if(RBIF) { // RAC has changed state asm("movf _PORTB, w"); // clear the interrupt RBIF = 0; isd.running = 1; // indicates ISD is active rac++; // updates every 200mSec // when the ISD is playing or // recording } } // delay routine that prevents the watchdog from barking void dly(unsigned char i) { timer = i; while(timer) { asm("nop"); } } /* send 8 bits to the SPI link and receive4 8 bits back * This routine clocks data out on the 0->1 edge of the * SCLK pulse and clocks data in from the peripheral on * the 1->0 transition */ isd_response_t spi(unsigned char c) { unsigned char i; isd_response_t r; for(i = 0; i < 8; i++) { if(c & 0x80) SDO = 1; else SDO = 0; SCLK = 1; // clock data out to ISD asm("nop"); SCLK = 0; // clock data in from ISD r.ALL = (r.ALL << 1); r.ALL = r.ALL & 0xfe; if(SDI) r.ALL |= 0x1; c = c << 1; // shift in next bit to send } return(r); } // turn device on. Includes mandatory power-on delay // This should be called twice, if device was powered off void power_on() { SCLK = 0; // idle at zero SS = 0; isd_status = spi(0); // first byte contains flags spi(0); spi(4); // switch device on SS = 1; dly(25); // power-up delay 25mSec isd.on = 1; } void play_from_0() { SCLK = 0; // idle at zero SS = 0; isd_status = spi(0); spi(0); spi(7); // play from address 0 SS = 1; rac = 0; // restart row count } void record_from_0() { SCLK = 0; // idle at zero SS = 0; isd_status = spi(0); spi(0); spi(5); // play from address 0 SS = 1; rac = 0 ; // restart row count } void power_off() { SCLK = 0; // idle at zero SS = 0; isd_status = spi(0); spi(0); spi(8); // stop and power down SS = 1; isd.ALL = 0; // device OFF, reset all // status bits } void isd_stop() { SCLK = 0; // idle at zero SS = 0; isd_status = spi(0); // holds OVR and EOM flags spi(0); spi(12); // stop and power down SS = 1; dly(50); // power down delay 50mSec isd.running = 0; rac = 0; }