An RC5 decoder and preamp controller. Written on the LPC11U24, Ported to LPC1114 and now 100% stable (January 2016)
Dependents: AppleRemoteController_copy_Production_Version AppleRemoteController_Reference_Only
main.cpp
- Committer:
- andrewcrussell
- Date:
- 22 months ago
- Revision:
- 9:c9fb1f8e2ab8
- Parent:
- 4:d900d90588d0
- Child:
- 10:7a93d34a419a
File content as of revision 9:c9fb1f8e2ab8:
/****************************** Apple TV Remote Decoder and Preamp Controller V1.0 *************************/ /* Andrew C. Russell (c) 2022 */ /* This decoder works by reading in the Apple TV Remote IR data stream from one of the serial port lines */ /* and saving the incoming stream into an array called stream, after which it is decoded and */ /* the command executed. . */ /* The following audio preamplifier facilities are catered for:- */ /* 1. Manual volume control adjustment via ALPS RK27 motorized potentiometer */ /* 2. Input select via rotary encoder */ /* 3. Output mute via remote controller only */ /* 4. Power ON output to drive the /standby input of a system power supply */ /* 5.Trigger output to power up an amplifier or other system components */ /* Facilities 1,2,3 and 5 are supported by an RC5 compliant remote control for preamplifiers */ /* The controller pin definitions are set in Pindef1114.h file. */ // UPDATE 26 July 2018: tone functionality removed. The associated pin (dp25) has been sequestrated // for the standby output to drive a power amplifier. Pin 8 on J2 (Controller board) #include "mbed.h" //#include "rc5codes.h" // RC code definitions - in this case for Apple TV Remote #include "Pindef1114.h" // all microcontroller I/O pin assignments defined here /************************************* Apple TV Remote control codes ****************************/ #define STANDBY 378 // toggle power ON and OFF #define MUTE 442 // toggle output signal on and off #define VUP 464 #define VDOWN 432 #define SELECT_R 480 // rotates input through inputs - must depress and then release each time #define SELECT_L 272 // rotates input through inputs - must depress and then release each time #define PREAMP 479 // this is the system code identifying an Apple Remote /*******************************************************************************************/ #define TRUE 1 #define FALSE 0 #define HIGH 1 #define LOW 0 #define tick 280 // quarter bit time in us #define tock 1120 // one bit time in us #define VUP_timeout 45 // defines max number of R/C cycles before the vol ctrl mtr drive stops #define VDWN_timeout 45 // as above but for volume decrease // Needed to ensure the motor is not burnt out #define DEBOUNCE 20000 // this is the switch debounce time // PHONO_ 1 // these are the input assignments written out // CD 2 // on select_out - see thePindef1114.h file for details // TUN 4 // MSERV 8 // AUX 16 // RECORDER 32 int startbit; int toggle; // this is the 3rd bit position in the input stream and checks for // subsequent button depresses from the r/control int toggle1; // temorary storage in the volume UP and volume DOWN functions //int toggle2; // temprary storage of the PB in the mute function //int toggle3; // temp storage for the r/control tone in-out function int standby; int command = 0; int vendor_id = 0; int pair_command = 0; int address = 0; int stop_bit = 0; int FLAG1; // this is used in the remote control input processing int FLAG2; // this is used in the select input processing int FLAG3; // this is for the mute pushbutton int FLAG4; // this is for the standby pushbutton //int FLAG5; // this is the recloop flag int RCFLAG = FALSE; // used to determine if the select command came via R/C int REPEATFLAG; // repaet command flag used for volume control int FLAGVOLUP; int FLAGVOLDWN; //int FLAG7 = FALSE; // this flag is set to TRUE if recloop is active int standbyflag; // used to save the standby condition int RECLOOP1 = 16; // this is the bus address 1 before the Recorder int RECLOOP2 = 32; // this is the bus address for the Recorder input - last input // and is used in the recloop service routine int muteflag = FALSE; // use to control mute and mute indicatoe independently int recloop_status = 0; // this is the initial value. This variable is used // in the select_out routine to indicate when the // input select should wrap around dependent upon // whether the record loop has been activated. int relay; int key_press = 1; // keeps track of key presses // delcarations below are all for the input select proceses int select = 0; int select_save = 2; // we save the status of select drive here. Initial setting is for CD int select_rot; // rotary encoder pulse counter // declare function prototypes here void select_out (void); // writes selected input out to the select_drv bus void select_isr(void); void rc5isr(void); // RC5 ISR for remote control void mute_isr(void); void mute_sel(void); //mutes select relays for a few ms during select //void recloop_isr(void); void standby_out(void); /****************************** volume increase ***********************************/ void vol_up (void) { if ((standbyflag == TRUE) && (key_press < VUP_timeout)) { FWD1 = HIGH; wait_us(100000); //drive the motors for a short while FWD1 = LOW; } if (toggle1 != toggle) { key_press = 0; // user released the button, so reset counter } else if (toggle1 == toggle) { key_press++; // button remained depressed, so increment counter } toggle1 = toggle; // wait_us(100000); } /******************************* volume decrease **********************************/ void vol_dwn (void) { if ((standbyflag == TRUE) && (key_press < VDWN_timeout)) { REV1 = HIGH; wait_us(100000); //drive the motors for a short while REV1 = LOW; } if (toggle1 != toggle) { key_press = 0; // user released the button, so reset counter } else if (toggle1 == toggle) { key_press++; // button remained depressed, so increment counter } toggle1 = toggle; wait_us(1000); } /********************************** stdby_isr *************************************/ void stdby_isr(void) { FLAG4 = TRUE; } /*********************************** standby **************************************/ /* this will require supporting hardware functionality to power down the */ /* analog board, LED's etc. Best option here is to use regulators with a */ /* shutdown option. for now, all the LED's are just turned off */ /* and input relays and mute relayes disabled. */ void standby_out(void) // both p/button and R/C come in here { __disable_irq(); stdby_int.fall(NULL); // on first power up cycle NO interrupts are accepted wait_us(DEBOUNCE); // a very simple debounce do { // that waits for the depressed button to be released continue; //(1); } while (stdby != 1); if (standbyflag == TRUE) { // was ON so now turn it OFF stby_pa = LOW; wait_us(500000); // make sure the power amp is OFF // muteind = LOW; wait_us(1000000); // make sure the power amp output goes OFF muteout = LOW; // now mute the preamp // turn off all interrupts except the standby and rc5int select_int.fall(NULL); // mute_int.fall(NULL); // recloop_int.fall(NULL); // recloop_out = LOW; // make sure the recloop is OFF [its active HIGH] // recloop_status = RECLOOP2; // reset the select so on subsequent power up it does //not skip recorder input select_save = select_drv; // save the status of select_drv wait_us(200000); select_drv = 0; // all input select relays are OFF wait_us(3000000); standbyflag = FALSE; // muteind = HIGH; } else if (standbyflag == FALSE) {// was OFF so we will turn it ON muteLED = LOW; // turn the mute indicator ON rc5int.rise(&rc5isr); // trigger int on rising edge - go service it at rc5dat select_int.fall(&select_isr); // input from rotary encoder or input select // mute_int.fall(&mute_isr); // recloop_int.fall(&recloop_isr); // tone_pb.fall(tone_isr); // recloop_out = LOW; // make sure the recloop is OFF [its active HIGH] wait_us(100000); select_drv = select_save; // recall the input select setting and write to output wait_us(2000000); // let things settle a bit muteout = HIGH; // enable output muteflag = TRUE; muteLED = HIGH; // turn the mute indicator OFF standbyflag = TRUE; stby_pa = HIGH; // now power up the amplifier } wait_us(500000); // let things settle a bit __enable_irq(); stdby_int.fall(&stdby_isr); // re-enable the standby interrupt } /********************************** record loop isr *******************************/ //void recloop_isr(void) //{ // FLAG5 = TRUE; //} /********************************** recloop ***********************************/ //void recloop() //{ // // if (select_drv != RECLOOP2) { // if its anything other than recloop we can activate the recloop relay // recloop_int.fall(NULL); // to prevent re-entrance when coming here from the R/C // wait_ms(DEBOUNCE); // simple debounce for when mute is via the f/p p/b switch // // do { // continue; // wait here until the button is released // } while (recloop_in != 1); // // if (recloop_rly == HIGH) { // the recloop relay was activated // recloop_rly = LOW; // so turn it off // recloop_out = LOW; // FLAG7 = 0; // } // // else if (recloop_rly == LOW) { // it was OFF so activate it // recloop_rly = HIGH; // recloop_out = HIGH; // FLAG7 = 32; // } // // wait_ms(DEBOUNCE); // // } // // recloop_int.fall(&recloop_isr); // //} /************************************ mute_isr ************************************/ //void mute_isr(void) //{ // FLAG3 = TRUE; // toggle2 = !toggle2; // so the p/button input is recognized in mute_out() //} /************************************** mute ************************************/ void mute_out() { muteout = !muteout; if (muteout == HIGH) { muteLED = LOW; } else if (muteout == LOW) { muteLED = HIGH; } wait_us(100000); } // mute_int.fall(NULL); // to prevent re-entance when coming here from the R/C // wait_ms(DEBOUNCE); //simple debounce for when mute is via the f/p p/b switch // do { // continue; //wait here until the button is released // } while (mute != 1); // // if (muteflag == FALSE) { // mute was inactive so it will now get activated // muteout = TRUE; // muteind = HIGH; // muteflag = TRUE; // indicate its been activated // } // // else if (muteflag == TRUE) { //it was active, so it must be deactivated here // muteout = FALSE; // muteind = LOW; // muteflag = FALSE; // } // // wait_ms(800); // make sure relay state is settled // // mute_int.fall(&mute_isr); //} // /************************************ rc5isr **************************************/ /* Interrupt triggered by a rising edge on p18 which is R/C data in */ void rc5isr(void) { FLAG1 = TRUE; RCFLAG = TRUE; REPEATFLAG = TRUE; } /******************* save bit stream from remote controller ***********************/ /* This function reads the input data on pin rc5dat at 1120us ('tock')intervals */ /* and saves the data into an array stream[i]. */ void save_stream(void) { if (RCFLAG == TRUE) { wait_us(13500); // this is the IR AGC header - wait until passed //} bool stream[63];// the array is initialized each time it is used and is local only int bitloop = 0; // number of bit positions - local int i = 0; // counter int k = 0; // temp storage vendor_id = 0; pair_command = 0; address = 0; command = 0; stop_bit = 0; //must always return a 1 to be valid, so reset it wait_us(tick); // locate read point in middle of 1st half bit time of the 1st start bit for (bitloop = 0; bitloop <64; bitloop ++) { stream[bitloop] = rc5dat; //read the data and save it to array position [i] // bitstreamsync = !bitstreamsync; // the recovered IR bitstream is output on p14 for debug if (rc5dat == HIGH) { wait_us(tock); // wait for start of next bit time } wait_us(tock); //wait here until ready to read the next bit in } // now have 32 bits loaded into stream[i] /* now put data in the array into the start, toggle, address and command variables - array counts from stream[0] */ for (i=0; i<11; i++) { // first 11 bit positions are vendor ID k = stream[i]; // k will hold the vendor ID vendor_id = (vendor_id << 1); vendor_id = vendor_id|k; } for (i = 11; i <16; i++) { // command or pair k = stream[i]; pair_command = (pair_command << 1); pair_command = pair_command|k; } for (i = 16; i <25; i++) { // device pairing address k = stream[i]; address = (address << 1); address = address|k; } for (i = 25; i <31; i++) { // bit positions 25 to 30 are the command - 7 bit positions k = stream[i]; command = (command << 1); command = command|k; printf("\n here \r "); } stop_bit = stream[31]; printf("\n vendor_id = %d pair_command = %d address = %d command = %d stop_bit = %d \r", vendor_id, pair_command, address, command, stop_bit); } } /********************************* process_stream() *******************************/ /* handles commands coming in from the remote controller only */ void process_stream (void) { if ((RCFLAG == TRUE) && ((vendor_id == 479) || (vendor_id == 2047))) { // basic error checking - must be preamp + startbit ok to get executed otherwise skip completly switch (address) { case VUP: vol_up(); FLAGVOLUP = TRUE; break; case VDOWN: vol_dwn(); FLAGVOLDWN = TRUE; break; case MUTE: mute_out(); break; case SELECT_R: select_out(); break; case SELECT_L: select_out(); break; case STANDBY: standby_out(); break; } if ((FLAGVOLUP == TRUE) && (vendor_id == 2047)) { vol_up(); } if ((FLAGVOLDWN == TRUE) && (vendor_id ==2047)) { vol_dwn(); } } RCFLAG = FALSE; } /*********************************** select_isr ***********************************/ void select_isr(void) { FLAG2 = TRUE; } /****************************** mute inter select*********************************/ //void mute_sel(void) //{ // select_drv = 0; // wait_ms(2); //} /********************************* select_process *********************************/ /* Used for selecting the input source. This function is used by the */ /* rotary encoder only */ void select_process(void) { if (RCFLAG == FALSE) { // if used R/C skip completely - extra safety check wait_us(5000); // debounce - very short for the rotary encoder select = 0; // flush select select = (select | sela) <<1; // read the two port lines associated with the select rotary encoder select = (select | selb); switch (select) { case 1: // select encoder is being rotated CW so increment select_rot select_rot <<= 1; if (select_rot > recloop_status ) { select_rot = 1; // wrap around to 1 } break; case 0: select_rot >>= 1; // encoder is being rotated CCW so decrement select_rot if (select_rot < 1) { select_rot = recloop_status; //wrap around to 32 } break; case 2: break; // indeterminate fall through values - ignore case 3: break; // and do not change the output } } // select_rot = (select_rot | FLAG7); select_drv = select_rot; // write the value out to the bus // printf("\n RCFLAG %d \r", RCFLAG); } /********************************* select_out *********************************/ // this is only used by the IR remote void select_out (void) { if (address == SELECT_L) { select_rot >>= 1; if (select_rot <1) { select_rot = 32; } } if (address == SELECT_R) { select_rot <<= 1; if (select_rot >32) { select_rot = 1; } } select_drv = select_rot; // select_drv = (select_rot | FLAG7); //write the selection out to the bus. printf("\n select_rot = %d \r", select_rot); } /************************************ main() ***************************************/ int main(void) { // Serial pc(USBTX, USBRX); __disable_irq(); // just to make sure we can set up correctly without problems stby_pa = LOW; // make sure the power amp is OFF via the trigger output muteout = LOW; //make sure the outputis muted from the get go // muteind = HIGH; //mute LED must be ON - power up preamble select_drv = 0; // no input relays must be on // bitstreamsync = LOW; // this is the IR bitsteeam output on pin 14 for debug // recloop_out = LOW; // make sure the recloop LED is OFF [its active HIGH] // recloop_rly = LOW; // make sure the recloop relay is OFF // mute_int.mode(PullUp); // set up all the pin states per Pindef1114.h rc5dat.mode(PullUp); // pin 17 sela.mode(PullUp); // pin 28 selb.mode(PullUp); // pin 27 stdby.mode(PullUp); // pin 26 // recloop_in.mode(PullUp); // pin 14 wait_us(200000); FLAG1 = FALSE; FLAG2 = FALSE; // FLAG5 = FALSE; // this is the recloop flag FWD1=0; //make sure the volume control motor is OFF REV1=0; // set up the ISR's that will be used rc5int.fall(&rc5isr); // trigger int on falling edge - go service it at rc5dat select_int.fall(&select_isr); // input from rotary encoder or input select // mute_int.fall(&mute_isr); // mute push button interrupt // recloop_int.fall(&recloop_isr); // record loop push button interrupt stdby_int.fall(&stdby_isr); // the system power/standby switch - on sel rotenc //now disable them, leaving only the stand by p/button and rc5int interrupts active select_int.fall(NULL); // mute_int.fall(NULL); // recloop_int.fall(NULL); standbyflag = TRUE; // preamp will be set-up first time for OFF standby_out(); // set system up standbyflag = FALSE; select_save = 2; // CD always slected on initital power up select_rot = select_save; // CD will be selected when power is first turned on wait_us(3000000); // muteind = LOW; __enable_irq(); // all ready and in standby from this point forward LOOP: // this is the main operating loop __WFI(); // wait here until interrupt if (FLAG1 == TRUE) { // FLAG1 indicates remote control was used save_stream(); process_stream(); FLAG1 = FALSE; } if (FLAG2 == TRUE) { select_process(); //select process FLAG2 = FALSE; } if (FLAG3 == TRUE) { mute_out(); //mute FLAG3 = FALSE; } if (FLAG4 == TRUE) { // standby ON/OFF standby_out(); FLAG4 = FALSE; } // if (FLAG5 == TRUE) { // recloop(); //recloop // FLAG5 = FALSE; // } //printf("\r Command = %d Address = %d \n",command, address); goto LOOP; }