Skip to content

Instantly share code, notes, and snippets.

@partlyhuman
Created December 31, 2022 20:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save partlyhuman/128e6f78a665cf9293773b3f752a3484 to your computer and use it in GitHub Desktop.
Save partlyhuman/128e6f78a665cf9293773b3f752a3484 to your computer and use it in GitHub Desktop.
IR Remote for Custom Game Boy Camera ROMs
#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)
}
// 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;
}
}
@partlyhuman
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment