Modifi input pad esp_8_bit arduino gpio

Hello, I have an esp32 in which I load the esp_8_bit code, this is an emulator with composite video for nes, the problem is that the person who designed it did it to make an original nes controller to connect, and I don't have that, I wanted to change the code so that when it reads a state on a GPIO pin of the esp32, it fulfills a function of the command, for example pin 21 start, pin 34 button A and so on. ..

I didn't make it.

source code GitHub - CornN64/esp_8_bit: Atari 8 bit computers, NES and SMS game consoles on your TV with nothing more than a ESP32 and a sense of nostalgia

This is the code part of a file called ir_input.h inside the src folder, when I want to modify part of this code, it doesn't compile anymore. I honestly don't know what to change to be able to work with the esp32 pins.

1 Like

Sound like you have bugs in your modifications.

Instead of clocking in 8 bits from each controller, read the 8 values directly:

    unit16_t buttonsA = 0;
    buttonsA |= digitalRead(A_FIRE_PIN) * map_nes[0];
    buttonsA |= digitalRead(B_FIRE_PIN) * map_nes[1];
    buttonsA |= digitalRead(A_SELECT_PIN) * map_nes[2];
    buttonsA |= digitalRead(A_START_PIN) * map_nes[3];
    buttonsA |= digitalRead(A_UP_PIN) * map_nes[4];
    buttonsA |= digitalRead(A_DOWN_PIN) * map_nes[5];
    buttonsA |= digitalRead(A_LEFT_PIN) * map_nes[6];
    buttonsA |= digitalRead(A_RIGHT_PIN) * map_nes[7];
1 Like

Good morning, thanks for your answer, I tried your code but I still have the same problem, when pin 34 is energized it does nothing, I clarify that I only test the start button to do a quick test, if at the moment of energizing the pin it starts the game, it means that it works, but it doesn't do anything, maybe I'm not locating the code in the right place, I leave you an image of how I modified the original code.


I put the values ​​here and it looks like this


also declare the start pin as 34 in the config.h file


the code compiles fine, it loads to the board, but when I try to energize the pin, it does nothing.

This is how I energize the pin, with a pull down resistor

10k is more suitable as a pulldown resistor.

Post your code. Not screenshots of part of it. Use code tags (this button </> in the editor).

//==================================================================
//Classic hard wired NES controllers
//==================================================================
#ifdef NES_CONTROLLER
const uint16_t map_nes[8] = 
{
	GENERIC_FIRE | GENERIC_FIRE_A,	//NES_A
	GENERIC_FIRE_B,	//NES_B
	GENERIC_SELECT,	//NES_SELECT
	GENERIC_START,	//NES_START
	GENERIC_UP,		//NES_UP
	GENERIC_DOWN,	//NES_DOWN
	GENERIC_LEFT,	//NES_LEFT
	GENERIC_RIGHT	//NES_RIGHT
};

IRState _nes;
int get_hid_nes(uint8_t* dst)
{
	digitalWrite(NES_CTRL_LATCH, 1);
	delayMicroseconds(0);
	digitalWrite(NES_CTRL_LATCH, 0);
	delayMicroseconds(0);

	uint16_t buttonsA = 0;
	
	buttonsA |= digitalRead(A_FIRE_PIN) * map_nes[0];
    buttonsA |= digitalRead(B_FIRE_PIN) * map_nes[1];
    buttonsA |= digitalRead(A_SELECT_PIN) * map_nes[2];
    buttonsA |= digitalRead(A_START_PIN) * map_nes[3];
    buttonsA |= digitalRead(A_UP_PIN) * map_nes[4];
    buttonsA |= digitalRead(A_DOWN_PIN) * map_nes[5];
    buttonsA |= digitalRead(A_LEFT_PIN) * map_nes[6];
    buttonsA |= digitalRead(A_RIGHT_PIN) * map_nes[7];
	
	uint16_t buttonsB = 0;
	for (int i = 0; i < 8; i++)
		{
			buttonsA |= (1^digitalRead(NES_CTRL_ADATA)) * map_nes[i];
			buttonsB |= (1^digitalRead(NES_CTRL_BDATA)) * map_nes[i];
			digitalWrite(NES_CTRL_CLK, 0);
			delayMicroseconds(0);
			digitalWrite(NES_CTRL_CLK, 1);
			delayMicroseconds(0);
		}
	//printf("NESCTRL:%04X %04X\n", buttonsA, buttonsB);
  
	//Setup Controller A
	if (buttonsA == (GENERIC_LEFT | GENERIC_SELECT)) buttonsA |= GENERIC_OTHER;	//Press LEFT & SELECT to open file menu
	_nes.set(0,buttonsA,0); // no repeat period

	//Setup Controller B
	_nes.set(1,buttonsB,0); // no repeat period
  
  return _nes.get_hid(dst);		
}
#endif

Excuse me, I wanted to show where I inserted the code that Johnwasser recommended

Better... but please post ALL the code.

/* Copyright (c) 2010-2020, Peter Barrett
 **
 ** Permission to use, copy, modify, and/or distribute this software for
 ** any purpose with or without fee is hereby granted, provided that the
 ** above copyright notice and this permission notice appear in all copies.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 ** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 ** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
 ** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 ** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 ** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 ** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 ** SOFTWARE.
 */

#ifndef __ir_input__
#define __ir_input__

// generate events on state changes of IR pin, feed to state machines.
// event timings  have a resolution of HSYNC, timing is close enough between 15720 and 15600 to make this work
// poll to synthesize hid events at every frame
// i know there is a perfectly good peripheral for this in the ESP32 but this seems more fun somehow

uint8_t _ir_last = 0;
uint8_t _ir_count = 0;
uint8_t _keyDown = 0;
uint8_t _keyUp = 0;

void IRAM_ATTR ir_event(uint8_t ticks, uint8_t value); // t is HSYNCH ticks, v is value

inline void IRAM_ATTR ir_sample()
{
    uint8_t ir = (GPIO.in & (1 << IR_PIN)) != 0;
    if (ir != _ir_last)
    {
        ir_event(_ir_count,_ir_last);
        _ir_count = 0;
        _ir_last = ir;
    }
    if (_ir_count != 0xFF)
        _ir_count++;
}

class IRState {
public:
    uint8_t _state = 0;
    uint16_t _code = 0;
    uint16_t _output = 0;
    uint16_t _joy[2] = {0};
    uint16_t _joy_last[2] = {0};
    uint8_t _timer[2] = {0};

    void set(int i, int m, int t)
    {
        uint8_t b = 0;
        if ((m & (GENERIC_LEFT | GENERIC_RIGHT)) == (GENERIC_LEFT | GENERIC_RIGHT))
            b++; // bogus?
        if ((m & (GENERIC_UP | GENERIC_DOWN)) == (GENERIC_UP | GENERIC_DOWN))
            b++; // bogus?
        if (b) {
            //printf("bogus:%04X\n",m);
            return;
        }

        _joy[i] = m;
        _timer[i] = t;
    }

    // make a fake hid event
    int get_hid(uint8_t* dst)
    {
        if (_timer[0] && !--_timer[0])
            _joy[0] = 0;
        if (_timer[1] && !--_timer[1])
            _joy[1] = 0;
        if ((_joy[0] != _joy_last[0]) || (_joy[1] != _joy_last[1])) {
            _joy_last[0] = _joy[0];
            _joy_last[1] = _joy[1];
            dst[0] = 0xA1;
            dst[1] = 0x42;
            dst[2] = _joy[0];
            dst[3] = _joy[0]>>8;
            dst[4] = _joy[1];
            dst[5] = _joy[1]>>8;
            return 6;           // only sent if it changes
        }
        return 0;
    }
};

//==================================================================
//Classic hard wired NES controllers
//==================================================================
#ifdef NES_CONTROLLER
const uint16_t map_nes[8] = 
{
	GENERIC_FIRE | GENERIC_FIRE_A,	//NES_A
	GENERIC_FIRE_B,	//NES_B
	GENERIC_SELECT,	//NES_SELECT
	GENERIC_START,	//NES_START
	GENERIC_UP,		//NES_UP
	GENERIC_DOWN,	//NES_DOWN
	GENERIC_LEFT,	//NES_LEFT
	GENERIC_RIGHT	//NES_RIGHT
};

IRState _nes;
int get_hid_nes(uint8_t* dst)
{
	digitalWrite(NES_CTRL_LATCH, 1);
	delayMicroseconds(0);
	digitalWrite(NES_CTRL_LATCH, 0);
	delayMicroseconds(0);

	uint16_t buttonsA = 0;
	
	buttonsA |= digitalRead(A_FIRE_PIN) * map_nes[0];
    buttonsA |= digitalRead(B_FIRE_PIN) * map_nes[1];
    buttonsA |= digitalRead(A_SELECT_PIN) * map_nes[2];
    buttonsA |= digitalRead(A_START_PIN) * map_nes[3];
    buttonsA |= digitalRead(A_UP_PIN) * map_nes[4];
    buttonsA |= digitalRead(A_DOWN_PIN) * map_nes[5];
    buttonsA |= digitalRead(A_LEFT_PIN) * map_nes[6];
    buttonsA |= digitalRead(A_RIGHT_PIN) * map_nes[7];
	
	uint16_t buttonsB = 0;
	for (int i = 0; i < 8; i++)
		{
			buttonsA |= (1^digitalRead(NES_CTRL_ADATA)) * map_nes[i];
			buttonsB |= (1^digitalRead(NES_CTRL_BDATA)) * map_nes[i];
			digitalWrite(NES_CTRL_CLK, 0);
			delayMicroseconds(0);
			digitalWrite(NES_CTRL_CLK, 1);
			delayMicroseconds(0);
		}
	//printf("NESCTRL:%04X %04X\n", buttonsA, buttonsB);
  
	//Setup Controller A
	if (buttonsA == (GENERIC_LEFT | GENERIC_SELECT)) buttonsA |= GENERIC_OTHER;	//Press LEFT & SELECT to open file menu
	_nes.set(0,buttonsA,0); // no repeat period

	//Setup Controller B
	_nes.set(1,buttonsB,0); // no repeat period
  
  return _nes.get_hid(dst);		
}
#endif

//==================================================================
//Classic hard wired SNES controllers
//==================================================================
#ifdef SNES_CONTROLLER
const uint16_t map_snes[12] = 
{
	GENERIC_FIRE_B,	//SNES_B
	GENERIC_FIRE_C,	//SNES_Y
	GENERIC_SELECT,	//SNES_SELECT
	GENERIC_START,	//SNES_START
	GENERIC_UP,		//SNES_UP
	GENERIC_DOWN,	//SNES_DOWN
	GENERIC_LEFT,	//SNES_LEFT
	GENERIC_RIGHT,	//SNES_RIGHT
	GENERIC_FIRE | GENERIC_FIRE_A,	//SNES_A
	GENERIC_FIRE_X,	//SNES_X
	GENERIC_FIRE_Y,	//SNES_L
	GENERIC_FIRE_Z	//SNES_R
};

IRState _snes;
int get_hid_snes(uint8_t* dst)
{
	digitalWrite(NES_CTRL_LATCH, 1);
	delayMicroseconds(0);
	digitalWrite(NES_CTRL_LATCH, 0);
	delayMicroseconds(0);

	uint16_t buttonsA = 0;
	uint16_t buttonsB = 0;
	for (int i = 0; i < 12; i++)
		{
			buttonsA |= (1^digitalRead(NES_CTRL_ADATA)) * map_snes[i];
			buttonsB |= (1^digitalRead(NES_CTRL_BDATA)) * map_snes[i];
			digitalWrite(NES_CTRL_CLK, 0);
			delayMicroseconds(0);
			digitalWrite(NES_CTRL_CLK, 1);
			delayMicroseconds(0);
		}
	//printf("SNESCTRL:%04X %04X\n", buttonsA, buttonsB);
  
	//Setup Controller A
	if (buttonsA == (GENERIC_LEFT | GENERIC_SELECT)) buttonsA |= GENERIC_OTHER;	//Press LEFT & SELECT to open file menu
	_snes.set(0,buttonsA,0); // no repeat period

	//Setup Controller B
	_snes.set(1,buttonsB,0); // no repeat period
  
  return _snes.get_hid(dst);		
}
#endif

//==========================================================
//==========================================================
// Apple remote NEC code
// pretty easy to adapt to any NEC remote

#ifdef APPLE_TV_CONTROLLER

// Silver apple remote, 7 Bit code
// should work with both silvers and white
#define APPLE_MENU      0x40
#define APPLE_PLAY      0x7A
#define APPLE_CENTER    0x3A
#define APPLE_RIGHT     0x60
#define APPLE_LEFT      0x10
#define APPLE_UP        0x50
#define APPLE_DOWN      0x30

#define APPLE_RELEASE   0x20 // sent after menu and play?

//    generic repeat code
#define NEC_REPEAT    0xAAAA

/*
  9ms preamble ~142
  4.5ms 1 ~71 - start
  2.25ms ~35 - repeat

  32 bits
  0 – a 562.5µs/562.5µs   9ish wide
  1 – a 562.5µs/1.6875ms  27ish wide
*/

IRState _apple;
int get_hid_apple(uint8_t* dst)
{
    if (_apple._output)
    {
        if (_apple._output != NEC_REPEAT)
            _keyDown = (_apple._output >> 8) & 0x7F;  // 7 bit code
        _apple._output = 0;

        uint16_t k = 0;
        switch (_keyDown) {
            case APPLE_UP:     k = GENERIC_UP;     break;
            case APPLE_DOWN:   k = GENERIC_DOWN;   break;
            case APPLE_LEFT:   k = GENERIC_LEFT;   break;
            case APPLE_RIGHT:  k = GENERIC_RIGHT;  break;
            case APPLE_CENTER: k = GENERIC_FIRE;   break;
            case APPLE_MENU:   k = GENERIC_RESET;  break;
            case APPLE_PLAY:   k = GENERIC_SELECT; break;
        }
        _apple.set(0,k,15); // 108ms repeat period
    }
    return _apple.get_hid(dst);
}

// NEC codes used by apple remote
void IRAM_ATTR ir_apple(uint8_t t, uint8_t v)
{
  if (!v) {
    if (t > 32)
      _apple._state = 0;
  } else {
    if (t < 32)
    {
      _apple._code <<= 1;
      if (t >= 12)
        _apple._code |= 1;
      if (++_apple._state == 32)
        _apple._output = _apple._code;    // Apple code in bits 14-8*
    } else {
        if (t > 32 && t < 40 && !_apple._state)  // Repeat 2.25ms pulse 4.5ms start
          _apple._output = NEC_REPEAT;
        _apple._state = 0;
    }
  }
}

#endif

//==========================================================
//==========================================================
//  Atari Flashback 4 wireless controllers

#ifdef FLASHBACK_CONTROLLER

// HSYNCH period is 44/315*455 or 63.55555..us
// 18 bit code 1.87khz clock
// 2.3ms zero preamble  // 36
// 0 is 0.27ms pulse    // 4
// 1 is 0.80ms pulse    // 13

// Keycodes...
// Leading bit is 1 for player 1 control..

#define PREAMBLE(_t) (_t >= 34 && _t <= 38)
#define SHORT(_t)    (_t >= 2 && _t <= 6)
#define LONG(_t)     (_t >= 11 && _t <= 15)

// Codes come in pairs 33ms apart
// Sequence repeats every 133ms
// bitmap is released if no code for 10 vbls (167ms) or 0x01 (p1) / 0x0F (p2)
// up to 12 button bits, 4 bits of csum/p1/p2 indication

//  called at every loop ~60Hz

IRState _flashback;
int get_hid_flashback(uint8_t* dst)
{
    if (_flashback._output)
    {
        uint16_t m = _flashback._output >> 4;        // 12 bits of buttons
        printf("F:%04X\n",m);
        uint8_t csum = _flashback._output & 0xF;     // csum+1 for p1, csum-1 for p2
        uint8_t s = m + (m >> 4) + (m >> 8);
        if (((s+1) & 0xF) == csum)
            _flashback.set(0,m,15);
        else if (((s-1) & 0xF) == csum)
            _flashback.set(1,m,20);
        _flashback._output = 0;
    }
    return _flashback.get_hid(dst);
}

void IRAM_ATTR ir_flashback(uint8_t t, uint8_t v)
{
  if (_flashback._state == 0)
  {
    if (PREAMBLE(t) && (v == 0))  // long 0, rising edge of start bit
    {
      _flashback._state = 1;
    }
  }
  else
  {
    if (v)
    {
      _flashback._code <<= 1;
      if (LONG(t))
      {
        _flashback._code |= 1;
      }
      else if (!SHORT(t))
      {
        _flashback._state = 0;  // framing error
        return;
      }

      if (++_flashback._state == 19)
      {
        _flashback._output = _flashback._code;
        _flashback._state = 0;
      }
    }
    else
    {
      if (!SHORT(t))
        _flashback._state = 0;  // Framing err
    }
  }
}

#endif

//==========================================================
//==========================================================
// RETCON controllers
// 75ms keyboard repeat
// Preamble is 0.80ms low, 0.5 high
// Low: 0.57ms=0,0.27,s=1, high 0.37
// 16 bits
// Preamble = 800,500/63.55555 ~ 12.6,7.87
// LOW0 = 8.97
// LOW1 = 4.25
// HIGH = 5.82

#ifdef RETCON_CONTROLLER

// number of 63.55555 cycles per bit
#define PREAMBLE_L(_t) (_t >= 12 && _t <= 14) // 12/13/14 preamble
#define PREAMBLE_H(_t) (_t >= 6 && _t <= 10)  // 8
#define LOW_0(_t)     (_t >= 8 && _t <= 10)   // 8/9/10
#define LOW_1(_t)     (_t >= 4 && _t <= 6)    // 4/5/6

// map retcon to generic
const uint16_t _jmap[] = {
  0x0400, GENERIC_UP,
  0x0200, GENERIC_DOWN,
  0x0100, GENERIC_LEFT,
  0x0080, GENERIC_RIGHT,

  0x1000, GENERIC_SELECT,
  0x0800, GENERIC_START,

  0x0020, GENERIC_FIRE_X,
  0x0040, GENERIC_FIRE_Y,
  0x0002, GENERIC_FIRE_Z,

  0x2000, GENERIC_FIRE_A,
  0x4000, GENERIC_FIRE_B,
  0x0008, GENERIC_FIRE_C,
};

IRState _retcon;
int get_hid_retcon(uint8_t* dst)
{
    if (_retcon._output)
    {
        uint16_t m = 0;
        const uint16_t* jmap = _jmap;
        int16_t i = 12;
        uint16_t k = _retcon._output;
        _retcon._output = 0;
        while (i--)
        {
            if (jmap[0] & k)
                m |= jmap[1];
            jmap += 2;
        }
        printf("R:%04X\n",m);
        _retcon.set(k >> 15,m,20);
    }
    return _retcon.get_hid(dst);
}

void IRAM_ATTR ir_retcon(uint8_t t, uint8_t v)
{
  if (_retcon._state == 0)
  {
    if (v == 0)  {   // start bit
      if (PREAMBLE_L(t))
        _retcon._state = 1;
    }
  }
  else
  {
    if (!v)
    {
      _retcon._code <<= 1;
      if (LOW_1(t))
        _retcon._code |= 1;
      if (_retcon._state++ == 16)
      {
        _retcon._output = _retcon._code;
        _retcon._state = 0;
      }
    }
  }
}

#endif


//==========================================================
//==========================================================
//  Webtv keyboard
#ifdef WEBTV_KEYBOARD

#define BAUDB   12  // Width of uart bit in HSYNCH
#define WT_PREAMBLE(_t) (_t >= 36 && _t <= 40)   // 3.25 baud
#define SHORTBIT(_t) (_t >= 9 && _t <= 13)     // 1.5ms ish

// converts webtv keyboard to common scancodes
const uint8_t _ir2scancode[128] = {
    0x00, // 00
    0x00, // 02
    0x05, // 04 B
    0x00, // 06
    0x00, // 08
    0x51, // 0A Down
    0x00, // 0C
    0x00, // 0E
    0x00, // 10
    0x50, // 12 Left
    0xE6, // 14 Right Alt
    0x38, // 16 /
    0xE2, // 18 Left Alt
    0x4F, // 1A Right
    0x2C, // 1C Space
    0x11, // 1E N
    0x32, // 20 #
    0x00, // 22
    0x22, // 24 5
    0x41, // 26 F8
    0x3B, // 28 F2
    0xE4, // 2A Right Ctrl
    0x00, // 2C
    0x2E, // 2E =
    0x3A, // 30 F1
    0x4A, // 32 Home
    0x00, // 34
    0x2D, // 36 -
    0xE0, // 38 Left Ctrl
    0x35, // 3A `
    0x42, // 3C F9
    0x23, // 3E 6
    0x00, // 40
    0x00, // 42
    0x19, // 44 V
    0x37, // 46 .
    0x06, // 48 C
    0x68, // 4A F13
    0xE5, // 4C Right Shift
    0x36, // 4E ,
    0x1B, // 50 X
    0x4D, // 52 End
    0x00, // 54
    0x00, // 56
    0x1D, // 58 Z
    0x00, // 5A
    0x28, // 5C Return
    0x10, // 5E M
    0x00, // 60
    0xE7, // 62 Right GUI
    0x09, // 64 F
    0x0F, // 66 L
    0x07, // 68 D
    0x4E, // 6A PageDown
    0x00, // 6C
    0x0E, // 6E K
    0x16, // 70 S
    0x4B, // 72 PageUp
    0x00, // 74
    0x33, // 76 ;
    0x04, // 78 A
    0x00, // 7A
    0x31, // 7C |
    0x0D, // 7E J
    0x00, // 80
    0x00, // 82
    0x17, // 84 T
    0x40, // 86 F7
    0x3C, // 88 F3
    0x00, // 8A
    0xE1, // 8C Left Shift
    0x30, // 8E ]
    0x39, // 90 CapsLock
    0x00, // 92
    0x29, // 94 Escape
    0x2F, // 96 [
    0x2B, // 98 Tab
    0x00, // 9A
    0x2A, // 9C Backspace
    0x1C, // 9E Y
    0x00, // A0
    0x00, // A2
    0x21, // A4 4
    0x26, // A6 9
    0x20, // A8 3
    0x44, // AA F11
    0x00, // AC
    0x25, // AE 8
    0x1F, // B0 2
    0x00, // B2
    0x46, // B4 PrintScreen
    0x27, // B6 0
    0x1E, // B8 1
    0x45, // BA F12
    0x43, // BC F10
    0x24, // BE 7
    0x00, // C0
    0x00, // C2
    0x0A, // C4 G
    0x00, // C6
    0x3D, // C8 F4
    0x00, // CA
    0x00, // CC
    0x00, // CE
    0x3E, // D0 F5
    0x52, // D2 Up
    0xE3, // D4 Left GUI
    0x34, // D6 '
    0x29, // D8 Escape
    0x48, // DA Pause
    0x3F, // DC F6
    0x0B, // DE H
    0x00, // E0
    0x00, // E2
    0x15, // E4 R
    0x12, // E6 O
    0x08, // E8 E
    0x00, // EA
    0x00, // EC
    0x0C, // EE I
    0x1A, // F0 W
    0x00, // F2
    0x53, // F4 Numlock
    0x13, // F6 P
    0x14, // F8 Q
    0x00, // FA
    0x00, // FC
    0x18, // FE U
};


// IR Keyboard State
uint8_t _state = 0;
uint16_t _code = 0;
uint8_t _wt_keys[6] = {0};
uint8_t _wt_expire[6] = {0};
uint8_t _wt_modifiers = 0;

static
uint8_t parity_check(uint8_t k)
{
    uint8_t c;
    uint8_t v = k;
    for (c = 0; v; c++)
      v &= v-1;
    return (c & 1) ? k : 0;
}

// make a mask from modifier keys
static
uint8_t ctrl_mask(uint8_t k)
{
    switch (k) {
        case 0x38:  return KEY_MOD_LCTRL;
        case 0x8C:  return KEY_MOD_LSHIFT;
        case 0x18:  return KEY_MOD_LALT;
        case 0xD4:  return KEY_MOD_LGUI;
        case 0x2A:  return KEY_MOD_RCTRL;
        case 0x4C:  return KEY_MOD_RSHIFT;
        case 0x14:  return KEY_MOD_RALT;
        case 0x62:  return KEY_MOD_RGUI;
    }
    return 0;
}

// update state of held keys
// produce a hid keyboard record
int get_hid_webtv(uint8_t* dst)
{
    bool dirty = false;
    uint8_t k = parity_check(_keyUp);
    _keyUp = 0;
    if (k) {
       _wt_modifiers &= ~ctrl_mask(k);
        for (int i = 0; i < 6; i++) {
            if (_wt_keys[i] == k) {
                _wt_expire[i] = 1;  // key will expire this frame
                break;
            }
        }
    }

    k = parity_check(_keyDown);
    _keyDown = 0;
    if (k) {
        _wt_modifiers |= ctrl_mask(k);

        // insert key into list of pressed keys
        int j = 0;
        for (int i = 0; i < 6; i++) {
            if ((_wt_keys[i] == 0) || (_wt_expire[i] == 0) || (_wt_keys[i] == k)) {
                j = i;
                break;
            }
            if (_wt_expire[i] < _wt_expire[j])
                j = i;
        }
        if (_wt_keys[j] != k) {
            _wt_keys[j] = k;
            dirty = true;
        }
        _wt_expire[j] = 8;  // key will be down for ~130ms or 8 frames
    }

    // generate hid keyboard events if anything was pressed or changed...
    // A1 01 mods XX k k k k k k
    dst[0] = 0xA1;
    dst[1] = 0x01;
    dst[2] = _wt_modifiers;
    dst[3] = 0;
    int j = 0;
    for (int i = 0; i < 6; i++) {
        dst[4+i] = 0;
        if (_wt_expire[i]) {
            if (!--_wt_expire[i])
                dirty = true;
        }
        if (_wt_expire[i] == 0) {
            _wt_keys[i] = 0;
        } else {
            dst[4 + j++] = _ir2scancode[_wt_keys[i] >> 1];
        }
    }
    return dirty ? 10 : 0;
}

// WebTV UART like keyboard protocol
// 3.25 bit 0 start preamble the 19 bits
// 10 bit code for keyup, keydown, all keys released etc
// 7 bit keycode + parity bit.
//

#define KEYDOWN     0x4A
#define KEYUP       0x5E
void IRAM_ATTR ir_webtv(uint8_t t, uint8_t v)
{
  if (_state == 0)
  {
    if (WT_PREAMBLE(t) && (v == 0))  // long 0, rising edge of start bit
      _state = 1;
  }
  else if (_state == 1)
  {
    _state = (SHORTBIT(t) && (v == 1)) ? 2 : 0;
  }
  else
  {
      t += BAUDB>>1;
      uint8_t bits = _state-2;
      while ((t > BAUDB) && (bits < 16))
      {
          t -= BAUDB;
          _code = (_code << 1) | v;
          bits++;
      }
      if (bits == 16)
      {
        v = t <= BAUDB;
        uint8_t md = _code >> 8;
        _code |= v;                 // Low bit of code is is parity
        if (md == KEYDOWN)
            _keyDown = _code;
        else if (md == KEYUP)
            _keyUp = _code;
        _state = 0;                 // got one
        return;
      }
      _state = bits+2;
    }
}
#endif

// called from interrupt
void IRAM_ATTR ir_event(uint8_t t, uint8_t v)
{
#ifdef WEBTV_KEYBOARD
    ir_webtv(t,v);
#endif
#ifdef RETCON_CONTROLLER
    ir_retcon(t,v);
#endif
#ifdef APPLE_TV_CONTROLLER
    ir_apple(t,v);
#endif
#ifdef FLASHBACK_CONTROLLER
    ir_flashback(t,v);
#endif
}

// called every frame from emu
int get_hid_ir(uint8_t* dst)
{
    int n = 0;
#ifdef APPLE_TV_CONTROLLER
    if (n = get_hid_apple(dst))
        return n;
#endif
#ifdef RETCON_CONTROLLER
    if (n = get_hid_retcon(dst))
        return n;
#endif
#ifdef FLASHBACK_CONTROLLER
    if (n = get_hid_flashback(dst))
        return n;
#endif
#ifdef NES_CONTROLLER
    if (n = get_hid_nes(dst))
        return n;
#endif
#ifdef SNES_CONTROLLER
    if (n = get_hid_snes(dst))
        return n;
#endif
#ifdef WEBTV_KEYBOARD
        return get_hid_webtv(dst);
#endif
	return 0;
}
#endif

original:

/* Copyright (c) 2010-2020, Peter Barrett
 **
 ** Permission to use, copy, modify, and/or distribute this software for
 ** any purpose with or without fee is hereby granted, provided that the
 ** above copyright notice and this permission notice appear in all copies.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 ** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 ** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
 ** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 ** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 ** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 ** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 ** SOFTWARE.
 */

#ifndef __ir_input__
#define __ir_input__

// generate events on state changes of IR pin, feed to state machines.
// event timings  have a resolution of HSYNC, timing is close enough between 15720 and 15600 to make this work
// poll to synthesize hid events at every frame
// i know there is a perfectly good peripheral for this in the ESP32 but this seems more fun somehow

uint8_t _ir_last = 0;
uint8_t _ir_count = 0;
uint8_t _keyDown = 0;
uint8_t _keyUp = 0;

void IRAM_ATTR ir_event(uint8_t ticks, uint8_t value); // t is HSYNCH ticks, v is value

inline void IRAM_ATTR ir_sample()
{
    uint8_t ir = (GPIO.in & (1 << IR_PIN)) != 0;
    if (ir != _ir_last)
    {
        ir_event(_ir_count,_ir_last);
        _ir_count = 0;
        _ir_last = ir;
    }
    if (_ir_count != 0xFF)
        _ir_count++;
}

class IRState {
public:
    uint8_t _state = 0;
    uint16_t _code = 0;
    uint16_t _output = 0;
    uint16_t _joy[2] = {0};
    uint16_t _joy_last[2] = {0};
    uint8_t _timer[2] = {0};

    void set(int i, int m, int t)
    {
        uint8_t b = 0;
        if ((m & (GENERIC_LEFT | GENERIC_RIGHT)) == (GENERIC_LEFT | GENERIC_RIGHT))
            b++; // bogus?
        if ((m & (GENERIC_UP | GENERIC_DOWN)) == (GENERIC_UP | GENERIC_DOWN))
            b++; // bogus?
        if (b) {
            //printf("bogus:%04X\n",m);
            return;
        }

        _joy[i] = m;
        _timer[i] = t;
    }

    // make a fake hid event
    int get_hid(uint8_t* dst)
    {
        if (_timer[0] && !--_timer[0])
            _joy[0] = 0;
        if (_timer[1] && !--_timer[1])
            _joy[1] = 0;
        if ((_joy[0] != _joy_last[0]) || (_joy[1] != _joy_last[1])) {
            _joy_last[0] = _joy[0];
            _joy_last[1] = _joy[1];
            dst[0] = 0xA1;
            dst[1] = 0x42;
            dst[2] = _joy[0];
            dst[3] = _joy[0]>>8;
            dst[4] = _joy[1];
            dst[5] = _joy[1]>>8;
            return 6;           // only sent if it changes
        }
        return 0;
    }
};

//==================================================================
//Classic hard wired NES controllers
//==================================================================
#ifdef NES_CONTROLLER
const uint16_t map_nes[8] = 
{
	GENERIC_FIRE | GENERIC_FIRE_A,	//NES_A
	GENERIC_FIRE_B,	//NES_B
	GENERIC_SELECT,	//NES_SELECT
	GENERIC_START,	//NES_START
	GENERIC_UP,		//NES_UP
	GENERIC_DOWN,	//NES_DOWN
	GENERIC_LEFT,	//NES_LEFT
	GENERIC_RIGHT	//NES_RIGHT
};

IRState _nes;
int get_hid_nes(uint8_t* dst)
{
	digitalWrite(NES_CTRL_LATCH, 1);
	delayMicroseconds(0);
	digitalWrite(NES_CTRL_LATCH, 0);
	delayMicroseconds(0);

	uint16_t buttonsA = 0;
	uint16_t buttonsB = 0;
	for (int i = 0; i < 8; i++)
		{
			buttonsA |= (1^digitalRead(NES_CTRL_ADATA)) * map_nes[i];
			buttonsB |= (1^digitalRead(NES_CTRL_BDATA)) * map_nes[i];
			digitalWrite(NES_CTRL_CLK, 0);
			delayMicroseconds(0);
			digitalWrite(NES_CTRL_CLK, 1);
			delayMicroseconds(0);
		}
	//printf("NESCTRL:%04X %04X\n", buttonsA, buttonsB);
  
	//Setup Controller A
	if (buttonsA == (GENERIC_LEFT | GENERIC_SELECT)) buttonsA |= GENERIC_OTHER;	//Press LEFT & SELECT to open file menu
	_nes.set(0,buttonsA,0); // no repeat period

	//Setup Controller B
	_nes.set(1,buttonsB,0); // no repeat period
  
  return _nes.get_hid(dst);		
}
#endif

//==================================================================
//Classic hard wired SNES controllers
//==================================================================
#ifdef SNES_CONTROLLER
const uint16_t map_snes[12] = 
{
	GENERIC_FIRE_B,	//SNES_B
	GENERIC_FIRE_C,	//SNES_Y
	GENERIC_SELECT,	//SNES_SELECT
	GENERIC_START,	//SNES_START
	GENERIC_UP,		//SNES_UP
	GENERIC_DOWN,	//SNES_DOWN
	GENERIC_LEFT,	//SNES_LEFT
	GENERIC_RIGHT,	//SNES_RIGHT
	GENERIC_FIRE | GENERIC_FIRE_A,	//SNES_A
	GENERIC_FIRE_X,	//SNES_X
	GENERIC_FIRE_Y,	//SNES_L
	GENERIC_FIRE_Z	//SNES_R
};

IRState _snes;
int get_hid_snes(uint8_t* dst)
{
	digitalWrite(NES_CTRL_LATCH, 1);
	delayMicroseconds(0);
	digitalWrite(NES_CTRL_LATCH, 0);
	delayMicroseconds(0);

	uint16_t buttonsA = 0;
	uint16_t buttonsB = 0;
	for (int i = 0; i < 12; i++)
		{
			buttonsA |= (1^digitalRead(NES_CTRL_ADATA)) * map_snes[i];
			buttonsB |= (1^digitalRead(NES_CTRL_BDATA)) * map_snes[i];
			digitalWrite(NES_CTRL_CLK, 0);
			delayMicroseconds(0);
			digitalWrite(NES_CTRL_CLK, 1);
			delayMicroseconds(0);
		}
	//printf("SNESCTRL:%04X %04X\n", buttonsA, buttonsB);
  
	//Setup Controller A
	if (buttonsA == (GENERIC_LEFT | GENERIC_SELECT)) buttonsA |= GENERIC_OTHER;	//Press LEFT & SELECT to open file menu
	_snes.set(0,buttonsA,0); // no repeat period

	//Setup Controller B
	_snes.set(1,buttonsB,0); // no repeat period
  
  return _snes.get_hid(dst);		
}
#endif

//==========================================================
//==========================================================
// Apple remote NEC code
// pretty easy to adapt to any NEC remote

#ifdef APPLE_TV_CONTROLLER

// Silver apple remote, 7 Bit code
// should work with both silvers and white
#define APPLE_MENU      0x40
#define APPLE_PLAY      0x7A
#define APPLE_CENTER    0x3A
#define APPLE_RIGHT     0x60
#define APPLE_LEFT      0x10
#define APPLE_UP        0x50
#define APPLE_DOWN      0x30

#define APPLE_RELEASE   0x20 // sent after menu and play?

//    generic repeat code
#define NEC_REPEAT    0xAAAA

/*
  9ms preamble ~142
  4.5ms 1 ~71 - start
  2.25ms ~35 - repeat

  32 bits
  0 – a 562.5µs/562.5µs   9ish wide
  1 – a 562.5µs/1.6875ms  27ish wide
*/

IRState _apple;
int get_hid_apple(uint8_t* dst)
{
    if (_apple._output)
    {
        if (_apple._output != NEC_REPEAT)
            _keyDown = (_apple._output >> 8) & 0x7F;  // 7 bit code
        _apple._output = 0;

        uint16_t k = 0;
        switch (_keyDown) {
            case APPLE_UP:     k = GENERIC_UP;     break;
            case APPLE_DOWN:   k = GENERIC_DOWN;   break;
            case APPLE_LEFT:   k = GENERIC_LEFT;   break;
            case APPLE_RIGHT:  k = GENERIC_RIGHT;  break;
            case APPLE_CENTER: k = GENERIC_FIRE;   break;
            case APPLE_MENU:   k = GENERIC_RESET;  break;
            case APPLE_PLAY:   k = GENERIC_SELECT; break;
        }
        _apple.set(0,k,15); // 108ms repeat period
    }
    return _apple.get_hid(dst);
}

// NEC codes used by apple remote
void IRAM_ATTR ir_apple(uint8_t t, uint8_t v)
{
  if (!v) {
    if (t > 32)
      _apple._state = 0;
  } else {
    if (t < 32)
    {
      _apple._code <<= 1;
      if (t >= 12)
        _apple._code |= 1;
      if (++_apple._state == 32)
        _apple._output = _apple._code;    // Apple code in bits 14-8*
    } else {
        if (t > 32 && t < 40 && !_apple._state)  // Repeat 2.25ms pulse 4.5ms start
          _apple._output = NEC_REPEAT;
        _apple._state = 0;
    }
  }
}

#endif

//==========================================================
//==========================================================
//  Atari Flashback 4 wireless controllers

#ifdef FLASHBACK_CONTROLLER

// HSYNCH period is 44/315*455 or 63.55555..us
// 18 bit code 1.87khz clock
// 2.3ms zero preamble  // 36
// 0 is 0.27ms pulse    // 4
// 1 is 0.80ms pulse    // 13

// Keycodes...
// Leading bit is 1 for player 1 control..

#define PREAMBLE(_t) (_t >= 34 && _t <= 38)
#define SHORT(_t)    (_t >= 2 && _t <= 6)
#define LONG(_t)     (_t >= 11 && _t <= 15)

// Codes come in pairs 33ms apart
// Sequence repeats every 133ms
// bitmap is released if no code for 10 vbls (167ms) or 0x01 (p1) / 0x0F (p2)
// up to 12 button bits, 4 bits of csum/p1/p2 indication

//  called at every loop ~60Hz

IRState _flashback;
int get_hid_flashback(uint8_t* dst)
{
    if (_flashback._output)
    {
        uint16_t m = _flashback._output >> 4;        // 12 bits of buttons
        printf("F:%04X\n",m);
        uint8_t csum = _flashback._output & 0xF;     // csum+1 for p1, csum-1 for p2
        uint8_t s = m + (m >> 4) + (m >> 8);
        if (((s+1) & 0xF) == csum)
            _flashback.set(0,m,15);
        else if (((s-1) & 0xF) == csum)
            _flashback.set(1,m,20);
        _flashback._output = 0;
    }
    return _flashback.get_hid(dst);
}

void IRAM_ATTR ir_flashback(uint8_t t, uint8_t v)
{
  if (_flashback._state == 0)
  {
    if (PREAMBLE(t) && (v == 0))  // long 0, rising edge of start bit
    {
      _flashback._state = 1;
    }
  }
  else
  {
    if (v)
    {
      _flashback._code <<= 1;
      if (LONG(t))
      {
        _flashback._code |= 1;
      }
      else if (!SHORT(t))
      {
        _flashback._state = 0;  // framing error
        return;
      }

      if (++_flashback._state == 19)
      {
        _flashback._output = _flashback._code;
        _flashback._state = 0;
      }
    }
    else
    {
      if (!SHORT(t))
        _flashback._state = 0;  // Framing err
    }
  }
}

#endif

//==========================================================
//==========================================================
// RETCON controllers
// 75ms keyboard repeat
// Preamble is 0.80ms low, 0.5 high
// Low: 0.57ms=0,0.27,s=1, high 0.37
// 16 bits
// Preamble = 800,500/63.55555 ~ 12.6,7.87
// LOW0 = 8.97
// LOW1 = 4.25
// HIGH = 5.82

#ifdef RETCON_CONTROLLER

// number of 63.55555 cycles per bit
#define PREAMBLE_L(_t) (_t >= 12 && _t <= 14) // 12/13/14 preamble
#define PREAMBLE_H(_t) (_t >= 6 && _t <= 10)  // 8
#define LOW_0(_t)     (_t >= 8 && _t <= 10)   // 8/9/10
#define LOW_1(_t)     (_t >= 4 && _t <= 6)    // 4/5/6

// map retcon to generic
const uint16_t _jmap[] = {
  0x0400, GENERIC_UP,
  0x0200, GENERIC_DOWN,
  0x0100, GENERIC_LEFT,
  0x0080, GENERIC_RIGHT,

  0x1000, GENERIC_SELECT,
  0x0800, GENERIC_START,

  0x0020, GENERIC_FIRE_X,
  0x0040, GENERIC_FIRE_Y,
  0x0002, GENERIC_FIRE_Z,

  0x2000, GENERIC_FIRE_A,
  0x4000, GENERIC_FIRE_B,
  0x0008, GENERIC_FIRE_C,
};

IRState _retcon;
int get_hid_retcon(uint8_t* dst)
{
    if (_retcon._output)
    {
        uint16_t m = 0;
        const uint16_t* jmap = _jmap;
        int16_t i = 12;
        uint16_t k = _retcon._output;
        _retcon._output = 0;
        while (i--)
        {
            if (jmap[0] & k)
                m |= jmap[1];
            jmap += 2;
        }
        printf("R:%04X\n",m);
        _retcon.set(k >> 15,m,20);
    }
    return _retcon.get_hid(dst);
}

void IRAM_ATTR ir_retcon(uint8_t t, uint8_t v)
{
  if (_retcon._state == 0)
  {
    if (v == 0)  {   // start bit
      if (PREAMBLE_L(t))
        _retcon._state = 1;
    }
  }
  else
  {
    if (!v)
    {
      _retcon._code <<= 1;
      if (LOW_1(t))
        _retcon._code |= 1;
      if (_retcon._state++ == 16)
      {
        _retcon._output = _retcon._code;
        _retcon._state = 0;
      }
    }
  }
}

#endif


//==========================================================
//==========================================================
//  Webtv keyboard
#ifdef WEBTV_KEYBOARD

#define BAUDB   12  // Width of uart bit in HSYNCH
#define WT_PREAMBLE(_t) (_t >= 36 && _t <= 40)   // 3.25 baud
#define SHORTBIT(_t) (_t >= 9 && _t <= 13)     // 1.5ms ish

// converts webtv keyboard to common scancodes
const uint8_t _ir2scancode[128] = {
    0x00, // 00
    0x00, // 02
    0x05, // 04 B
    0x00, // 06
    0x00, // 08
    0x51, // 0A Down
    0x00, // 0C
    0x00, // 0E
    0x00, // 10
    0x50, // 12 Left
    0xE6, // 14 Right Alt
    0x38, // 16 /
    0xE2, // 18 Left Alt
    0x4F, // 1A Right
    0x2C, // 1C Space
    0x11, // 1E N
    0x32, // 20 #
    0x00, // 22
    0x22, // 24 5
    0x41, // 26 F8
    0x3B, // 28 F2
    0xE4, // 2A Right Ctrl
    0x00, // 2C
    0x2E, // 2E =
    0x3A, // 30 F1
    0x4A, // 32 Home
    0x00, // 34
    0x2D, // 36 -
    0xE0, // 38 Left Ctrl
    0x35, // 3A `
    0x42, // 3C F9
    0x23, // 3E 6
    0x00, // 40
    0x00, // 42
    0x19, // 44 V
    0x37, // 46 .
    0x06, // 48 C
    0x68, // 4A F13
    0xE5, // 4C Right Shift
    0x36, // 4E ,
    0x1B, // 50 X
    0x4D, // 52 End
    0x00, // 54
    0x00, // 56
    0x1D, // 58 Z
    0x00, // 5A
    0x28, // 5C Return
    0x10, // 5E M
    0x00, // 60
    0xE7, // 62 Right GUI
    0x09, // 64 F
    0x0F, // 66 L
    0x07, // 68 D
    0x4E, // 6A PageDown
    0x00, // 6C
    0x0E, // 6E K
    0x16, // 70 S
    0x4B, // 72 PageUp
    0x00, // 74
    0x33, // 76 ;
    0x04, // 78 A
    0x00, // 7A
    0x31, // 7C |
    0x0D, // 7E J
    0x00, // 80
    0x00, // 82
    0x17, // 84 T
    0x40, // 86 F7
    0x3C, // 88 F3
    0x00, // 8A
    0xE1, // 8C Left Shift
    0x30, // 8E ]
    0x39, // 90 CapsLock
    0x00, // 92
    0x29, // 94 Escape
    0x2F, // 96 [
    0x2B, // 98 Tab
    0x00, // 9A
    0x2A, // 9C Backspace
    0x1C, // 9E Y
    0x00, // A0
    0x00, // A2
    0x21, // A4 4
    0x26, // A6 9
    0x20, // A8 3
    0x44, // AA F11
    0x00, // AC
    0x25, // AE 8
    0x1F, // B0 2
    0x00, // B2
    0x46, // B4 PrintScreen
    0x27, // B6 0
    0x1E, // B8 1
    0x45, // BA F12
    0x43, // BC F10
    0x24, // BE 7
    0x00, // C0
    0x00, // C2
    0x0A, // C4 G
    0x00, // C6
    0x3D, // C8 F4
    0x00, // CA
    0x00, // CC
    0x00, // CE
    0x3E, // D0 F5
    0x52, // D2 Up
    0xE3, // D4 Left GUI
    0x34, // D6 '
    0x29, // D8 Escape
    0x48, // DA Pause
    0x3F, // DC F6
    0x0B, // DE H
    0x00, // E0
    0x00, // E2
    0x15, // E4 R
    0x12, // E6 O
    0x08, // E8 E
    0x00, // EA
    0x00, // EC
    0x0C, // EE I
    0x1A, // F0 W
    0x00, // F2
    0x53, // F4 Numlock
    0x13, // F6 P
    0x14, // F8 Q
    0x00, // FA
    0x00, // FC
    0x18, // FE U
};


// IR Keyboard State
uint8_t _state = 0;
uint16_t _code = 0;
uint8_t _wt_keys[6] = {0};
uint8_t _wt_expire[6] = {0};
uint8_t _wt_modifiers = 0;

static
uint8_t parity_check(uint8_t k)
{
    uint8_t c;
    uint8_t v = k;
    for (c = 0; v; c++)
      v &= v-1;
    return (c & 1) ? k : 0;
}

// make a mask from modifier keys
static
uint8_t ctrl_mask(uint8_t k)
{
    switch (k) {
        case 0x38:  return KEY_MOD_LCTRL;
        case 0x8C:  return KEY_MOD_LSHIFT;
        case 0x18:  return KEY_MOD_LALT;
        case 0xD4:  return KEY_MOD_LGUI;
        case 0x2A:  return KEY_MOD_RCTRL;
        case 0x4C:  return KEY_MOD_RSHIFT;
        case 0x14:  return KEY_MOD_RALT;
        case 0x62:  return KEY_MOD_RGUI;
    }
    return 0;
}

// update state of held keys
// produce a hid keyboard record
int get_hid_webtv(uint8_t* dst)
{
    bool dirty = false;
    uint8_t k = parity_check(_keyUp);
    _keyUp = 0;
    if (k) {
       _wt_modifiers &= ~ctrl_mask(k);
        for (int i = 0; i < 6; i++) {
            if (_wt_keys[i] == k) {
                _wt_expire[i] = 1;  // key will expire this frame
                break;
            }
        }
    }

    k = parity_check(_keyDown);
    _keyDown = 0;
    if (k) {
        _wt_modifiers |= ctrl_mask(k);

        // insert key into list of pressed keys
        int j = 0;
        for (int i = 0; i < 6; i++) {
            if ((_wt_keys[i] == 0) || (_wt_expire[i] == 0) || (_wt_keys[i] == k)) {
                j = i;
                break;
            }
            if (_wt_expire[i] < _wt_expire[j])
                j = i;
        }
        if (_wt_keys[j] != k) {
            _wt_keys[j] = k;
            dirty = true;
        }
        _wt_expire[j] = 8;  // key will be down for ~130ms or 8 frames
    }

    // generate hid keyboard events if anything was pressed or changed...
    // A1 01 mods XX k k k k k k
    dst[0] = 0xA1;
    dst[1] = 0x01;
    dst[2] = _wt_modifiers;
    dst[3] = 0;
    int j = 0;
    for (int i = 0; i < 6; i++) {
        dst[4+i] = 0;
        if (_wt_expire[i]) {
            if (!--_wt_expire[i])
                dirty = true;
        }
        if (_wt_expire[i] == 0) {
            _wt_keys[i] = 0;
        } else {
            dst[4 + j++] = _ir2scancode[_wt_keys[i] >> 1];
        }
    }
    return dirty ? 10 : 0;
}

// WebTV UART like keyboard protocol
// 3.25 bit 0 start preamble the 19 bits
// 10 bit code for keyup, keydown, all keys released etc
// 7 bit keycode + parity bit.
//

#define KEYDOWN     0x4A
#define KEYUP       0x5E
void IRAM_ATTR ir_webtv(uint8_t t, uint8_t v)
{
  if (_state == 0)
  {
    if (WT_PREAMBLE(t) && (v == 0))  // long 0, rising edge of start bit
      _state = 1;
  }
  else if (_state == 1)
  {
    _state = (SHORTBIT(t) && (v == 1)) ? 2 : 0;
  }
  else
  {
      t += BAUDB>>1;
      uint8_t bits = _state-2;
      while ((t > BAUDB) && (bits < 16))
      {
          t -= BAUDB;
          _code = (_code << 1) | v;
          bits++;
      }
      if (bits == 16)
      {
        v = t <= BAUDB;
        uint8_t md = _code >> 8;
        _code |= v;                 // Low bit of code is is parity
        if (md == KEYDOWN)
            _keyDown = _code;
        else if (md == KEYUP)
            _keyUp = _code;
        _state = 0;                 // got one
        return;
      }
      _state = bits+2;
    }
}
#endif

// called from interrupt
void IRAM_ATTR ir_event(uint8_t t, uint8_t v)
{
#ifdef WEBTV_KEYBOARD
    ir_webtv(t,v);
#endif
#ifdef RETCON_CONTROLLER
    ir_retcon(t,v);
#endif
#ifdef APPLE_TV_CONTROLLER
    ir_apple(t,v);
#endif
#ifdef FLASHBACK_CONTROLLER
    ir_flashback(t,v);
#endif
}

// called every frame from emu
int get_hid_ir(uint8_t* dst)
{
    int n = 0;
#ifdef APPLE_TV_CONTROLLER
    if (n = get_hid_apple(dst))
        return n;
#endif
#ifdef RETCON_CONTROLLER
    if (n = get_hid_retcon(dst))
        return n;
#endif
#ifdef FLASHBACK_CONTROLLER
    if (n = get_hid_flashback(dst))
        return n;
#endif
#ifdef NES_CONTROLLER
    if (n = get_hid_nes(dst))
        return n;
#endif
#ifdef SNES_CONTROLLER
    if (n = get_hid_snes(dst))
        return n;
#endif
#ifdef WEBTV_KEYBOARD
        return get_hid_webtv(dst);
#endif
	return 0;
}
#endif

the complete code is here GitHub - CornN64/esp_8_bit: Atari 8 bit computers, NES and SMS game consoles on your TV with nothing more than a ESP32 and a sense of nostalgia

I tried with a 10k resistor but everything remains the same

Can you print the value of the following to the monitor...

digitalRead(A_START_PIN)

The esp32 works fine, the poor thing with the following code and a 1k resistor and it reads the pin fine, with a 10k resistor it doesn't work very well.

#define A_START_PIN 34
int VALUE;
void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
pinMode(A_START_PIN, INPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
VALUE=digitalRead(A_START_PIN);
Serial.println(VALUE);
delay(500);
}

Where do you declare it as INPUT in your full version of the code?

I really have no idea, the code is not mine, I took it from github, I imagine that whoever designed it would have declared that somewhere

I was thinking about your question and it occurred to me that pin 34 might not be declared so I tried with pin 21 which is already declared in the code as NES_CTRL_ADATA and replaced it with A_START_PIN and to my surprise it worked, the new pins need to be configured as INPUT, otherwise it won't work.

Your code worked great, I just had to declare the pins as INPUT in Void_setup(), thanks a lot.

hi, I'm a beginner, I'm also trying to add the buttons via GPIO to cornN64/esp_8_bit, like you did, following this topic,
but i'm doing something wrong,
I tried declaring the inputs in the void setup(), but they don't activate,
I also tried declaring them in video-out.h (where the pinMode of the NES controllers are declared),
but still nothing...
i'm using wemos lolin32 lite (it doesn't have pin 21)
...ask for help or suggestions... thanks
(google traslate)

/* Copyright (c) 2020, Peter Barrett
**
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/

#include "esp_system.h"
#include "esp_int_wdt.h"
#include "esp_spiffs.h"

#define PERF  // some stats about where we spend our time
#include "src/emu.h"
#include "src/video_out.h"

// esp_8_bit
// Atari 8 computers, NES and SMS game consoles on your TV with nothing more than a ESP32 and a sense of nostalgia
// Supports NTSC/PAL composite video, Bluetooth Classic keyboards and joysticks

//  Choose one of the video standards: PAL,NTSC
#define VIDEO_STANDARD PAL

//  Choose one of the following emulators: EMU_NES,EMU_SMS,EMU_ATARI
#define EMULATOR EMU_NES

//  Many emus work fine on a single core (S2), file system access can cause a little flickering
//  #define SINGLE_CORE

// The filesystem should contain folders named for each of the emulators i.e.
//    atari800
//    nofrendo
//    smsplus
// Folders will be auto-populated on first launch with a built in selection of sample media.
// Use 'ESP32 Sketch Data Upload' from the 'Tools' menu to copy a prepared data folder to ESP32

// Create a new emulator, messy ifdefs ensure that only one links at a time
Emu* NewEmulator()
{  
  #if (EMULATOR==EMU_NES)
  return NewNofrendo(VIDEO_STANDARD);
  #endif
  #if (EMULATOR==EMU_SMS)
  return NewSMSPlus(VIDEO_STANDARD);
  #endif
  #if (EMULATOR==EMU_ATARI)
  return NewAtari800(VIDEO_STANDARD);
  #endif
  printf("Must choose one of the following emulators: EMU_NES,EMU_SMS,EMU_ATARI\n");
}

Emu* _emu = 0;            // emulator running on core 0
uint32_t _frame_time = 0;
uint32_t _drawn = 1;
bool _inited = false;

void emu_init()
{
    std::string folder = "/" + _emu->name;
    gui_start(_emu,folder.c_str());
    _drawn = _frame_counter;
}

void emu_loop()
{
    // wait for blanking before drawing to avoid tearing
    video_sync();

    // Draw a frame, update sound, process hid events
    uint32_t t = xthal_get_ccount();
    gui_update();
    _frame_time = xthal_get_ccount() - t;
    _lines = _emu->video_buffer();
    _drawn++;
}

// dual core mode runs emulator on comms core
void emu_task(void* arg)
{
    printf("emu_task %s running on core %d at %dmhz\n",
      _emu->name.c_str(),xPortGetCoreID(),rtc_clk_cpu_freq_value(rtc_clk_cpu_freq_get()));
    emu_init();
    for (;;)
      emu_loop();
}

esp_err_t mount_filesystem()
{
  printf("\n\n\nesp_8_bit\n\nmounting spiffs (will take ~15 seconds if formatting for the first time)....\n");
  uint32_t t = millis();
  esp_vfs_spiffs_conf_t conf = {
    .base_path = "",
    .partition_label = NULL,
    .max_files = 5,
    .format_if_mount_failed = true  // force?
  };
  esp_err_t e = esp_vfs_spiffs_register(&conf);
  if (e != 0)
    printf("Failed to mount or format filesystem: %d. Use 'ESP32 Sketch Data Upload' from 'Tools' menu\n",e);
  vTaskDelay(1);
  printf("... mounted in %d ms\n",millis()-t);
  return e;
}

void setup()
{ 
  rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M);  
  mount_filesystem();                       // mount the filesystem!
  _emu = NewEmulator();                     // create the emulator!
  hid_init("emu32");                        // bluetooth hid on core 1!

  #ifdef SINGLE_CORE
  emu_init();
  video_init(_emu->cc_width,_emu->flavor,_emu->composite_palette(),_emu->standard); // start the A/V pump on app core
  #else
  xTaskCreatePinnedToCore(emu_task, "emu_task", EMULATOR == EMU_NES ? 5*1024 : 3*1024, NULL, 0, NULL, 0); // nofrendo needs 5k word stack, start on core 0
  #endif

 //aggiunti tasti
  pinMode(A_START_PIN, INPUT);
  pinMode(A_FIRE_PIN, INPUT);
  pinMode(B_FIRE_PIN, INPUT);
  pinMode(A_SELECT_PIN, INPUT);
  pinMode(A_UP_PIN, INPUT);
  pinMode(A_DOWN_PIN, INPUT);
  pinMode(A_LEFT_PIN, INPUT);
  pinMode(A_RIGHT_PIN, INPUT);

}

#ifdef PERF
void perf()
{
  static int _next = 0;
  if (_drawn >= _next) {
    float elapsed_us = 120*1000000/(_emu->standard ? 60 : 50);
    _next = _drawn + 120;
    
    printf("frame_time:%d drawn:%d displayed:%d blit_ticks:%d->%d, isr time:%2.2f%%\n",
      _frame_time/240,_drawn,_frame_counter,_blit_ticks_min,_blit_ticks_max,(_isr_us*100)/elapsed_us);
      
    _blit_ticks_min = 0xFFFFFFFF;
    _blit_ticks_max = 0;
    _isr_us = 0;
  } 
}
#else
void perf(){};
#endif

// this loop always runs on app_core (1).
void loop()
{    
  #ifdef SINGLE_CORE
  emu_loop();
  #else
  // start the video after emu has started
  if (!_inited) {
    if (_lines) {
      printf("video_init\n");
      video_init(_emu->cc_width,_emu->flavor,_emu->composite_palette(),_emu->standard); // start the A/V pump
      _inited = true;
    } else {
      vTaskDelay(1);
    }
  }
  #endif
  
  // update the bluetooth edr/hid stack
  hid_update();

  // Dump some stats
  perf();
}

/* Copyright (c) 2020, Peter Barrett
**
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted, provided that the
** above copyright notice and this permission notice appear in all copies.
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
** SOFTWARE.
*/

#ifdef ESP_PLATFORM
#include "esp_types.h"
#include "esp_heap_caps.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_err.h"
#include "soc/gpio_reg.h"
#include "soc/rtc.h"
#include "soc/soc.h"
#include "soc/i2s_struct.h"
#include "soc/i2s_reg.h"
#include "soc/ledc_struct.h"
#include "soc/rtc_io_reg.h"
#include "soc/io_mux_reg.h"
#include "rom/gpio.h"
#include "rom/lldesc.h"
#include "driver/periph_ctrl.h"
#include "driver/dac.h"
#include "driver/gpio.h"
#include "driver/i2s.h"

#include "../config.h"

#ifdef IR_PIN || NES_CTRL_LATCH
#include "ir_input.h"  // ir & HW peripherals
#endif

//====================================================================================================
// low level HW setup of DAC/DMA/APLL/PWM
//====================================================================================================
lldesc_t _dma_desc[4] = {0};
intr_handle_t _isr_handle;

extern "C"
void IRAM_ATTR video_isr(volatile void* buf);

// simple isr
void IRAM_ATTR i2s_intr_handler_video(void *arg)
{
    if (I2S0.int_st.out_eof)
        video_isr(((lldesc_t*)I2S0.out_eof_des_addr)->buf); // get the next line of video
    I2S0.int_clr.val = I2S0.int_st.val;                     // reset the interrupt
}

static esp_err_t start_dma(int line_width,int samples_per_cc, int ch = 1)
{
    periph_module_enable(PERIPH_I2S0_MODULE);

    // setup interrupt
    if (esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM,
        i2s_intr_handler_video, 0, &_isr_handle) != ESP_OK)
        return -1;

    // reset conf
    I2S0.conf.val = 1;
    I2S0.conf.val = 0;
    I2S0.conf.tx_right_first = 1;
    I2S0.conf.tx_mono = (ch == 2 ? 0 : 1);

    I2S0.conf2.lcd_en = 1;
    I2S0.fifo_conf.tx_fifo_mod_force_en = 1;
    I2S0.sample_rate_conf.tx_bits_mod = 16;
    I2S0.conf_chan.tx_chan_mod = (ch == 2) ? 0 : 1;

    // Create TX DMA buffers
    for (int i = 0; i < 2; i++) {
        int n = line_width*2*ch;
        if (n >= 4092) {
            printf("DMA chunk too big:%s\n",n);
            return -1;
        }
        _dma_desc[i].buf = (uint8_t*)heap_caps_calloc(1, n, MALLOC_CAP_DMA);
        if (!_dma_desc[i].buf)
            return -1;
        
        _dma_desc[i].owner = 1;
        _dma_desc[i].eof = 1;
        _dma_desc[i].length = n;
        _dma_desc[i].size = n;
        _dma_desc[i].empty = (uint32_t)(i == 1 ? _dma_desc : _dma_desc+1);
    }
    I2S0.out_link.addr = (uint32_t)_dma_desc;

    //  Setup up the apll: See ref 3.2.7 Audio PLL
    //  f_xtal = (int)rtc_clk_xtal_freq_get() * 1000000;
    //  f_out = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536); // 250 < f_out < 500
    //  apll_freq = f_out/((o_div + 2) * 2)
    //  operating range of the f_out is 250 MHz ~ 500 MHz
    //  operating range of the apll_freq is 16 ~ 128 MHz.
    //  select sdm0,sdm1,sdm2 to produce nice multiples of colorburst frequencies

    //  see calc_freq() for math: (4+a)*10/((2 + b)*2) mhz
    //  up to 20mhz seems to work ok:
    //  rtc_clk_apll_enable(1,0x00,0x00,0x4,0);   // 20mhz for fancy DDS

#if VIDEO_STANDARD > 0	//NTSC
        switch (samples_per_cc) {
            case 3: rtc_clk_apll_enable(1,0x46,0x97,0x4,2);   break;    // 10.7386363636 3x NTSC (10.7386398315mhz)
            case 4: rtc_clk_apll_enable(1,0x46,0x97,0x4,1);   break;    // 14.3181818182 4x NTSC (14.3181864421mhz)
        }
#else	//PAL
        rtc_clk_apll_enable(1,0x04,0xA4,0x6,1);     // 17.734476mhz ~4x PAL
#endif

    I2S0.clkm_conf.clkm_div_num = 1;            // I2S clock divider’s integral value.
    I2S0.clkm_conf.clkm_div_b = 0;              // Fractional clock divider’s numerator value.
    I2S0.clkm_conf.clkm_div_a = 1;              // Fractional clock divider’s denominator value
    I2S0.sample_rate_conf.tx_bck_div_num = 1;
    I2S0.clkm_conf.clka_en = 1;                 // Set this bit to enable clk_apll.
    I2S0.fifo_conf.tx_fifo_mod = (ch == 2) ? 0 : 1; // 32-bit dual or 16-bit single channel data

    dac_output_enable(DAC_CHANNEL_1 );           // DAC, video on GPIO25
    dac_i2s_enable();                           // start DAC!

    I2S0.conf.tx_start = 1;                     // start DMA!
    I2S0.int_clr.val = 0xFFFFFFFF;
    I2S0.int_ena.out_eof = 1;
    I2S0.out_link.start = 1;
    return esp_intr_enable(_isr_handle);        // start interruprs!
}

void video_init_hw(int line_width, int samples_per_cc)
{
    // setup apll 4x NTSC or PAL colorburst rate
    start_dma(line_width,samples_per_cc,1);

    // Now ideally we would like to use the decoupled left DAC channel to produce audio
    // But when using the APLL there appears to be some clock domain conflict that causes
    // nasty digitial spikes and dropouts. You are also limited to a single audio channel.
    // So it is back to PWM/PDM and a 1 bit DAC for us. Good news is that we can do stereo
    // if we want to and have lots of different ways of doing nice noise shaping etc.

    // PWM audio out of pin 18 -> can be anything
    // lots of other ways, PDM by hand over I2S1, spi circular buffer etc
    // but if you would like stereo the led pwm seems like a fine choice
    // needs a simple rc filter (1k->1.2k resistor & 10nf->15nf cap work fine)

    // 18 ----/\/\/\/----|------- a out
    //          1k       |
    //                  ---
    //                  --- 10nf
    //                   |
    //                   v gnd

    ledcSetup(0,2000000,7);    // 625000 khz is as fast as we go w 7 bits
    ledcAttachPin(AUDIO_PIN, 0);
    ledcWrite(0,0);

#ifdef IR_PIN    //  IR input if used
    pinMode(IR_PIN,INPUT);

    

  
#endif

#ifdef NES_CTRL_LATCH	// Pin mappings for NES controller input

//aggiunti tasti
  pinMode(A_START_PIN, INPUT);
  pinMode(A_FIRE_PIN, INPUT);
  pinMode(B_FIRE_PIN, INPUT);
  pinMode(A_SELECT_PIN, INPUT);
  pinMode(A_UP_PIN, INPUT);
  pinMode(A_DOWN_PIN, INPUT);
  pinMode(A_LEFT_PIN, INPUT);
  pinMode(A_RIGHT_PIN, INPUT);

	pinMode(NES_CTRL_LATCH, GPIO_MODE_OUTPUT);
	digitalWrite(NES_CTRL_LATCH, 0);
	pinMode(NES_CTRL_CLK, GPIO_MODE_OUTPUT);
	digitalWrite(NES_CTRL_CLK, 1);
	pinMode(NES_CTRL_ADATA, INPUT_PULLUP);	//use pull up to avoid issues if controller is unplugged
	pinMode(NES_CTRL_BDATA, INPUT_PULLUP);	//use pull up to avoid issues if controller is unplugged

#endif

}

// send an audio sample every scanline (15720hz for ntsc, 15600hz for PAL)
inline void IRAM_ATTR audio_sample(uint8_t s)
{
    auto& reg = LEDC.channel_group[0].channel[0];
    reg.duty.duty = s << 4; // 25 bit (21.4)
    reg.conf0.sig_out_en = 1; // This is the output enable control bit for channel
    reg.conf1.duty_start = 1; // When duty_num duty_cycle and duty_scale has been configured. these register won't take effect until set duty_start. this bit is automatically cleared by hardware
    reg.conf0.clk_en = 1;
}

//  Appendix

/*
static void calc_freq(double f)
{
    f /= 1000000;
    printf("looking for sample rate of %fmhz\n",(float)f);
    int xtal_freq = 40;
    for (int o_div = 0; o_div < 3; o_div++) {
        float f_out = 4*f*((o_div + 2)*2);          // 250 < f_out < 500
        if (f_out < 250 || f_out > 500)
            continue;
        int sdm = round((f_out/xtal_freq - 4)*65536);
        float apll_freq = 40 * (4 + (float)sdm/65536)/((o_div + 2)*2);    // 16 < apll_freq < 128 MHz
        if (apll_freq < 16 || apll_freq > 128)
            continue;
        printf("f_out:%f %d:0x%06X %fmhz %f\n",f_out,o_div,sdm,apll_freq/4,f/(apll_freq/4));
    }
    printf("\n");
}

static void freqs()
{
    calc_freq(PAL_FREQUENCY*3);
    calc_freq(PAL_FREQUENCY*4);
    calc_freq(NTSC_FREQUENCY*3);
    calc_freq(NTSC_FREQUENCY*4);
    calc_freq(20000000);
}
*/

extern "C"
void* MALLOC32(int x, const char* label)
{
    printf("MALLOC32 %d free, %d biggest, allocating %s:%d\n",
      heap_caps_get_free_size(MALLOC_CAP_32BIT),heap_caps_get_largest_free_block(MALLOC_CAP_32BIT),label,x);
    void * r = heap_caps_malloc(x,MALLOC_CAP_32BIT);
    if (!r) {
        printf("MALLOC32 FAILED allocation of %s:%d!!!!####################\n",label,x);
        esp_restart();
    }
    else
        printf("MALLOC32 allocation of %s:%d %08X\n",label,x,r);
    return r;
}

#else	//ESP_PLATFORM
//====================================================================================================
//  Simulator
//====================================================================================================
#define IRAM_ATTR
#define DRAM_ATTR

void video_init_hw(int line_width, int samples_per_cc);

uint32_t xthal_get_ccount() {
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return lo;
    //return ((uint64_t)hi << 32) | lo;
}

void audio_sample(uint8_t s);

void ir_sample();

int get_hid_ir(uint8_t* buf)
{
    return 0;
}
#endif	//ESP_PLATFORM

//===================================================================================================
// ntsc tables
//===================================================================================================
// AA AA                // 2 pixels, 1 color clock - atari
// AA AB BB             // 3 pixels, 2 color clocks - nes
// AAA ABB BBC CCC      // 4 pixels, 3 color clocks - sms

// cc == 3 gives 684 samples per line, 3 samples per cc, 3 pixels for 2 cc
// cc == 4 gives 912 samples per line, 4 samples per cc, 2 pixels per cc
//====================================================================================================
//GLOBAL
//====================================================================================================
// Color clock frequency is 315/88 (3.57954545455)
// DAC_MHZ is 315/11 or 8x color clock
// 455/2 color clocks per line, round up to maintain phase
// HSYNCH period is 44/315*455 or 63.55555..us
// Field period is 262*44/315*455 or 16651.5555us

#define P0 (color >> 16)
#define P1 (color >> 8)
#define P2 (color)
#define P3 (color << 8)

#define NTSC_COLOR_CLOCKS_PER_SCANLINE 228       // really 227.5 for NTSC but want to avoid half phase fiddling for now
#define NTSC_FREQUENCY (315000000.0/88)
#define NTSC_LINES 262

#define PAL_COLOR_CLOCKS_PER_SCANLINE 284        // really 283.75 ?
#define PAL_FREQUENCY 4433618.75
#define PAL_LINES 312

#ifdef PERF
#define BEGIN_TIMING()  uint32_t t = cpu_ticks()
#define END_TIMING() t = cpu_ticks() - t; _blit_ticks_min = min(_blit_ticks_min,t); _blit_ticks_max = max(_blit_ticks_max,t);
#define ISR_BEGIN() uint32_t t = cpu_ticks()
#define ISR_END() t = cpu_ticks() - t;_isr_us += (t+120)/240;
uint32_t _blit_ticks_min = 0;
uint32_t _blit_ticks_max = 0;
uint32_t _isr_us = 0;
#else
#define BEGIN_TIMING()
#define END_TIMING()
#define ISR_BEGIN()
#define ISR_END()
#endif

uint8_t** _lines; // filled in by emulator
volatile int _line_counter = 0;
volatile int _frame_counter = 0;

int _active_lines;
int _line_count;

int _line_width;
int _samples_per_cc;
int _machine; // 2:1 3:2 4:3 3:4 input pixel to color clock ratio
const uint32_t* _palette;

float _sample_rate;

int _hsync;
int _hsync_long;
int _hsync_short;
int _burst_start;
int _burst_width;
int _active_start;

int16_t* _burst0 = 0; // pal bursts
int16_t* _burst1 = 0;

uint32_t cpu_ticks()
{
  return xthal_get_ccount();
}

uint32_t us() {
    return cpu_ticks()/240;
}

static int usec(float us)
{
    return _samples_per_cc * round(us * _sample_rate / _samples_per_cc);  // multiple of color clock, word align
}
//=====================================================================================
//AUDIO
//=====================================================================================
// audio is buffered as 6 bit unsigned samples
uint8_t _audio_buffer[1024];
uint32_t _audio_r = 0;
uint32_t _audio_w = 0;
void audio_write_16(const int16_t* s, int len, int channels)
{
    int b;
    while (len--) {
        if (_audio_w == (_audio_r + sizeof(_audio_buffer)))
            break;
        if (channels == 2) {
            b = (s[0] + s[1]) >> 9;
            s += 2;
        } else
            b = *s++ >> 8;
        if (b < -32) b = -32;
        if (b > 31) b = 31;
        _audio_buffer[_audio_w++ & (sizeof(_audio_buffer)-1)] = b + 32;
    }
}

// test pattern, must be ram
/*uint8_t _sin64[64] = {
    0x20,0x22,0x25,0x28,0x2B,0x2E,0x30,0x33,
    0x35,0x37,0x38,0x3A,0x3B,0x3C,0x3D,0x3D,
    0x3D,0x3D,0x3D,0x3C,0x3B,0x3A,0x38,0x37,
    0x35,0x33,0x30,0x2E,0x2B,0x28,0x25,0x22,
    0x20,0x1D,0x1A,0x17,0x14,0x11,0x0F,0x0C,
    0x0A,0x08,0x07,0x05,0x04,0x03,0x02,0x02,
    0x02,0x02,0x02,0x03,0x04,0x05,0x07,0x08,
    0x0A,0x0C,0x0F,0x11,0x14,0x17,0x1A,0x1D,
};
uint8_t _x;

// test the fancy DAC
void IRAM_ATTR test_wave(volatile void* vbuf, int t = 1)
{
    uint16_t* buf = (uint16_t*)vbuf;
    int n = _line_width;
    switch (t) {
        case 0: // f/64 sinewave
            for (int i = 0; i < n; i += 2) {
                buf[0^1] = GRAY_LEVEL + (_sin64[_x++ & 0x3F] << 8);
                buf[1^1] = GRAY_LEVEL + (_sin64[_x++ & 0x3F] << 8);
                buf += 2;
            }
            break;
        case 1: // fast square wave
            for (int i = 0; i < n; i += 2) {
                buf[0^1] = GRAY_LEVEL - (0x10 << 8);
                buf[1^1] = GRAY_LEVEL + (0x10 << 8);
                buf += 2;
            }
            break;
    }
}*/

#if VIDEO_STANDARD > 0
//=====================================================================================
//NTSC VIDEO
//=====================================================================================
void video_init(int samples_per_cc, int machine, const uint32_t* palette, int ntsc)
{
    _samples_per_cc = samples_per_cc;
    _machine = machine;
    _palette = palette;

    _sample_rate = 315.0/88 * samples_per_cc;   // DAC rate
    _line_width = NTSC_COLOR_CLOCKS_PER_SCANLINE*samples_per_cc;
    _line_count = NTSC_LINES;
    _hsync_long = usec(63.555-4.7);
    _active_start = usec(samples_per_cc == 4 ? 10 : 10.5);
    _hsync = usec(4.7);
    _active_lines = 240;
    video_init_hw(_line_width,_samples_per_cc);    // init the hardware
}

// draw a line of game in NTSC
void IRAM_ATTR blit(uint8_t* src, uint16_t* dst)
{
    uint32_t* d = (uint32_t*)dst;
    const uint32_t* p = _palette;
    uint32_t color,c;
    uint32_t mask = 0xFF;
    int i;

    BEGIN_TIMING();

    switch (_machine) {
        case EMU_ATARI:
            // 2 pixels per color clock, 4 samples per cc, used by atari
            // AA AA
            // 192 color clocks wide
            // only show 336 pixels
            src += 24;
            d += 16;
            for (i = 0; i < (384-48); i += 4) {
                uint32_t c = *((uint32_t*)src); // screen may be in 32 bit mem
                d[0] = p[(uint8_t)c];
                d[1] = p[(uint8_t)(c>>8)] << 8;
                d[2] = p[(uint8_t)(c>>16)];
                d[3] = p[(uint8_t)(c>>24)] << 8;
                d += 4;
                src += 4;
            }
            break;

        /*case EMU_NES:
            // 3 pixels to 2 color clocks, 3 samples per cc, used by nes
            // could be faster with better tables: 2953 cycles ish
            // about 18% of the core at 240Mhz
            // 170 color clocks wide: not all that attractive
            // AA AB BB
            for (i = 0; i < 255; i += 3) {
                color = p[src[i+0] & 0x3F];
                dst[0^1] = P0;
                dst[1^1] = P1;
                color = p[src[i+1] & 0x3F];
                dst[2^1] = P2;
                dst[3^1] = P0;
                color = p[src[i+2] & 0x3F];
                dst[4^1] = P1;
                dst[5^1] = P2;
                dst += 6;
            }
            // last pixel
            color = p[src[i+0]];
            dst[0^1] = P0;
            dst[1^1] = P1;
            break;*/

        case EMU_NES:
            mask = 0x3F;
        case EMU_SMS:
            // AAA ABB BBC CCC
            // 4 pixels, 3 color clocks, 4 samples per cc
            // each pixel gets 3 samples, 192 color clocks wide
            for (i = 0; i < 256; i += 4) {
                c = *((uint32_t*)(src+i));
                color = p[c & mask];
                dst[0^1] = P0;
                dst[1^1] = P1;
                dst[2^1] = P2;
                color = p[(c >> 8) & mask];
                dst[3^1] = P3;
                dst[4^1] = P0;
                dst[5^1] = P1;
                color = p[(c >> 16) & mask];
                dst[6^1] = P2;
                dst[7^1] = P3;
                dst[8^1] = P0;
                color = p[(c >> 24) & mask];
                dst[9^1] = P1;
                dst[10^1] = P2;
                dst[11^1] = P3;
                dst += 12;
            }
            break;

    }
    END_TIMING();
}

void IRAM_ATTR burst(uint16_t* line)
{
    int i,phase;
    switch (_samples_per_cc) {
        case 4:
            // 4 samples per color clock
			//Breezeway (delay colorburst by two cycles following the sync pulse)
			for (int i = _hsync + 8; i < _hsync + 16; i++)
				line[i] = BLANKING_LEVEL;
            //Color burst 9 cycles
			for (i = _hsync + 16; i < _hsync + 16 + (4*9); i += 4) {
                line[i+1] = BLANKING_LEVEL;
                line[i+0] = BLANKING_LEVEL + BLANKING_LEVEL/2;
                line[i+3] = BLANKING_LEVEL;
                line[i+2] = BLANKING_LEVEL - BLANKING_LEVEL/2;
            }
            break;
        case 3:
            // 3 samples per color clock
            phase = 0.866025f*BLANKING_LEVEL/2.f;
            for (i = _hsync; i < _hsync + (3*10); i += 6) {
                line[i+1] = BLANKING_LEVEL;
                line[i+0] = BLANKING_LEVEL + phase;
                line[i+3] = BLANKING_LEVEL - phase;
                line[i+2] = BLANKING_LEVEL;
                line[i+5] = BLANKING_LEVEL + phase;
                line[i+4] = BLANKING_LEVEL - phase;
            }
            break;
    }
}

void IRAM_ATTR sync(uint16_t* line, int syncwidth)
{
    //Front porch
	for (int i = 0; i < 8; i++)
        line[i] = BLANKING_LEVEL;
    //Sync pulse
	for (int i = 8; i < syncwidth + 8; i++)
        line[i] = SYNC_LEVEL;
}

void IRAM_ATTR blanking(uint16_t* line, bool vbl)
{
    int syncwidth = vbl ? _hsync_long : _hsync;
    sync(line,syncwidth);
    for (int i = syncwidth; i < _line_width; i++)
        line[i] = BLANKING_LEVEL;
    if (!vbl)
        burst(line);    // no burst during vbl
}

// Wait for blanking before starting drawing
// avoids tearing in our unsynchonized world
#ifdef ESP_PLATFORM
void video_sync()
{
  if (!_lines)
    return;
  int n = 0;
  if (_line_counter < _active_lines)
    n = (_active_lines - _line_counter)*1000/15720;
  vTaskDelay(n+1);
}
#endif

// Workhorse ISR handles audio and video updates
extern "C"
void IRAM_ATTR video_isr(volatile void* vbuf)
{
    if (!_lines)
        return;

    ISR_BEGIN();

    uint8_t s = _audio_r < _audio_w ? _audio_buffer[_audio_r++ & (sizeof(_audio_buffer)-1)] : 0x20;
    audio_sample(s);
    //audio_sample(_sin64[_x++ & 0x3F]);

#ifdef IR_PIN
    ir_sample();
#endif

    int i = _line_counter++;
    uint16_t* buf = (uint16_t*)vbuf;
    if (i < _active_lines) {                // active video
        sync(buf,_hsync);
        burst(buf);
        blit(_lines[i],buf + _active_start);

    } else if (i < (_active_lines + 5)) {   // post render/black
        blanking(buf,false);

    } else if (i < (_active_lines + 8)) {   // vsync
        blanking(buf,true);

    } else {                                // pre render/black
        blanking(buf,false);
    }

    if (_line_counter == _line_count) {
        _line_counter = 0;                      // frame is done
        _frame_counter++;
    }

    ISR_END();
}

#else
//=====================================================================================
//PAL VIDEO
//=====================================================================================
void video_init(int samples_per_cc, int machine, const uint32_t* palette, int ntsc)
{
    int cc_width = 4;
    _samples_per_cc = samples_per_cc;
    _machine = machine;
    _palette = palette;
    _sample_rate = PAL_FREQUENCY*cc_width/1000000.0;       // DAC rate in mhz
    _line_width = PAL_COLOR_CLOCKS_PER_SCANLINE*cc_width;
    _line_count = PAL_LINES;
    _hsync_short = usec(2.f);
    _hsync_long = usec(30.f);
    _hsync = usec(4.7f);
    _burst_start = usec(5.6f);
    _burst_width = (int)(10*cc_width + 4) & 0xFFFE;
    _active_start = usec(10.4f);

    // make colorburst tables for even and odd lines
    _burst0 = new int16_t[_burst_width];
    _burst1 = new int16_t[_burst_width];
    float phase = M_PI;
    for (int i = 0; i < _burst_width; i++)
    {
        _burst0[i] = BLANKING_LEVEL + sin(phase + 3.f*M_PI/4.f) * BLANKING_LEVEL/1.5f;
        _burst1[i] = BLANKING_LEVEL + sin(phase - 3.f*M_PI/4.f) * BLANKING_LEVEL/1.5f;
        phase += 2.f*M_PI/cc_width;
    }
    
    _active_lines = 240;
    video_init_hw(_line_width,_samples_per_cc);    // init the hardware
}

void IRAM_ATTR blit(uint8_t* src, uint16_t* dst)
{
    uint32_t c,color;
    bool even = _line_counter & 1;
    const uint32_t* p = even ? _palette : _palette + 256;
    int left = 0;
    int right = 256;
    uint8_t mask = 0xFF;
    uint8_t c0,c1,c2,c3,c4;
    uint8_t y1,y2,y3;

    switch (_machine) {
        case EMU_ATARI:
            // pal is 5/4 wider than ntsc to account for pal 288 color clocks per line vs 228 in ntsc
            // so do an ugly stretch on pixels (actually luma) to accomodate -> 384 pixels are now 240 pal color clocks wide
            left = 24;
            right = 384-24; // only show center 336 pixels
            dst += 40;
            for (int i = left; i < right; i += 4) {
                c = *((uint32_t*)(src+i));

                // make 5 colors out of 4 by interpolating y: 0000 0111 1122 2223 3333
                c0 = c;
                c1 = c >> 8;
                c3 = c >> 16;
                c4 = c >> 24;
                y1 = (((c1 & 0xF) << 1) + ((c0 + c1) & 0x1F) + 2) >> 2;    // (c0 & 0xF)*0.25 + (c1 & 0xF)*0.75;
                y2 = ((c1 + c3 + 1) >> 1) & 0xF;                           // (c1 & 0xF)*0.50 + (c2 & 0xF)*0.50;
                y3 = (((c3 & 0xF) << 1) + ((c3 + c4) & 0x1F) + 2) >> 2;    // (c2 & 0xF)*0.75 + (c3 & 0xF)*0.25;
                c1 = (c1 & 0xF0) + y1;
                c2 = (c1 & 0xF0) + y2;
                c3 = (c3 & 0xF0) + y3;

                color = p[c0];
                dst[0^1] = P0;
                dst[1^1] = P1;
                color = p[c1];
                dst[2^1] = P2;
                dst[3^1] = P3;
                color = p[c2];
                dst[4^1] = P0;
                dst[5^1] = P1;
                color = p[c3];
                dst[6^1] = P2;
                dst[7^1] = P3;
                color = p[c4];
                dst[8^1] = P0;
                dst[9^1] = P1;

                i += 4;
                c = *((uint32_t*)(src+i));
                
                // make 5 colors out of 4 by interpolating y: 0000 0111 1122 2223 3333
                c0 = c;
                c1 = c >> 8;
                c3 = c >> 16;
                c4 = c >> 24;
                y1 = (((c1 & 0xF) << 1) + ((c0 + c1) & 0x1F) + 2) >> 2;    // (c0 & 0xF)*0.25 + (c1 & 0xF)*0.75;
                y2 = ((c1 + c3 + 1) >> 1) & 0xF;                           // (c1 & 0xF)*0.50 + (c2 & 0xF)*0.50;
                y3 = (((c3 & 0xF) << 1) + ((c3 + c4) & 0x1F) + 2) >> 2;    // (c2 & 0xF)*0.75 + (c3 & 0xF)*0.25;
                c1 = (c1 & 0xF0) + y1;
                c2 = (c1 & 0xF0) + y2;
                c3 = (c3 & 0xF0) + y3;

                color = p[c0];
                dst[10^1] = P2;
                dst[11^1] = P3;
                color = p[c1];
                dst[12^1] = P0;
                dst[13^1] = P1;
                color = p[c2];
                dst[14^1] = P2;
                dst[15^1] = P3;
                color = p[c3];
                dst[16^1] = P0;
                dst[17^1] = P1;
                color = p[c4];
                dst[18^1] = P2;
                dst[19^1] = P3;
                dst += 20;
            }
            return;

        case EMU_NES:
            // 192 of 288 color clocks wide: roughly correct aspect ratio
            mask = 0x3F;
            if (!even)
              p = _palette + 64;
            dst += 88;
            break;
          
        case EMU_SMS:
            // 192 of 288 color clocks wide: roughly correct aspect ratio
            dst += 88;
            break;
    }

    // 4 pixels over 3 color clocks, 12 samples
    // do the blitting
    for (int i = left; i < right; i += 4) {
        c = *((uint32_t*)(src+i));
        color = p[c & mask];
        dst[0^1] = P0;
        dst[1^1] = P1;
        dst[2^1] = P2;
        color = p[(c >> 8) & mask];
        dst[3^1] = P3;
        dst[4^1] = P0;
        dst[5^1] = P1;
        color = p[(c >> 16) & mask];
        dst[6^1] = P2;
        dst[7^1] = P3;
        dst[8^1] = P0;
        color = p[(c >> 24) & mask];
        dst[9^1] = P1;
        dst[10^1] = P2;
        dst[11^1] = P3;
        dst += 12;
    }
}

void IRAM_ATTR burst(uint16_t* line)
{
    line += _burst_start;
    int16_t* b = (_line_counter & 1) ? _burst0 : _burst1;
    for (int i = 0; i < _burst_width; i += 2) {
        line[i^1] = b[i];
        line[(i+1)^1] = b[i+1];
    }
}
	
void IRAM_ATTR sync(uint16_t* line, int syncwidth)
{
    //Front porch
	for (int i = 0; i < 8; i++)
        line[i] = BLANKING_LEVEL;
    //Sync
	for (int i = 8; i < syncwidth; i++)
        line[i] = SYNC_LEVEL;
}

void IRAM_ATTR blanking(uint16_t* line, bool vbl)
{
    int syncwidth = vbl ? _hsync_long : _hsync;
    sync(line,syncwidth);
    for (int i = syncwidth; i < _line_width; i++)
        line[i] = BLANKING_LEVEL;
    if (!vbl)
        burst(line);    // no burst during vbl
}

// Fancy pal non-interlace
// http://martin.hinner.info/vga/pal.html
void IRAM_ATTR vsync2(uint16_t* line, int width, int swidth)
{
    swidth = swidth ? _hsync_long : _hsync_short;
    int i;
    for (i = 0; i < swidth; i++)
        line[i] = SYNC_LEVEL;
    for (; i < width; i++)
        line[i] = BLANKING_LEVEL;
}

uint8_t DRAM_ATTR _sync_type[8] = {0,0,0,3,3,2,0,0};
void IRAM_ATTR vsync(uint16_t* line, int i)
{
    uint8_t t = _sync_type[i-304];
    vsync2(line,_line_width/2, t & 2);
    vsync2(line+_line_width/2,_line_width/2, t & 1);
}

// Wait for blanking before starting drawing
// avoids tearing in our unsynchonized world
#ifdef ESP_PLATFORM
void video_sync()
{
  if (!_lines)
    return;
  int n = 0;
  if (_line_counter < _active_lines)
    n = (_active_lines - _line_counter)*1000/15600;
  vTaskDelay(n+1);
}
#endif

// Workhorse ISR handles audio and video updates
extern "C"
void IRAM_ATTR video_isr(volatile void* vbuf)
{
    if (!_lines)
        return;

    ISR_BEGIN();

    uint8_t s = _audio_r < _audio_w ? _audio_buffer[_audio_r++ & (sizeof(_audio_buffer)-1)] : 0x20;
    audio_sample(s);
    //audio_sample(_sin64[_x++ & 0x3F]);

#ifdef IR_PIN
    ir_sample();
#endif

    int i = _line_counter++;
    uint16_t* buf = (uint16_t*)vbuf;
    if (i < 32) {
        blanking(buf,false);                // pre render/black 0-32
    } else if (i < _active_lines + 32) {    // active video 32-272
        sync(buf,_hsync);
        burst(buf);
        blit(_lines[i-32],buf + _active_start);
    } else if (i < 304) {                   // post render/black 272-304
        blanking(buf,false);
    } else {
        vsync(buf,i);                    // 8 lines of sync 304-312
    }

    if (_line_counter == _line_count) {
        _line_counter = 0;                      // frame is done
        _frame_counter++;
    }

    ISR_END();
}
#endif

I solved it, finding this project, thanks.

Hola loquequema, estoy haciendo una maquina arcade como proyecto para una materia y me mate buscando alguien que hubiera modificado el cĂłdigo para usar las entradas gpio para controlar el emulador ya que no tengo muchas habilidades de programaciĂłn, si pudieras pasarme el cĂłdigo completo con los pines asignados te lo agradecerĂ­a mucho.
Desde ya muchas gracias :wink: