Skip to content

Commit

Permalink
6pin n64 Joystick option (#9)
Browse files Browse the repository at this point in the history
Add support for SIXPIN, digital Joysitck
  • Loading branch information
craigmillard86 authored May 28, 2023
1 parent e181325 commit e7026c9
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 50 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# wireless-n64-controller
This project and its documentation is a Work-In-Progress. I'm still working on writing everything down and working out kinks in the design files and prototypes.

**The idea is to publish the design files and software I used to upgrade a cheap, wired N64 controller to a wireless, Bluetooth controller**. Hopefully these published files can assist others in completing similar projects.
**The idea is to publish the design files and software used to upgrade an OEM or cheap, wired N64 controller to a wireless, Bluetooth controller**. Hopefully these published files can assist others in completing similar projects.

<img src=images/controller_prototype.jpg width=1024>

## Motivation
When this project was started, no original-form-factor N64 wireless controllers were available for purchase. There were some two-handle controllers, inspired by N64 original controllers, but none with the original three-handle setup.
When this project was started, no original-form-factor bluetooth N64 wireless controllers were available for purchase. There were some two-handle controllers, inspired by N64 original controllers, but none with the original three-handle setup.

The main goal of this project was to upgrade an existing, cheap three-handle N64 wired controller to use bluetooth, so it could be used with a PC and/or Raspberry Pi.
The main goal of this project was to upgrade an existing OEM or cheap three-handle N64 wired controller to use bluetooth, so it could be used with a PC and/or Raspberry Pi.

## Operation

Expand Down
39 changes: 37 additions & 2 deletions building.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ See the [BOM for more details and some example-component links](BOM.md), but you

* Base N64 controller you plan to modify.
* I'm not sure how standardized N64 controller internals are, so it is probably a good idea to confirm the measurements of your controller match up with the spacing of the PCB mounting/securing holes.
* OEM controllers use a 6 pin connector compared to the cheaper alternatives which use a 4 pin. Additional steps are required to configure the software as well as modifications to the PCB for an OEM 6 pin controller.
* ESP32 microcontroller
* AAA terminals
* LiPo battery
Expand Down Expand Up @@ -42,7 +43,13 @@ Alternatively, you could skip using a battery pak and directly plug/wire a batte
### 1. Program the microcontroller

You will need to flash the software for this project on your ESP32 microcontroller. After `ESP-IDF` is setup on your PC, you can connect the microcontroller to your PC via USB and build and flash the software.


If you are using and OEM controller the software must be updated to enable SIXPIN mode before building:

```
input_poll.h - #define SIXPIN_ENABLED 1
```

See [software readme](software/README.md) for more details.

### 2. Assemble the components
Expand All @@ -61,10 +68,38 @@ Components:

4. **2-pin cables**: Solder one 2-pin JST cable to each external board: right trigger, left trigger, and Z board (you should cut down the length of the cable before hand so there isn't a ton of extra slack taking up space). Each board has two pads to solder to, and it doesn't matter which wire is soldered to which pad. You will need to cut or desolder the existing wires running to these boards.

5. **4-pin header**: Solder the 4-pin PH header/connector for `Analog`. I solder this so the connector opening is facing downward.
5. **4-pin/6-pin header**: Solder the 4-pin PH header/connector for non OEM controllers `Analog`. I solder this so the connector opening is facing downward.

<img src=images/4pin_header.jpg width=360>

If using the OEM controller, the 6-pin connector needs modifying to work with the current PCB until this is updated. Carefully bend two of the pins up so the header/connector fits into the 4 pin position on the PCB. Then two additional wires are required to be soldered from these bent pins to the correct ESP32 Pins on the PCB (6 -> Pin 13,5 -> Pin 35) as per the picture below:

<img src=images/6pin_header.jpg width=360>

Additionally, for the 6 Pin connectors the wires from the joystick need swapping around in the 6pin header to match the PCB (a small screwdriver can be used to lift the holding clips and release the wires from the connector):

<img src=images/6pin_pcb.png width=360>

* Pin 1 - X
* Pin 2 - Power
* Pin 3 - Ground
* Pin 4 - XQ
* Pin 5 - Y
* Pin 6 - YQ

These should be reordered to:

* Pin 1 - power
* Pin 2 - Y
* Pin 3 - Ground
* Pin 4 - X
* Pin 5 - XQ
* Pin 6 - YQ

(More information can be found here: https://dpedu.io/article/2015-03-11/nintendo-64-joystick-pinout-arduino)

<img src=images/6pin_header_after.jpg width=360>

6. **4-pin cables**: Solder the 4-pin PH cable to the analog joystick board (you should cut down the length of the cable before hand so there isn't a ton of extra slack taking up space). You will need to cut or desolder the existing wires running to this board - note which wires are labeled as V, X, G, and Y before removing them. Take care to solder the appropriate PH cable to the appropriate pad on the joystick board; on my board the pins were X, Gnd, Y, and V+ (from top to bottom) but yours may be different.

7. **Test**: At this point, I do another quick test to make sure the ESP32 is registering analog joystick data (same as `#2` above).
Expand Down
Binary file added images/6pin_header.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/6pin_header_after.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/6pin_pcb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 77 additions & 12 deletions software/main/ble_gamepad_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,45 @@
#include "driver/adc.h"
#include "driver/gpio.h"


// LED mode and misc related status indicators
uint32_t led_mode = 0;
bool bt_connected = false;
// Controller state-machine
#define STATE_STARTUP 0
#define STATE_CALIBRATION 1
#define STATE_RUNNING 2

uint32_t current_state = STATE_STARTUP;
bool startup_routine_running = true;

BleGamepad bleGamepad;
xQueueHandle gpio_evt_queue = NULL; // Button-press event queue

// For 6-pin joysticks: count movement when an edge change is detected
int countx = 0;
int county = 0;
// For 6-pin joysticks: X Interrupt Hook
static void IRAM_ATTR gpiox_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
if(gpio_get_level((gpio_num_t) SIXPIN_ANALOG_X) == gpio_get_level((gpio_num_t) SIXPIN_ANALOG_XQ)){
countx++;
}
else{
countx--;
}

}
// For 6-pin joysticks: Y Interrupt Hook
static void IRAM_ATTR gpioy_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
if(gpio_get_level((gpio_num_t) SIXPIN_ANALOG_Y) == gpio_get_level((gpio_num_t) SIXPIN_ANALOG_YQ)){
county--;
}
else{
county++;
}
}

// Setup specified pin number as pulled-down, input pin
void setup_input_pin(uint32_t pin) {
Expand Down Expand Up @@ -57,12 +82,47 @@ void setup_gpio()
previousDpadStates[i] = 0;
currentDpadStates[i] = 0;
}
// Analog in

// Battery Monitor, Pin also used with 6 pin Joystick, therefore only availble with 4 pin joysticks
if(!SIXPIN_ENABLED){
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ANALOG_BAT, ADC_ATTEN_DB_11);
}

// For 6-pin joysticks: Joystick setup
if(SIXPIN_ENABLED){
//zero-initialize the config structure.
gpio_config_t io_conf = {};
//disable pull-down mode
io_conf.pull_down_en = (gpio_pulldown_t) 0;
//interrupt of any edge
io_conf.intr_type = GPIO_INTR_ANYEDGE;
//bit mask of the pins
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
//set as input mode
io_conf.mode = GPIO_MODE_INPUT;
//disable pull-up mode
io_conf.pull_up_en = (gpio_pullup_t) 0;
gpio_config(&io_conf);

//SIXPIN Joystick Interrupts and Handler setup
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
//hook isr handler for specific gpio pin
gpio_isr_handler_add((gpio_num_t) SIXPIN_ANALOG_X, gpiox_isr_handler, (void*) SIXPIN_ANALOG_X);
//hook isr handler for specific gpio pin
gpio_isr_handler_add((gpio_num_t) SIXPIN_ANALOG_Y, gpioy_isr_handler, (void*) SIXPIN_ANALOG_Y);

//Adjust max ADC reading for SIXPIN joystick potentiometers
max_x = SIXPIN_ANALOG_MAX;
max_y = SIXPIN_ANALOG_MAX;
center_x = SIXPIN_ANALOG_CENTER;
center_y = SIXPIN_ANALOG_CENTER;
}
else{
adc1_config_channel_atten(ANALOG_X, ADC_ATTEN_DB_11);
adc1_config_channel_atten(ANALOG_Y, ADC_ATTEN_DB_11);
adc1_config_channel_atten(ANALOG_BAT, ADC_ATTEN_DB_11);

}
// Other IO
gpio_config_t io_conf;
// Button feedback LED output
Expand Down Expand Up @@ -203,30 +263,35 @@ extern "C" {
}


// Calibrate analog X-Y inputs and optionally write to persistent storage. Uses initial values as "centered", then monitors min and max values for ~5 seconds to determine range.
// Calibrate analog X-Y inputs and optionally write to persistent storage. Uses initial values as "centered", then monitors min and max values for ~10 seconds to determine range.
void calibrate(bool write_to_storage) {
const TickType_t xDelay = 10 / portTICK_PERIOD_MS; // 10ms

center_x = get_analog_raw(ANALOG_X);
if (SIXPIN_ENABLED){center_x = 0;} else{center_x = get_analog_raw(ANALOG_X);};
min_x = center_x;
max_x = center_x;

center_y = get_analog_raw(ANALOG_Y);
if (SIXPIN_ENABLED){center_y = 0;} else{center_y = get_analog_raw(ANALOG_Y);};
min_y = center_y;
max_y = center_y;

// Read extreme values for ~5 seconds
for (int i = 0; i < 500; i++) {
uint16_t x = get_analog_raw(ANALOG_X);
// Read extreme values for ~10 seconds
for (int i = 0; i < 1000; i++) {
int32_t x;
if (SIXPIN_ENABLED) {x = countx;} else{x = get_analog_raw(ANALOG_X);};

if (x < min_x) min_x = x;
if (x > max_x) max_x = x;

uint16_t y = get_analog_raw(ANALOG_Y);
int32_t y;
if (SIXPIN_ENABLED) {y = county;} else{y = get_analog_raw(ANALOG_Y);};

if (y < min_y) min_y = y;
if (y > max_y) max_y = y;

vTaskDelay(xDelay);
}

printf("Calibration results:\n");
printf("X (left, center, right): %d, %d, %d\n", min_x, center_x, max_x);
printf("Y (up, center, down): %d, %d, %d\n", min_y, center_y, max_y);
Expand Down
60 changes: 45 additions & 15 deletions software/main/input_poll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ uint32_t buttonPins[NUM_OF_BUTTONS] = {
BTN_Z_PIN,
BTN_L_PIN,
BTN_R_PIN,
BTN_IDK1_PIN,
BTN_IDK2_PIN,
BTN_IDK3_PIN
BTN_IDK2_PIN
};

// "Soft" buttons
Expand All @@ -44,6 +42,7 @@ uint32_t previousButtonStates[NUM_OF_BUTTONS];
uint32_t currentButtonStates[NUM_OF_BUTTONS];
uint32_t previousSoftButtonStates[NUM_OF_SOFT_BUTTONS];
uint32_t currentSoftButtonStates[NUM_OF_SOFT_BUTTONS];

int16_t previousXState;
int16_t currentXState;
int16_t previousYState;
Expand All @@ -57,17 +56,17 @@ uint32_t dpadPins[4] = {25, 27, 26, 33};


// Analog input center and range
uint16_t center_x = ANALOG_CENTER;
uint16_t min_x = ANALOG_MIN;
uint16_t max_x = ANALOG_MAX;
uint16_t center_y = ANALOG_CENTER;
uint16_t min_y = ANALOG_MIN;
uint16_t max_y = ANALOG_MAX;

int16_t center_x = ANALOG_CENTER;
int16_t min_x = ANALOG_MIN;
int16_t max_x = ANALOG_MAX;
int16_t center_y = ANALOG_CENTER;
int16_t min_y = ANALOG_MIN;
int16_t max_y = ANALOG_MAX;

// Scale x from `in` range to `out` range
int32_t map(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;

}


Expand All @@ -91,14 +90,34 @@ int16_t analog_to_joystick_value(uint16_t raw, uint16_t min, uint16_t med, uint1

// Negative
if (raw < med) {
int32_t joystick_val = map(raw, min, med, JOYSTICK_MIN, 0) * ANALOG_OVERSCALE;
int32_t joystick_val = map(raw, min, med, JOYSTICK_MIN, 0) ;
// printf("negative joystick_val val before scaling : %d \n", joystick_val);
if(SIXPIN_ENABLED)
{
joystick_val = joystick_val * SIXPIN_ANALOG_OVERSCALE;
}
else{
joystick_val = joystick_val * ANALOG_OVERSCALE;
}
//printf("joystick_val val after scaling: %d \n", joystick_val);
if (joystick_val < JOYSTICK_MIN) return JOYSTICK_MIN;

return (int16_t) joystick_val;
}

// Positive
int32_t joystick_val = map(raw, med, max, 0, JOYSTICK_MAX) * ANALOG_OVERSCALE;
int32_t joystick_val = map(raw, med, max, 0, JOYSTICK_MAX);
//printf("positive joystick_val valbefore scaling : %d \n", joystick_val);
if(SIXPIN_ENABLED)
{
joystick_val = joystick_val * SIXPIN_ANALOG_OVERSCALE;
}
else{
joystick_val = joystick_val * ANALOG_OVERSCALE;
}
//printf("joystick_val val after scaling: %d \n", joystick_val);
if (joystick_val > JOYSTICK_MAX) return JOYSTICK_MAX;

return (int16_t) joystick_val;
}

Expand Down Expand Up @@ -182,7 +201,12 @@ bool poll_joystick() {
bool changed = false;

// X
if(SIXPIN_ENABLED){
currentXState = analog_to_joystick_value(countx+abs(min_x), min_x+abs(min_x), center_x+abs(min_x), max_x+abs(min_x));
}
else{
currentXState = analog_to_joystick_value(get_analog_raw(ANALOG_X), min_x, center_x, max_x);
}
if ((currentXState > 0 && currentXState < JOYSTICK_DEADZONE) || (currentXState < 0 && currentXState > (-1 * JOYSTICK_DEADZONE))) {
currentXState = 0;
}
Expand All @@ -194,7 +218,12 @@ bool poll_joystick() {
}

// Y
if(SIXPIN_ENABLED){
currentYState = analog_to_joystick_value(county+abs(min_y), min_y+abs(min_y), center_y+abs(min_y), max_y+abs(min_y));;
}
else{
currentYState = analog_to_joystick_value(get_analog_raw(ANALOG_Y), min_y, center_y, max_y);
}
if ((currentYState > 0 && currentYState < JOYSTICK_DEADZONE) || (currentYState < 0 && currentYState > (-1 * JOYSTICK_DEADZONE))) {
currentYState = 0;
}
Expand Down Expand Up @@ -231,10 +260,11 @@ void input_poll_loop(void* args)
bool changed = false;

// Battery
if (ENABLE_BATTERY_CHECK) {
bleGamepad.setBatteryLevel(get_battery_level());
if (!SIXPIN_ENABLED){
if (ENABLE_BATTERY_CHECK) {
bleGamepad.setBatteryLevel(get_battery_level());
}
}

// Joystick
bool joystick_changed = poll_joystick();
changed |= joystick_changed;
Expand Down
Loading

0 comments on commit e7026c9

Please sign in to comment.