RobTillaart / PCA9553

Arduino library for for I2C PCA9553 4 channel PWM
MIT License
2 stars 0 forks source link

PCA9553 - get 0.2.0 working #2

Closed ITstreet1 closed 1 year ago

ITstreet1 commented 1 year ago

Although not related to this repo, I wonder do you intend to make support for PCA9553?

RobTillaart commented 1 year ago

Hi Dejan, thanks for the question.

I do not have the intention at the moment to make a library for the PCA9553. It might be not too difficult to derive from this one or the PCA9634 or PCA9685.

As I did not read the datasheet yet it is hard to say if it is compatible and can be derived pretty easy. Or that it is made rather complete different.

Did you read the datasheet, and if so are these "close" like same registers etc? Do you need full functionality or a subset to start?

As I do not have the hardware I cannot test a library. Can you do that?

RobTillaart commented 1 year ago

Datasheet - https://www.nxp.com/docs/en/data-sheet/PCA9553.pdf

4-bit I2C-bus LED driver with programmable blink rates Rev. 06 — 29 December 2008 26 pages

So 15 years old device.

RobTillaart commented 1 year ago

Quick look at the datasheet shows it not a look alike. Good news is that it is not a complex one, so creating a library is expected to have little problems.

ITstreet1 commented 1 year ago

Hi,

Thank you for your fast response.

I need it only for driving some MOSFETs, and LED strips over the PWM. Old or not, I have them, so why not use them? :) If you have any suggestions, I am all ears.

RobTillaart commented 1 year ago

I can have a look to make a quick and dirty first version of such library and clean it up based upon your tests?

Have an hour or two this evening (almost dinner time now, and I have a few other tasks to do)

ITstreet1 commented 1 year ago

Mate, I need you. :) You may need me, or you may not. :) Of course, I have as many hours as you need. I just connected everything, the I2C address is 0x62. The MCU I use is ATmega324p. Again, the chip I have, so it will go along the expander. :)

RobTillaart commented 1 year ago

@ITstreet1

Hi Dejan,

created a develop branch and PR here - https://github.com/RobTillaart/PCA9553 Stripped a copy of the PCA9635 and throwed in the basic access to the registers with some "meaningfull" names.

I propose to move this issue to that repository to keep the discussions in the right repo.

(now I have to attend other tasks.

RobTillaart commented 1 year ago

Almost got initial version build stable.

RobTillaart commented 1 year ago

Merged and released the 0.1.0 initial version. Also published it for the Arduino Library Manager and platformIO.

ITstreet1 commented 1 year ago

Ok,

I installed the library from the library manager. There is an example Test01. After uploading, I get Lib Version and Done

What else to check?

RobTillaart commented 1 year ago

There should be different signals on the 4 outputs.

RobTillaart commented 1 year ago

FYI, the interface will probably change as the PCA9553 has a 8 and 16 LED variant. I need to check the datasheets if the output count is the only difference (from outside) If so I wan to give them all the same interface.

ITstreet1 commented 1 year ago

There should be different signals on the 4 outputs.

I am afraid I have none.

The PCA9553 I have is in the TSSOP8 package. The project I would like to use is to drive led strips over the MOSFETs. So the PCA9553 is soldered on a PCB. This is the schematic:

pca9553

I can unsolder 220R resistors and check the output on pads if it would make any difference.

I believe for the start, the basic set HIGH/LOW logic would be better for testing, and later the PWM. But, I will try everything you suggest.

RobTillaart commented 1 year ago

In the test sketch I connected every possible source to 1 pin, so there should be differences. Desoldering the device makes sense, Do not forget to add 4K7 pull up resistors to the SDA and SCL

  //  all channels a different source
  leds.setLEDSource(0, 0);   ==> LOW
  leds.setLEDSource(1, 1);   ==> HIGH
  leds.setLEDSource(2, 2);  ==> PWM duty cycle 50%
  leds.setLEDSource(3, 3);  ==> PWM duty cycle 12%
ITstreet1 commented 1 year ago

I have 10K. I will desolder the 220R, and change pull-up resistors to 4.7K, and let you know.

RobTillaart commented 1 year ago

I created a new develop branch (and PR) as I want to have the new interface in place asap.

main change are a generic setPrescaler() and setPWM().
This allows to have the 8 and 16 outputs versions to have the same interface.

Get and set the pre-scaler of the PWM generator.

Get and set the duty cycle of the PWM generator.

RobTillaart commented 1 year ago

Added two functions to the interface of the develop branch. Now you should be able to set the HIGH LOW as you asked for.

RobTillaart commented 1 year ago

Added example using digitalWrite() and digitalRead()

ITstreet1 commented 1 year ago

20230717_112259

ITstreet1 commented 1 year ago

I just soldered only the PCA9553, and two 4.7K resistors, and connect it to the Uno. Nothing on the R14-117 pads. Or pins of the PCA9553.

The sketch is Test02

PS. I2C scanner sees it on 0x62

ITstreet1 commented 1 year ago

Just tried basic:

void loop(){
  leds.digitalWrite(0, HIGH);
  delay(1000);
  leds.digitalWrite(0, LOW);
  delay(1000);
}

Nothing

RobTillaart commented 1 year ago

And if you try

void loop
{
  leds.digitalWrite(0, HIGH);
  leds.digitalWrite(1, LOW);
  leds.digitalWrite(2, HIGH);
  leds.digitalWrite(3, LOW);

  uint8_t val = leds.input();
  Serial.println(val, HEX);
  delay(100);
}
RobTillaart commented 1 year ago

Can you change the private functions in the .cpp file, to see if the low level I2C gives errors.

(adding error handling is on the todo

uint8_t PCA9553::writeReg(uint8_t reg, uint8_t value)
{
  Serial.println(__FUNCTION__);

  _wire->beginTransmission(_address);
  _wire->write(reg);
  _wire->write(value);
  _error = _wire->endTransmission();

  Serial.println(_error, HEX);

  if (_error == 0) _error = PCA9553_OK;
  else _error = PCA9553_ERROR;
  return _error;
}

uint8_t PCA9553::readReg(uint8_t reg)
{
  Serial.println(__FUNCTION__);

  _wire->beginTransmission(_address);
  _wire->write(reg);
  _error = _wire->endTransmission();

  Serial.println(_error, HEX);

  if (_wire->requestFrom(_address, (uint8_t)1) != 1)
  {
    _error = PCA9553_ERROR;
    return 0;
  }
  _error = PCA9553_OK;
  return _wire->read();
}
ITstreet1 commented 1 year ago

And if you try

void loop
{
  leds.digitalWrite(0, HIGH);
  leds.digitalWrite(1, LOW);
  leds.digitalWrite(2, HIGH);
  leds.digitalWrite(3, LOW);

  uint8_t val = leds.input();
  Serial.println(val, HEX);
  delay(100);
}

'class PCA9553' has no member named 'input'; did you mean 'getInput'?

RobTillaart commented 1 year ago

yes

RobTillaart commented 1 year ago

Have to attend other work, so will be "offline" for several hours

ITstreet1 commented 1 year ago

Can you change the private functions in the .cpp file, to see if the low level I2C gives errors.

(adding error handling is on the todo

uint8_t PCA9553::writeReg(uint8_t reg, uint8_t value)
{
  Serial.println(__FUNCTION__);

  _wire->beginTransmission(_address);
  _wire->write(reg);
  _wire->write(value);
  _error = _wire->endTransmission();

  Serial.println(_error, HEX);

  if (_error == 0) _error = PCA9553_OK;
  else _error = PCA9553_ERROR;
  return _error;
}

uint8_t PCA9553::readReg(uint8_t reg)
{
  Serial.println(__FUNCTION__);

  _wire->beginTransmission(_address);
  _wire->write(reg);
  _error = _wire->endTransmission();

  Serial.println(_error, HEX);

  if (_wire->requestFrom(_address, (uint8_t)1) != 1)
  {
    _error = PCA9553_ERROR;
    return 0;
  }
  _error = PCA9553_OK;
  return _wire->read();
}

writeReg 0 readReg 0 writeReg 0 readReg 0 writeReg 0 readReg 0 writeReg 0 readReg 0 writeReg 0 readReg 0 writeReg 0 readReg 0

ITstreet1 commented 1 year ago

yes

With old CPP file:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

With the new CPP file, just add another 0 after redReg

RobTillaart commented 1 year ago

So I2C looks like working.

Check if the source register is set and can be read back.

void loop()
{
  for (uint8_t ch = 0; ch < 4; ch ++)
  {
    for (uint8_t src = 0; src < 4; src++)
    {
      leds.setLEDSource(ch, src);
      Serial.print(leds.getLEDSource(ch));
    }
    Serial.println();
  }
  Serial.println();
}
ITstreet1 commented 1 year ago

0123 0123 0123 0123

0123 0123 0123 0123

0123 0123 0123 0123

old CPP file

RobTillaart commented 1 year ago

So it looks like the register is written and read correctly.

ITstreet1 commented 1 year ago

Do I need to declare output to the pins in a setup()?

RobTillaart commented 1 year ago

Do I need to declare output to the pins in a setup()?

Have you seen something in the datasheet about this? I may have missed it.

ITstreet1 commented 1 year ago

Just my guess.

So what have we got here? Uno can see the IC. It can write and read correctly.

There is nothing on the IO pins.

RobTillaart commented 1 year ago

I'm going to read the datasheet again, this evening or tonight to see if I missed something.

In the mean time I have created initial experimental versions for the PCA9552 (16 lines) and PCA9551 (8 lines). Have to go through those datasheets as well. Advantage is that these have is 3 address lines so you can have 8 of those on one I2C bus (x 16 == 128!).

ITstreet1 commented 1 year ago

OK, I used PCA9553 as it has 4ch. All that I need.

Waiting for your suggestions. And thank you for your time.

RobTillaart commented 1 year ago

new test sketch that reads back all registers, please run and check if output is as expected.

//
//    FILE: PCA9553_test_registers.ino
//  AUTHOR: Rob Tillaart
// PURPOSE: test PCA9553 device registers readback
//     URL: https://github.com/RobTillaart/PCA9553

#include "Arduino.h"
#include "Wire.h"
#include "PCA9553.h"

PCA9553 leds(0x62);

void test_GPIO()
{
  Serial.println(__FUNCTION__);
  for (int i = 0; i < leds.channelCount(); i++)
  {
    leds.digitalWrite(i, LOW);
    Serial.print(i);
    Serial.print("\t");
    Serial.print(leds.digitalRead(i));
    Serial.print("\t");
    leds.digitalWrite(i, HIGH);
    Serial.print(leds.digitalRead(i));
    Serial.print("\n");
  }
  Serial.println();
}

void test_prescaler()
{
  Serial.println(__FUNCTION__);
  for (uint8_t val = 0; val < 255; val += 5)
  {
    leds.setPrescaler(0, val);
    leds.setPrescaler(1, val);
    Serial.print(leds.getPrescaler(0));
    Serial.print("\t");
    Serial.println(leds.getPrescaler(1));
    Serial.print("\n");
  }
  Serial.println();
}

void test_PWM()
{
  Serial.println(__FUNCTION__);
  for (uint8_t val = 0; val < 255; val += 5)
  {
    leds.setPWM(0, val);
    leds.setPWM(1, val);
    Serial.print(leds.getPWM(0));
    Serial.print("\t");
    Serial.println(leds.getPWM(1));
    Serial.print("\n");
  }
  Serial.println();
}

void test_source()
{
  Serial.println(__FUNCTION__);
  for (uint8_t val = 0; val < 4; val++)
  {
    leds.setLEDSource(0, val);
    leds.setLEDSource(1, val);
    leds.setLEDSource(2, val);
    leds.setLEDSource(3, val);

    Serial.print(leds.getLEDSource(0));
    Serial.print("\t");
    Serial.print(leds.getLEDSource(1));
    Serial.print("\t");
    Serial.print(leds.getLEDSource(2));
    Serial.print("\t");
    Serial.print(leds.getLEDSource(3));
    Serial.print("\n");
  }
  Serial.println();
}

void setup()
{
  Serial.begin(115200);
  Serial.print("PCA9553_LIB_VERSION: ");
  Serial.println(PCA9553_LIB_VERSION);
  Serial.println();

  if (leds.begin() == false)
  {
    Serial.println("Could not connect.");
    while(1);
  }

  Serial.println(leds.getAddress(), HEX);
  Serial.println(leds.channelCount());
  Serial.println();

  test_GPIO();
  test_prescaler();
  test_PWM();
  test_source();

  Serial.println("\ndone...");
}

void loop()
{
}

//  -- END OF FILE --
RobTillaart commented 1 year ago

this might be needed to get digitalRead() working correctly Datasheet: 7.4 Pins used as general purpose I/Os

LED pins not used to control LEDs can be used as general purpose I/Os.

For use as input: Set LEDn to high-impedance (01) and then read the pin state via the Input register.

For use as output: Connect external pull-up resistor to the pin and size it according to the DC recommended operating characteristics. LED output pin is HIGH when the output is programmed as high-impedance, and LOW when the output is programmed LOW through the ‘LED selector’ register.

.cpp file

void PCA9553::pinMode(uint8_t led, uint8_t mode)
{
  if (mode != OUTPUT) setLEDSource(led, 1);
}

added to develop branch, build runs.

ITstreet1 commented 1 year ago

new test sketch that reads back all registers, please run and check if output is as expected.

//
//    FILE: PCA9553_test_registers.ino
//  AUTHOR: Rob Tillaart
// PURPOSE: test PCA9553 device registers readback
//     URL: https://github.com/RobTillaart/PCA9553

#include "Arduino.h"
#include "Wire.h"
#include "PCA9553.h"

PCA9553 leds(0x62);

void test_GPIO()
{
  Serial.println(__FUNCTION__);
  for (int i = 0; i < leds.channelCount(); i++)
  {
    leds.digitalWrite(i, LOW);
    Serial.print(i);
    Serial.print("\t");
    Serial.print(leds.digitalRead(i));
    Serial.print("\t");
    leds.digitalWrite(i, HIGH);
    Serial.print(leds.digitalRead(i));
    Serial.print("\n");
  }
  Serial.println();
}

void test_prescaler()
{
  Serial.println(__FUNCTION__);
  for (uint8_t val = 0; val < 255; val += 5)
  {
    leds.setPrescaler(0, val);
    leds.setPrescaler(1, val);
    Serial.print(leds.getPrescaler(0));
    Serial.print("\t");
    Serial.println(leds.getPrescaler(1));
    Serial.print("\n");
  }
  Serial.println();
}

void test_PWM()
{
  Serial.println(__FUNCTION__);
  for (uint8_t val = 0; val < 255; val += 5)
  {
    leds.setPWM(0, val);
    leds.setPWM(1, val);
    Serial.print(leds.getPWM(0));
    Serial.print("\t");
    Serial.println(leds.getPWM(1));
    Serial.print("\n");
  }
  Serial.println();
}

void test_source()
{
  Serial.println(__FUNCTION__);
  for (uint8_t val = 0; val < 4; val++)
  {
    leds.setLEDSource(0, val);
    leds.setLEDSource(1, val);
    leds.setLEDSource(2, val);
    leds.setLEDSource(3, val);

    Serial.print(leds.getLEDSource(0));
    Serial.print("\t");
    Serial.print(leds.getLEDSource(1));
    Serial.print("\t");
    Serial.print(leds.getLEDSource(2));
    Serial.print("\t");
    Serial.print(leds.getLEDSource(3));
    Serial.print("\n");
  }
  Serial.println();
}

void setup()
{
  Serial.begin(115200);
  Serial.print("PCA9553_LIB_VERSION: ");
  Serial.println(PCA9553_LIB_VERSION);
  Serial.println();

  if (leds.begin() == false)
  {
    Serial.println("Could not connect.");
    while(1);
  }

  Serial.println(leds.getAddress(), HEX);
  Serial.println(leds.channelCount());
  Serial.println();

  test_GPIO();
  test_prescaler();
  test_PWM();
  test_source();

  Serial.println("\ndone...");
}

void loop()
{
}

//  -- END OF FILE --

Downloaded lib again, with this example and get this on Serial monitor:

PCA9553_LIB_VERSION: 0.1.0

62 4

test_GPIO 0 0 0 PCA9553_LIB_VERSION: 0.1.0

62 4

test_GPIO 0 0 0 1 0 0 2 0 0 3 0 0

test_prescaler 0 0

5 5

PCA9553_LIB_VERSION: 0.1.0

62 4

test_GPIO 0 0 0 1 0 0 2 0 0 3 0 0

test_prescaler 0 0

5 5

10 10

15 15

20 20

25 25

30 30

35 35

40 40

45 45

50 50

55 55

60 60

65 65

70 70

75 75

80 80

85 85

90 90

95 95

100 100

105 105

110 110

115 115

120 120

125 125

130 130

135 135

140 140

145 145

150 150

155 155

160 160

165 165

170 170

175 175

180 180

185 185

190 190

195 195

200 200

205 205

210 210

215 215

220 220

225 225

230 230

235 235

240 240

245 245

250 250

test_PWM 0 0

5 5

10 10

15 15

20 20

25 25

30 30

35 35

40 40

45 45

50 50

55 55

60 60

65 65

70 70

75 75

80 80

85 85

90 90

95 95

100 100

105 105

110 110

115 115

120 120

125 125

130 130

135 135

140 140

145 145

150 150

155 155

160 160

165 165

170 170

175 175

180 180

185 185

190 190

195 195

200 200

205 205

210 210

215 215

220 220

225 225

230 230

235 235

240 240

245 245

250 250

test_source 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3

done...

But nothing on the pins. I am using a classic multimeter.

ITstreet1 commented 1 year ago

this might be needed to get digitalRead() working correctly Datasheet: 7.4 Pins used as general purpose I/Os

LED pins not used to control LEDs can be used as general purpose I/Os.

For use as input: Set LEDn to high-impedance (01) and then read the pin state via the Input register.

For use as output: Connect external pull-up resistor to the pin and size it according to the DC recommended operating characteristics. LED output pin is HIGH when the output is programmed as high-impedance, and LOW when the output is programmed LOW through the ‘LED selector’ register.

.cpp file

void PCA9553::pinMode(uint8_t led, uint8_t mode)
{
  if (mode != OUTPUT) setLEDSource(led, 1);
}

added to develop branch, build runs.

According to this, to use it as HIGH/LOW logic, I need a pull-up resistor on IO pins. PCA9553 can go LOW, but can not go HIGH. The only way is to use a resistor. I will try this.

But what about PWM? Do I need the same with a PWM? If I set 255 value, it should go "HIGH", it should have a 5V on the pin. Right now, as is, on the pins there is nothing, no matter I measure pins from the GND, or from the 5V.

RobTillaart commented 1 year ago

The output looks good (except for the digitalWrite() and digitalRead()

What I learned from the datasheet is that it is not possible to switch runtime from INPUT to OUTPUT mode. Exactly because for the INPUT mode an external pull up is needed.

The only way is to use a resistor. I will try this.

Good action!

But what about PWM? Do I need the same with a PWM? If I set 255 value, it should go "HIGH", it should have a 5V on the pin. Right now, as is, on the pins there is nothing, no matter I measure pins from the GND, or from the 5V.

As far as I understand (cannot test it) is PWM an automatic OUTPUT mode so it needs no external PULLLUP. Only INPUT mode needs an internal PULLUP.

TODO: document this in the readme.md file.

RobTillaart commented 1 year ago

Found no other things in the datasheet. To be continued tomorrow (other tasks are asking for attention)

ITstreet1 commented 1 year ago

As far as I understand (cannot test it) is PWM an automatic OUTPUT mode so it needs no external PULLLUP. Only INPUT mode needs an internal PULLUP.

In DS it says it needs resistors for output, not for input.

I soldered a resistor on ch0 and 5V rail. Nothing happened.

Didn't make it write a HIGH logic state...

In DS 9.1 - fig15 it says it can sink LEDs. Tomorrow I will solder one LED in sink to try it this way.

If any suggestions, I am all ears...

RobTillaart commented 1 year ago

Good morning,

This is how I understand the working after a good night sleep.

image

What is missing in the picture is an internal pull up resistor between LEDn and 5V (VDD)

GPIO input mode

source mode must be set to 01 ==> the transistor blocks and the PULL UP set LEDn to HIGH

GPIO output mode

If the source mode == 00 the transistor should connect GND to LEDn resulting in LOW If the source mode == 01 the transistor blocks and the PULL UP set LEDn to HIGH

Connected a LED to 5V on one side and LEDn on the other, and you should be able to let it blink. An external PULL UP will minimize the RC time to get nicer "square wave" => faster and thus cleaner switching.

PWM mode

The MUX will select the signal from the PWM and alternatingly open/close the transistor giving a pulse on LEDn It makes sense to add an external PULL UP for the same reason as above.

Dangerous

Writing down the above made me aware that setting the source mode to 00 and putting 5V directly on LEDn is dangerous as there would flow an "unrestricted" current through the transistor. This can cause permanent damage, Also in PWM mode a direct connection to 5V would probably damage the transistor (in a pulsating way).

Does this story makes sense?

ITstreet1 commented 1 year ago

Can't help you much other than bare tests. If there is anything else you suggest me to try?

In a few minutes I will try an LED in a sink, according to the DS.

ITstreet1 commented 1 year ago

Now we can start all over again.

The LED BLINKS!!!!

#include "Arduino.h"
#include "Wire.h"
#include "PCA9553.h"
PCA9553 leds(0x62);
void setup() {
  if (leds.begin() == false)
  {
    Serial.println("Could not connect.");
    while(1);
  }
}

void loop() {
  leds.digitalWrite(1, LOW);
  delay(1000);
  leds.digitalWrite(1,HIGH);
  delay(1000);

}

It is connected as on the DS, in sink on pin1. Shall we try barebone PWM?

ITstreet1 commented 1 year ago
#include "Arduino.h"
#include "Wire.h"
#include "PCA9553.h"
PCA9553 leds(0x62);
void setup() {
  if (leds.begin() == false)
  {
    Serial.println("Could not connect.");
    while(1);
  }
}

void loop() {
  for (uint8_t val = 0; val < 255; val += 5){
    leds.setPWM(1, val);
    delay(100);
  }
}

There is nothing on the LED. The LED is connected as on the Blink sketch.

RobTillaart commented 1 year ago

You must connect an output to the PWM.

Leds setLEDSource(0, 3);

0 is pin, 3 is pwm0. (4 is pwm1)

(From my phone, will be back later)

RobTillaart commented 1 year ago
//
//    FILE: PCA9553_PWM.ino
//  AUTHOR: Rob Tillaart
//    DATE: 2023-07-18
// PURPOSE: test PCA9553 library
//     URL: https://github.com/RobTillaart/PCA9553
//
//  Connect LEDs from pin 0 and pin 1 with a resistor to 5V
//  See datasheet

#include "Arduino.h"
#include "Wire.h"
#include "PCA9553.h"

PCA9553 leds(0x62);

void setup()
{
  Serial.begin(115200);
  Serial.print("PCA9553_LIB_VERSION: ");
  Serial.println(PCA9553_LIB_VERSION);
  Serial.println();

  if (leds.begin() == false)
  {
    Serial.println("Could not connect.");
    while(1);
  }

  //  not mandatory, just to make it explicit
  leds.pinMode(0, OUTPUT);
  leds.pinMode(1, OUTPUT);

  leds.setPrescaler(0, 44);
  leds.setPWM(0, 128);
  leds.setPrescaler(1, 22);
  leds.setPWM(1, 32);
}

void loop()
{
}

//  -- END OF FILE --
ITstreet1 commented 1 year ago

Right now I have only one LED, I will add another one later. The one connected is lit. But...

Can you put little light here? What setPrescaler() function does here? Does it set the above limit? If so, what are the options? I noticed the LED is lit with the same brightness no matter what value I set for setPWM(). Do I need to adjust the setPrescaler, too?

This works in sink configuration. Can this work in source, too?