Using I2C communication with analog joysticks

Hello, I'm using two Arduino Uno R3 boards and i want to use I2C to have one Arduino send joystick signals (I'm using 2 analog joysticks including SW) to another Arduino that will read the signals and control DC motors (5 analog servos on three MDD10A Cytrons) based on the signals (each motor operates on one axis and the 5th motor operates on SW inputs). When using this system the motors don't operate in association with the inputs.

master code:

#include <Wire.h>

// Pin definitions for pushbutton
const int buttonPin = 2;  // Change this pin according to your setup

void setup() {
  Wire.begin();        // Join I2C bus (address optional for master)
  Serial.begin(9600);  // Start serial for output
  pinMode(buttonPin, INPUT_PULLUP); // Setup pushbutton pin
}

void loop() {
  // Read joystick values for X and Y axes
  int joystick1X = analogRead(A0);  // Read X axis for joystick 1
  int joystick1Y = analogRead(A1);  // Read Y axis for joystick 1
  int joystick2X = analogRead(A2);  // Read X axis for joystick 2
  int joystick2Y = analogRead(A3);  // Read Y axis for joystick 2
  
  // Read pushbutton state (LOW when pressed)
  int buttonState = digitalRead(buttonPin);

  // Send joystick values and pushbutton state to slave (peripheral)
  Wire.beginTransmission(8);  // Begin transmission to slave address #8
  Wire.write(joystick1X >> 8);  // Send high byte of joystick 1 X
  Wire.write(joystick1X & 0xFF); // Send low byte of joystick 1 X
  Wire.write(joystick1Y >> 8);  // Send high byte of joystick 1 Y
  Wire.write(joystick1Y & 0xFF); // Send low byte of joystick 1 Y
  Wire.write(joystick2X >> 8);  // Send high byte of joystick 2 X
  Wire.write(joystick2X & 0xFF); // Send low byte of joystick 2 X
  Wire.write(joystick2Y >> 8);  // Send high byte of joystick 2 Y
  Wire.write(joystick2Y & 0xFF); // Send low byte of joystick 2 Y
  Wire.write(buttonState);       // Send pushbutton state (1 byte)
  Wire.endTransmission();       // End transmission

  delay(100);  // Delay to avoid flooding the I2C bus
}

slave code:

#include <Wire.h>

// Pin definitions for motors
const int motorPins[] = {3, 5, 6, 9, 10};  // 5 motors connected to these pins
const int numMotors = 5;

void setup() {
  Wire.begin(8);                // Join I2C bus with address #8 (slave address)
  Wire.onReceive(receiveEvent); // Register event for receiving data

  // Set motor pins as outputs
  for (int i = 0; i < numMotors; i++) {
    pinMode(motorPins[i], OUTPUT);
  }
}

void loop() {
  delay(100);  // Wait for data to arrive
}

// Function that executes whenever data is received from the master
void receiveEvent(int howMany) {
  if (Wire.available() >= 11) { // Check if enough data (11 bytes) is available
    // Read joystick values for both joysticks and pushbutton state
    int joystick1X = Wire.read() << 8 | Wire.read();  // Joystick 1 X
    int joystick1Y = Wire.read() << 8 | Wire.read();  // Joystick 1 Y
    int joystick2X = Wire.read() << 8 | Wire.read();  // Joystick 2 X
    int joystick2Y = Wire.read() << 8 | Wire.read();  // Joystick 2 Y
    int buttonState = Wire.read(); // Pushbutton state

    // Map joystick values to motor speeds (adjust these ranges as necessary)
    int motorSpeed1 = map(joystick1X, 0, 1023, 0, 255);
    int motorSpeed2 = map(joystick1Y, 0, 1023, 0, 255);
    int motorSpeed3 = map(joystick2X, 0, 1023, 0, 255);
    int motorSpeed4 = map(joystick2Y, 0, 1023, 0, 255);
    int motorSpeed5 = buttonState == LOW ? 255 : 0;  // Turn motor 5 on if button is pressed

    // Control motors using PWM (adjust motor pins as needed)
    analogWrite(motorPins[0], motorSpeed1);
    analogWrite(motorPins[1], motorSpeed2);
    analogWrite(motorPins[2], motorSpeed3);
    analogWrite(motorPins[3], motorSpeed4);
    analogWrite(motorPins[4], motorSpeed5);

    // Print the joystick and button states for debugging
    Serial.print("Joystick 1 X: ");
    Serial.print(joystick1X);
    Serial.print(" Y: ");
    Serial.print(joystick1Y);
    Serial.print(" Joystick 2 X: ");
    Serial.print(joystick2X);
    Serial.print(" Y: ");
    Serial.println(joystick2Y);
    Serial.print("Button: ");
    Serial.println(buttonState);
  }
}

Could this issue be solved by changing the master to an Arduino Mega?

Any help is greatly appreciated!

Welcome to the forum.

Have you tried a simple i2c to i2c example like the one in this tutorial?

What do you mean by "don't operate in association with the inputs?"

Changing to a Mega will not alter the i2c bus.

In your slave, you never set of Serial, but you are printing a ton of information, which takes time and if you are doing this at 9600 baud, you may be trying to send out more data faster than you can handle the incoming data.

As for the incoming data, it seems a bit silly to send a value from 0-1023 (2 bytes) to the slave, just to map it to 0-255 (1 byte) and then use it. Why not cut it down to 1 byte before sending it. Transmission speed is slowing you down. You can also do the math much faster without using map(). Just bit-shift the result 2 bits to the left (same as divide by 4)

I have not yet, I will do this later today and update you.
Thank you for your input!

Noted, I will adjust my code later today and update you. Thank you for your input!

A few things to note about the above bit of code:

  1. Waiting for 11 bytes may work but you will likely encounter a problem if for some reason one byte gets lost in transmission. Your bytes will now be out of sequence.
  2. Why 11 bytes when the transmitter appears to send 9 bytes?
  3. The Wire.read function returns a byte. If you shift it left 8 bits, then you will get zero in the byte. You need to convert this to a 16-bit value first before doing the shift left.

No, the function is defined as returning int so the code works as expected. Much code/examples would not work if it was defined as returning byte

Not according to the Arduino documentation I was reading:
It says:

This function reads a byte that was transmitted from a peripheral device

and

Returns
The next byte received.

Or am I looking at the wrong documentation?

look at the source code. It's right there on your PC. The documentation is a bit "loose" with the terms, imo. Yes, a byte is read, but that byte is assigned to an int as the return value from the function so things like a shift work properly.

class TwoWire : public Stream
{
...
public:
...
virtual int read(void);
virtual int peek(void);
};

Ah, that explains that then! :frowning_face:

It also has to be able to return -1 if you call read() with an empty buffer which can't be done if you only use bytes.