Created
December 31, 2022 20:53
-
-
Save partlyhuman/128e6f78a665cf9293773b3f752a3484 to your computer and use it in GitHub Desktop.
IR Remote for Custom Game Boy Camera ROMs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#undef DEBUG | |
#define GBP_SO_PIN 3 | |
#define GBP_SC_PIN 4 | |
#define PIN_LED 1 | |
#define PIN_IRIN 0 | |
#define J_START 0x80u | |
#define J_SELECT 0x40u | |
#define J_B 0x20u | |
#define J_A 0x10u | |
#define J_DOWN 0x08u | |
#define J_UP 0x04u | |
#define J_LEFT 0x02u | |
#define J_RIGHT 0x01u | |
inline uint8_t count_parity(uint8_t v) { | |
uint8_t c = 0, b = 1; | |
for (uint8_t i = 0; i != 4; i++, b <<= 1) { | |
c += (v & b) ? 1 : 0; | |
} | |
return c & 1; | |
} | |
void send_bit(bool b) { | |
digitalWrite(GBP_SO_PIN, b); | |
digitalWrite(GBP_SC_PIN, true); | |
delay(1); | |
digitalWrite(GBP_SC_PIN, false); | |
delay(1); | |
} | |
void send_byte(uint8_t b) { | |
for (int i = 0; i != 8; i++, b <<= 1) { | |
send_bit(b & 0x80); | |
} | |
} | |
void send_joypad(uint8_t j) { | |
send_byte((j & 0x0f) | ((count_parity(j)) ? 0x90 : 0x80)); // send D-pad | |
send_byte((j >> 4) | ((count_parity(j >> 4)) ? 0xB0 : 0xA0)); // send buttons | |
} | |
void setup() { | |
#ifdef DEBUG | |
Serial.begin(9600); | |
#endif | |
pinMode(GBP_SO_PIN, OUTPUT); | |
pinMode(GBP_SC_PIN, OUTPUT); | |
pinMode(PIN_LED, OUTPUT); | |
pinMode(PIN_IRIN, INPUT); | |
// Enable an interrupt that's called when the IR receiver pin signal falls | |
// from high to low. This indicates a remote control code being received. | |
// attachInterrupt(PIN_IRIN, receiverFalling, FALLING); | |
PCMSK |= bit (PCINT0); // want pin D0 | |
GIFR |= bit (PCIF); // clear any outstanding interrupts | |
GIMSK |= bit (PCIE); // enable pin change interrupts | |
send_joypad(0x00); | |
for (uint8_t i = 0; i < 25; i++) { | |
digitalWrite(PIN_LED, i % 2); | |
delay(30); | |
} | |
} | |
// for sprintf | |
char strbuffer[128]; | |
uint8_t value = 1; | |
uint8_t prevValue = 0; | |
void loop() { | |
uint16_t code; | |
value = 0; | |
if (readNEC(&code)) { | |
#ifdef DEBUG | |
sprintf(strbuffer, "IR code %d (%#x)\n", code, code); | |
Serial.println(strbuffer); | |
#endif | |
switch (code) { | |
case 8: | |
value |= J_LEFT; | |
break; | |
case 90: | |
value |= J_RIGHT; | |
break; | |
case 24: | |
value |= J_UP; | |
break; | |
case 82: | |
value |= J_DOWN; | |
break; | |
case 22: | |
value |= J_SELECT; | |
break; | |
case 13: | |
value |= J_START; | |
break; | |
case 25: | |
value |= J_B; | |
break; | |
case 28: | |
value |= J_A; | |
break; | |
default: | |
return; | |
} | |
} | |
// if (value != prevValue) { | |
//#ifdef DEBUG | |
// Serial.println(value, HEX); | |
//#endif | |
// send_joypad(value); | |
// prevValue = value; | |
// } | |
if (value) { | |
send_joypad(value); | |
delay(20); | |
send_joypad(0); | |
} | |
digitalWrite(PIN_LED, value ? HIGH : LOW); | |
//digitalWrite(PIN_LED, (millis() >> 10) & 0x01); // Blink every (~1s) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The code this is adapted from is floating around without clear attribution | |
// it can be found at https://learn.adafruit.com/techno-tiki-rgb-led-torch/ but I don't believe it originated there | |
// comment if you have attribution or license information. | |
//Register to receive voltage drop on IR pin very rapidly as an interrupt | |
volatile bool receiverFell = false; | |
ISR (PCINT0_vect) // this is the Interrupt Service Routine | |
{ | |
// Interrupt function that's called when the IR receiver pin falls from high to | |
// low and indicates the start of an IR command being received. Note that | |
// interrupts need to be very fast and perform as little processing as possible. | |
// This just sets a global variable which the main loop will periodically check | |
// and perform appropriate IR command decoding when necessary. | |
receiverFell = true; | |
} | |
bool readNEC(uint16_t* result) { | |
// Check if a NEC IR remote command can be read and decoded from the IR receiver. | |
// If the command is decoded then the result is stored in the provided pointer and | |
// true is returned. Otherwise if the command was not decoded then false is returned. | |
// First check that a falling signal was detected and start reading pulses. | |
if (!receiverFell) { | |
return false; | |
} | |
// Read the first pulse with a large timeout since it's 9ms long, then | |
// read subsequent pulses with a shorter 2ms timeout. | |
uint32_t durations[33]; | |
durations[0] = pulseIn(PIN_IRIN, HIGH, 20000); | |
for (uint8_t i = 1; i < 33; ++i) { | |
durations[i] = pulseIn(PIN_IRIN, HIGH, 5000); | |
} | |
// Reset any state changed by the interrupt. | |
receiverFell = false; | |
// Check the received pulses are in a NEC format. | |
// First verify the initial pulse is around 4.5ms long. | |
if ((durations[0] < 4000) || (durations[1] > 5000)) { | |
return false; | |
} | |
// Now read the bits from subsequent pulses. Stop if any were a timeout (0 value). | |
uint8_t data[4] = {0}; | |
for (uint8_t i = 0; i < 32; ++i) { | |
if (durations[1 + i] == 0) { | |
return false; // Timeout | |
} | |
uint8_t b = durations[1 + i] < 1000 ? 0 : 1; | |
data[i / 8] |= b << (i % 8); | |
} | |
// Verify bytes and their inverse match. Use the same two checks as the NEC IR remote | |
// library here: https://github.com/adafruit/Adafruit-NEC-remote-control-library | |
if ((data[0] == (~data[1] & 0xFF)) && (data[2] == (~data[3] & 0xFF))) { | |
*result = data[0] << 8 | data[2]; | |
return true; | |
} | |
else if ((data[0] == 0) && (data[1] == 0xBF) && (data[2] == (~data[3] & 0xFF))) { | |
*result = data[2]; | |
return true; | |
} | |
else { | |
// Something didn't match, fail! | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Code is adapted from https://github.com/gameboycamera/NES-controller-to-GB-linkport
Info on the comms format https://github.com/untoxa/gb-photo#remote-control-packet-format
Home of the project https://www.thingiverse.com/thing:5756793