diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 8fc0a503276..ba23217486e 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -3,7 +3,6 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" -#include 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 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(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(data >> 8), static_cast(data)}; + return this->bus_->write_readv(this->address_, buffer, sizeof(buffer), nullptr, 0) == ERROR_OK; } I2CRegister &I2CRegister::operator=(uint8_t value) { diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 00929db6207..0d60f519e32 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -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 diff --git a/tests/components/i2c/write_bytes_16.cpp b/tests/components/i2c/write_bytes_16.cpp new file mode 100644 index 00000000000..1f9670c62ba --- /dev/null +++ b/tests/components/i2c/write_bytes_16.cpp @@ -0,0 +1,59 @@ +#include + +#include + +#include "esphome/components/i2c/i2c.h" + +namespace esphome::i2c::testing { + +class RecordingBus final : public I2CBus { + public: + std::vector 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{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{0x7E, 0x01, 0x02, 0x03, 0x04, 0xFF, 0x00})); +} + +TEST_F(WriteBytes16Test, MoreWordsThanTheStackBufferHolds) { + uint16_t words[12]; + std::vector 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