wordclock_esp8266
Software for a Wordclock based on ESP8266 and Neopixel LEDs with PONG, TETRIS, SNAKE game modes
PCF8574 library. i2c digital expander for Arduino, Raspberry Pi Pico and rp2040 boards, esp32, SMT32 and ESP8266. Can r…
A simple and efficient library to use the PCF8574 I2C 8-bit digital I/O expander with Arduino, ESP8266, ESP32, and other platforms.
Author: Renzo Mischianti
Website: www.mischianti.org
GitHub: xreef/PCF8574_library
Complete documentation, tutorials, and examples are available on mischianti.org.
INPUT or OUTPUT.digitalReadAll().pulseIn() and pulseInPoll() for reading pulse widths, plus NewPing-style ultrasonic sensor methods (ping(), ping_cm(), ping_median(), etc.) for easy HC-SR04 integration.| Platform | Support | Notes |
|---|---|---|
| ESP32 | ✅ | Full support |
| ESP8266 | ✅ | Full support |
| Raspberry Pi Pico (RP2040) | ✅ | Full support |
| Arduino (AVR, Uno, Mega) | ✅ | Full support |
| Arduino SAMD (Nano 33 IoT, etc.) | ✅ | Full support |
| STM32 | ✅ | Full support |
Sketch > Include Library > Manage Libraries....Install.Add the library to your platformio.ini file:
lib_deps = xreef/PCF8574 library
PCF8574_library.libraries folder.// Basic constructor with I2C address PCF8574(uint8_t address); // For ESP8266/ESP32 with custom SDA/SCL pins PCF8574(uint8_t address, uint8_t sda, uint8_t scl); // Constructor with interrupt pin PCF8574(uint8_t address, uint8_t interruptPin, void (*interruptFunction)());
// Initialize the library (must be called in setup()) bool begin(uint8_t defaultVal = 0xFF); // Set a pin as INPUT or OUTPUT void pinMode(uint8_t pin, uint8_t mode); // Write a value (HIGH/LOW) to a pin void digitalWrite(uint8_t pin, uint8_t value); // Read a value (HIGH/LOW) from a pin uint8_t digitalRead(uint8_t pin); // Read all 8 pins at once PCF8574::DigitalInput digitalReadAll(); // Pulse measurement (low-level) unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout); unsigned long pulseInPoll(uint8_t pin, uint8_t state, unsigned long timeout, unsigned int pollIntervalMicros); // Ultrasonic sensor methods (HC-SR04 compatible, NewPing-style) unsigned long ping(uint8_t trigPin, uint8_t echoPin, unsigned long maxDistance_cm = 500); unsigned long pingPoll(uint8_t trigPin, uint8_t echoPin, unsigned long maxDistance_cm = 500, unsigned int pollIntervalMicros = 100); unsigned long ping_cm(uint8_t trigPin, uint8_t echoPin, unsigned long maxDistance_cm = 500); unsigned long ping_cm_poll(uint8_t trigPin, uint8_t echoPin, unsigned long maxDistance_cm = 500, unsigned int pollIntervalMicros = 100); unsigned long ping_in(uint8_t trigPin, uint8_t echoPin, unsigned long maxDistance_cm = 500); unsigned long ping_in_poll(uint8_t trigPin, uint8_t echoPin, unsigned long maxDistance_cm = 500, unsigned int pollIntervalMicros = 100); unsigned long ping_median(uint8_t trigPin, uint8_t echoPin, uint8_t iterations = 5, unsigned long maxDistance_cm = 500); unsigned long ping_median_poll(uint8_t trigPin, uint8_t echoPin, uint8_t iterations = 5, unsigned long maxDistance_cm = 500, unsigned int pollIntervalMicros = 100); static unsigned long microsecondsToDistance_cm(unsigned long microseconds); static unsigned long microsecondsToDistance_in(unsigned long microseconds);
Here is a simple example of how to use the library to control an LED and read a button.
#include <Wire.h>
#include <PCF8574.h>
// Set I2C address (e.g., 0x20)
PCF8574 pcf8574(0x20);
const uint8_t LED_PIN = P0;
const uint8_t BUTTON_PIN = P1;
void setup() {
Serial.begin(115200);
// Initialize the PCF8574
if (!pcf8574.begin()) {
Serial.println("Couldn't find PCF8574");
while (1);
}
// Set pin modes
pcf8574.pinMode(LED_PIN, OUTPUT);
pcf8574.pinMode(BUTTON_PIN, INPUT);
Serial.println("PCF8574 initialized!");
}
void loop() {
// Read the button state
uint8_t buttonState = pcf8574.digitalRead(BUTTON_PIN);
// If button is pressed (assuming active-low), turn on the LED
if (buttonState == LOW) {
pcf8574.digitalWrite(LED_PIN, HIGH);
} else {
pcf8574.digitalWrite(LED_PIN, LOW);
}
delay(100);
}
/** Important:** Always configure PCF8574 pins with `pinMode()` (and set initial output levels with `digitalWrite()` if needed) before calling `begin()`. Some platforms or usage patterns rely on the initial pin configuration and calling `begin()` before `pinMode()` can lead to unexpected behavior. Example:
```cpp
pcf.pinMode(P0, OUTPUT);
pcf.pinMode(P1, INPUT);
// optional: pcf.digitalWrite(P0, LOW);
if (!pcf.begin()) {
Serial.println(F("ERROR: Could not initialize PCF8574! Check wiring, I2C address, SDA/SCL and power."));
while (1) delay(100);
}
``` **/
## ⚡ Interrupts
You can use an interrupt to detect input changes without constantly polling the device.
1. Connect the `INT` pin of the PCF8574 to an interrupt-capable pin on your microcontroller.
2. Define an interrupt handler function (ISR).
3. Initialize the library with the interrupt pin and handler.
```cpp
// Global flag to be set by the ISR
volatile bool keyPressed = false;
// Interrupt Service Routine (ISR)
// NOTE: Keep this function as short and fast as possible.
void ICACHE_RAM_ATTR keyPressedOnPCF8574() {
keyPressed = true;
}
// Initialize with interrupt pin and ISR
PCF8574 pcf8574(0x20, D3, keyPressedOnPCF8574); // Address, Interrupt Pin, ISR
void setup() {
// ... setup code ...
pcf8574.begin();
}
void loop() {
if (keyPressed) {
// Reset the flag
keyPressed = false;
// An input on the PCF8574 has changed, read all values
PCF8574::DigitalInput values = pcf8574.digitalReadAll();
Serial.print("Pin P0 is: ");
Serial.println(values.p0);
// ... check other pins ...
}
}
The library offers built-in support for rotary encoders. To use it, enable one of the encoder implementations in the PCF8574.h file by uncommenting the corresponding #define.
Example implementation:
// In PCF8574.h, uncomment one of these:
// #define PCF8574_ENCODER_SUPPORT
// #define PCF8574_ENCODER_SUPPORT_OPTIMIZED
#include <Wire.h>
#include <PCF8574.h>
PCF8574 pcf8574(0x20);
// Define encoder pins
const uint8_t ENCODER_A = P0;
const uint8_t ENCODER_B = P1;
volatile long encoderValue = 0;
void setup() {
Serial.begin(115200);
pcf8574.begin();
// Initialize the encoder
pcf8574.encoder(ENCODER_A, ENCODER_B);
pcf8574.setEncoderValue(0);
}
void loop() {
long newEncoderValue = pcf8574.getEncoderValue();
if (newEncoderValue != encoderValue) {
Serial.print("Encoder value: ");
Serial.println(newEncoderValue);
encoderValue = newEncoderValue;
}
delay(10);
}
For devices with very limited RAM, you can enable a low-memory mode.
#define PCF8574_LOW_MEMORY in PCF8574.h.digitalReadAll() works.Instead of returning a struct, digitalReadAll() will return a single byte. You must then use bitwise operations to get the value of each pin.
// In your sketch, after enabling low memory mode: byte pinValues = pcf8574.digitalReadAll(); bool p0 = (pinValues & bit(0)) > 0; bool p1 = (pinValues & bit(1)) > 0; // ... and so on for all 8 pins
The library includes specialized methods for working with HC-SR04 ultrasonic sensors, similar to the NewPing library. These methods simplify distance measurement and reduce I2C traffic.
⚠️ Accuracy Warning: Measuring pulses via an I2C expander is less precise than using a direct microcontroller pin. I2C communication adds latency and jitter. The polling methods (
_pollvariants) trade some precision for much better I2C bus efficiency by reading at intervals instead of continuously. For high-accuracy needs, connect the sensor'sECHOpin directly to your microcontroller.
| Method | Description | Returns |
|---|---|---|
ping(trigPin, echoPin, maxDistance_cm) |
Send trigger and measure echo pulse | Microseconds |
pingPoll(trigPin, echoPin, maxDistance_cm, pollInterval) |
Ping with polling to reduce I2C traffic | Microseconds |
ping_cm(trigPin, echoPin, maxDistance_cm) |
Get distance in centimeters | Centimeters |
ping_cm_poll(trigPin, echoPin, maxDistance_cm, pollInterval) |
Distance in cm with polling (recommended) | Centimeters |
ping_in(trigPin, echoPin, maxDistance_cm) |
Get distance in inches | Inches |
ping_in_poll(trigPin, echoPin, maxDistance_cm, pollInterval) |
Distance in inches with polling | Inches |
ping_median(trigPin, echoPin, iterations, maxDistance_cm) |
Median of multiple readings (most stable) | Centimeters |
ping_median_poll(trigPin, echoPin, iterations, maxDistance_cm, pollInterval) |
Median with polling | Centimeters |
#include <Wire.h>
#include <PCF8574.h>
PCF8574 pcf(0x20);
const int trigPin = 9; // A standard digital pin on your Arduino/ESP
const uint8_t echoPinPCF = P0; // A pin on the PCF8574
void setup() {
Serial.begin(115200);
// Configure pin modes BEFORE initializing the PCF8574
pcf.pinMode(echoPinPCF, INPUT);
pinMode(trigPin, OUTPUT);
// Initialize the PCF8574 (after pinMode)
if (!pcf.begin()) {
Serial.println("Couldn't find PCF8574");
while (1);
}
}
void loop() {
// Trigger the sensor
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Measure the echo pulse duration on the PCF8574 pin
// Timeout is 30000 microseconds (30ms)
unsigned long duration = pcf.pulseIn(echoPinPCF, HIGH, 30000UL);
// Calculate distance
unsigned long distanceCm = duration / 29 / 2;
Serial.print("Distance: ");
Serial.print(distanceCm);
Serial.println(" cm");
delay(500);
}
For easier use and better I2C efficiency, use the new ultrasonic methods:
#include <Wire.h>
#include <PCF8574.h>
PCF8574 pcf(0x20);
const uint8_t trigPinPCF = P1; // TRIG on PCF8574 P1
const uint8_t echoPinPCF = P0; // ECHO on PCF8574 P0
const unsigned long MAX_DISTANCE = 400; // Maximum distance in cm
void setup() {
Serial.begin(115200);
// Configure PCF8574 pins BEFORE calling begin()
pcf.pinMode(trigPinPCF, OUTPUT);
pcf.pinMode(echoPinPCF, INPUT);
pcf.digitalWrite(trigPinPCF, LOW);
// Initialize PCF8574 after pins are configured
if (!pcf.begin()) {
Serial.println(F("ERROR: Could not initialize PCF8574! Check wiring, I2C address, SDA/SCL and power."));
while (1) delay(100);
}
}
void loop() {
// Simple distance measurement in cm (with polling for efficiency)
unsigned long distance = pcf.ping_cm_poll(trigPinPCF, echoPinPCF, MAX_DISTANCE, 100);
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm");
delay(500);
}
The pollInterval parameter (in microseconds) controls how often the PCF8574 is read:
Example: ping_cm_poll(trigPin, echoPin, 400, 100) reads every 100µs, reducing I2C requests by ~10x compared to continuous reading.
See the HC-SR04_via_PCF8574 and HC-SR04_via_PCF8574_extended examples in the library for complete demonstrations.
This library is released under the MIT License. See the LICENSE file for more details.
Copyright (c) 2017-2025 Renzo Mischianti
Contributions are welcome! Please fork the repository, create a feature branch, and submit a pull request.
⭐ If this library helped your project, please give it a star on GitHub!
more like this
Software for a Wordclock based on ESP8266 and Neopixel LEDs with PONG, TETRIS, SNAKE game modes
Arduino library and MATLAB/Simulink API for the AutomationShield Arduino expansion boards for control engineering educa…
search projects, people, and tags