Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help with a custom controller implementation on CYD. #56

Open
aedile opened this issue Dec 1, 2024 · 4 comments
Open

Help with a custom controller implementation on CYD. #56

aedile opened this issue Dec 1, 2024 · 4 comments

Comments

@aedile
Copy link

aedile commented Dec 1, 2024

Greetings,

I have things working on a CYD, but I don't want to use a Wii nunchuck. CYD doesn't have adequate GPIO pins for a proper controller, so I've worked on making my own i2c device using an Seeeduino Xiao. I tested this i2c device using an ESP32 and it seems to work just fine when using the default i2c pins on the esp32. Here is the slave sketch:

#include <Arduino.h>
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

#define XPIN  0 // Arduino pin connected to VRX pin
#define YPIN  1 // Arduino pin connected to VRY pin
#define ZPIN  2  // Arduino pin connected to VRZ pin

#define APIN 3 // Arduino pin connected to button A pin
#define BPIN 6 // Arduino pin connected to button A pin

// Input values
int joystick_calibration_x = -3;
int joystick_calibration_y = 6;
int xValue = 0; // To store value of the X axis
int yValue = 0; // To store value of the Y axis
int xPinValue = 0;
int yPinValue = 0;
bool zButton = 0; // To store value of the Z button
bool aButton = 0; // To store value of the A button
bool bButton = 0; // To store value of the B button

// Banger
int bcount = 0;

// I2C Communication
void requestEvent() {
  // Define a byte to hold data
  byte bval = 0;
  
  // Cycle through data
  // First response is always 255 to mark beginning
  switch (bcount) {
    case 0:
      bval = 255;
      break;
    case 1:
      bval = xValue;
      break;
    case 2:
      bval = yValue;
      break;
    // Note: Buttons read high normally, so we invert them  
    case 3:
      bval = zButton;
      break;
    case 4:
      bval = aButton;
      break;
    case 5:
      bval = bButton;
      break;
  }
  
  // Send response back to Master
  Wire.write(bval);
  Serial.printf("Sent: %d\n", bval);
  // Increment byte counter
  bcount++;
  if (bcount > 5) bcount = 0; 
}

void setup() {
  Serial.begin(115200) ;
  // Joystick X and Y pins are analog
  pinMode(XPIN, INPUT);
  pinMode(YPIN, INPUT);
  pinMode(ZPIN, INPUT_PULLUP);

  // Button A and B pins are digital
  pinMode(APIN, INPUT_PULLUP);
  pinMode(BPIN, INPUT_PULLUP);

    // Initialize I2C communications as Slave
  Wire.begin(SLAVE_ADDR);
 
   // Function to run when data requested from master
  Wire.onRequest(requestEvent); 
}

void loop() {
  // read analog X and Y analog values
  xPinValue = analogRead(XPIN) + joystick_calibration_x;
  yPinValue = analogRead(YPIN) + joystick_calibration_y;

  // Convert analog values to 8-bit values
  float xpct = (xPinValue / 1024.0);
  float ypct = (yPinValue / 1024.0);
  xValue = (floor(xpct*255)>255)?255:floor(xpct*255);
  yValue = (floor(ypct*255)>255)?255:floor(ypct*255);
  
  // read digital Z button value
  zButton = digitalRead(ZPIN);
  // read digital A button value
  aButton = digitalRead(APIN);
  // read digital B button value
  bButton = digitalRead(BPIN);

  // print data to Serial Monitor on Arduino IDE
  Serial.printf("x = %d, y = %d, z = %d, a = %d, b = %d   \n", xValue, yValue, zButton, aButton, bButton);
  delay(10);
}

And here is the master sketch:

#include <Arduino.h>
#include <Wire.h>
#include <vector>

// Define Slave I2C Address
#define SLAVE_ADDR 9

// Define counter to count bytes in response
int bcount;

// Define array for return data
byte controller[5];

void setup()
{
  Wire.begin();
  Serial.begin(9600);
}

byte readI2C(int address) {
  // Define a variable to hold byte of data
  byte bval ;
  long entry = millis();
  // Read one byte at a time
  Wire.requestFrom(address, 1); 
  // Wait 100 ms for data to stabilize
  while (Wire.available() == 0 && (millis() - entry) < 100)  Serial.print("Waiting");
  // Place data into byte
  if  (millis() - entry < 100) bval = Wire.read();
  return bval;
}

void loop()
{
  
  while (readI2C(SLAVE_ADDR) < 255) {
  // Until first byte has been received print a waiting message
    Serial.print("Waiting"); 
  }
  for (bcount = 0; bcount < 5; bcount++) {
    controller[bcount] = readI2C(SLAVE_ADDR);
  }

  // Vector of 5 strings
  std::vector<String> labels = {"X: ", "Y: ", "Z: ", "A: ", "B: "};

  for (int i = 0; i < 5; i++) {
    Serial.printf("%s%d ", labels[i].c_str(), controller[i]);
  }
  Serial.println();
  delay(20);
}

As I said, this seems to be working just fine. So I turned my hand toward modifying it to use with Galagino.

I got Galagino compiling and running on my CYD with just a few modifications to config.h per the instructions. The serial is reporting Nunchuck disconnected! of course, but the roms load and go through the motions, I can get them to start with the button as you'd expect.

I thought the best way to try to implement my i2c controller was to just overwrite the existing nunchuck.h include with my own i2c.h include with the same functions implemented for my device. I updated the main sketch to include my file instead of the original, confirmed it's being loaded, and got it compiling and loading just fine. So here is my implementation:

#ifndef _NUNCHUCK_H_
#define _NUNCHUCK_H_

// ----------------------------
// Standard Libraries
// ----------------------------
#include <Arduino.h>
#include <Wire.h>
#include <vector>

// Define Slave I2C Address
#define SLAVE_ADDR 9

#define CTRL_SDA 22
#define CTRL_SCL 27

// Define counter to count bytes in response
int bcount;

// Define array for return data
byte controller[5];

byte readI2C(int address);

//TwoWire Wire = TwoWire(1);

void nunchuckSetup() {
  //Serial.begin(115200);
  pinMode(CTRL_SDA, INPUT_PULLUP);
  pinMode(CTRL_SCL, INPUT_PULLUP);
  Serial.println("About to begin I2C.");
  Wire.begin(CTRL_SDA, CTRL_SCL,100000);
  Serial.println("I2C begun!");
}

unsigned char getNunchuckInput() {
    //Wire.end();
    //Wire.begin(CTRL_SDA,CTRL_SCL);
    byte test = readI2C(SLAVE_ADDR);
    while (test < 255) {
      Serial.printf("Byte is currently:%d\n",test );
      test = readI2C(SLAVE_ADDR);
    // Until first byte has been received print a waiting message
      Serial.printf("Waiting to end read\n");
    }
    for (bcount = 0; bcount < 5; bcount++) {
      controller[bcount] = readI2C(SLAVE_ADDR);
    }
    //Wire.end();
    // Read a joystick axis (0-255, X and Y)
    // Roughly 127 will be the axis centered
    int joyX = controller[0];
    int joyY = controller[1];
    int joyZ = controller[2];
    int btnA = controller[3];
    int btnB = controller[4];

    Serial.printf("%d, %d, %d, %d, %d\n", joyX, joyY, joyZ, btnA, btnB);

    return ((joyX < 127 - NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_LEFT : 0) | //Move Left
           ((joyX > 127 + NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_RIGHT : 0) | //Move Right
           ((joyY > 127 + NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_UP : 0) | //Move Up
           ((joyY < 127 - NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_DOWN : 0) | //Move Down
           (btnA ? BUTTON_FIRE : 0) |
           (btnB ? BUTTON_EXTRA : 0) ;
  
}

byte readI2C(int address) {
  // Define a variable to hold byte of data
  byte bval ;
  long entry = millis();
  // Read one byte at a time
  Wire.requestFrom(address, 1); 
  // Wait 100 ms for data to stabilize
  //while (Wire.available() == 0)  Serial.printf("Waiting for read\n");
  while (Wire.available() == 0 && (millis() - entry) < 100)  Serial.printf("Waiting for read\n");
  // Place data into byte
  if  (millis() - entry < 100) bval = Wire.read();
  return bval;
}


#endif //_NUNCHUCK_H_

For some reason, when I use this implementation, I can see the values I'm expecting via serial print messages, except for the start byte, which is reading as "1073429516" instead of 255. Here is what I am getting:

Waiting to end read
Byte is currently:1
Waiting to end read
Byte is currently:1073429516
Waiting to end read
Byte is currently:127
Waiting to end read
Byte is currently:127
Waiting to end read
Byte is currently:1
Waiting to end read
Byte is currently:1
Waiting to end read

This is really strange to me, but I am new to i2c, Arduino IDE (platformio guy), and Galagino. I have tried the following things:

  1. Using TwoWire instead of wire, 0, 1, and 9 as indices for the constructor (9 just for funsies, it compiled and worked the same as 0, and 1 which behaved the same as just plain "Wire".
  2. Swapping both the code and the wiring of the SDA and SCL (and NO I didn't pull a two doctors BOTH reversing the polarity when making that test). I got hung up at a different area when I did this and confirmed I am wired correctly for the CYD with my current setup.
  3. Initializing the pins with pullup resistors (this seems prudent as I don't know if the Wire library will initialize the pins appropriately, but may not be necessary, I've tried it both ways and left it in for now). I realize this may be part of the issue since I have extended the wires by necessity at this point due to breadboarding needs. I will try adding pullups inline if nothing else works by tomorrow (not in my workshop rn so no resistors on hand).
  4. Adding a bunch of serial debugging messages - this helped narrow down where I am going wrong.
  5. Reinitializing the wire each time - this adds way too much latency and also doesn't work.

Things I haven't tried:

  1. Just setting it to say if the byte is equal to "1073429516" assume it is the start byte. I don't want to do this because I don't understand why I'm getting that number and it seems like cheating. I may try this later just for funsies, but it's NOT a solution.
  2. Adding in the pullup resistors. I will likely try this tomorrow.

Any suggestions are welcome. Thanks for the great software! So cool!!

@harbaum
Copy link
Owner

harbaum commented Dec 3, 2024

There are a few things that don't make much sense. The value printed is way too big for a single byte. This often indicates that signed and unsiged are messed up, but in this case the value is 0x3ffb3c0c which actually isn't negative as a 32 bit value either. You really need to figure out what this byte type is. sizeof(byte) may give some hints.

Another thing I noticed is that bval is neither initialized nor set later in case of a timeout. So in that case readi2c would return random stuff from the stack in case of a timeout,

This all isn't Arduino or galagino specific but rather just plain C. You simply need to debug this further. What exact size is a "byte" here? Is it signed or unsigned? How is this expanded when doing a printf? etc etc ...

@aedile
Copy link
Author

aedile commented Dec 15, 2024

@harbaum - Thank you. I am a programming generalist and you could inscribe what I know about C and C++, specifically, on a grain of rice with a blunt crayon. That said, I think this is definitely something to consider. Appreciate your feedback. Will persist with debugging and hopefully post working code here soon!

@aedile
Copy link
Author

aedile commented Dec 15, 2024

I actually accidentally posted my last update to a completely different issue board like a dork (bluepad32 board) anyways, I have changed tracks, and tried to move to a bluetooth controller implementation. I thought I'd take a crack at getting the bluepad32 library working and I've mostly succeeded.
https://github.com/ricardoquesada/bluepad32
For now I've just made a custom implementation of Nunchuck.h. Here is that file:

#ifndef _NUNCHUCK_H_
#define _NUNCHUCK_H_


#include <Bluepad32.h>

ControllerPtr gamepad = nullptr;

void onConnectedController(ControllerPtr gp) {
    gamepad = gp;
    Serial.println("Gamepad connected");
}

void onDisconnectedController(ControllerPtr gp) {
    if (gamepad == gp) {
        gamepad = nullptr;
        Serial.println("Gamepad disconnected");
    }
}

void nunchuckSetup() {
    delay(3000);
    BP32.setup(&onConnectedController, &onDisconnectedController);
    Serial.println("Bluepad32 setup completed");
}

unsigned char getNunchuckInput() {
  bool dataUpdated = BP32.update();
  if(gamepad && dataUpdated){
    Serial.printf("idx=%d, dpad: 0x%02x, buttons: 0x%04x, axis L: %4d, %4d, A: %d, B: %d, X: %d, Y: %d\n",
        gamepad->index(),        // Controller Index
        gamepad->dpad(),         // D-pad
        gamepad->buttons(),      // bitmask of pressed buttons
        gamepad->axisX(),        // (-511 - 512) left X Axis
        gamepad->axisY(),        // (-511 - 512) left Y axis
        gamepad->a(),            // A button
        gamepad->b(),            // B button
        gamepad->x(),            // X button
        gamepad->y()             // Y button
    );
    int joyY = gamepad->axisY();
    int joyX = gamepad->axisX();

    return ((joyX < -127) ? BUTTON_LEFT : 0) | //Move Left
            ((joyX >  127) ? BUTTON_RIGHT : 0) | //Move Right
            ((joyY < -127) ? BUTTON_UP : 0) | //Move Up
            ((joyY >  127) ? BUTTON_DOWN : 0) | //Move Down
            (gamepad->a() ? BUTTON_FIRE : 0) |
            (gamepad->b() ? BUTTON_EXTRA : 0) ;
  }else{
    Serial.println("No gamepad");
    return 0;
  }
  vTaskDelay(1);
}

#endif //_NUNCHUCK_H_

The cool thing is this is MOSTLY working. When the library is compiled with the Bluepad32 version of ESP32 Dev Module, everything compiles, the controller is detected. The buttons work and Up and Down work in the menu for picking which game you want to play. The problem I'm having right now, and I'm just completely stuck on, is that, using the serial prints, I know I'm getting left and right from the x and y axes (-512 for left/up, 512 for rt/dn on press with it centered at maybe 3 on x axis and -3 on y axis). I've even tried some debugging to verify that when dpad is pushed, that getNunchuckInput is returning a non-zero value different from normal with distinct values for left, right, up and down. It seems to work fine in the menu, just not in any of the games. Not sure what I am doing wrong here, unfortunately. I feel like it's something to do with the return object. Here is what the original Nunchuck.h returns:

return ((joyX < 127 - NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_LEFT : 0) | //Move Left
           ((joyX > 127 + NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_RIGHT : 0) | //Move Right
           ((joyY > 127 + NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_UP : 0) | //Move Up
           ((joyY < 127 - NUNCHUCK_MOVE_THRESHOLD) ? BUTTON_DOWN : 0) | //Move Down
           (nchuk.buttonZ() ? BUTTON_FIRE : 0) |
           (nchuk.buttonC() ? BUTTON_EXTRA : 0) ;
  }

I feel like what I'm returning is not markedly different (just different expected values from the joystick).

Example serial output dead stick:

idx=0, dpad: 0x00, buttons: 0x0000, axis L:    3,   -3, A: 0, B: 0, X: 0, Y: 0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    3,   -3, A: 0, B: 0, X: 0, Y: 0

Example serial output left stick:

idx=0, dpad: 0x00, buttons: 0x0000, axis L: -512,   -3, A: 0, B: 0, X: 0, Y: 0
idx=0, dpad: 0x00, buttons: 0x0000, axis L: -512,   -3, A: 0, B: 0, X: 0, Y: 0

My only other guess is that MAYBE something about the bluepad32 library itself is causing issues detecting input. Not 100% sure if that's the case, but stranger things have happened. Anyways, delving into the C code where control is parsed is not something I'm skilled enough to do with any sort of speed, so at this point, while I am working my way through it, thought I'd ask again since you were helpful with the first version. I'm incredibly close to things working 100% right now. Any suggestions are welcome. For reference, I'm not at all a C or C++ guy but I am fluent in psuedocode and many other languages, as well as googling and R-ing TFM.

If I can get this updated, it's a few simple steps to a working PR. Bluepad32 works with many controllers, you've probably got one laying around. If you'd like to try this out, follow the instructions for the bluepad32 installation in arduino ide here: https://github.com/ricardoquesada/bluepad32 , compile galagino for the CYD board under bluepad32 esp32 instead of esp32 and drop in the above file for Nunchuck.h, it compiles and lets you select a ROM in the menu. Works without the controller connected as well in "demo mode" where it selects a master on it's own. I've got this last little issue to clear up is all. Cheers!

@aedile
Copy link
Author

aedile commented Dec 15, 2024

Sorry, for the above, button presses are detected in the menu and the game, but not directions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants