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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /****************************** Apple TV Remote Decoder  and Preamp Controller V1.0 *************************/
00002 /*                                  Andrew C. Russell (c) 2022                                  */
00003 /* This decoder works by reading in the Apple TV Remote IR data stream from one of the serial port lines        */
00004 /* and saving the incoming stream into  an array called stream, after which it is decoded and   */
00005 /* the command executed. .                                                */
00006 
00007 /* The following audio preamplifier facilities  are catered for:-                               */
00008 /* 1. Manual volume control adjustment via ALPS RK27 motorized potentiometer                    */
00009 /* 2. Input select via rotary encoder                                                           */
00010 /* 3. Output mute via remote controller only                                                    */
00011 /* 4. Power ON output to drive the /standby input of a system power supply                      */
00012 /* 5.Trigger output to power up an amplifier or other system components                         */
00013 /* Facilities 1,2,3 and 5 are supported by an RC5 compliant remote control for preamplifiers    */
00014 /* The controller pin definitions are set in Pindef1114.h file.                                 */
00015 
00016 
00017 // UPDATE 26 July 2018: tone functionality removed. The associated pin (dp25) has been sequestrated
00018 // for the standby output to drive a power amplifier. Pin 8 on J2 (Controller board)
00019 
00020 #include "mbed.h"
00021 #include "apple_codes.h"      // remote codes for Apple TV Remote
00022 #include "Pindef1114.h"    // all microcontroller I/O pin assignments defined here
00023 
00024 /************************************* Apple TV Remote control codes ****************************/
00025 #define STANDBY 378      // toggle  power ON and OFF
00026 #define MUTE 442         // toggle output signal on and off
00027 #define VUP 464
00028 #define VDOWN 432
00029 #define SELECT_R 480      // rotates input through inputs - must depress and then release each time
00030 #define SELECT_L 272      // rotates input through inputs - must depress and then release each time
00031 #define PREAMP 479          // this is the system code identifying an Apple Remote
00032 
00033 
00034 
00035 /*******************************************************************************************/
00036 
00037 #define TRUE 1
00038 #define FALSE 0
00039 #define HIGH 1
00040 #define LOW 0
00041 #define tick 280            // quarter bit time in us 
00042 #define tock  1120          // one bit time in us
00043 #define VUP_timeout 45      // defines max number of R/C cycles before the vol ctrl mtr drive stops
00044 #define VDWN_timeout 45     // as above but for volume decrease
00045 // Needed to ensure the motor is not burnt out
00046 #define DEBOUNCE 20000     // this is the switch debounce time
00047 
00048 // PHONO_   1               // these are the input assignments written out
00049 // CD       2               // on select_out - see thePindef1114.h file for details
00050 // TUN      4
00051 // MSERV    8
00052 // AUX      16
00053 // RECORDER 32
00054 
00055 int startbit;
00056 int toggle;                 // this is the 3rd bit position in the input stream and checks for
00057 // subsequent button depresses from the r/control
00058 int toggle1;                // temorary storage in the volume UP and volume DOWN functions
00059 //int toggle2;                // temprary storage of the PB in the mute function
00060 //int toggle3;                // temp storage for the r/control tone in-out function
00061 int standby;
00062 int command = 0;
00063 int vendor_id = 0;
00064 int pair_command = 0;
00065 int address = 0;
00066 int stop_bit = 0;
00067 
00068 int FLAG1;                  // this is used in the remote control input processing
00069 int FLAG2;                  // this is used in the select input processing
00070 int FLAG3;                  // this is for the mute pushbutton
00071 int FLAG4;                  // this is for the standby pushbutton
00072 //int FLAG5;                  // this is the recloop flag
00073 int RCFLAG = FALSE;         // used to determine if the select command came via R/C
00074 int REPEATFLAG;             // repaet command flag used for volume control
00075 int FLAGVOLUP;
00076 int FLAGVOLDWN;
00077 //int FLAG7 = FALSE;          // this flag is set to TRUE if recloop is active
00078 int standbyflag;            // used to save the standby condition
00079 int RECLOOP1 = 16;          // this is the bus address 1 before the Recorder
00080 int RECLOOP2 = 32;          // this is the bus address for the Recorder input - last input
00081 // and is used in the recloop service routine
00082 int muteflag = FALSE;        // use to control mute and mute indicatoe independently
00083 int recloop_status = 0;    // this is the initial value. This variable is used
00084 // in the select_out routine to indicate when the
00085 // input select should wrap around dependent upon
00086 // whether the record loop has been activated.
00087 int relay;
00088 int key_press = 1;          // keeps track of key presses
00089 
00090 // delcarations below are all for the input select proceses
00091 int select = 0;
00092 int select_save = 2;        // we save the status of select drive here. Initial setting is for CD
00093 int select_rot;             // rotary encoder pulse counter
00094 
00095 // declare function prototypes here
00096 void select_out (void);     // writes selected input out to the select_drv bus
00097 void select_isr(void);
00098 void rc5isr(void);          // RC5 ISR for remote control
00099 void mute_isr(void);
00100 void mute_sel(void);        //mutes select relays for a few ms during select
00101 //void recloop_isr(void);
00102 void standby_out(void);
00103 
00104 /****************************** volume increase ***********************************/
00105 void vol_up (void)
00106 {
00107     if ((standbyflag == TRUE) && (key_press < VUP_timeout)) {
00108 
00109         FWD1 = HIGH;
00110         wait_us(100000);           //drive the motors for a short while
00111         FWD1 = LOW;
00112 
00113     }
00114     if (toggle1 != toggle) {
00115         key_press = 0;      // user released the button, so reset counter
00116     } else if (toggle1 == toggle) {
00117         key_press++;        // button remained depressed, so increment counter
00118     }
00119     toggle1 = toggle;
00120 //    wait_us(100000);
00121 }
00122 
00123 /******************************* volume decrease **********************************/
00124 void vol_dwn (void)
00125 {
00126     if ((standbyflag == TRUE) && (key_press < VDWN_timeout)) {
00127 
00128         REV1 = HIGH;
00129         wait_us(100000);           //drive the motors for a short while
00130         REV1 = LOW;
00131     }
00132     if (toggle1 != toggle) {
00133         key_press = 0;      // user released the button, so reset counter
00134     } else if (toggle1 == toggle) {
00135         key_press++;        // button remained depressed, so increment counter
00136     }
00137     toggle1 = toggle;
00138     wait_us(1000);
00139 }
00140 
00141 /********************************** stdby_isr *************************************/
00142 void stdby_isr(void)
00143 {
00144     FLAG4 = TRUE;
00145 }
00146 
00147 /*********************************** standby **************************************/
00148 /* this will require supporting hardware functionality to power down the          */
00149 /* analog board, LED's etc. Best option here is to use regulators with a          */
00150 /* shutdown option. for now,  all the LED's are just turned off                   */
00151 /* and input relays and mute relayes disabled.                                    */
00152 
00153 void standby_out(void)      // both p/button and R/C come in here
00154 {
00155     __disable_irq();
00156     stdby_int.fall(NULL);   // on first power up cycle NO interrupts are accepted
00157     wait_us(DEBOUNCE);       // a very simple debounce
00158     do {                    // that waits for the depressed button to be released
00159         continue; //(1);
00160     } while (stdby != 1);
00161 
00162     if (standbyflag == TRUE) {      // was ON so now turn it OFF
00163         stby_pa = LOW;
00164         wait_us(500000); // make sure the power amp is OFF
00165 //        muteind = LOW;
00166         wait_us(1000000);               // make sure the power amp output goes OFF
00167         muteout = LOW;              // now mute the preamp
00168         // turn off all interrupts except the standby and rc5int
00169         select_int.fall(NULL);
00170 //        mute_int.fall(NULL);
00171 //        recloop_int.fall(NULL);
00172 //        recloop_out = LOW;          // make sure the recloop is OFF [its active HIGH]
00173 //        recloop_status = RECLOOP2;  // reset the select so on subsequent power up it does
00174         //not skip recorder input
00175         select_save = select_drv;   // save the status of select_drv
00176         wait_us(200000);
00177         select_drv = 0;             // all input select relays are OFF
00178         wait_us(3000000);
00179         standbyflag = FALSE;
00180 //        muteind = HIGH;
00181     }
00182 
00183 
00184     else if (standbyflag == FALSE) {// was OFF so we will turn it ON
00185 
00186         muteLED = LOW;              // turn the mute indicator ON
00187         rc5int.rise(&rc5isr);       // trigger int on rising edge - go service it at rc5dat
00188         select_int.fall(&select_isr);   // input from rotary encoder or input select
00189 //        mute_int.fall(&mute_isr);
00190 //        recloop_int.fall(&recloop_isr);
00191 //        tone_pb.fall(tone_isr);
00192 //        recloop_out = LOW;          // make sure the recloop is OFF [its active HIGH]
00193         wait_us(100000);
00194         select_drv = select_save;   // recall the input select setting and write to output
00195         wait_us(2000000);                    // let things settle a bit
00196         muteout = HIGH;             // enable output
00197         muteflag = TRUE;
00198         muteLED = HIGH;             // turn the mute indicator OFF
00199         standbyflag = TRUE;
00200         stby_pa = HIGH;             // now power up the amplifier
00201     }
00202     wait_us(500000);                   // let things settle a bit
00203     __enable_irq();
00204     stdby_int.fall(&stdby_isr);     // re-enable the standby interrupt
00205 
00206 }
00207 
00208 /********************************** record loop isr *******************************/
00209 
00210 //void recloop_isr(void)
00211 //{
00212 //    FLAG5 = TRUE;
00213 //}
00214 /********************************** recloop  ***********************************/
00215 //void recloop()
00216 //{
00217 //
00218 //    if (select_drv != RECLOOP2) {           // if its anything other than recloop we can activate the recloop relay
00219 //        recloop_int.fall(NULL);             // to prevent re-entrance when coming here from the R/C
00220 //        wait_ms(DEBOUNCE);                  // simple debounce for when mute is via the f/p p/b switch
00221 //
00222 //        do {
00223 //            continue;                       // wait here until the button is released
00224 //        } while (recloop_in != 1);
00225 //
00226 //        if (recloop_rly == HIGH) {          // the recloop relay was activated
00227 //            recloop_rly = LOW;              // so turn it off
00228 //            recloop_out = LOW;
00229 //            FLAG7 = 0;
00230 //        }
00231 //
00232 //        else if (recloop_rly == LOW) {      // it was OFF so activate it
00233 //            recloop_rly = HIGH;
00234 //            recloop_out = HIGH;
00235 //            FLAG7 = 32;
00236 //        }
00237 //
00238 //        wait_ms(DEBOUNCE);
00239 //
00240 //    }
00241 //
00242 //    recloop_int.fall(&recloop_isr);
00243 //
00244 //}
00245 /************************************ mute_isr ************************************/
00246 
00247 //void mute_isr(void)
00248 //{
00249 //    FLAG3 = TRUE;
00250 //    toggle2 = !toggle2;         // so the p/button input is recognized in mute_out()
00251 
00252 //}
00253 /************************************** mute  ************************************/
00254 void mute_out()
00255 {
00256     muteout = !muteout;
00257 
00258     if (muteout == HIGH) {
00259         muteLED = LOW;
00260     }
00261 
00262     else if (muteout == LOW) {
00263         muteLED = HIGH;
00264     }
00265 
00266     wait_us(100000);
00267 }
00268 
00269 
00270 
00271 //    mute_int.fall(NULL);        // to prevent re-entance when coming here from the R/C
00272 //    wait_ms(DEBOUNCE);          //simple debounce for when mute is via the f/p p/b switch
00273 //    do {
00274 //        continue;               //wait here until the button is released
00275 //    } while (mute != 1);
00276 //
00277 //    if (muteflag == FALSE) {    // mute was inactive so it will now get activated
00278 //        muteout = TRUE;
00279 //        muteind = HIGH;
00280 //        muteflag = TRUE;        // indicate its been activated
00281 //    }
00282 //
00283 //    else if (muteflag == TRUE) {   //it was active, so it must be deactivated here
00284 //        muteout = FALSE;
00285 //        muteind = LOW;
00286 //        muteflag = FALSE;
00287 //    }
00288 //
00289 //    wait_ms(800);               // make sure relay state is settled
00290 //
00291 //    mute_int.fall(&mute_isr);
00292 //}
00293 //
00294 /************************************ rc5isr **************************************/
00295 /* Interrupt triggered by a rising edge on p18 which is R/C data in               */
00296 
00297 void rc5isr(void)
00298 {
00299     FLAG1 = TRUE;
00300     RCFLAG = TRUE;
00301     REPEATFLAG = TRUE;
00302 }
00303 
00304 /******************* save bit stream from remote controller ***********************/
00305 /* This function reads the input data on pin rc5dat at 1120us ('tock')intervals   */
00306 /* and saves the data into an array stream[i].                                    */
00307 
00308 void save_stream(void) {
00309 
00310     if (RCFLAG == TRUE) {
00311         wait_us(13500); // this is the IR AGC header - wait until passed
00312    //}
00313 
00314     bool stream[63];// the array is initialized each time it is used and is local only
00315     int bitloop = 0;    // number of bit positions - local
00316     int i = 0;      // counter
00317     int k = 0;      // temp storage
00318     vendor_id = 0;
00319     pair_command = 0;
00320     address = 0;
00321     command = 0;
00322     stop_bit = 0; //must always return a 1 to be valid, so reset it
00323     wait_us(tick);   // locate read point in middle of 1st half bit time of the 1st start bit
00324 
00325     for (bitloop = 0; bitloop <64; bitloop ++) {
00326 
00327         stream[bitloop] = rc5dat;  //read the data and save it to array position [i]
00328  //       bitstreamsync = !bitstreamsync; // the recovered IR bitstream is output on p14 for debug
00329         if (rc5dat == HIGH) {
00330             wait_us(tock); // wait for start of next bit time
00331         }
00332    
00333     wait_us(tock); //wait here until ready to read the next bit in
00334    
00335    }     // now have 32 bits loaded into stream[i]
00336 
00337     /* now put data in the array into the start, toggle, address and command variables - array counts from stream[0] */
00338 
00339     for (i=0; i<11; i++) {   // first 11 bit positions are vendor ID
00340 
00341         k = stream[i];      // k will hold the vendor ID
00342         vendor_id = (vendor_id << 1);
00343         vendor_id = vendor_id|k;
00344 
00345     }
00346 
00347     for (i = 11; i <16; i++) {        // command or pair
00348         k = stream[i];
00349         pair_command = (pair_command << 1);
00350         pair_command = pair_command|k;
00351     }
00352 
00353     for (i = 16; i <25; i++) {        // device pairing address
00354         k = stream[i];
00355         address = (address << 1);
00356         address = address|k;
00357     }
00358 
00359 
00360     for (i = 25; i <31; i++) {   // bit positions 25 to 30 are the command - 7 bit positions
00361         k = stream[i];
00362         command = (command << 1);
00363         command = command|k;
00364         printf("\n here \r ");
00365     }
00366     
00367     stop_bit = stream[31];
00368 
00369     printf("\n vendor_id = %d pair_command = %d address = %d command = %d  stop_bit = %d \r", vendor_id, pair_command, address, command, stop_bit);
00370 }
00371 
00372 }
00373 
00374 /********************************* process_stream() *******************************/
00375 /* handles commands coming in from the remote controller only                     */
00376 
00377 void process_stream (void)
00378 {
00379     if ((RCFLAG == TRUE) && ((vendor_id == 479) || (vendor_id == 2047)))  {
00380 // basic error checking - must be preamp + startbit ok to get executed otherwise skip completly
00381         switch (address) {
00382 
00383             case VUP:
00384                 vol_up();
00385                 FLAGVOLUP = TRUE;
00386                 break;
00387 
00388             case VDOWN:
00389                 vol_dwn();
00390                 FLAGVOLDWN = TRUE;
00391                 break;
00392 
00393             case MUTE:
00394                 mute_out();
00395                 break;
00396 
00397             case SELECT_R:
00398                 select_out();
00399                 break;
00400 
00401             case SELECT_L:
00402                 select_out();
00403                 break;
00404 
00405             case STANDBY:
00406                 standby_out();
00407                 break;
00408 
00409         }
00410 
00411         if ((FLAGVOLUP == TRUE) && (vendor_id == 2047))  {
00412             vol_up();
00413         }
00414 
00415         if ((FLAGVOLDWN == TRUE) && (vendor_id ==2047))   {
00416             vol_dwn();
00417         }
00418 
00419     }
00420     RCFLAG = FALSE;
00421 
00422 }
00423 /*********************************** select_isr ***********************************/
00424 
00425 void select_isr(void)
00426 {
00427     FLAG2 = TRUE;
00428 }
00429 
00430 /****************************** mute inter select*********************************/
00431 
00432 //void mute_sel(void)
00433 //{
00434 //    select_drv = 0;
00435 //    wait_ms(2);
00436 //}
00437 
00438 /********************************* select_process *********************************/
00439 /* Used for selecting the input source.  This function is used by the             */
00440 /* rotary encoder only                                          */
00441 
00442 void select_process(void)
00443 {
00444 
00445     if (RCFLAG == FALSE) {  // if used R/C skip completely - extra safety check
00446         wait_us(5000); // debounce - very short for the rotary encoder
00447         select = 0; // flush select
00448 
00449         select = (select | sela) <<1; // read the two port lines associated with the select rotary encoder
00450         select = (select | selb);
00451 
00452 
00453         switch (select) {
00454             case 1:                 // select encoder is being rotated CW so increment select_rot
00455                 select_rot <<= 1;
00456                 if (select_rot > recloop_status ) {
00457                     select_rot = 1;   // wrap around to 1
00458                 }
00459 
00460                 break;
00461 
00462             case 0:
00463                 select_rot >>= 1;   // encoder is being rotated CCW so decrement select_rot
00464                 if (select_rot < 1) {
00465                     select_rot = recloop_status; //wrap around to 32
00466                 }
00467 
00468                 break;
00469 
00470             case 2:
00471                 break;   // indeterminate fall through values - ignore
00472             case 3:
00473                 break;   // and do not change the output
00474         }
00475     }
00476 
00477 //   select_rot = (select_rot | FLAG7);
00478     select_drv = select_rot;   // write the value out to the bus
00479 
00480     // printf("\n RCFLAG %d \r", RCFLAG);
00481 }
00482 
00483 
00484 
00485 
00486 /********************************* select_out *********************************/
00487 // this is only  used by the IR remote
00488 
00489 void select_out (void)
00490 {
00491 
00492     if (address == SELECT_L) {
00493         select_rot >>= 1;
00494         if (select_rot <1) {
00495             select_rot = 32;
00496         }
00497     }
00498 
00499 
00500     if (address == SELECT_R) {
00501         select_rot <<= 1;
00502         if (select_rot >32) {
00503             select_rot = 1;
00504         }
00505 
00506     }
00507 
00508     select_drv = select_rot;
00509 
00510 //    select_drv = (select_rot | FLAG7);   //write the selection out to the bus.
00511 
00512     printf("\n select_rot = %d     \r", select_rot);
00513 
00514 }
00515 
00516 /************************************ main() ***************************************/
00517 int main(void)
00518 {
00519 //    Serial pc(USBTX, USBRX);
00520     __disable_irq();            // just to make sure we can set up correctly without problems
00521     stby_pa = LOW;              // make sure the power amp is OFF via the trigger output
00522     muteout = LOW;              //make sure the outputis muted from the get go
00523 //    muteind  = HIGH;             //mute LED must be ON - power up preamble
00524     select_drv = 0;             // no input relays must be on
00525  //   bitstreamsync = LOW;        // this is the IR bitsteeam output on pin 14 for debug
00526     //  recloop_out = LOW;          // make sure the recloop LED is OFF [its active HIGH]
00527     //  recloop_rly = LOW;          // make sure the recloop relay is OFF
00528 
00529 //    mute_int.mode(PullUp);      // set up all the pin states per Pindef1114.h
00530     rc5dat.mode(PullUp);        // pin 17
00531     sela.mode(PullUp);          // pin 28
00532     selb.mode(PullUp);          // pin 27
00533     stdby.mode(PullUp);         // pin 26
00534     //  recloop_in.mode(PullUp);    // pin 14
00535 
00536     wait_us(200000);
00537     FLAG1 = FALSE;
00538     FLAG2 = FALSE;
00539 //    FLAG5 = FALSE;              // this is the recloop flag
00540     FWD1=0;                     //make sure the volume control motor is OFF
00541     REV1=0;
00542 
00543     // set up the ISR's that will be used
00544     rc5int.fall(&rc5isr);               // trigger int on falling edge - go service it at rc5dat
00545     select_int.fall(&select_isr);       // input from rotary encoder or input select
00546 //    mute_int.fall(&mute_isr);           // mute push button interrupt
00547 //   recloop_int.fall(&recloop_isr);     // record loop push button interrupt
00548     stdby_int.fall(&stdby_isr);         // the system power/standby switch - on sel rotenc
00549 
00550     //now disable them, leaving only the stand by p/button and rc5int interrupts active
00551     select_int.fall(NULL);
00552 //    mute_int.fall(NULL);
00553 //    recloop_int.fall(NULL);
00554 
00555     standbyflag = TRUE;                 // preamp will be set-up first time for OFF
00556     standby_out();                      // set system up
00557     standbyflag = FALSE;
00558     select_save = 2;                    // CD always slected on initital power up
00559     select_rot = select_save;          // CD will be selected when power is first turned on
00560     wait_us(3000000);
00561 //    muteind = LOW;
00562     __enable_irq();
00563 
00564 // all ready and in standby from this point forward
00565 
00566 
00567 LOOP:                                   // this is the main operating loop
00568 
00569     __WFI();                            // wait here until interrupt
00570 
00571     if (FLAG1 == TRUE) {                // FLAG1 indicates remote control was used
00572         save_stream();
00573         process_stream();
00574 
00575         FLAG1 = FALSE;
00576     }
00577 
00578     if (FLAG2 == TRUE) {
00579         select_process();               //select process
00580         FLAG2 = FALSE;
00581     }
00582 
00583     if (FLAG3 == TRUE) {
00584         mute_out();                     //mute
00585         FLAG3 = FALSE;
00586     }
00587 
00588     if (FLAG4 == TRUE) {                // standby ON/OFF
00589         standby_out();
00590         FLAG4 = FALSE;
00591     }
00592 
00593 //    if (FLAG5 == TRUE) {
00594 //        recloop();                      //recloop
00595 //        FLAG5 = FALSE;
00596 //    }
00597 
00598     //printf("\r Command = %d     Address = %d      \n",command, address);
00599 
00600     goto LOOP;
00601 
00602 }
00603 
00604 
00605 
00606