[i2c] Add I2CDevice::update_register for read-modify-write of register bits

This commit is contained in:
J. Nick Koston
2026-10-15 14:44:25 -10:00
parent 6817960365
commit 8145b7ae93
3 changed files with 94 additions and 0 deletions
+7
View File
@@ -58,6 +58,13 @@ bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
return true;
}
bool I2CDevice::update_register(uint8_t a_register, uint8_t mask, uint8_t value) {
uint8_t data;
if (!this->read_byte(a_register, &data))
return false;
return this->write_byte(a_register, (data & ~mask) | (value & mask));
}
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]};
+5
View File
@@ -266,6 +266,11 @@ class I2CDevice {
bool write_byte_16(uint8_t a_register, uint16_t data) const { return write_bytes_16(a_register, &data, 1); }
/// Read a register, replace the bits in @p mask with those of @p value and write it back.
/// The write always happens; bits of @p value outside @p mask are ignored.
/// @return false if the read or the write failed (nothing is written after a failed read)
bool update_register(uint8_t a_register, uint8_t mask, uint8_t value);
protected:
uint8_t address_{0x00}; ///< store the address of the device on the bus
I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance
@@ -0,0 +1,82 @@
#include <gtest/gtest.h>
#include <array>
#include <vector>
#include "esphome/components/i2c/i2c.h"
namespace esphome::i2c::testing {
static constexpr uint8_t ADDRESS = 0x40;
static constexpr uint8_t REG = 0x12;
class RegisterBus final : public I2CBus {
public:
std::array<uint8_t, 256> regs{};
std::vector<uint8_t> writes;
bool fail_read{false};
bool fail_write{false};
ErrorCode write_readv(uint8_t address, const uint8_t *out, size_t out_size, uint8_t *in, size_t in_size) override {
EXPECT_EQ(address, ADDRESS);
if (out_size == 0)
return ERROR_TIMEOUT;
const uint8_t reg = out[0];
if (in_size > 0) {
if (this->fail_read)
return ERROR_TIMEOUT;
for (size_t i = 0; i < in_size; i++)
in[i] = this->regs[reg + i];
return ERROR_OK;
}
if (this->fail_write)
return ERROR_TIMEOUT;
this->writes.push_back(reg);
for (size_t i = 1; i < out_size; i++)
this->regs[reg + i - 1] = out[i];
return ERROR_OK;
}
};
class UpdateRegisterTest : public ::testing::Test {
protected:
void SetUp() override {
this->device_.set_i2c_bus(&this->bus_);
this->device_.set_i2c_address(ADDRESS);
}
RegisterBus bus_;
I2CDevice device_;
};
TEST_F(UpdateRegisterTest, ReplacesOnlyMaskedBits) {
this->bus_.regs[REG] = 0b1010'0101;
EXPECT_TRUE(this->device_.update_register(REG, 0b0000'1111, 0b0000'0011));
EXPECT_EQ(this->bus_.regs[REG], 0b1010'0011);
}
TEST_F(UpdateRegisterTest, IgnoresValueBitsOutsideMask) {
this->bus_.regs[REG] = 0x00;
EXPECT_TRUE(this->device_.update_register(REG, 0x0F, 0xFF));
EXPECT_EQ(this->bus_.regs[REG], 0x0F);
}
TEST_F(UpdateRegisterTest, WritesEvenWhenUnchanged) {
this->bus_.regs[REG] = 0x3C;
EXPECT_TRUE(this->device_.update_register(REG, 0x0C, 0x0C));
EXPECT_EQ(this->bus_.regs[REG], 0x3C);
EXPECT_EQ(this->bus_.writes, std::vector<uint8_t>{REG});
}
TEST_F(UpdateRegisterTest, FailedReadWritesNothing) {
this->bus_.fail_read = true;
EXPECT_FALSE(this->device_.update_register(REG, 0xFF, 0x01));
EXPECT_TRUE(this->bus_.writes.empty());
}
TEST_F(UpdateRegisterTest, FailedWriteReturnsFalse) {
this->bus_.fail_write = true;
EXPECT_FALSE(this->device_.update_register(REG, 0xFF, 0x01));
}
} // namespace esphome::i2c::testing