I'm trying to control 14 RGB LED's using 3 PCA9685 pwm drivers, communicating through the Bela using I2C. The boards are chained together and have the addresses 40, 41 and 42. https://www.adafruit.com/product/815
I've verified the boards are seen by the Bela using
i2cdetect -r -y 1
It tells me that indeed 40, 41 and 42 are on the I2C bus. It also says 70 is there, I'm not sure what that is?
I've written a class extending the Bela I2c class, see attachment.
Each class instance addresses one board.
Initially the class worked fine. I tested it with all three boards connected and created one instance of the class to control the first board. I was able to set 16 pwm channels on the first board and thus control 5 RGB LED's.
Then I created a second instance of the class and from that point it refused to write registers.
When calling the write function inside the writeRegister function it returns -1 and when I check errno it gives me 121 (Remote I/O error).
I'm kinda clueless as what to do now..
I saw that at a certain point there was a better version of I2c communication in development but that seems abandoned..
To clarify, the class instances are created globally in render.cpp. The begin function is called in setup() Then in a auxiliaryTask setRGB() is called (which calls setPWM(), which calls writeRegister()).
In both cases writeRegister() fails..
/***** pca9685.h *****/
#pragma once
#include "I2c.h"
#include <cstdint>
class PCA9685 : public I2c
{
public:
PCA9685();
~PCA9685();
// Initialize the I2c connection
bool begin(uint8_t address = 0x40, uint8_t bus = 1);
// Write a single byte directly to memory
void writeRegister(uint8_t reg, uint8_t value);
// Set a PWM value for specified channel
// For speed, the value is not clipped inside range
void setPWM(unsigned channel, unsigned value);
// Set RGB value of a LED
void setRGB(unsigned led, unsigned r, unsigned g, unsigned b);
// Unused function which has to be implemented by I2c.h
int readI2C() { return 0; }
private:
int address;
};
..
/***** pca9685.cpp *****/
#include "pca9685.h"
#include "Bela.h"
#include <errno.h>
// https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf
// https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/blob/master/Adafruit_PWMServoDriver.cpp
// https://github.com/BelaPlatform/Bela/blob/master/include/I2c.h
// https://forum.bela.io/d/422-reading-gy-521-in-pure-data/8
// https://forum.bela.io/d/442-i2c-accelerometer-class/8
// https://github.com/BelaPlatform/Bela/blob/better-i2c/core/default_libpd_render.cpp
// https://github.com/BelaPlatform/Bela/tree/better-i2c/include
// https://forum.bela.io/d/476-i2c-troubles/3
// write returning -1 is maybe EINTR????
// http://pubs.opengroup.org/onlinepubs/009604499/functions/write.html
// ================================
PCA9685::PCA9685() {}
PCA9685::~PCA9685() {}
// ================================
bool PCA9685::begin(uint8_t address, uint8_t bus)
{
this->address = address;
// This functions returns 1 or 2
if(initI2C_RW(bus, address, 0) > 0)
{
int err = errno;
rt_printf("Something happened while initializing the I2c connection. \tError code %i\n", err);
return false;
}
usleep(10000);
// Set MODE1 to listen to all calls (bit 0)
writeRegister(0, 0x80);
// Set all PWM to output a constant high voltage essentialy turning OFF all LED's
writeRegister(250, 0 & 0xFF); // on time byte 1
writeRegister(251, 0 >> 8); // on time byte 2
writeRegister(252, 4095 & 0xFF); // off time byte 1
writeRegister(253, 4095 >> 8); // off time byte 2
return true;
}
// ================================
void PCA9685::setPWM(unsigned channel, unsigned value)
{
writeRegister(6 + (4 * channel), value & 0xFF); // on
writeRegister(7 + (4 * channel), value >> 8);
writeRegister(8 + (4 * channel), 4095 & 0xFF); // off
writeRegister(9 + (4 * channel), 4095 >> 8);
}
// ================================
void PCA9685::setRGB(unsigned led, unsigned r, unsigned g, unsigned b)
{
setPWM(led * 3, b);
setPWM(led * 3 + 1, g);
setPWM(led * 3 + 2, r);
}
// ================================
void PCA9685::writeRegister(uint8_t reg, uint8_t value)
{
uint8_t buf[2] = {reg, value};
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;
}
}
Ward It tells me that indeed 40, 41 and 42 are on the I2C bus. It also says 70 is there, I'm not sure what that is?
not sure either, does 70 it disappear if you disconnect the boards?
Ward I saw that at a certain point there was a better version of I2c communication in development but that seems abandoned..
that was about properly-threaded I2C communication, so that the user didn't have to manage their own AuxiliaryTask, but it seems like you are doing it already, so that wouldn't bring any improvements to your case I think.
Ward Then I created a second instance of the class and from that point it refused to write registers.
Does the call to initI2C_RW() succeed or fail? If that fails, then all calls to writeRegister() would also fail.
Do the calls to writeRegister() in begin() succeed or fail? That is, are the calls to writeRegister() failing only in the AuxiliaryTask or also in setup()?
Also, are all the writeRegister() calls happening in the same thread? I see how concurrent access to the bus would cause some errors. Note that if you call Bela_scheduleAuxiliaryTask() from within setup, the callback of the AuxiliaryTask will get executed immediately,
I think what would happen here is that as soon as you schedule auxTaskForObj1, callbackThatDoesIoForI2cObj1() would get executed (that is: the thread running setup() is preempted before it runs i2cObj2.begin(...);. So callbackThatDoesIoForI2cObj1() starts running and it calls writeRegister(). At that point, this thread suspends waiting for the bus transmission to happen, and the thread running setup() is resumed, so it runs i2cObj2.begin() and this also tries to call writeRegister() on the same I2C bus, while the call from callbackThatDoesIoForI2cObj1() is still in progress. Now, I am not 100% sure how this gets handled by the kernel: it should be able to handle concurrent requests, and either block or return an error (I would expect something like EAGAIN on the second request that comes in).
As a reference, the kernel driver in use is this one I think, but I can't understand under which conditions omap_i2c_xfer_msg() would return EREMOTEIO.
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.
Incidentally, do you see any errors pop up in dmesg when your transactions fail?
one last thing: I have sometimes experienced the I2C bus getting stuck following an error, and I had to power off and on the board to solve the issue (a simple reboot wouldn't work).
giuliomoro Does the call to initI2C_RW() succeed or fail? If that fails, then all calls to writeRegister() would also fail.
Do the calls to writeRegister() in begin() succeed or fail? That is, are the calls to writeRegister() failing only in the AuxiliaryTask or also in setup()?
initI2C_RW() succeeds and then all calls to writeRegister()fail. So both in the auxiliaryTask and setup().
giuliomoro As a reference, the kernel driver in use is this one I think, but I can't understand under which conditions omap_i2c_xfer_msg() would return EREMOTEIO.
Could it be that the board sends a NACK and therefore it fails? Would this mean it is a hardware issue?
I'm going to try controlling the boards with an Arduino to see if the issue is on the hardware side.
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.
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.
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?
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 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.
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:
I tried running the above also in an AuxiliaryTask, and it all works as expected. E.g.:
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!
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!
Loading...
Something went wrong while trying to load the full version of this site. Try hard-refreshing this page to fix the error.