This tutorial covers reading SNES controllers including button detection and multi-player support.
SNES Controller Layout
L R
┌───────────────────────────────────────┐
│ ┌───┐ ┌───┐ │
│ │ ↑ │ (X) │ X │ │
│ ┌───┼───┼───┐ (Y) (A)├───┤ │
│ │ ← │ │ → │ [SEL][STA] │ Y │ A │ │
│ └───┼───┼───┘ ├───┼───┤ │
│ │ ↓ │ │ B │ │
│ └───┘ └───┘ │
└───────────────────────────────────────┘
Button Constants
OpenSNES defines these button masks in input.h:
#define KEY_UP 0x0800
#define KEY_DOWN 0x0400
#define KEY_LEFT 0x0200
#define KEY_RIGHT 0x0100
#define KEY_A 0x0080
#define KEY_B 0x8000
#define KEY_X 0x0040
#define KEY_Y 0x4000
#define KEY_L 0x0020
#define KEY_R 0x0010
#define KEY_START 0x1000
#define KEY_SELECT 0x2000
Reading Controllers
Direct Register Access (Recommended)
while (1) {
if (pad == 0xFFFF) {
continue;
}
}
}
}
void WaitForVBlank(void)
Wait for next VBlank period.
#define REG_JOY1H
Joypad 1 data high (R).
Definition registers.h:334
#define REG_JOY1L
Joypad 1 data low (R).
Definition registers.h:331
#define REG_HVBJOY
H/V blank and joypad status (R).
Definition registers.h:307
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53
OpenSNES common-case master header.
Reading Controller 2
#define REG_JOY2H
Joypad 2 data high (R).
Definition registers.h:340
#define REG_JOY2L
Joypad 2 data low (R).
Definition registers.h:337
Edge Detection
Detect button presses (not just held state):
while (1) {
pad_pressed = pad_current & ~pad_previous;
pad_previous = pad_current;
if (pad_current == 0xFFFF) {
pad_pressed = 0;
}
if (pad_pressed &
KEY_A) {
player_jump();
}
}
}
static s16 player_x
Player X position in screen coordinates.
Definition main.c:56
Movement Example
void update_player(
u16 pad) {
}
}
}
}
}
static s16 player_y
Player Y position in screen coordinates.
Definition main.c:58
Two-Player Example
u16 p1_x = 64, p1_y = 112;
u16 p2_x = 192, p2_y = 112;
while (1) {
if (pad1 != 0xFFFF) {
if (pad1 &
KEY_UP) {
if (p1_y > 0) p1_y--; }
if (pad1 &
KEY_DOWN) {
if (p1_y < 224) p1_y++; }
if (pad1 &
KEY_LEFT) {
if (p1_x > 0) p1_x--; }
if (pad1 &
KEY_RIGHT) {
if (p1_x < 248) p1_x++; }
}
if (pad2 != 0xFFFF) {
if (pad2 &
KEY_UP) {
if (p2_y > 0) p2_y--; }
if (pad2 &
KEY_DOWN) {
if (p2_y < 224) p2_y++; }
if (pad2 &
KEY_LEFT) {
if (p2_x > 0) p2_x--; }
if (pad2 &
KEY_RIGHT) {
if (p2_x < 248) p2_x++; }
}
oamSet(0, p1_x, p1_y, 0, 0, 0, 0);
oamSet(1, p2_x, p2_y, 0, 1, 0, 0);
}
void oamSet(u16 id, u16 x, u16 y, u16 tile, u16 palette, u16 priority, u16 flags)
Set sprite properties.
void oamUpdate(void)
Copy OAM buffer to hardware.
Button Combinations
}
}
#define COMBO_HADOUKEN (KEY_DOWN | KEY_RIGHT | KEY_A)
if ((pad & COMBO_HADOUKEN) == COMBO_HADOUKEN) {
}
Menu Navigation
if (menu_selection > 0) {
menu_selection--;
}
}
if (menu_selection < MENU_ITEMS - 1) {
menu_selection++;
}
}
if (pad_pressed &
KEY_A) {
activate_menu_item(menu_selection);
}
if (pad_pressed &
KEY_B) {
close_menu();
}
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47
Hardware Registers
For reference, the joypad registers:
| Register | Address | Description |
| REG_JOY1L | $4218 | Controller 1, low byte |
| REG_JOY1H | $4219 | Controller 1, high byte |
| REG_JOY2L | $421A | Controller 2, low byte |
| REG_JOY2H | $421B | Controller 2, high byte |
| REG_JOY3L | $421C | Controller 3 (multitap) |
| REG_JOY3H | $421D | Controller 3 (multitap) |
| REG_JOY4L | $421E | Controller 4 (multitap) |
| REG_JOY4H | $421F | Controller 4 (multitap) |
| REG_HVBJOY | $4212 | Status (bit 0 = auto-read busy) |
Tips
- Always wait for auto-joypad - Check REG_HVBJOY & 0x01 before reading
- Check for disconnected controllers - 0xFFFF means no controller
- Use edge detection for menus - Prevents rapid-fire selection
- Use held state for movement - Smoother continuous motion
Example
See examples/input/two_players/ for a complete two-player demo.
Next Steps