SkillAgentSearch skills...

PCF8574

Arduino library for PCF8574 - I2C IO expander

Install / Use

/learn @RobTillaart/PCF8574
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Arduino CI Arduino-lint JSON check GitHub issues

License: MIT GitHub release PlatformIO Registry

PCF8574

Arduino library for PCF8574 - 8 channel I2C IO expander.

Description

This library gives easy control over the 8 pins of a PCF8574 and PCF8574A chip. These chips are identical in behaviour although there are two distinct address ranges.

| type | address-range | notes | |:-----------|:---------------:|:-------------------------:| | PCF8574 | 0x20 to 0x27 | same range as PCF8575 ! | | PCF8574A | 0x38 to 0x3F |

So you can connect up to 16 PCF8574 on one I2C bus, giving access to 16 x 8 = 128 IO lines. To maximize IO lines combine 8 x PCF8575 + 8 x PCF8574A giving 128 + 64 = 192 IO lines. Be sure to have a well dimensioned power supply.

The library allows to read and write both single pins or 8 pins at once. Furthermore some additional functions are implemented that are playful and useful.

Interrupts intro

The PCF8574 has an interrupt output line (INT) to notify an MCU that one of the input lines has changed. This can be used to prevent active polling of the PCF8574, which can be more efficient.

From the datasheet:

An interrupt is generated by any rising or falling edge of the port inputs in the input mode. After time, (Tiv), INT is valid. Resetting and reactivating the interrupt circuit is achieved when data on the port is changed to the original setting or data is read from, or written to, the port that generated the interrupt. Resetting occurs in the read mode at the acknowledge bit after the rising edge of the SCL signal, or in the write mode at the acknowledge bit after the high-to-low transition of the SCL signal.

So there are three scenarios how the INT is reset.

  1. pins revert to original state (lesser known).
  2. read from the device (well known)
  3. write to the device (well known)

This implies that polling the PCF8574 can miss an INT in scenario 1. (see #48) In practice if you have faster polling than your signals changes this would not be a problem. E.g. tactile switches and a polling frequency > 100 Hz will work.

Interrupts library

The library cannot handle the PCF8574 interrupts as it has no code for it. The user should catch the interrupt in his own code to set a flag and can use the library to see which line has changed.

There are two examples to show how interrupts can be handled:

  • PCF8574_interrupt.ino
  • PCF8574_rotaryEncoder.ino

A more advanced interrupt handler would not set a boolean flag in the interrupt routine but increase a counter (uint8_t or larger). Then it would be possible to see that:

  1. an interrupt occurred. (counter > 0)
  2. if one or more interrupts are not handled (counter > 1)

A minimal example that shows catching missed interrupts:

  • PCF8574_interrupt_advanced.ino

0.4.0 Breaking change

Version 0.4.0 introduced a breaking change. You cannot set the pins in begin() any more. This reduces the dependency of processor dependent Wire implementations. The user has to call Wire.begin() and can optionally set the Wire pins before calling begin().

Related

16 bit port expanders

  • https://github.com/RobTillaart/MCP23017_RT I2C 16 IO lines.
  • https://github.com/RobTillaart/MCP23S17 SPI 16 IO lines.
  • https://github.com/RobTillaart/PCF8575 I2C 16 IO lines.
  • https://github.com/RobTillaart/PCA9671 I2C 16 IO lines. - successor PCF8575
  • https://github.com/RobTillaart/TCA9555 I2C 16 IO lines.

8 bit port expanders

  • https://github.com/RobTillaart/MCP23008 I2C 8 IO lines.
  • https://github.com/RobTillaart/MCP23S08 SPI 8 IO lines.
  • https://github.com/RobTillaart/PCF8574 I2C 8 IO lines.
  • https://github.com/RobTillaart/TCA9554 I2C 8 IO lines.

Other libs based upon PCF8574

  • https://github.com/RobTillaart/I2CKeyPad
  • https://github.com/RobTillaart/rotaryDecoder
  • https://github.com/RobTillaart/rotaryDecoderSwitch

I2C

Performance

Tested on UNO with PCF8574_performance showed that the PCF8574 still works at 500 kHz and failed at 600 kHz. These values are outside the specs of the datasheet so they are not recommended. However when performance is needed you can try to overclock the chip.

| clock speed | Read | Write | Notes | |:-----------:|:------:|:-------:|:--------------------| | 100000 | 236 | 240 | spec datasheet | | 200000 | 132 | 140 | | 300000 | 104 | 108 | | 400000 | 96 | 96 | max speed advised | | 500000 | 92 | 92 | not recommended | | 600000 | crash | crash |

I2C multiplexing

Sometimes you need to control more devices than possible with the default address range the device provides. This is possible with an I2C multiplexer e.g. TCA9548 which creates up to eight channels (think of it as I2C subnets) which can use the complete address range of the device.

Drawback of using a multiplexer is that it takes more administration in your code e.g. which device is on which channel. This will slow down the access, which must be taken into account when deciding which devices are on which channel. Also note that switching between channels will slow down other devices too if they are behind the multiplexer.

  • https://github.com/RobTillaart/TCA9548

Interface

#include "PCF8574.h"

PCF8574_INITIAL_VALUE is a define 0xFF that can be set compile time or before the include of "pcf8574.h" to overrule the default value used with the begin() call.

Constructor

  • PCF8574(uint8_t deviceAddress = 0x20, TwoWire *wire = &Wire) Constructor with optional address, default 0x20, and the optional Wire interface as parameter.
  • bool begin(uint8_t value = PCF8574_INITIAL_VALUE) set the initial value (default 0xFF) for the pins and masks.
  • bool isConnected() checks if the address set in the constructor or by setAddress() is visible on the I2C bus.
  • bool setAddress(const uint8_t deviceAddress) sets the device address after construction. Can be used to switch between PCF8574 modules runtime. Note this corrupts internal buffered values, so one might need to call read8() and/or write8(). Returns true if address can be found on I2C bus.
  • uint8_t getAddress() Returns the device address.

Read and Write

  • uint8_t read8() reads all 8 pins at once. This one does the actual reading.
  • uint8_t read(uint8_t pin) reads a single pin; pin = 0..7
  • uint8_t value() returns the last read inputs again, as this information is buffered in the class this is faster than reread the pins.
  • void write8(const uint8_t value) writes all 8 pins at once. This one does the actual writing.
  • uint8_t write(const uint8_t pin, const uint8_t value) writes a single pin; pin = 0..7; value is HIGH(1) or LOW (0)
  • uint8_t valueOut() returns the last written data.

ReadArray and WriteArray

Experimental (0.4.4)

Needs testing and verification with scope. See issue #60 and datasheet, 7.4 Device Functional Modes

These array functions read / write up to 30 bytes in one I2C transaction. 30 bytes is the maximum length of an AVR I2C buffer. Other lengths are possible by patching the PCF8574.cpp file.

  • bool writeArray(uint8_t *array, uint8_t size) writes size bytes in one blocking call. Blocking time depends on the I2C bus speed. ValueOut() is set to the last byte send.
  • bool readArray(uint8_t *array, uint8_t size) reads size bytes in one blocking call. Value() is set to the last byte received.

With writeArray() one can implement short pulses on the output pins. The duration of those pulses are 9 I2C clock pulses. So the duration of the pulse depends on (1) in how many bytes the pin is HIGH, (2) the I2C clock speed, and (3) the precision of the I2C clock.

uint8_t arr[10] = { 0, 1, 0, 1, 1, 0, 1, 1, 1, 0 };
Wire.setClock(300000);
PCF.writeArray(arr, 10);
Wire.setClock(100000);

Indicative pulse duration, depends also on the precision of the I2C clock. By varying the clock speed one can adjust pulse length. (e.g. a pulse of 50 us needs about 9000/50 = 180 kHz, with len = 1)

| I2C CLK | len = 1 | len = 2 | len = 3 | notes | |:---------:|:---------:|:----------:|:----------:|:--------| | 45 kHz | 200.0 us | 400.0 us | 600.0 us | | 90 kHz | 100.0 us | 200.0 us | 300.0 us | | 100 kHz | 90.0 us | 180.0 us | 270.0 us | | 200 kHz | 45.0 us | 90.0 us | 135.0 us | | 300 kHz | 30.0 us | 60.0 us | 90.0 us | | 400 kHz | 22.5 us | 45.0 us | 67.5 us | max speed advised | 450 kHz | 20.0 us | 40.0 us | 60.0 us | could work.

Note 1: that one writes to all pins so one might need a read8() to get the status of the pins that should not be affected.

Note 2: I2C clocks might not be able to be set to all possible frequencies.

With readArray() one can implement a parallel sampling of up to 32 bytes of up to 8 input lines at

View on GitHub
GitHub Stars150
CategoryDevelopment
Updated1d ago
Forks40

Languages

C++

Security Score

100/100

Audited on Mar 26, 2026

No findings