Skip to content

Help with a custom controller implementation on CYD. #56

@aedile

Description

@aedile

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!!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions