/* * "SPDIF-Master(tm)" * * An open-source arduino controller-based spdif input selector * * Author: Bryan Levin (linux-works) * * Revision: v1.11 */ #define SplashScrnTime 3 // Splash Screen display time, seconds const char Title[] = { "LinuxWorks Labs "}; const char Version[] = { "SPDIFmaster 1.11"}; #include #include #include #include #include #include #include #include /* * IR key scans for sony remotes */ #define KEYSCAN_COAX1 134 // keypad 7 #define KEYSCAN_TOSLINK1 135 // keypad 8 #define KEYSCAN_TOSLINK2 136 // keypad 9 #define KEYSCAN_TOSLINK3 137 // keypad 0 #define KEYSCAN_ROTATE_THRU_INPUTS 1379 // subtitle #define KEYSCAN_POWER 149 #define KEYSCAN_ARROW_UP 1401 #define KEYSCAN_ARROW_DOWN 1402 #define KEYSCAN_ARROW_LEFT 1403 #define KEYSCAN_ARROW_RIGHT 1404 #define KEYSCAN_ARROW_ENTER 1291 // IR decoding #define start_bit 2000 // Start bit threshold (Microseconds) #define bin_1 1000 // Binary 1 threshold (Microseconds) #define bin_0 400 // Binary 0 threshold (Microseconds) #define PULSE_DUR 1000 #define PULSE_2200 2200 #define SONY_IR_PROTO_PULSE_TRAIN_LEN 11 /* * arduino (not atmel!) pins for physical computing i/o */ // IR receiver (required) #define IR_PIN 8 // Sensor pin 1 wired direct // LCD backlight (optional) #define PWM_BACKLIGHT_PIN 9 // standard status led for arduino (optional) #define LED_PIN 13 // input selector relays or TTL signals (toslink, coax, analog) (required) #define RELAY_INPUT_SEL1_PIN 14 #define RELAY_INPUT_SEL2_PIN 15 #define LCD_DB4_PIN 2 // hitachi pin 11 #define LCD_DB5_PIN 3 // hitachi pin 12 #define LCD_DB6_PIN 4 // hitachi pin 13 #define LCD_DB7_PIN 5 // hitachi pin 14 #define LCD_RS_PIN 6 // hitachi pin 4 ('register select') #define LCD_ENABLE_PIN 7 // hitachi pin 6 ('E') // LiquidCrystal display with: // rs on pin 2 // (rw on pin DUMMY) // enable on pin 3 // d4, d5, d6, d7 on pins 4, 5, 6, 7 //LiquidCrystal lcd(2, 3, 3, 4, 5, 6, 7); LiquidCrystal lcd(LCD_RS_PIN, 0, LCD_ENABLE_PIN, LCD_DB4_PIN, LCD_DB5_PIN, LCD_DB6_PIN, LCD_DB7_PIN); // input selector { enum => relay } matrix mapping #define MAX_INPUTS 4 struct _input_sel_mapping { char short_name[17]; char long_name[17]; byte relay1; byte relay2; byte relay3; } input_sel_mapping[MAX_INPUTS] = { { "1. [Coax 1]", "Popcorn Hour ", 1, 0, 1 } , { "2. [Opto 1]", "PC (gamma1-lite)", 0, 0, 0 } , { "3. [Opto 2]", "Tivo ", 0, 1, 0 } , { "4. [Opto 3]", "Toslink 3 ", 1, 1, 1 } }; /* * eeprom locations (slot numbers in eeprom address space) */ #define EEPROM_MAGIC 0 #define EEPROM_POWER 1 #define EEPROM_INPUT_SEL 2 #define EEPROM_BACKLIGHT_LEVEL 3 // current working level #define EEPROM_BACKLIGHT_MIN 4 // our 'dimest' setting #define EEPROM_BACKLIGHT_MAX 5 // our 'brightest' setting // decode the value of eeprom[ EEPROM_INPUT_SEL ] #define INPUT_SEL_COAX1 0 #define INPUT_SEL_TOSLINK1 1 #define INPUT_SEL_TOSLINK2 2 #define INPUT_SEL_TOSLINK3 3 #define DEFAULT_BL_LEVEL 255 /* * globals (that may init from EEPROM at power-up) */ byte power; byte backlight_currently_on=1; // boolean byte backlight_level=DEFAULT_BL_LEVEL; // color-independant 'intensity' byte input_selector=0; byte toplevel_mode=0; // 0=main 'use' mode, 1=edit_vol_window mode /* * lcd physical constants */ #define LCD_PHYS_ROWS 16 #define LCD_PHYS_LINES 2 // char cell 'memory map' locations for start of line1 and line2 on the lcd #define LCD_CURS_POS_L1_HOME 0x80 #define LCD_CURS_POS_L2_HOME 0xC0 #define LCD_CURS_POS_L3_HOME 0x94 #define LCD_CURS_POS_L4_HOME 0xD4 /*********************************** * start of main C code * **********************************/ void setup() { delay(200); // Let everything wake up /* * startup the core function of our system */ init_system(); } void loop() { while(1) { top_scan(); } } void draw_selector_string() { lcd.clear(); // input name (short) LCD_send_string(input_sel_mapping[input_selector].short_name, LCD_CURS_POS_L1_HOME); // input name (long) LCD_send_string(input_sel_mapping[input_selector].long_name, LCD_CURS_POS_L2_HOME); } void do_relay_Input_selection() { // TTL logic pin1 (A0) if (input_sel_mapping[input_selector].relay1 == 1) { digitalWrite(RELAY_INPUT_SEL1_PIN, HIGH); } else { digitalWrite(RELAY_INPUT_SEL1_PIN, LOW); } // TTL logic pin2 (A1) if (input_sel_mapping[input_selector].relay2 == 1) { digitalWrite(RELAY_INPUT_SEL2_PIN, HIGH); } else { digitalWrite(RELAY_INPUT_SEL2_PIN, LOW); } #if 0 // TTL logic pin3 (A2) if (input_sel_mapping[input_selector].relay3 == 1) { digitalWrite(RELAY_INPUT_SEL3_PIN, HIGH); } else { digitalWrite(RELAY_INPUT_SEL3_PIN, LOW); } #endif } void change_input_selector(byte flag) { // update the input/output selector string names draw_selector_string(); if (flag != 0) { // save the new input selector in EEPROM EEwrite(EEPROM_INPUT_SEL, input_selector); } // engage the right relays based on this new input-selector do_relay_Input_selection(); } void top_scan() { //scan_front_panel_buttons(); // TBD /* * IR keyscan and case-statement routine */ if (toplevel_mode == 0) { // main 'everyday use' mode handle_IR_keys_normal_mode(); } else if (toplevel_mode == 2) { // backlight adjust if (power == 1) { // only allow this if power is on handle_IR_keys_edit_backlight_mode(); } } } void handle_IR_keys_normal_mode() { byte ms,ls; int key; /* * we got a valid IR start pulse! fetch the keycode, now. */ key = get_IR_key(); switch (key) { // no key pressed - quick return case 0: case -1: return; /* * 'immediate mode' method of adjusting brightness (backlight) via 'alternate arrow keys' */ case 1328: // previous if (power == 0) return; // not allowed if power is off if (backlight_level <= 1) { // our lower boundary, don't let user go below this! break; } else if (backlight_level <= 10) { // smaller steps at the extremes backlight_level--; } else if (backlight_level < (255-10)) { // larger steps in the middle backlight_level -= 2; } else { // smaller steps at the extremes backlight_level--; } analogWrite(PWM_BACKLIGHT_PIN, backlight_level); EEwrite(EEPROM_BACKLIGHT_LEVEL, backlight_level); delay(50); //Debounce switch break; case 1329: // next if (power == 0) return; // not allowed if power is off if (backlight_level > 254) { // our upper boundary, don't let user go above this! break; } else if (backlight_level <= 10) { // smaller steps at the extremes backlight_level++; } else if ( backlight_level < (255-10) ) { // larger steps in the middle backlight_level += 2; } else { // smaller steps at the extremes backlight_level++; } analogWrite(PWM_BACKLIGHT_PIN, backlight_level); EEwrite(EEPROM_BACKLIGHT_LEVEL, backlight_level); delay(50); //Debounce switch break; /* * toggle (cycle thru) the backlight display modes */ case 1364: // display if (power == 0) return; // not allowed if power is off if (backlight_currently_on == 1) { for (int bl=backlight_level; bl>=0; bl--) { analogWrite(PWM_BACKLIGHT_PIN, bl); // temporarily turn backlight entirely off (or go to its lowest 'low' setting) delay(2); } backlight_currently_on = 0; // flag it as now off } else { for (int bl=0; bl<=backlight_level; bl++) { analogWrite(PWM_BACKLIGHT_PIN, bl); // restore to normal brightness again delay(2); } backlight_currently_on = 1; // flag it as now on } delay(500); //Debounce switch break; case KEYSCAN_ROTATE_THRU_INPUTS: if (power == 0) return; // not allowed if power is off /* * rotate thru the values, circling back at the end */ if (input_selector == INPUT_SEL_COAX1) input_selector = INPUT_SEL_TOSLINK1; else if (input_selector == INPUT_SEL_TOSLINK1) input_selector = INPUT_SEL_TOSLINK2; else if (input_selector == INPUT_SEL_TOSLINK2) input_selector = INPUT_SEL_TOSLINK3; else if (input_selector == INPUT_SEL_TOSLINK3) input_selector = INPUT_SEL_COAX1; change_input_selector(1); delay(500); //Debounce switch break; /* * input selectors (restore last-used vol setting for THAT input) */ case KEYSCAN_COAX1: if (power == 0) return; // not allowed if power is off input_selector = INPUT_SEL_COAX1; change_input_selector(1); delay(500); //Debounce switch break; case KEYSCAN_TOSLINK1: if (power == 0) return; // not allowed if power is off input_selector = INPUT_SEL_TOSLINK1; change_input_selector(1); delay(500); //Debounce switch break; case KEYSCAN_TOSLINK2: if (power == 0) return; // not allowed if power is off input_selector = INPUT_SEL_TOSLINK2; change_input_selector(1); delay(500); //Debounce switch break; case KEYSCAN_TOSLINK3: if (power == 0) return; // not allowed if power is off input_selector = INPUT_SEL_TOSLINK3; change_input_selector(1); delay(500); //Debounce switch break; /* * power button */ case KEYSCAN_POWER: if (power == 0) { // power was in the 'off' or 'standby' state power = 1; // we're not power=on EEwrite(EEPROM_POWER, power); toplevel_mode = 0; //backlight_currently_on = 1; // flag it as now on LCD_turn_display_on(); analogWrite(PWM_BACKLIGHT_PIN, backlight_level); // restore to normal brightness again signon_msg(); if (backlight_currently_on == 0) { analogWrite(PWM_BACKLIGHT_PIN, 0); // respect user's backlight 'display' setting } change_input_selector(1); // power was ON when user pressed the power button } else { // save the new input selector in EEPROM EEwrite(EEPROM_INPUT_SEL, input_selector); analogWrite(PWM_BACKLIGHT_PIN, backlight_level); // restore to normal brightness again lcd.clear(); LCD_send_string("(Powering Off)", LCD_CURS_POS_L1_HOME); analogWrite(PWM_BACKLIGHT_PIN, 0); // respect user's backlight 'display' setting backlight_currently_on = 1; // flag it as now on for when we finally power-up again power = 0; EEwrite(EEPROM_POWER, power); lcd.clear(); LCD_turn_display_off(); } delay(500); break; /* * go from 'main use mode' to 'edit backlight' mode */ case 1333: /* shuffle */ // 1381 'angle' button if (power == 0) return; // not allowed if power is off toplevel_mode = 2; lcd.clear(); //LCD_cgram_load_boxed_bargraph(); LCD_send_string("Brightness", LCD_CURS_POS_L1_HOME); draw_backlight_labels(); delay(500); // Debounce switch break; } } int get_IR_key() { int data[SONY_IR_PROTO_PULSE_TRAIN_LEN+1]; byte i; int result = 0; int seed = 1; if (pulseIn(IR_PIN, LOW, PULSE_DUR) < PULSE_2200) { return -1; } for (i=0; i bin_1) { // is it a 1? data[i] = 1; } else { if (data[i] > bin_0) { // is it a 0? data[i] = 0; } else { data[i] = 2; // Flag the data as invalid; I don't know what it is! } } } for (i=0; i 1) { return -1; // Return -1 on invalid data } } for (i=0; i> 4) + '0'; *ls = (val & 0x0f) + '0'; } void bin2ascii(const byte val, byte* ms, byte* ls) { *ms = val / 10 + '0'; *ls = val % 10 + '0'; } byte bcd2bin(const byte val) { return (((val >> 4) * 10) + (val & 0x0f)); } byte bin2bcd(const byte val) { return ((val / 10 * 16) + (val % 10)); } // ==================== LCD Functions =====================================// void clear_line(byte l_num) { if (l_num == 1) { lcd.command(LCD_CURS_POS_L1_HOME); } else { lcd.command(LCD_CURS_POS_L2_HOME); } for (int i=0; i 0, or cursor position if addr == 0 if (addr != 0) { lcd.command(addr); } byte i = 0; while (str[i] != 0) { lcd.write(str[i++]); } } void draw_backlight_labels() { update_backlight_level(); } void update_backlight_level() { byte ls, ms; lcd.command(LCD_CURS_POS_L2_HOME+0); // red bin2ascii(backlight_level / 100, &ms, &ls); //lcd.write(ms); lcd.write(ls); bin2ascii(backlight_level % 100, &ms, &ls); lcd.write(ms); lcd.write(ls); analogWrite(PWM_BACKLIGHT_PIN, backlight_level); } void handle_IR_keys_edit_backlight_mode() { byte ms,ls; int key=0; /* * we got a valid IR start pulse! fetch the keycode, now. */ key = get_IR_key(); switch (key) { // no key pressed - quick return case 0: case -1: return; case 1314: // scan_left if (backlight_level <= 1) { // our lower boundary, don't let user go below this! break; } else if (backlight_level <= 10) { // smaller steps at the extremes backlight_level--; } else if (backlight_level < (255-10)) { // larger steps in the middle backlight_level -= 2; } else { // smaller steps at the extremes backlight_level--; } update_backlight_level(); delay(50); //Debounce switch break; case 1315: // slow_right if (backlight_level >= 255) { // our upper boundary, don't let user go above this! break; } else if (backlight_level <= 10) { // smaller steps at the extremes backlight_level++; } else if ( backlight_level < (255-10) ) { // larger steps in the middle backlight_level += 2; // 8 } else { // smaller steps at the extremes backlight_level++; } update_backlight_level(); delay(50); //Debounce switch break; case 1381: // 'angle' button case 1294: // 'return' case KEYSCAN_ARROW_ENTER: toplevel_mode = 0; // we're done in this edit mode // save our working values to EEPROM EEwrite(EEPROM_BACKLIGHT_LEVEL, backlight_level); /* * redraw the screen in '0' (main) mode */ if (backlight_currently_on == 0) { // if BL was turned off via 'display' then leave it off before we return! analogWrite(PWM_BACKLIGHT_PIN, 0); } // line1 clear_L1(); draw_selector_string(); delay(200); //Debounce switch break; } // switch } // end sketch