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
This commit is contained in:
parent
4ee17dea04
commit
0a45a06142
6 changed files with 184 additions and 17 deletions
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <inttypes.h>
|
||||
#include "Stream.h"
|
||||
#include <functional>
|
||||
|
||||
class HardwareI2C : public Stream {
|
||||
public:
|
||||
|
|
@ -36,6 +37,7 @@ public:
|
|||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -347,20 +347,147 @@ 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
|
||||
^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"requires": [
|
||||
"CONFIG_SOC_I2C_SUPPORT_SLAVE=y"
|
||||
]
|
||||
}
|
||||
|
|
@ -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 */
|
||||
{
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -77,8 +73,8 @@ protected:
|
|||
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 */
|
||||
|
|
@ -116,8 +112,8 @@ public:
|
|||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue