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 <inttypes.h>
|
||||||
#include "Stream.h"
|
#include "Stream.h"
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class HardwareI2C : public Stream {
|
class HardwareI2C : public Stream {
|
||||||
public:
|
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, bool stopBit) = 0;
|
||||||
virtual size_t requestFrom(uint8_t address, size_t len) = 0;
|
virtual size_t requestFrom(uint8_t address, size_t len) = 0;
|
||||||
|
|
||||||
virtual void onReceive(void (*)(int)) = 0;
|
// Update base class to use std::function
|
||||||
virtual void onRequest(void (*)(void)) = 0;
|
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
|
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
|
.. 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
|
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
|
.. 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
|
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
|
#endif
|
||||||
#if SOC_I2C_SUPPORT_SLAVE
|
#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 */
|
#endif /* SOC_I2C_SUPPORT_SLAVE */
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -596,14 +596,14 @@ void TwoWire::flush() {
|
||||||
//i2cFlush(num); // cleanup
|
//i2cFlush(num); // cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwoWire::onReceive(void (*function)(int)) {
|
void TwoWire::onReceive(const std::function<void(int)> &function) {
|
||||||
#if SOC_I2C_SUPPORT_SLAVE
|
#if SOC_I2C_SUPPORT_SLAVE
|
||||||
user_onReceive = function;
|
user_onReceive = function;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets function called on slave read
|
// 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
|
#if SOC_I2C_SUPPORT_SLAVE
|
||||||
user_onRequest = function;
|
user_onRequest = function;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,6 @@
|
||||||
#ifndef I2C_BUFFER_LENGTH
|
#ifndef I2C_BUFFER_LENGTH
|
||||||
#define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t)
|
#define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t)
|
||||||
#endif
|
#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 {
|
class TwoWire : public HardwareI2C {
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -77,8 +73,8 @@ protected:
|
||||||
private:
|
private:
|
||||||
#if SOC_I2C_SUPPORT_SLAVE
|
#if SOC_I2C_SUPPORT_SLAVE
|
||||||
bool is_slave;
|
bool is_slave;
|
||||||
void (*user_onRequest)(void);
|
std::function<void()> user_onRequest;
|
||||||
void (*user_onReceive)(int);
|
std::function<void(int)> user_onReceive;
|
||||||
static void onRequestService(uint8_t, void *);
|
static void onRequestService(uint8_t, void *);
|
||||||
static void onReceiveService(uint8_t, uint8_t *, size_t, bool, void *);
|
static void onReceiveService(uint8_t, uint8_t *, size_t, bool, void *);
|
||||||
#endif /* SOC_I2C_SUPPORT_SLAVE */
|
#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, bool stopBit) override;
|
||||||
size_t requestFrom(uint8_t address, size_t len) override;
|
size_t requestFrom(uint8_t address, size_t len) override;
|
||||||
|
|
||||||
void onReceive(void (*)(int)) override;
|
void onReceive(const std::function<void(int)> &) override;
|
||||||
void onRequest(void (*)(void)) override;
|
void onRequest(const std::function<void()> &) override;
|
||||||
|
|
||||||
//call setPins() first, so that begin() can be called without arguments from libraries
|
//call setPins() first, so that begin() can be called without arguments from libraries
|
||||||
bool setPins(int sda, int scl);
|
bool setPins(int sda, int scl);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue