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:
17 months ago
Revision:
10:7a93d34a419a
Parent:
9:c9fb1f8e2ab8

File content as of revision 10:7a93d34a419a:

/****************************** 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 "apple_codes.h"      // remote codes 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;

}