Skip to content

Commit 0a45a06

Browse files
authored
feat(wire): std::functional Wire slave callback functions (#11582)
This PR enhances the Wire library to support std::function–based callbacks for I2C slave mode, enabling the use of lambdas and captured contexts. - Replaces raw function pointers in TwoWire and HardwareI2C with std::function for onRequest and onReceive - Updates constructors, method signatures, and default initializations to use std::function - Adds new example sketch, CI config, and documentation updates demonstrating the functional callback API
1 parent 4ee17de commit 0a45a06

File tree

6 files changed

+184
-17
lines changed

6 files changed

+184
-17
lines changed

cores/esp32/HardwareI2C.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <inttypes.h>
2222
#include "Stream.h"
23+
#include <functional>
2324

2425
class HardwareI2C : public Stream {
2526
public:
@@ -36,6 +37,7 @@ class HardwareI2C : public Stream {
3637
virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) = 0;
3738
virtual size_t requestFrom(uint8_t address, size_t len) = 0;
3839

39-
virtual void onReceive(void (*)(int)) = 0;
40-
virtual void onRequest(void (*)(void)) = 0;
40+
// Update base class to use std::function
41+
virtual void onReceive(const std::function<void(int)> &) = 0;
42+
virtual void onRequest(const std::function<void()> &) = 0;
4143
};

docs/en/api/i2c.rst

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,20 +347,147 @@ This function will return ``true`` if the peripheral was initialized correctly.
347347
onReceive
348348
^^^^^^^^^
349349

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

352352
.. code-block:: arduino
353353
354-
void onReceive( void (*)(int) );
354+
void onReceive(const std::function<void(int)>& callback);
355+
356+
**Function Signature:**
357+
358+
The callback function must have the signature ``void(int numBytes)`` where ``numBytes`` indicates how many bytes were received from the master.
359+
360+
**Usage Examples:**
361+
362+
.. code-block:: arduino
363+
364+
// Method 1: Regular function
365+
void handleReceive(int numBytes) {
366+
Serial.printf("Received %d bytes: ", numBytes);
367+
while (Wire.available()) {
368+
char c = Wire.read();
369+
Serial.print(c);
370+
}
371+
Serial.println();
372+
}
373+
Wire.onReceive(handleReceive);
374+
375+
// Method 2: Lambda function
376+
Wire.onReceive([](int numBytes) {
377+
Serial.printf("Master sent %d bytes\n", numBytes);
378+
while (Wire.available()) {
379+
uint8_t data = Wire.read();
380+
// Process received data
381+
Serial.printf("Data: 0x%02X\n", data);
382+
}
383+
});
384+
385+
// Method 3: Lambda with capture (for accessing variables)
386+
int deviceId = 42;
387+
Wire.onReceive([deviceId](int numBytes) {
388+
Serial.printf("Device %d received %d bytes\n", deviceId, numBytes);
389+
// Process data...
390+
});
391+
392+
// Method 4: Using std::function variable
393+
std::function<void(int)> receiveHandler = [](int bytes) {
394+
Serial.printf("Handling %d received bytes\n", bytes);
395+
};
396+
Wire.onReceive(receiveHandler);
397+
398+
// Method 5: Class member function (using lambda wrapper)
399+
class I2CDevice {
400+
private:
401+
int deviceAddress;
402+
public:
403+
I2CDevice(int addr) : deviceAddress(addr) {}
404+
405+
void handleReceive(int numBytes) {
406+
Serial.printf("Device 0x%02X received %d bytes\n", deviceAddress, numBytes);
407+
}
408+
409+
void setup() {
410+
Wire.onReceive([this](int bytes) {
411+
this->handleReceive(bytes);
412+
});
413+
}
414+
};
415+
416+
.. note::
417+
The ``onReceive`` callback is triggered when the I2C master sends data to this slave device.
418+
Use ``Wire.available()`` and ``Wire.read()`` inside the callback to retrieve the received data.
355419

356420
onRequest
357421
^^^^^^^^^
358422

359-
The ``onRequest`` function is used to define the callback for the data to be send to the master.
423+
The ``onRequest`` function is used to define the callback for responding to master read requests.
424+
425+
.. code-block:: arduino
426+
427+
void onRequest(const std::function<void()>& callback);
428+
429+
**Function Signature:**
430+
431+
The callback function must have the signature ``void()`` with no parameters. This callback is triggered when the master requests data from this slave device.
432+
433+
**Usage Examples:**
360434

361435
.. code-block:: arduino
362436
363-
void onRequest( void (*)(void) );
437+
// Method 1: Regular function
438+
void handleRequest() {
439+
static int counter = 0;
440+
Wire.printf("Response #%d", counter++);
441+
}
442+
Wire.onRequest(handleRequest);
443+
444+
// Method 2: Lambda function
445+
Wire.onRequest([]() {
446+
// Send sensor data to master
447+
int sensorValue = analogRead(A0);
448+
Wire.write(sensorValue >> 8); // High byte
449+
Wire.write(sensorValue & 0xFF); // Low byte
450+
});
451+
452+
// Method 3: Lambda with capture (for accessing variables)
453+
int deviceStatus = 1;
454+
String deviceName = "Sensor1";
455+
Wire.onRequest([&deviceStatus, &deviceName]() {
456+
Wire.write(deviceStatus);
457+
Wire.write(deviceName.c_str(), deviceName.length());
458+
});
459+
460+
// Method 4: Using std::function variable
461+
std::function<void()> requestHandler = []() {
462+
Wire.write("Hello Master!");
463+
};
464+
Wire.onRequest(requestHandler);
465+
466+
// Method 5: Class member function (using lambda wrapper)
467+
class TemperatureSensor {
468+
private:
469+
float temperature;
470+
public:
471+
void updateTemperature() {
472+
temperature = 25.5; // Read from actual sensor
473+
}
474+
475+
void sendTemperature() {
476+
// Convert float to bytes and send
477+
uint8_t* tempBytes = (uint8_t*)&temperature;
478+
Wire.write(tempBytes, sizeof(float));
479+
}
480+
481+
void setup() {
482+
Wire.onRequest([this]() {
483+
this->sendTemperature();
484+
});
485+
}
486+
};
487+
488+
.. note::
489+
The ``onRequest`` callback is triggered when the I2C master requests data from this slave device.
490+
Use ``Wire.write()`` inside the callback to send response data back to the master.
364491

365492
slaveWrite
366493
^^^^^^^^^^
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// This example demonstrates the use of functional callbacks with the Wire library
2+
// for I2C slave communication. It shows how to handle requests and data reception
3+
4+
#include "Wire.h"
5+
6+
#define I2C_DEV_ADDR 0x55
7+
8+
uint32_t i = 0;
9+
10+
void setup() {
11+
Serial.begin(115200);
12+
Serial.setDebugOutput(true);
13+
14+
Wire.onRequest([]() {
15+
Wire.print(i++);
16+
Wire.print(" Packets.");
17+
Serial.println("onRequest");
18+
});
19+
20+
Wire.onReceive([](int len) {
21+
Serial.printf("onReceive[%d]: ", len);
22+
while (Wire.available()) {
23+
Serial.write(Wire.read());
24+
}
25+
Serial.println();
26+
});
27+
28+
Wire.begin((uint8_t)I2C_DEV_ADDR);
29+
30+
#if CONFIG_IDF_TARGET_ESP32
31+
char message[64];
32+
snprintf(message, 64, "%lu Packets.", i++);
33+
Wire.slaveWrite((uint8_t *)message, strlen(message));
34+
#endif
35+
}
36+
37+
void loop() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"requires": [
3+
"CONFIG_SOC_I2C_SUPPORT_SLAVE=y"
4+
]
5+
}

libraries/Wire/src/Wire.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ TwoWire::TwoWire(uint8_t bus_num)
4848
#endif
4949
#if SOC_I2C_SUPPORT_SLAVE
5050
,
51-
is_slave(false), user_onRequest(NULL), user_onReceive(NULL)
51+
is_slave(false), user_onRequest(nullptr), user_onReceive(nullptr)
5252
#endif /* SOC_I2C_SUPPORT_SLAVE */
5353
{
5454
}
@@ -596,14 +596,14 @@ void TwoWire::flush() {
596596
//i2cFlush(num); // cleanup
597597
}
598598

599-
void TwoWire::onReceive(void (*function)(int)) {
599+
void TwoWire::onReceive(const std::function<void(int)> &function) {
600600
#if SOC_I2C_SUPPORT_SLAVE
601601
user_onReceive = function;
602602
#endif
603603
}
604604

605605
// sets function called on slave read
606-
void TwoWire::onRequest(void (*function)(void)) {
606+
void TwoWire::onRequest(const std::function<void()> &function) {
607607
#if SOC_I2C_SUPPORT_SLAVE
608608
user_onRequest = function;
609609
#endif

libraries/Wire/src/Wire.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@
4848
#ifndef I2C_BUFFER_LENGTH
4949
#define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t)
5050
#endif
51-
#if SOC_I2C_SUPPORT_SLAVE
52-
typedef void (*user_onRequest)(void);
53-
typedef void (*user_onReceive)(uint8_t *, int);
54-
#endif /* SOC_I2C_SUPPORT_SLAVE */
5551

5652
class TwoWire : public HardwareI2C {
5753
protected:
@@ -77,8 +73,8 @@ class TwoWire : public HardwareI2C {
7773
private:
7874
#if SOC_I2C_SUPPORT_SLAVE
7975
bool is_slave;
80-
void (*user_onRequest)(void);
81-
void (*user_onReceive)(int);
76+
std::function<void()> user_onRequest;
77+
std::function<void(int)> user_onReceive;
8278
static void onRequestService(uint8_t, void *);
8379
static void onReceiveService(uint8_t, uint8_t *, size_t, bool, void *);
8480
#endif /* SOC_I2C_SUPPORT_SLAVE */
@@ -116,8 +112,8 @@ class TwoWire : public HardwareI2C {
116112
size_t requestFrom(uint8_t address, size_t len, bool stopBit) override;
117113
size_t requestFrom(uint8_t address, size_t len) override;
118114

119-
void onReceive(void (*)(int)) override;
120-
void onRequest(void (*)(void)) override;
115+
void onReceive(const std::function<void(int)> &) override;
116+
void onRequest(const std::function<void()> &) override;
121117

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

0 commit comments

Comments
 (0)