Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
854 lines (753 sloc) 26.5 KB
/*******************************************************************************
* This file is part of MegaDrive++. *
* *
* Copyright (C) 2015-2016 by SukkoPera <software@sukkology.net> *
* *
* MegaDrive++ is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* MegaDrive++ is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with MegaDrive++. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
*
* MegaDrive++ - Universal Region mod, 50/60 Hz switch and In-Game-Reset (IGR)
* for Sega Mega Drive (AKA Genesis)
*
* Please refer to the GitHub page and wiki for any information:
* https://github.com/SukkoPera/MegaDrivePlusPlus
*/
/*******************************************************************************
* PLATFORM SELECTION
******************************************************************************/
// Check if we should disable some features because of low flash space
#if FLASHEND < 2048
/* We only have 2 kb flash, let's take special measures:
* - Only use a single flashing led to signal current mode
* - On ATtiny24 we also always save video mode when changed, without
* checking if it has actually changed: this will wear out EEPROM a bit
* more quickly but it will still take ages ;)
*/
#warning Low flash space mode enabled
#define LOW_FLASH
#endif
#if defined __AVR_ATtinyX5__
/*
* On ATtinyX5's we only support Reset-From-Pad.
* ,-----_-----.
* |1 (5) 8| +5V
* Reset In |2 3 2 7| Pad Port Pin 7
* Reset Out |3 4 1 6| Pad Port Pin 9
* GND |4 0 5| Pad Port Pin 6
* `-----------'
*/
#define RESET_IN_PIN 3
#define RESET_OUT_PIN 4
#elif defined __AVR_ATtinyX4__
/*
* On ATtinyX4's most features are supported. The only exception is that RIGHT
* and LEFT cannot be used in combos.
*
* The connection layout is derived from that of the Seb/D4s mod, so that if you
* already have a socket wired properly in you console, you will just need to
* add a few wires and replace the chip to get the new features. The wires to be
* added are all those coming from the controller pad port.
*
* ,-----_-----.
* +5V |1 14| GND
* Pad Port Pin 1 |2 0 10 13| Reset In
* Pad Port Pin 2 |3 1 9 12| Pad Port Pin 6
* |4 (11) 8 11| Pad Port Pin 9
* LED Red |5 2 7 10| JP1/2 (Language)
* LED Green |6 3 6 9| JP3/4 (Video Mode)
* Pad Port Pin 7 |7 4 5 8| Reset Out
* `-----------'
*/
#define RESET_IN_PIN 10
#define RESET_OUT_PIN 5
#define VIDEOMODE_PIN 6
#define LANGUAGE_PIN 7
#ifdef LOW_FLASH
#define MODE_LED_SINGLE_PIN 2
#else
#define MODE_LED_R_PIN 2
#define MODE_LED_G_PIN 3
// No blue pin!
#endif
#elif defined __AVR_ATtinyX61__
/*
* On ATtinyX61's all features are supported. We even read all buttons with a
* single instruction.
*
* The connection layout puts the SELECT signal on the INT1 pin. This will
* probably be needed if we ever want to read 6-button pads. LED is connected to
* PWM-capable pins.
*
* ,-----_-----.
* Reset In |1 9 0 20| Pad Port Pin 1
* LED Red |2 8 1 19| Pad Port Pin 2
* Reset Out |3 7 2 18| Pad Port Pin 7
* LED Green |4 6 14 17| Pad Port Pin 3
* +5V |5 16| GND
* GND |6 15| +5V
* JP3/4 (Video Mode) |7 5 10 14| Pad Port Pin 4
* LED Blue |8 4 11 13| Pad Port Pin 6
* JP1/2 (Language) |9 3 12 12| Pad Port Pin 9
* |10(15)13 11|
* `-----------'
*/
#define RESET_IN_PIN 9
#define RESET_OUT_PIN 7
#define VIDEOMODE_PIN 5
#define LANGUAGE_PIN 3
#ifdef LOW_FLASH
#define MODE_LED_SINGLE_PIN 8
#else
#define MODE_LED_R_PIN 8
#define MODE_LED_G_PIN 6
#define MODE_LED_B_PIN 4
#endif
#elif defined __AVR_ATtinyX313__
/*
* On ATtinyX13's all features are supported. We even read all buttons with a
* single instruction.
*
* Again, the connection layout puts the SELECT signal on the INT1 pin. LED is
* connected to PWM-capable pins.
*
* ,-----_-----.
* |1 (17) 20| +5V
* Pad Port Pin 1 |2 0 16 19| JP3/4 (Video Mode)
* Pad Port Pin 2 |3 1 15 18| JP1/2 (Language)
* |4 2 14 17| Reset Out
* |5 3 13 16| Reset In
* Pad Port Pin 7 |6 4 12 15| LED Blue
* Pad Port Pin 3 |7 5 11 14| LED Green
* Pad Port Pin 4 |8 6 10 13| LED Red
* Pad Port Pin 6 |9 7 9 12|
* GND |10(15) 8 11| Pad Port Pin 9
* `-----------'
*/
#define RESET_IN_PIN 13
#define RESET_OUT_PIN 14
#define VIDEOMODE_PIN 16
#define LANGUAGE_PIN 15
#ifdef LOW_FLASH
#define MODE_LED_SINGLE_PIN 10
#else
#define MODE_LED_R_PIN 10
#define MODE_LED_G_PIN 11
#define MODE_LED_B_PIN 12
#endif
#elif defined __AVR_ATmega328__ || defined __AVR_ATmega328P__ || defined __AVR_ATmega168__
/*
* Arduino Uno/Nano/Micro/Whatever, use a convenience #define till we come up
* with something better
*/
#define ARDUINO328
/*
* On an full Arduino board all features are supported. Unfortunately, there is
* no port fully available, so we resort again to reading UP and DOWN from a
* different port. Technically we could use PORTD, but since working on a full
* Arduino board is mainly useful to get debugging messages through the serial
* port, we don't do that (PD0 and PD1 are used by the hardware serial port).
* But if you put a single ATmega328 on a board and use its internal clock you
* also get PORTB, so we might support that in the future. On a side note, PORTD
* also has INT1 on pin2, so we could easily use the X61 read function...
*
* ,-----_-----.
* |1 A5 28| JP1/2 (Language)
* |2 0 A4 27| JP3/4 (Video Mode)
* |3 1 A3 26| Reset In
* Pad Port Pin 7 |4 2 A2 25| Reset Out
* Pad Port Pin 3 |5 3 A1 24| Pad Port Pin 2
* Pad Port Pin 4 |6 4 A0 23| Pad Port Pin 1
* +5V |7 22| GND
* GND |8 21| +5V
* |9 20| +5V
* |10 13 19| (Built-in LED)
* Pad Port Pin 6 |11 5 12 18|
* Pad Port Pin 9 |12 6 11 17| LED Blue
* |13 7 10 16| LED Green
* |14 8 9 15| LED Red
* `-----------'
*/
#define RESET_IN_PIN A3
#define RESET_OUT_PIN A2
#define VIDEOMODE_PIN A4
#define LANGUAGE_PIN A5
#define MODE_LED_R_PIN 9
#define MODE_LED_G_PIN 10
#define MODE_LED_B_PIN 11
#define PAD_LED_PIN LED_BUILTIN
#define ENABLE_SERIAL_DEBUG
#else
#error "Unsupported platform!"
#endif
/*******************************************************************************
* BUTTON COMBO SETTINGS
******************************************************************************/
/* DON'T TOUCH THIS! Just look at it for the button names you can use below!
*
* Technical note: This has been organized (together with the controller port
* wiring) to minimize bit twiddling in the controller reading function.
*/
enum PadButton {
MD_BTN_START = 1 << 7,
MD_BTN_A = 1 << 6,
MD_BTN_C = 1 << 5,
MD_BTN_B = 1 << 4,
MD_BTN_RIGHT = 1 << 3,
MD_BTN_LEFT = 1 << 2,
MD_BTN_DOWN = 1 << 1,
MD_BTN_UP = 1 << 0
};
/* Button combo that enables the other combos
*
* Note: That vertical bar ("pipe") means that the buttons must be pressed
* together.
*/
#define TRIGGER_COMBO (MD_BTN_START | MD_BTN_B)
/* Button combos to perform other actions. These are to be considered in
* addition to TRIGGER_COMBO.
*
* Note that we cannot detect certain buttons on some platforms
*/
#define RESET_COMBO (MD_BTN_A | MD_BTN_C)
#if defined __AVR_ATtinyX4__
/* On ATtinyX4's we can't use LEFT and RIGHT, so just use UP and DOWN to
* cycle through modes
*/
#define NEXT_MODE_COMBO MD_BTN_DOWN
#define PREV_MODE_COMBO MD_BTN_UP
#elif defined __AVR_ATtinyX61__ || defined __AVR_ATtinyX313__ || defined ARDUINO328
/* On ATtinyX61's, ATtinyX313's and Arduinos we can detect all buttons, so we
* can make up a specific combo for every mode that switches straight to it,
* no need to cycle among modes.
*/
#define EUR_COMBO MD_BTN_DOWN
#define USA_COMBO MD_BTN_RIGHT
#define JAP_COMBO MD_BTN_LEFT
#endif
/*******************************************************************************
* ADVANCED SETTINGS
******************************************************************************/
#if !defined __AVR_ATtinyX5__
/* Offset in the EEPROM at which the current mode should be saved. Undefine to
* disable mode saving.
*/
#define MODE_ROM_OFFSET 42
// Time to wait after mode change before saving the new mode (milliseconds)
#define MODE_SAVE_DELAY 5000L
// Force the reset line level when active. Undefine to enable auto-detection.
//#define FORCE_RESET_ACTIVE_LEVEL LOW
#endif // !__AVR_ATtinyX5__
/* Colors to use to indicate the video mode, in 8-bit RGB componentes. You can
* use any value here if your led is connected to PWM-capable pins, otherwise
* values specified here will be interpreted as either fully off (if 0) or fully
* on (if anything else).
*
* Note that using PWM-values here sometimes causes unpredictable problems. This
* happened to me on an ATtiny861, and it's probably due to how pins and timers
* interact. It seems to work fine on a full Arduino, but unless you really want
* weird colors, use only 0x00 and 0xFF.
*
* Oh, and good luck trying to fit a 5mm RGB led in the MegaDrive ;).
*/
#if defined __AVR_ATtinyX4__
/* We only have two LED pins, so let's use a dual-color led and stick to the
* D4s/Seb colors
*/
#define MODE_LED_EUR_COLOR {0x00, 0xFF} // Green
#define MODE_LED_USA_COLOR {0xFF, 0xFF} // Orange
#define MODE_LED_JAP_COLOR {0xFF, 0x00} // Red
#elif !defined __AVR_ATtinyX5__
#define MODE_LED_EUR_COLOR {0x00, 0xFF, 0x00} // Green
#define MODE_LED_USA_COLOR {0x00, 0x00, 0xFF} // Blue
#define MODE_LED_JAP_COLOR {0xFF, 0x00, 0x00} // Red
#endif
// Define this if your led is common-anode, comment out for common-cathode
//#define MODE_LED_COMMON_ANODE
/* Use a single led to indicate the video mode. This is enabled automatically
* in place of the RGB led when low flash space is detected, but since this
* does NOT disable the RGB led, it can be used together with it, provided that
* you have a free pin.
*
* Basically, the single led is blinked 1-3 times according to which mode is set
* (1 is EUR, see enum VideoMode below).
*/
//#define MODE_LED_SINGLE_PIN 3
/* Presses of the reset button longer than this amount of milliseconds will
* switch to the next mode, shorter presses will reset the console.
*/
#define LONGPRESS_LEN 700
// Debounce duration for the reset button
#define DEBOUNCE_MS 20
// Duration of the reset pulse (milliseconds)
#define RESET_LEN 350
/*******************************************************************************
* END OF SETTINGS
******************************************************************************/
#ifdef ENABLE_SERIAL_DEBUG
#define debug(...) Serial.print (__VA_ARGS__)
#define debugln(...) Serial.println (__VA_ARGS__)
#else
#define debug(...)
#define debugln(...)
#endif
#ifdef MODE_ROM_OFFSET
#include <EEPROM.h>
#endif
enum VideoMode {
EUR,
USA,
JAP,
MODES_NO // Leave at end
};
// This will be handy
#if (defined MODE_LED_R_PIN || defined MODE_LED_G_PIN || defined MODE_LED_B_PIN)
#define ENABLE_MODE_LED_RGB
byte mode_led_colors[][MODES_NO] = {
MODE_LED_EUR_COLOR,
MODE_LED_USA_COLOR,
MODE_LED_JAP_COLOR
};
#endif
VideoMode current_mode;
unsigned long mode_last_changed_time;
// Reset level when NOT ACTIVE
byte reset_inactive_level;
void save_mode () {
#ifdef MODE_ROM_OFFSET
if (mode_last_changed_time > 0 && millis () - mode_last_changed_time >= MODE_SAVE_DELAY) {
debug ("Saving video mode to EEPROM: ");
debugln (current_mode);
#if !defined LOW_FLASH || !defined __AVR_ATtinyX4__
byte saved_mode = EEPROM.read (MODE_ROM_OFFSET);
if (current_mode != saved_mode) {
#endif
EEPROM.write (MODE_ROM_OFFSET, static_cast<byte> (current_mode));
#if !defined LOW_FLASH || !defined __AVR_ATtinyX4__
} else {
debugln ("Mode unchanged, not saving");
}
#endif
mode_last_changed_time = 0; // Don't save again
// Blink led to tell the user that mode was saved
#ifdef ENABLE_MODE_LED_RGB
byte c = 0;
#ifdef RGB_LED_COMMON_ANODE
c = 255 - c;
#endif
#ifdef MODE_LED_R_PIN
analogWrite (MODE_LED_R_PIN, c);
#endif
#ifdef MODE_LED_G_PIN
analogWrite (MODE_LED_G_PIN, c);
#endif
#ifdef MODE_LED_B_PIN
analogWrite (MODE_LED_B_PIN, c);
#endif
// Keep off for a bit
delay (200);
// Turn led back on
update_mode_leds ();
#endif // ENABLE_MODE_LED_RGB
#ifdef MODE_LED_SINGLE_PIN
// Make one long flash
digitalWrite (MODE_LED_SINGLE_PIN, LOW);
delay (500);
digitalWrite (MODE_LED_SINGLE_PIN, HIGH);
#endif
}
#endif // MODE_ROM_OFFSET
}
#if !defined __AVR_ATtinyX5__
void change_mode (int increment) {
// This also loops in [0, MODES_NO) backwards
VideoMode new_mode = static_cast<VideoMode> ((current_mode + increment + MODES_NO) % MODES_NO);
set_mode (new_mode);
}
void next_mode () {
change_mode (+1);
}
void prev_mode () {
change_mode (-1);
}
void update_mode_leds () {
#ifdef ENABLE_MODE_LED_RGB
byte *colors = mode_led_colors[current_mode];
byte c;
#ifdef MODE_LED_R_PIN
c = colors[0];
#ifdef MODE_LED_COMMON_ANODE
c = 255 - c;
#endif
analogWrite (MODE_LED_R_PIN, c);
#endif
#ifdef MODE_LED_G_PIN
c = colors[1];
#ifdef MODE_LED_COMMON_ANODE
c = 255 - c;
#endif
analogWrite (MODE_LED_G_PIN, c);
#endif
#ifdef MODE_LED_B_PIN
c = colors[2];
#ifdef MODE_LED_COMMON_ANODE
c = 255 - c;
#endif
analogWrite (MODE_LED_B_PIN, c);
#endif
#endif // ENABLE_MODE_LED_RGB
#ifdef MODE_LED_SINGLE_PIN
// WARNING: This loop must be reasonably shorter than LONGPRESS_LEN in the worst case!
for (int i = 0; i < current_mode + 1; ++i) {
digitalWrite (MODE_LED_SINGLE_PIN, LOW);
delay (40);
digitalWrite (MODE_LED_SINGLE_PIN, HIGH);
delay (80);
}
#endif
}
void set_mode (VideoMode m) {
switch (m) {
default:
case EUR:
digitalWrite (VIDEOMODE_PIN, LOW); // PAL 50Hz
digitalWrite (LANGUAGE_PIN, HIGH); // ENG
break;
case USA:
digitalWrite (VIDEOMODE_PIN, HIGH); // NTSC 60Hz
digitalWrite (LANGUAGE_PIN, HIGH); // ENG
break;
case JAP:
digitalWrite (VIDEOMODE_PIN, HIGH); // NTSC 60Hz
digitalWrite (LANGUAGE_PIN, LOW); // JAP
break;
}
current_mode = m;
update_mode_leds ();
mode_last_changed_time = millis ();
}
#endif
void handle_reset_button () {
static byte debounce_level = LOW;
static bool reset_pressed_before = false;
static long last_int = 0, reset_press_start = 0;
static unsigned int hold_cycles = 0;
byte reset_level = digitalRead (RESET_IN_PIN);
if (reset_level != debounce_level) {
// Reset debouncing timer
last_int = millis ();
debounce_level = reset_level;
} else if (millis () - last_int > DEBOUNCE_MS) {
// OK, button is stable, see if it has changed
if (reset_level != reset_inactive_level && !reset_pressed_before) {
// Button just pressed
reset_press_start = millis ();
hold_cycles = 0;
}
else if (reset_level == reset_inactive_level && reset_pressed_before) {
// Button released
if (hold_cycles == 0) {
debugln ("Reset button pushed for a short time");
reset_console ();
}
#if !defined __AVR_ATtinyX5__
} else {
// Button has not just been pressed/released
if (reset_level != reset_inactive_level && millis () % reset_press_start >= LONGPRESS_LEN * (hold_cycles + 1)) {
// Reset has been hold for a while
debugln ("Reset button hold");
++hold_cycles;
next_mode ();
}
#endif
}
reset_pressed_before = (reset_level != reset_inactive_level);
}
}
void reset_console () {
debugln ("Resetting console");
digitalWrite (RESET_OUT_PIN, !reset_inactive_level);
delay (RESET_LEN);
digitalWrite (RESET_OUT_PIN, reset_inactive_level);
}
void setup () {
#ifdef ENABLE_SERIAL_DEBUG
Serial.begin (9600);
#endif
debugln ("Starting up...");
/* Rant: As per D4s's installation schematics out there (which we use too), it
* seems that on consoles with an active low reset signal, the Reset In input
* is taken before the pull-up resistor, while on consoles with active-high
* reset it is taken AFTER the pull-down resistor. This means that detecting
* the reset level by sampling the same line on both consoles is tricky, as in
* both cases one of the Reset In/Out signals is left floating :(. The
* following should work reliably, but we allow for a way to force the reset
* line level.
*/
#ifndef FORCE_RESET_ACTIVE_LEVEL
// Let things settle down and then sample the reset line
delay (100);
pinMode (RESET_IN_PIN, INPUT_PULLUP);
reset_inactive_level = digitalRead (RESET_IN_PIN);
debug ("Reset line is ");
debug (reset_inactive_level ? "HIGH" : "LOW");
debugln (" at startup");
#else
reset_inactive_level = !FORCE_RESET_ACTIVE_LEVEL;
debug ("Reset line is forced to active-");
debugln (FORCE_RESET_ACTIVE_LEVEL ? "HIGH" : "LOW");
#endif
if (reset_inactive_level == LOW) {
// No need for pull-up
pinMode (RESET_IN_PIN, INPUT);
} else {
pinMode (RESET_IN_PIN, INPUT_PULLUP);
}
// Enable reset
pinMode (RESET_OUT_PIN, OUTPUT);
digitalWrite (RESET_OUT_PIN, !reset_inactive_level);
// Setup leds
#ifdef MODE_LED_R_PIN
pinMode (MODE_LED_R_PIN, OUTPUT);
#endif
#ifdef MODE_LED_G_PIN
pinMode (MODE_LED_G_PIN, OUTPUT);
#endif
#ifdef MODE_LED_B_PIN
pinMode (MODE_LED_B_PIN, OUTPUT);
#endif
#ifdef MODE_LED_SINGLE_PIN
pinMode (MODE_LED_SINGLE_PIN, OUTPUT);
#endif
#ifdef PAD_LED_PIN
pinMode (PAD_LED_PIN, OUTPUT);
#endif
#if !defined __AVR_ATtinyX5__
// Init video mode
pinMode (VIDEOMODE_PIN, OUTPUT);
pinMode (LANGUAGE_PIN, OUTPUT);
current_mode = EUR;
#ifdef MODE_ROM_OFFSET
byte tmp = EEPROM.read (MODE_ROM_OFFSET);
debug ("Loaded video mode from EEPROM: ");
debugln (tmp);
if (tmp < MODES_NO) {
// Palette EEPROM value is good
current_mode = static_cast<VideoMode> (tmp);
}
#endif
set_mode (current_mode);
mode_last_changed_time = 0; // No need to save what we just loaded
#endif
// Prepare to read pad
setup_pad ();
// Finally release the reset line
digitalWrite (RESET_OUT_PIN, reset_inactive_level);
}
void setup_pad () {
// Set port directions
#if defined __AVR_ATtinyX5__
DDRB &= ~((1 << DDB2) | (1 << DDB1) | (1 << DDB0));
#elif defined __AVR_ATtinyX4__
DDRA &= ~((1 << DDA6) | (1 << DDA2) | (1 << DDA1));
DDRB &= ~((1 << DDB1) | (1 << DDB0));
#elif defined __AVR_ATtinyX61__
DDRA &= ~((1 << DDA6) | (1 << DDA5) | (1 << DDA4) | (1 << DDA3) | (1 << DDA2) | (1 << DDA1) | (1 << DDA0));
#elif defined __AVR_ATtinyX313__
DDRD &= ~((1 << DDD6) | (1 << DDD5) | (1 << DDD4) | (1 << DDD3) | (1 << DDD2) | (1 << DDD1) | (1 << DDD0));
#elif defined ARDUINO328
DDRC &= ~((1 << DDC1) | (1 << DDC0));
DDRD &= ~((1 << DDD6) | (1 << DDD5) | (1 << DDD4) | (1 << DDD3) | (1 << DDD2));
#endif
}
/******************************************************************************/
/*
* The basic idea here is to make up a byte where each bit represents the state
* of a button, where 1 means pressed, for commodity's sake. The bit-button
* mapping is defined in the PadButton enum above.
*
* To get consistent readings, we should really read all of the pad pins at
* once, since some of them must be interpreted according to the value of the
* SELECT signal. In order to do this we could connect all pins to a single port
* of our MCU, but this is a luxury we cannot often afford, for various reasons.
* Luckily, the UP and DOWN signals do not depend upon the SELECT pins, so we
* can read them anytime, and this often takes us out of trouble.
*
* Note that printing readings through serial slows down the code so much that
* it misses most of the readings with SELECT low!
*/
inline byte read_pad () {
static byte pad_status = 0;
#if defined __AVR_ATtinyX5__
/*
* On ATtinyX4's all we can do is read A/B and Start/C together with SELECT:
* - Pin 6 (A/B) -> PB0
* - Pin 7 (SELECT) -> PB2
* - Pin 9 (Start/C) -> PB1
*/
byte portb = PINB;
if (portb & (1 << PINB2)) {
// Select is high, we have C & B
pad_status = (pad_status & 0xCF)
| ((~portb & ((1 << PINB1) | (1 << PINB0))) << 4);
} else {
// Select is low, we have Start & A
pad_status = (pad_status & 0x3F)
| ((~portb & ((1 << PINB1) | (1 << PINB0))) << 6);
}
#elif defined __AVR_ATtinyX4__
/*
* On ATtinyX4's we read A/B and Start/C together with SELECT through PORTA.
* Then we read UP and DOWN through PORTB.
* Connections are made like this to only use pins left spare from the D4s/Seb
* mod. In principle, we could read all of them through a single port (which
* would also allow us to read LEFT and RIGHT instead of UP and DOWN) if we
* reorganize all the connections.
*
* If connections are made as per the diagram above we have:
* - Pin 1 (UP) -> PB0
* - Pin 2 (DOWN) -> PB1
* - Pin 6 (A/B) -> PA1
* - Pin 7 (SELECT) -> PA6
* - Pin 9 (Start/C) -> PA2
*/
// Update UP and DOWN, which are always valid and on PORTB alone
pad_status = (pad_status & 0xFC) | (~PINB & ((1 << PINB1) | (1 << PINB0)));
// Then deal with the rest
byte porta = PINA;
if (porta & (1 << PINA6)) {
// Select is high, we have C & B
pad_status = (pad_status & 0xCF)
| ((~porta & ((1 << PINA2) | (1 << PINA1))) << 3)
;
} else {
// Select is low, we have Start & A
pad_status = (pad_status & 0x3F)
| ((~porta & ((1 << PINA2) | (1 << PINA1))) << 5)
;
}
#elif defined __AVR_ATtinyX61__
/*
* On ATtinyX61 we have all the buttons on a single port, so we can read all
* of them at once. We still have to play a bit with the bits (pun intended)
* since we want to have SELECT connected to INT1, which will probably help
* with the 6-button pad.
*/
byte porta = PINA;
if (porta & (1 << PINA2)) {
// Select is high, we have the 4 directions, C & B
pad_status = (pad_status & 0xC0)
| ((~porta & ((1 << PINA6) | (1 << PINA5) | (1 << PINA4) | (1 << PINA3))) >> 1)
| (~porta & ((1 << PINA1) | (1 << PINA0)))
;
} else {
// Select is low, we have Up, Down, Start & A
pad_status = (pad_status & 0x30)
| ((~porta & ((1 << PINA6) | (1 << PINA5))) << 1)
| (~porta & ((1 << PINA1) | (1 << PINA0)))
;
}
#elif defined __AVR_ATtinyX313__
/*
* Same as above, but with port D instead of port A. SELECT is connected to
* INT0.
*/
byte portd = PIND;
if (portd & (1 << PIND2)) {
// Select is high, we have the 4 directions, C & B
pad_status = (pad_status & 0xC0)
| ((~portd & ((1 << PIND6) | (1 << PIND5) | (1 << PIND4) | (1 << PIND3))) >> 1)
| (~portd & ((1 << PIND1) | (1 << PIND0)))
;
} else {
// Select is low, we have Up, Down, Start & A
pad_status = (pad_status & 0x30)
| ((~portd & ((1 << PIND6) | (1 << PIND5))) << 1)
| (~portd & ((1 << PIND1) | (1 << PIND0)))
;
}
#elif defined ARDUINO328
// Update UP and DOWN, which are always valid and on PORTC alone
pad_status = (pad_status & 0xFC)
| (~PINC & ((1 << PINC1) | (1 << PINC0)))
;
byte portd = PIND;
if (portd & (1 << PIND2)) {
// Select is high, we have Right, Left, C & B
pad_status = (pad_status & 0xC3)
| ((~portd & ((1 << PIND6) | (1 << PIND5) | (1 << PIND4) | (1 << PIND3))) >> 1)
;
} else {
// Select is low, we have Start & A
pad_status = (pad_status & 0x3F)
| ((~portd & ((1 << PIND6) | (1 << PIND5))) << 1)
;
}
#endif
return pad_status;
}
#define IGNORE_COMBO_MS LONGPRESS_LEN
void handle_pad () {
static long last_combo_time = 0;
byte pad_status = read_pad ();
#ifdef PAD_LED_PIN
digitalWrite (PAD_LED_PIN, pad_status);
#endif
#if 0
Serial.print ("Pad status = ");
Serial.println (pad_status);
#endif
if ((pad_status & TRIGGER_COMBO) == TRIGGER_COMBO && millis () - last_combo_time > IGNORE_COMBO_MS) {
if ((pad_status & RESET_COMBO) == RESET_COMBO) {
debugln ("Reset combo detected");
reset_console ();
pad_status = 0; // Avoid continuous reset (pad_status might keep the last value during reset!)
last_combo_time = millis ();
#ifdef EUR_COMBO
} else if ((pad_status & EUR_COMBO) == EUR_COMBO) {
debugln ("EUR mode combo detected");
set_mode (EUR);
last_combo_time = millis ();
#endif
#ifdef USA_COMBO
} else if ((pad_status & USA_COMBO) == USA_COMBO) {
debugln ("USA mode combo detected");
set_mode (USA);
last_combo_time = millis ();
#endif
#ifdef JAP_COMBO
} else if ((pad_status & JAP_COMBO) == JAP_COMBO) {
debugln ("JAP mode combo detected");
set_mode (JAP);
last_combo_time = millis ();
#endif
#ifdef NEXT_MODE_COMBO
} else if ((pad_status & NEXT_MODE_COMBO) == NEXT_MODE_COMBO) {
debugln ("Next mode combo detected");
next_mode ();
last_combo_time = millis ();
#endif
#ifdef PREV_MODE_COMBO
} else if ((pad_status & PREV_MODE_COMBO) == PREV_MODE_COMBO) {
debugln ("Previous mode combo detected");
prev_mode ();
last_combo_time = millis ();
#endif
}
}
}
void loop () {
handle_reset_button ();
handle_pad ();
save_mode ();
}
Something went wrong with that request. Please try again.