Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cores/esp32/HardwareI2C.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <inttypes.h>
#include "Stream.h"
#include <functional>

class HardwareI2C : public Stream {
public:
Expand All @@ -36,6 +37,7 @@ class HardwareI2C : public Stream {
virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) = 0;
virtual size_t requestFrom(uint8_t address, size_t len) = 0;

virtual void onReceive(void (*)(int)) = 0;
virtual void onRequest(void (*)(void)) = 0;
// Update base class to use std::function
virtual void onReceive(const std::function<void(int)>&) = 0;
virtual void onRequest(const std::function<void()>&) = 0;
};
135 changes: 131 additions & 4 deletions docs/en/api/i2c.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,21 +347,148 @@ This function will return ``true`` if the peripheral was initialized correctly.
onReceive
^^^^^^^^^

The ``onReceive`` function is used to define the callback for the data received from the master.
The ``onReceive`` function is used to define the callback for data received from the master device.

.. code-block:: arduino

void onReceive( void (*)(int) );
void onReceive(const std::function<void(int)>& callback);

**Function Signature:**

The callback function must have the signature ``void(int numBytes)`` where ``numBytes`` indicates how many bytes were received from the master.

**Usage Examples:**

.. code-block:: arduino

// Method 1: Regular function
void handleReceive(int numBytes) {
Serial.printf("Received %d bytes: ", numBytes);
while (Wire.available()) {
char c = Wire.read();
Serial.print(c);
}
Serial.println();
}
Wire.onReceive(handleReceive);

// Method 2: Lambda function
Wire.onReceive([](int numBytes) {
Serial.printf("Master sent %d bytes\n", numBytes);
while (Wire.available()) {
uint8_t data = Wire.read();
// Process received data
Serial.printf("Data: 0x%02X\n", data);
}
});

// Method 3: Lambda with capture (for accessing variables)
int deviceId = 42;
Wire.onReceive([deviceId](int numBytes) {
Serial.printf("Device %d received %d bytes\n", deviceId, numBytes);
// Process data...
});

// Method 4: Using std::function variable
std::function<void(int)> receiveHandler = [](int bytes) {
Serial.printf("Handling %d received bytes\n", bytes);
};
Wire.onReceive(receiveHandler);

// Method 5: Class member function (using lambda wrapper)
class I2CDevice {
private:
int deviceAddress;
public:
I2CDevice(int addr) : deviceAddress(addr) {}

void handleReceive(int numBytes) {
Serial.printf("Device 0x%02X received %d bytes\n", deviceAddress, numBytes);
}

void setup() {
Wire.onReceive([this](int bytes) {
this->handleReceive(bytes);
});
}
};

.. note::
The ``onReceive`` callback is triggered when the I2C master sends data to this slave device.
Use ``Wire.available()`` and ``Wire.read()`` inside the callback to retrieve the received data.

onRequest
^^^^^^^^^

The ``onRequest`` function is used to define the callback for the data to be send to the master.
The ``onRequest`` function is used to define the callback for responding to master read requests.

.. code-block:: arduino

void onRequest( void (*)(void) );
void onRequest(const std::function<void()>& callback);

**Function Signature:**

The callback function must have the signature ``void()`` with no parameters. This callback is triggered when the master requests data from this slave device.

**Usage Examples:**

.. code-block:: arduino

// Method 1: Regular function
void handleRequest() {
static int counter = 0;
Wire.printf("Response #%d", counter++);
}
Wire.onRequest(handleRequest);

// Method 2: Lambda function
Wire.onRequest([]() {
// Send sensor data to master
int sensorValue = analogRead(A0);
Wire.write(sensorValue >> 8); // High byte
Wire.write(sensorValue & 0xFF); // Low byte
});

// Method 3: Lambda with capture (for accessing variables)
int deviceStatus = 1;
String deviceName = "Sensor1";
Wire.onRequest([&deviceStatus, &deviceName]() {
Wire.write(deviceStatus);
Wire.write(deviceName.c_str(), deviceName.length());
});

// Method 4: Using std::function variable
std::function<void()> requestHandler = []() {
Wire.write("Hello Master!");
};
Wire.onRequest(requestHandler);

// Method 5: Class member function (using lambda wrapper)
class TemperatureSensor {
private:
float temperature;
public:
void updateTemperature() {
temperature = 25.5; // Read from actual sensor
}

void sendTemperature() {
// Convert float to bytes and send
uint8_t* tempBytes = (uint8_t*)&temperature;
Wire.write(tempBytes, sizeof(float));
}

void setup() {
Wire.onRequest([this]() {
this->sendTemperature();
});
}
};

.. note::
The ``onRequest`` callback is triggered when the I2C master requests data from this slave device.
Use ``Wire.write()`` inside the callback to send response data back to the master.

slaveWrite
^^^^^^^^^^

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// This example demonstrates the use of functional callbacks with the Wire library
// for I2C slave communication. It shows how to handle requests and data reception

#include "Wire.h"

#define I2C_DEV_ADDR 0x55

uint32_t i = 0;

void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);

Wire.onRequest([]() {
Wire.print(i++);
Wire.print(" Packets.");
Serial.println("onRequest");
});

Wire.onReceive([](int len) {
Serial.printf("onReceive[%d]: ", len);
while (Wire.available()) {
Serial.write(Wire.read());
}
Serial.println();
});

Wire.begin((uint8_t)I2C_DEV_ADDR);

#if CONFIG_IDF_TARGET_ESP32
char message[64];
snprintf(message, 64, "%lu Packets.", i++);
Wire.slaveWrite((uint8_t *)message, strlen(message));
#endif
}

void loop() {}
5 changes: 5 additions & 0 deletions libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_I2C_SUPPORT_SLAVE=y"
]
}
6 changes: 3 additions & 3 deletions libraries/Wire/src/Wire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ TwoWire::TwoWire(uint8_t bus_num)
#endif
#if SOC_I2C_SUPPORT_SLAVE
,
is_slave(false), user_onRequest(NULL), user_onReceive(NULL)
is_slave(false), user_onRequest(nullptr), user_onReceive(nullptr)
#endif /* SOC_I2C_SUPPORT_SLAVE */
{
}
Expand Down Expand Up @@ -596,14 +596,14 @@ void TwoWire::flush() {
//i2cFlush(num); // cleanup
}

void TwoWire::onReceive(void (*function)(int)) {
void TwoWire::onReceive(const std::function<void(int)>& function) {
#if SOC_I2C_SUPPORT_SLAVE
user_onReceive = function;
#endif
}

// sets function called on slave read
void TwoWire::onRequest(void (*function)(void)) {
void TwoWire::onRequest(const std::function<void()>& function) {
#if SOC_I2C_SUPPORT_SLAVE
user_onRequest = function;
#endif
Expand Down
12 changes: 4 additions & 8 deletions libraries/Wire/src/Wire.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@
#ifndef I2C_BUFFER_LENGTH
#define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t)
#endif
#if SOC_I2C_SUPPORT_SLAVE
typedef void (*user_onRequest)(void);
typedef void (*user_onReceive)(uint8_t *, int);
#endif /* SOC_I2C_SUPPORT_SLAVE */

class TwoWire : public HardwareI2C {
protected:
Expand All @@ -77,8 +73,8 @@ class TwoWire : public HardwareI2C {
private:
#if SOC_I2C_SUPPORT_SLAVE
bool is_slave;
void (*user_onRequest)(void);
void (*user_onReceive)(int);
std::function<void()> user_onRequest;
std::function<void(int)> user_onReceive;
static void onRequestService(uint8_t, void *);
static void onReceiveService(uint8_t, uint8_t *, size_t, bool, void *);
#endif /* SOC_I2C_SUPPORT_SLAVE */
Expand Down Expand Up @@ -116,8 +112,8 @@ class TwoWire : public HardwareI2C {
size_t requestFrom(uint8_t address, size_t len, bool stopBit) override;
size_t requestFrom(uint8_t address, size_t len) override;

void onReceive(void (*)(int)) override;
void onRequest(void (*)(void)) override;
void onReceive(const std::function<void(int)>&) override;
void onRequest(const std::function<void()>&) override;

//call setPins() first, so that begin() can be called without arguments from libraries
bool setPins(int sda, int scl);
Expand Down
Loading