Ward I'm going to try controlling the boards with an Arduino to see if the issue is on the hardware side.

So using this code on arduino all three boards work.

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41);

void setup() {
    Serial.begin(9600);
    Serial.println("16 channel PWM test!");

    pwm1.begin();
    pwm1.setPWMFreq(1600);  // This is the maximum PWM frequency

    pwm2.begin();
    pwm2.setPWMFreq(1600);  // This is the maximum PWM frequency

    pwm1.setPWM(0, 0, 1000);
    pwm2.setPWM(1, 0, 2000);
}

void loop()
{

}

    Ward Could it be that the board sends a NACK and therefore it fails? Would this mean it is a hardware issue?

    No idea really, maybe this is some extra configuration to be done on the driver for it to work properly. Have a look at the file in/usr/include/linux/i2c.h on the board, if that helps.

    Ward So using this code on arduino all three boards work.

    good. Can you try something very similar on Bela, by running the same functions all in setup() ?

    Ward Could it be that the board sends a NACK and therefore it fails? Would this mean it is a hardware issue?

    Also, check this one that is perhaps more interesting : /usr/include/linux/i2c-dev.h

    The weird thing is that at the point where I only create a single instance of the PCA9685 class it worked fine. Once I created a second instance not a single ẁriteRegister() call succeeded anymore. Reverting back to only creating one instance of the class doesn't make a difference.

      And compiling the project on a different BeagleBone+Bela doesn't make a difference..

      Ward The weird thing is that at the point where I only create a single instance of the PCA9685 class it worked fine. Once I created a second instance not a single ẁriteRegister() call succeeded anymore. Reverting back to only creating one instance of the class doesn't make a difference.

      You mean now it no longer works even with just one instance? Have you tried a hard reboot (power off and back on)?

      There is also another thing: the address you write to is actually specified in the ioctl(i2C_file, I2C_SLAVE, i2C_address) cal in I2c::initI2C_RW. I think that you could change that at any time. So, could you try to factor that out so that it gets called once before each transaction, and so that all your instances use an underlying basic object that only opens the file descriptor, but each call to write() is preceded by an ioctl()?

      Also, do you get any relevant message if you run dmesg after the error occurs?

      Here is an example of a project that was using two I2C sensors at the same time, if that helps to double check things. https://github.com/BelaPlatform/Bela/blob/master/examples/10-Instruments/d-box/DboxSensors.cpp

      • Ward replied to this.

        giuliomoro As a further troubleshooting step, I would make sure that all the I2C transactions only happen in a single thread at a time, for instance: call all the begin() from setup() first , then have a single AuxiliaryTask that handles all the ongoing I2C transactions, and only start it after you called begin() on all the objects.

        So I've commented out the writeRegister() calls in begin() and called then from an auxiliaryTask but that still returns 121..

        giuliomoro You mean now it no longer works even with just one instance? Have you tried a hard reboot (power off and back on)?

        Yes. I've been working on this issue for several days, powering the Bela off when I go home.

        giuliomoro There is also another thing: the address you write to is actually specified in the ioctl(i2C_file, I2C_SLAVE, i2C_address) cal in I2c::initI2C_RW. I think that you could change that at any time. So, could you try to factor that out so that it gets called once before each transaction, and so that all your instances use an underlying basic object that only opens the file descriptor, but each call to write() is preceded by an ioctl()?

        I'll go try this now.

        giuliomoro Also, do you get any relevant message if you run dmesg after the error occurs?

        dmesg | grep 'i2c' gives me the following. I'm not really sure what else to look for indmesg results..

        [    0.539865] omap_i2c 44e0b000.i2c: could not find pctldev for node /ocp/l4_wkup@44c00000/scm@210000/pinmux@800/pinmux_i2c0_pins, deferring probe
        [    0.539932] omap_i2c 4802a000.i2c: could not find pctldev for node /ocp/l4_wkup@44c00000/scm@210000/pinmux@800/pinmux_i2c1_pins, deferring probe
        [    0.541655] omap_i2c 4819c000.i2c: bus 2 rev0.11 at 100 kHz
        [    0.882372] i2c /dev entries driver
        [    0.998351] input: tps65217_pwr_but as /devices/platform/ocp/44e0b000.i2c/i2c-0/0-0024/input/input0
        [    1.030820] omap_i2c 44e0b000.i2c: bus 0 rev0.11 at 400 kHz
        [    1.034640] omap_i2c 4802a000.i2c: bus 1 rev0.11 at 100 kHz

        you could do dmesg -w in another window while the terminal is running, so you will see new messages as they are printed out. There will be plenty of logging from the Bela driver (all sorts of stuff about mapping interrupts), at every start/stop, but possibly you will see some extra errors (though I wouldn't bet on it).

        We ordered three of those boards to help troubleshooting from our side, but I am afraid they will be in after Christmas!

          giuliomoro There is also another thing: the address you write to is actually specified in the ioctl(i2C_file, I2C_SLAVE, i2C_address) cal in I2c::initI2C_RW. I think that you could change that at any time. So, could you try to factor that out so that it gets called once before each transaction, and so that all your instances use an underlying basic object that only opens the file descriptor, but each call to write() is preceded by an ioctl()?

          So like this? This also returns 121

          /***** i2cbasic.h *****/
          
          #pragma once
          #include "I2c.h"
          
          class I2CBasic : public I2c
          {
          public:
          	I2CBasic();
          	~I2CBasic();
          
          	bool writeRegister(unsigned address, unsigned reg, unsigned val);	
          	int readI2C() { return 0; }	
          };
          /***** i2cbasic.cpp *****/
          
          #include "i2cbasic.h"
          
          I2CBasic::I2CBasic() {}
          
          I2CBasic::~I2CBasic() {}
          
          bool I2CBasic::writeRegister(unsigned address, unsigned reg, unsigned val)
          {
          	i2C_file = 1;
          	
          	// Open I2C file
          	if (initI2C_RW(i2C_file, address, 0) > 0)
          		return false;
          	
          	char buf[2] = { static_cast<char>(reg & 0xFF), static_cast<char>(val & 0xFF) };
          	
          	// Write a single byte
          	if(write(i2C_file, buf, 2) != 2)
          	{
          		int err = errno;
          		rt_printf("Failed to write! Register %i\tAdress %i\tError %i\n", static_cast<int>(reg), address, err);
          		return false;
          	}
          	
          	// Close I2C file
          	if (closeI2C() > 0)
          		return false;
          	
          	return true;
          }

          giuliomoro We ordered three of those boards to help troubleshooting from our side, but I am afraid they will be in after Christmas!

          Wow, that is appreciated! Thanks!

          Hmmm actually I was thinking something more like:

          I2CBasic::setFileHandleFrom(I2CBasic& i2c)
          {
              i2C_file = i2c.i2C_file;
          }
          
          I2CBasic::setAddress(unsigned int address)
          {
              i2C_address = address;
          }
          
          bool I2CBasic::writeRegister(unsigned address, unsigned reg)
          {
                  // target device as slave at stored address
                  if (ioctl(i2C_file, I2C_SLAVE, i2C_address) < 0){
                      cout << "I2C_SLAVE address " << i2C_address << " failed..." << endl;
                      return(2);
                  }	
          	char buf[2] = { static_cast<char>(reg & 0xFF), static_cast<char>(val & 0xFF) };
          	
          	// Write a single byte
          	if(write(i2C_file, buf, 2) != 2)
          	{
          		int err = errno;
          		rt_printf("Failed to write! Register %i\tAdress %i\tError %i\n", static_cast<int>(reg), address, err);
          		return false;
          	}
          	
          	// Close I2C file
          	if (closeI2C() > 0)
          		return false;
          	
          	return true;
          }

          Then you would have your application code like:

          I2CBasic i2cBasic1;
          I2CBasic i2cBasic2;
          unsigned int address1 = 40;
          unsigned int address2 = 41;
          
          bool setup(BelaContext* context, void*)
          {
              // init the first one from scratch
              if(i2cBasic1.initI2C_RW(bus, address1, 0) > 0)
              {
                   int err = errno;
                  fprintf(stderr, "Something happened while initializing the I2c connection. \tError code %i\n", err);
          		return false;
              }
              // the second instance re-uses the same open file as the first one
              i2cBasic2.setFileHandleFrom(i2cBasic1);
              // and we only sets the address
              i2cBasic2.setAddress(address2); 
              ...
              // ... so that each call to writeRegister will have the `ioctl()` (to set the register) followed by 
              // `write()`, with both objects acting on the same file handler
              if(!i2cBasic1.writeRegister(...))
                return false;
              ...
              if(!i2cBasic2.writeRegister(...))
                  return false;
              return true;
          }

          This is a bit ugly, and could definitely be refactored, but just see if this helps in any way. The example I linked above (10-Instruments/d-box) actually successfully opens the /dev/i2c... file twice, once for each instance, so I am not sure this would help, but worth giving it a try.

          so I got the boards, and I ran the code below and it works:

          #include <Bela.h>
          #include <pca9685.h>
          
          PCA9685 pwm1;
          PCA9685 pwm2;
          PCA9685 pwm3;
          
          bool setup(BelaContext* context, void* userData) {
          
          	pwm1.begin(0x40);
          	pwm2.begin(0x41);
          	pwm3.begin(0x42);
          
          	pwm1.setPWM(15, 0);
          	pwm2.setPWM(15, 0);
          	pwm3.setPWM(15, 0);
          	usleep(1000000);
          	pwm1.setPWM(15, 4000);
          	pwm2.setPWM(15, 4000);
          	pwm3.setPWM(15, 4000);
          	usleep(1000000);
          	for(unsigned int n = 0; n < 10; ++n)
          	{
          		pwm1.setPWM(15, 0);
          		pwm2.setPWM(15, 4000);
          		pwm3.setPWM(15, 4000);
          		usleep(300000);
          		pwm1.setPWM(15, 4000);
          		pwm2.setPWM(15, 0);
          		pwm3.setPWM(15, 4000);
          		usleep(300000);
          		pwm1.setPWM(15, 4000);
          		pwm2.setPWM(15, 4000);
          		pwm3.setPWM(15, 0);
          		usleep(300000);
          	}
          	pwm1.setPWM(15, 4095);
          	pwm2.setPWM(15, 4095);
          	pwm3.setPWM(15, 4095);
          	return true;
          }
          
          void render(BelaContext* context, void* userData) {
          }
          
          void cleanup(BelaContext* context, void* userData) {
          }

          see proof here:

          The only problems I encountered were weak/wobbly "jumpers" for the addresses: I don't have a soldering iron here, so I just taped some stranded wire across the pads. Initially, I was cutting the plastic sleeve off. This proved to be fairly unreliable, that a given board would occasionally (often!) change address. When that happened while the program was running, I would then get the 121 error. However, even if one of the boards failed (because of wrong address), the others would still work, and when two boards were on the same address, you could just address them both in one go!
          However, now that I leave the sleeve in place, as in the pic below, this is much more reliable:

          alt text

          I tried running the above also in an AuxiliaryTask, and it all works as expected. E.g.:

          #include <Bela.h>
          #include <pca9685.h>
          
          PCA9685 pwm1;
          PCA9685 pwm2;
          PCA9685 pwm3;
          
          void auxFunc(void*)
          {
          	pwm1.setPWM(15, 0);
          	pwm2.setPWM(15, 0);
          	pwm3.setPWM(15, 0);
          	usleep(1000000);
          	pwm1.setPWM(15, 4000);
          	pwm2.setPWM(15, 4000);
          	pwm3.setPWM(15, 4000);
          	usleep(1000000);
          	for(unsigned int n = 0; n < 10; ++n)
          	{
          		if(gShouldStop)
          			return;
          		pwm1.setPWM(15, 0);
          		pwm2.setPWM(15, 4000);
          		pwm3.setPWM(15, 4000);
          		usleep(300000);
          		pwm1.setPWM(15, 4000);
          		pwm2.setPWM(15, 0);
          		pwm3.setPWM(15, 4000);
          		usleep(300000);
          		pwm1.setPWM(15, 4000);
          		pwm2.setPWM(15, 4000);
          		pwm3.setPWM(15, 0);
          		usleep(300000);
          	}
          	pwm1.setPWM(15, 4095);
          	pwm2.setPWM(15, 4095);
          	pwm3.setPWM(15, 4095);
          }
          
          bool setup(BelaContext* context, void* userData) {
          
          	pwm1.begin(0x40);
          	pwm2.begin(0x41);
          	pwm3.begin(0x42);
          	Bela_scheduleAuxiliaryTask(Bela_createAuxiliaryTask(auxFunc, 90, "i2c", NULL));
          	return true;
          }
          
          void render(BelaContext* context, void* userData) {
          }
          
          void cleanup(BelaContext* context, void* userData) {
          }

          So, I am not sure what is the problem you are encountering, but I would suggest:
          - check your hardware connections and the jumper
          - make sure i2cdetect reliably detects the correct address. I run it like this watch -n0.2 i2cdetect -y -r 1 so it refreshes automatically every 0.2 seconds. Remember to stop this when running the Bela program, as it may interfere with that one, as both use the bus.
          - make sure you use 0x if you are specifying the hex address (and viceversa, don't use it if you are not). Pretty basic, I know, but it got me a couple of times today!

            Rethinking about the whole thing, if I was to place a bet on what your issue was, I would bet on this, as it would explain all of the above:

            giuliomoro - make sure you use 0x if you are specifying the hex address (and viceversa, don't use it if you are not). Pretty basic, I know, but it got me a couple of times today!

            🙂

            11 days later

            giuliomoro - make sure you use 0x if you are specifying the hex address (and viceversa, don't use it if you are not). Pretty basic, I know, but it got me a couple of times today!

            Yeah, that was the issue.. Thanks for the help! 🙂

              Ward Yeah, that was the issue

              Great! I guess I could've asked you to share your user code to spot it!

              That looks cool, what are you building?

              I'm building a 6 track synthesizer/instrument. The panel you are seeing is an interface to edit, store and recall presets. The LED's are going to be used to display the track you are editing (color) and what it's current value is (brightness).

              The instrument is taking shape but not done yet, still a lot of work on both the hard- and software side. I'll make a more in-depth post in the future about what it does and how it sounds.

              3 months later