Help with defining PWM pin addresses using I2C

Hi, I am currently in the process of coding a stewart platform using an Arduino Uno R3, PCA9685 and 6 hobbyist servos. However, I am finding it difficult addressing the servos using the I2C bus. I am mostly using coding from online however most guides use analog and digital pins to address the servos.

void loop()

{
  static float pe[6] = {0,0,0,radians(0),radians(0),radians(0)  }, theta_a[6], Pos[6],q[3][6], r[3][6], dl[3][6], dl2[6];


  pe[0] = (analogRead(0)-512)/51.2;
  pe[1] = (analogRead(1)-512)/51.2;
  pe[2] = (analogRead(2)-512)/51.2;
  pe[3] = (analogRead(3)-512)*ADC_mult;
  pe[4] = (analogRead(4)-512)*ADC_mult;
  pe[5] = (analogRead(5)-512)*ADC_mult;

  {
    HCPCA9685.Servo(0, Pos);
    HCPCA9685.Servo(1, Pos);
    HCPCA9685.Servo(2, Pos);
    HCPCA9685.Servo(3, Pos);
    HCPCA9685.Servo(4, Pos);
    HCPCA9685.Servo(5, Pos);
}

I wondered if anyone knew an alternative for analogRead that would work for I2C.
Thanks.

On the Arduino Uno, you either have analogRead on A4 and A5, or I2C via the hardware I2C module, but not both.

Software I2C can use any two free digital pins for the connection.

PWM pins are assigned to the timer modules and are unrelated to either analogRead or I2C.

1 Like

Sorry maybe I phrased it wrong, I wanted to know how you could address multiple devices using either the A4 and A5 pins or using the I2C SDA and SDL line. The analogRead function is the existing code and was wondering if there was a function that identified each of the addresses of the devices that use the I2C bus so I could then define them to the constants of pe[0], pe[1]... etc. I have heard about finding the hex address but was unable to find the hex addresses for a PCA9685.

If you use A4 and A5 for I2C communications, they are not available as analog inputs.

The Arduino program I2C Address Scanner will tell you the addresses of any devices attached to the I2C bus (there can be several devices connected in parallel, as long as the addresses are different). Be sure to use logic level shifters on SDA and SCL, if you connect 3.3V devices to a 5V Arduino.

Perhaps you should take the time to read one or more of the many on line tutorials on I2C communications. Here is the first I found: How I2C Communication Works & How To Use It with Arduino

1 Like

I might not understand the question but maybe this is what your looking for.

You can combine related information in a struct or class; being more a C programmer than a C++ programmer, I use structs.

struct SERVOCONTROL
{
  uint8_t servoNum;
  uint8_t analogPin;
  float (*calculator)(int16_t);
};

The servoNumber and analogPin are hopefully explaining themselves. float (*calculator)(int16_t) is a pointer to a function that takes an int as an argument and returns a float; I've implemented that as you have to different calculations. You can implement 2 functions as shown below for the calculations.

// calculator function 1
float calc1(int16_t value)
{
  Serial.println("calc1");
  return (value - 512) / 51.2;
}

Next you can declare an array of servo controls as demonstrated below; note that the calculations don't match with your original as A4 and A5 on the Uno are not available when using I2C.

SERVOCONTROL servoControls[]
{
  {0, A0, calc1},
  {3, A3, calc2},
 
};

The below code demonstrates how you can loop through the elements of the array and how you can use the fields in an element.

// macro to calculate number of elements in any type of array
#define NUM_ELEMENTS(x) (sizeof(x) / sizeof(x[0]))

// some constant value
const float ADC_mult = 2;

// calculator function 1
float calc1(int16_t value)
{
  Serial.println("calc1");
  return (value - 512) / 51.2;
}

// calculator function 2
float calc2(int16_t value)
{
  Serial.println("calc2");
  return (value - 512) * ADC_mult;
}

// struct to combine related information to control a servo
struct SERVOCONTROL
{
  uint8_t servoNum;
  uint8_t analogPin;
  float (*calculator)(int16_t);
};

// array with servo control information
SERVOCONTROL servoControls[] =
{
  {0, A0, calc1},
  {1, A1, calc2},
};


void setup()
{
  Serial.begin(57600);
  Serial.print("There are ");
  Serial.print(NUM_ELEMENTS(servoControls));
  Serial.println(" elements in servoControls");

  for (uint8_t cnt = 0; cnt < NUM_ELEMENTS(servoControls); cnt++)
  {
    // display the servo number
    Serial.print("servoNum = "); Serial.println(servoControls[cnt].servoNum);
    // display the analog input pin
    Serial.print("analogPin = "); Serial.println(servoControls[cnt].analogPin);
    // if a calculator was specified
    if (servoControls[cnt].calculator != NULL)
    {
      // calculate servo position
      float x = servoControls[cnt].calculator(analogRead(servoControls[cnt].analogPin));
      // and print it
      Serial.println(x);
    }
  }
}

void loop()
{
}

Example output (with floating analog inputs)

There are 2 elements in servoControls
servoNum = 0
analogPin = 14
calc1
-1.52
servoNum = 1
analogPin = 15
calc2
-254.00
1 Like

Thank you this is really helpful and useful for addressing the servos to analog pins.
I do have one question though, how would you assign more than 4 servos as I can only use A0 to A3 and A4 and A5 are already used for I2C. Would you have to start assigning them under digital pins instead?

Your existing code relies on analog values to calculate servo settings. You will loose that option when using digital inputs.

You can use a muliplexer IC to expand the number of analog inputs.

1 Like