ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
bq76972.cpp
Go to the documentation of this file.
1#include "bq76972.h"
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5
6namespace esphome::bq76972 {
7
8ESPHOME_LOG_TAG(TAG, "bq76972");
9
10static constexpr uint8_t BQ76972_REG_SUBCOMMAND = 0x3E; // 0x3E low byte, 0x3F high byte
11static constexpr uint8_t BQ76972_REG_TRANSFER_BUFFER = 0x40;
12static constexpr uint8_t BQ76972_REG_CHECKSUM = 0x60; // 0x60 checksum, 0x61 length
13static constexpr uint8_t BQ76972_CMD_BAT_STATUS = 0x12;
14static constexpr uint8_t BQ76972_CMD_CELL_START = 0x14;
15static constexpr uint8_t BQ76972_CMD_STACK_VOLTAGE = 0x34;
16static constexpr uint8_t BQ76972_CMD_TEMP_START = 0x68;
17static constexpr uint16_t BQ76972_SUBCMD_ENTER_CFG_UPDATE = 0x0090;
18static constexpr uint16_t BQ76972_SUBCMD_EXIT_CFG_UPDATE = 0x0092;
19static constexpr uint16_t BQ76972_MEM_REG12_CONFIG = 0x9236;
20static constexpr uint16_t BQ76972_MEM_REG0_CONFIG = 0x9237;
21static constexpr uint16_t BQ76972_MEM_I2C_ADDRESS = 0x923A;
22static constexpr uint16_t BQ76972_MEM_SWAP_COMM_MODE = 0x29BC;
23// Pin config of temperature reading N (1 = CFETOFF ... 9 = DDSG) is at this base + N
24static constexpr uint16_t BQ76972_MEM_TEMP_PIN_CONFIG_BASE = 0x92F9;
25static constexpr size_t BQ76972_TS1_INDEX = 4; // a thermistor input by default
26static constexpr uint8_t BQ76972_PIN_CONFIG_THERMISTOR = 0x07;
27
28// 16 words with a CRC byte after every data byte
29static constexpr size_t BQ76972_MAX_TRANSFER = 64;
30static constexpr uint8_t BQ76972_MAX_POLLS = 50;
31
32static float raw_to_celsius(uint16_t raw) { return static_cast<int16_t>(raw) * 0.1f - 273.15f; }
33
34bool BQ76972Component::read_block_(uint8_t start_register, uint8_t *data, size_t len) {
35 if (!this->crc_mode_)
36 return this->read_bytes(start_register, data, len);
37
38 // With CRC enabled, every data byte is followed by a CRC byte
39 std::array<uint8_t, BQ76972_MAX_TRANSFER> buffer;
40 if (len == 0 || len * 2 > buffer.size())
41 return false;
42 if (!this->read_bytes(start_register, buffer.data(), len * 2))
43 return false;
44
45 // The first CRC also covers the write address, register and read address
46 const uint8_t crc0_input[4] = {static_cast<uint8_t>(this->address_ << 1), start_register,
47 static_cast<uint8_t>((this->address_ << 1) | 1), buffer[0]};
48 if (crc8(crc0_input, 4, 0x00, 0x07, true) != buffer[1]) {
49 ESP_LOGD(TAG, "CRC mismatch at register 0x%02X byte 0", start_register);
50 return false;
51 }
52 data[0] = buffer[0];
53 for (size_t i = 1; i < len; ++i) {
54 if (crc8(&buffer[i * 2], 1, 0x00, 0x07, true) != buffer[i * 2 + 1]) {
55 ESP_LOGD(TAG, "CRC mismatch at register 0x%02X byte %u", start_register, static_cast<unsigned>(i));
56 return false;
57 }
58 data[i] = buffer[i * 2];
59 }
60 return true;
61}
62
63bool BQ76972Component::write_block_(uint8_t start_register, const uint8_t *data, size_t len) {
64 if (!this->crc_mode_)
65 return this->write_bytes(start_register, data, len);
66
67 std::array<uint8_t, BQ76972_MAX_TRANSFER> buffer;
68 if (len == 0 || len * 2 > buffer.size())
69 return false;
70
71 // The first CRC also covers the write address and register
72 const uint8_t crc0_input[3] = {static_cast<uint8_t>(this->address_ << 1), start_register, data[0]};
73 buffer[0] = data[0];
74 buffer[1] = crc8(crc0_input, 3, 0x00, 0x07, true);
75 for (size_t i = 1; i < len; ++i) {
76 buffer[i * 2] = data[i];
77 buffer[i * 2 + 1] = crc8(&data[i], 1, 0x00, 0x07, true);
78 }
79 return this->write_bytes(start_register, buffer.data(), len * 2);
80}
81
82bool BQ76972Component::read_words_(uint8_t start_register, uint16_t *data, size_t num_words) {
83 std::array<uint8_t, BQ76972_MAX_TRANSFER / 2> bytes;
84 if (num_words * 2 > bytes.size() || !this->read_block_(start_register, bytes.data(), num_words * 2))
85 return false;
86 for (size_t i = 0; i < num_words; i++)
87 data[i] = encode_uint16(bytes[2 * i + 1], bytes[2 * i]);
88 return true;
89}
90
91bool BQ76972Component::send_subcommand_(uint16_t subcommand) {
92 const uint8_t bytes[2] = {static_cast<uint8_t>(subcommand & 0xFF), static_cast<uint8_t>(subcommand >> 8)};
93 return this->write_block_(BQ76972_REG_SUBCOMMAND, bytes, 2);
94}
95
97 uint8_t bytes[2];
98 for (uint8_t i = 0; i < BQ76972_MAX_POLLS; i++) {
99 if (!this->read_block_(BQ76972_REG_SUBCOMMAND, bytes, 2))
100 return false;
101 if (bytes[0] != 0xFF && bytes[1] != 0xFF)
102 return true;
103 delay(1);
104 }
105 return false;
106}
107
109 uint16_t status;
110 for (uint8_t i = 0; i < BQ76972_MAX_POLLS; i++) {
111 if (!this->read_words_(BQ76972_CMD_BAT_STATUS, &status, 1))
112 return false;
113 if (status & 0x0001)
114 return true;
115 delay(1);
116 }
117 return false;
118}
119
120bool BQ76972Component::read_subcommand_(uint16_t subcommand, uint8_t *data, size_t len) {
121 return this->send_subcommand_(subcommand) && this->wait_for_subcommand_() &&
122 this->read_block_(BQ76972_REG_TRANSFER_BUFFER, data, len);
123}
124
125bool BQ76972Component::write_subcommand_(uint16_t subcommand, const uint8_t *data, size_t len) {
126 if (!this->send_subcommand_(subcommand))
127 return false;
128 if (len == 0)
129 return true;
130 if (!this->write_block_(BQ76972_REG_TRANSFER_BUFFER, data, len))
131 return false;
132
133 // Inverted 8-bit sum of 0x3E, 0x3F and the data; length counts 0x3E, 0x3F, 0x60, 0x61 and the data
134 uint8_t sum = static_cast<uint8_t>(subcommand & 0xFF) + static_cast<uint8_t>(subcommand >> 8);
135 for (size_t i = 0; i < len; ++i)
136 sum += data[i];
137 const uint8_t tail[2] = {static_cast<uint8_t>(~sum), static_cast<uint8_t>(4 + len)};
138 return this->write_block_(BQ76972_REG_CHECKSUM, tail, 2);
139}
140
142 if (!this->write_subcommand_(BQ76972_SUBCMD_ENTER_CFG_UPDATE, nullptr, 0))
143 return false;
144 delay(10);
145 return this->wait_for_cfgupdate_();
146}
147
149 return this->write_subcommand_(BQ76972_SUBCMD_EXIT_CFG_UPDATE, nullptr, 0);
150}
151
153 uint8_t reg12;
154 uint8_t reg0;
155 if (!this->read_subcommand_(BQ76972_MEM_REG12_CONFIG, &reg12, 1) ||
156 !this->read_subcommand_(BQ76972_MEM_REG0_CONFIG, &reg0, 1))
157 return false;
158 out = reg12 == 0 && (reg0 & 0x01) == 0;
159 return true;
160}
161
163 uint16_t internal_temp;
164 if (!this->read_words_(BQ76972_CMD_TEMP_START, &internal_temp, 1))
165 return LOG_STR(ESP_LOG_MSG_COMM_FAIL);
166
167 // Only write the regulator config when it differs, see
168 // https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/1583718/bq76972-undocumented-4-minute-config_update-auto-exit-timeout
169 bool regs_disabled = true;
170 if (this->reg_disable_ && !this->regs_disabled_(regs_disabled))
171 return LOG_STR("Reading regulator config failed");
172 const bool write_regs = !regs_disabled;
173
174 bool write_thermistors = false;
175 for (size_t i = 1; i < BQ76972_NUM_TEMPERATURES; i++) {
176 if (i != BQ76972_TS1_INDEX && this->temperature_sensors_[i] != nullptr)
177 write_thermistors = true;
178 }
179
180 if (write_regs || write_thermistors) {
181 if (!this->enter_cfg_update_())
182 return LOG_STR("Entering config update mode failed");
183 const LogString *error = this->write_config_(write_regs);
184 // The chip stops measuring in config update mode, so leave it even after a failed write
185 if (!this->exit_cfg_update_() && error == nullptr)
186 error = LOG_STR("Exiting config update mode failed");
187 if (error != nullptr)
188 return error;
189 if (write_regs && (!this->regs_disabled_(regs_disabled) || !regs_disabled))
190 return LOG_STR("Disabling regulators failed");
191 }
192
193 ESP_LOGD(TAG, "Internal temperature: %.1f°C", raw_to_celsius(internal_temp));
194 return nullptr;
195}
196
197const LogString *BQ76972Component::write_config_(bool write_regs) {
198 if (write_regs) {
199 const uint8_t zero = 0;
200 if (!this->write_subcommand_(BQ76972_MEM_REG12_CONFIG, &zero, 1) ||
201 !this->write_subcommand_(BQ76972_MEM_REG0_CONFIG, &zero, 1))
202 return LOG_STR("Writing regulator config failed");
203 }
204 for (size_t i = 1; i < BQ76972_NUM_TEMPERATURES; i++) {
205 if (i == BQ76972_TS1_INDEX || this->temperature_sensors_[i] == nullptr)
206 continue;
207 if (!this->write_subcommand_(BQ76972_MEM_TEMP_PIN_CONFIG_BASE + i, &BQ76972_PIN_CONFIG_THERMISTOR, 1))
208 return LOG_STR("Writing thermistor pin config failed");
209 }
210 return nullptr;
211}
212
214 if (const LogString *error = this->configure_(); error != nullptr)
215 this->mark_failed(error);
216}
217
219 ESP_LOGCONFIG(TAG, "BQ76972:");
220 LOG_I2C_DEVICE(this);
221 LOG_UPDATE_INTERVAL(this);
222 ESP_LOGCONFIG(TAG,
223 " CRC Mode: %s\n"
224 " Regulators disabled: %s",
225 this->crc_mode_ ? LOG_STR_LITERAL("Enabled") : LOG_STR_LITERAL("Disabled"), YESNO(this->reg_disable_));
226}
227
229 if (this->is_failed())
230 return;
231 const uint8_t shifted = address << 1;
232 if (!this->enter_cfg_update_()) {
233 this->status_set_warning(LOG_STR("Programming I2C address failed"));
234 return;
235 }
236 // The new address only takes effect with the swap, so after a failed write the chip still answers at the
237 // old one and must still be taken out of config update mode
238 const bool written = this->write_subcommand_(BQ76972_MEM_I2C_ADDRESS, &shifted, 1) &&
239 this->write_subcommand_(BQ76972_MEM_SWAP_COMM_MODE, nullptr, 0);
240 if (written) {
241 delay(10);
242 this->set_i2c_address(address);
243 }
244 if (!this->exit_cfg_update_() || !written) {
245 this->status_set_warning(LOG_STR("Programming I2C address failed"));
246 return;
247 }
248 ESP_LOGI(TAG, "I2C address set to 0x%02X", address);
249}
250
251template<size_t N> static bool any_set(const std::array<sensor::Sensor *, N> &sensors) {
252 for (auto *sensor : sensors) {
253 if (sensor != nullptr)
254 return true;
255 }
256 return false;
257}
258
260 // Each group publishes as soon as its read succeeds, so one failed read does not hold back the others
261 bool ok = true;
262 uint16_t words[BQ76972_NUM_CELLS];
263 if (this->stack_voltage_sensor_ != nullptr) {
264 if (this->read_words_(BQ76972_CMD_STACK_VOLTAGE, words, 1)) {
265 if (words[0] != 0)
266 this->stack_voltage_sensor_->publish_state(words[0] * 0.01f); // 10 mV units
267 } else {
268 ok = false;
269 }
270 }
271 if (any_set(this->cell_sensors_)) {
272 if (this->read_words_(BQ76972_CMD_CELL_START, words, BQ76972_NUM_CELLS)) {
273 for (size_t i = 0; i < BQ76972_NUM_CELLS; i++) {
274 if (this->cell_sensors_[i] != nullptr && words[i] != 0)
275 this->cell_sensors_[i]->publish_state(words[i] * 0.001f); // mV
276 }
277 } else {
278 ok = false;
279 }
280 }
281 if (any_set(this->temperature_sensors_)) {
282 if (this->read_words_(BQ76972_CMD_TEMP_START, words, BQ76972_NUM_TEMPERATURES)) {
283 for (size_t i = 0; i < BQ76972_NUM_TEMPERATURES; i++) {
284 if (this->temperature_sensors_[i] != nullptr)
285 this->temperature_sensors_[i]->publish_state(raw_to_celsius(words[i]));
286 }
287 } else {
288 ok = false;
289 }
290 }
291 if (ok) {
292 this->status_clear_warning();
293 } else {
294 this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
295 }
296}
297
298} // namespace esphome::bq76972
uint8_t address
Definition bl0906.h:4
uint8_t raw[35]
Definition bl0939.h:0
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void status_clear_warning()
Definition component.h:291
bool read_block_(uint8_t start_register, uint8_t *data, size_t len)
Definition bq76972.cpp:34
bool read_subcommand_(uint16_t subcommand, uint8_t *data, size_t len)
Definition bq76972.cpp:120
bool read_words_(uint8_t start_register, uint16_t *data, size_t num_words)
Definition bq76972.cpp:82
const LogString * write_config_(bool write_regs)
Definition bq76972.cpp:197
std::array< sensor::Sensor *, BQ76972_NUM_CELLS > cell_sensors_
Definition bq76972.h:47
bool write_subcommand_(uint16_t subcommand, const uint8_t *data, size_t len)
Definition bq76972.cpp:125
bool write_block_(uint8_t start_register, const uint8_t *data, size_t len)
Definition bq76972.cpp:63
void program_i2c_address(uint8_t address)
Definition bq76972.cpp:228
const LogString * configure_()
Definition bq76972.cpp:162
std::array< sensor::Sensor *, BQ76972_NUM_TEMPERATURES > temperature_sensors_
Definition bq76972.h:48
sensor::Sensor * stack_voltage_sensor_
Definition bq76972.h:46
bool send_subcommand_(uint16_t subcommand)
Definition bq76972.cpp:91
uint8_t address_
store the address of the device on the bus
Definition i2c.h:270
void set_i2c_address(uint8_t address)
We store the address of the device on the bus.
Definition i2c.h:139
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const
Definition i2c.h:251
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:217
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
std::vector< uint8_t > & out
Definition gif.cpp:49
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "bq76972")
const void size_t len
Definition hal.h:68
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first)
Calculate a CRC-8 checksum of data with size len.
Definition helpers.cpp:68
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:980
void HOT delay(uint32_t ms)
Definition hal.cpp:85
int written
Definition helpers.h:1186