[i2c] Write 16-bit registers without a heap allocation (#20907)

This commit is contained in:
J. Nick Koston
2026-10-16 15:54:53 -10:00
committed by GitHub
parent 9031df757b
commit b8afcdfb3c
3 changed files with 76 additions and 7 deletions
+16 -6
View File
@@ -3,7 +3,6 @@
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <memory>
namespace esphome::i2c {
@@ -59,11 +58,22 @@ bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
}
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) const {
// we have to copy in order to be able to change byte order
std::unique_ptr<uint16_t[]> temp{new uint16_t[len]};
for (size_t i = 0; i < len; i++)
temp[i] = htoi2cs(data[i]);
return write_register(a_register, reinterpret_cast<const uint8_t *>(temp.get()), len * 2) == ERROR_OK;
const size_t size = 1 + len * 2;
SmallBufferWithHeapFallback<17> buffer_alloc(size); // Register plus up to 8 words
uint8_t *buffer = buffer_alloc.get();
buffer[0] = a_register;
for (size_t i = 0; i < len; i++) {
const uint16_t word = data[i];
buffer[1 + i * 2] = word >> 8;
buffer[2 + i * 2] = word;
}
return this->bus_->write_readv(this->address_, buffer, size, nullptr, 0) == ERROR_OK;
}
bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) const {
const uint8_t buffer[3] = {a_register, static_cast<uint8_t>(data >> 8), static_cast<uint8_t>(data)};
return this->bus_->write_readv(this->address_, buffer, sizeof(buffer), nullptr, 0) == ERROR_OK;
}
I2CRegister &I2CRegister::operator=(uint8_t value) {
+1 -1
View File
@@ -264,7 +264,7 @@ class I2CDevice {
bool write_byte(uint8_t a_register, uint8_t data) const { return write_bytes(a_register, &data, 1); }
bool write_byte_16(uint8_t a_register, uint16_t data) const { return write_bytes_16(a_register, &data, 1); }
bool write_byte_16(uint8_t a_register, uint16_t data) const;
protected:
uint8_t address_{0x00}; ///< store the address of the device on the bus
+59
View File
@@ -0,0 +1,59 @@
#include <gtest/gtest.h>
#include <vector>
#include "esphome/components/i2c/i2c.h"
namespace esphome::i2c::testing {
class RecordingBus final : public I2CBus {
public:
std::vector<uint8_t> written;
ErrorCode result{ERROR_OK};
ErrorCode write_readv(uint8_t address, const uint8_t *data, size_t size, uint8_t *out, size_t out_size) override {
this->written.assign(data, data + size);
return this->result;
}
};
class WriteBytes16Test : public ::testing::Test {
protected:
void SetUp() override {
this->device_.set_i2c_bus(&this->bus_);
this->device_.set_i2c_address(0x40);
}
RecordingBus bus_;
I2CDevice device_;
};
TEST_F(WriteBytes16Test, WritesRegisterThenWordBigEndian) {
EXPECT_TRUE(this->device_.write_byte_16(0x12, 0xABCD));
EXPECT_EQ(this->bus_.written, (std::vector<uint8_t>{0x12, 0xAB, 0xCD}));
}
TEST_F(WriteBytes16Test, WritesEveryWordBigEndian) {
const uint16_t words[3] = {0x0102, 0x0304, 0xFF00};
EXPECT_TRUE(this->device_.write_bytes_16(0x7E, words, 3));
EXPECT_EQ(this->bus_.written, (std::vector<uint8_t>{0x7E, 0x01, 0x02, 0x03, 0x04, 0xFF, 0x00}));
}
TEST_F(WriteBytes16Test, MoreWordsThanTheStackBufferHolds) {
uint16_t words[12];
std::vector<uint8_t> expected{0x01};
for (uint16_t i = 0; i < 12; i++) {
words[i] = 0x1000 + i;
expected.push_back(0x10);
expected.push_back(i);
}
EXPECT_TRUE(this->device_.write_bytes_16(0x01, words, 12));
EXPECT_EQ(this->bus_.written, expected);
}
TEST_F(WriteBytes16Test, ReportsBusFailure) {
this->bus_.result = ERROR_NOT_ACKNOWLEDGED;
EXPECT_FALSE(this->device_.write_byte_16(0x12, 0x0001));
}
} // namespace esphome::i2c::testing