Refactor Sensirion Sensors (#3374)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Martin
2022-04-13 00:19:48 +02:00
committed by GitHub
parent 99335d986e
commit d620b6dd5e
36 changed files with 484 additions and 718 deletions
@@ -0,0 +1,10 @@
import esphome.codegen as cg
from esphome.components import i2c
CODEOWNERS = ["@martgras"]
sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common")
SensirionI2CDevice = sensirion_common_ns.class_("SensirionI2CDevice", i2c.I2CDevice)
@@ -0,0 +1,128 @@
#include "i2c_sensirion.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome {
namespace sensirion_common {
static const char *const TAG = "sensirion_i2c";
// To avoid memory allocations for small writes a stack buffer is used
static const size_t BUFFER_STACK_SIZE = 16;
bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
last_error_ = this->read(buf.data(), num_bytes);
if (last_error_ != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc);
last_error_ = i2c::ERROR_CRC;
return false;
}
data[i] = encode_uint16(buf[j], buf[j + 1]);
}
return true;
}
/***
* write command with parameters and insert crc
* use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters
*/
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
uint8_t data_len) {
uint8_t temp_stack[BUFFER_STACK_SIZE];
std::unique_ptr<uint8_t[]> temp_heap;
uint8_t *temp;
size_t required_buffer_len = data_len * 3 + 2;
// Is a dynamic allocation required ?
if (required_buffer_len >= BUFFER_STACK_SIZE) {
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
temp = temp_heap.get();
} else {
temp = temp_stack;
}
// First byte or word is the command
uint8_t raw_idx = 0;
if (command_len == 1) {
temp[raw_idx++] = command & 0xFF;
} else {
// command is 2 bytes
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
temp[raw_idx++] = command >> 8;
temp[raw_idx++] = command & 0xFF;
#else
temp[raw_idx++] = command & 0xFF;
temp[raw_idx++] = command >> 8;
#endif
}
// add parameters folllowed by crc
// skipped if len == 0
for (size_t i = 0; i < data_len; i++) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
temp[raw_idx++] = data[i] >> 8;
temp[raw_idx++] = data[i] & 0xFF;
#else
temp[raw_idx++] = data[i] & 0xFF;
temp[raw_idx++] = data[i] >> 8;
#endif
temp[raw_idx++] = sht_crc_(data[i]);
}
last_error_ = this->write(temp, raw_idx);
return last_error_ == i2c::ERROR_OK;
}
bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len,
uint8_t delay_ms) {
if (!this->write_command_(reg, command_len, nullptr, 0)) {
ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_);
return false;
}
delay(delay_ms);
bool result = this->read_data(data, len);
if (!result) {
ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_);
}
return result;
}
// The 8-bit CRC checksum is transmitted after each data word
uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) {
uint8_t bit;
uint8_t crc = 0xFF;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
crc ^= data >> 8;
#else
crc ^= data & 0xFF;
#endif
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ crc_polynomial_;
} else {
crc = (crc << 1);
}
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
crc ^= data & 0xFF;
#else
crc ^= data >> 8;
#endif
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ crc_polynomial_;
} else {
crc = (crc << 1);
}
}
return crc;
}
} // namespace sensirion_common
} // namespace esphome
@@ -0,0 +1,155 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace sensirion_common {
/**
* Implementation of a i2c functions for Sensirion sensors
* Sensirion data requires crc checking.
* Each 16 bit word is/must be followed 8 bit CRC code
* (Applies to read and write - note the i2c command code doesn't need a CRC)
* Format:
* | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | ..
*/
class SensirionI2CDevice : public i2c::I2CDevice {
public:
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
/** Read data words from i2c device.
* handles crc check used by Sensirion sensors
* @param data pointer to raw result
* @param len number of words to read
* @return true if reading succeded
*/
bool read_data(uint16_t *data, uint8_t len);
/** Read 1 data word from i2c device.
* @param data reference to raw result
* @return true if reading succeded
*/
bool read_data(uint16_t &data) { return this->read_data(&data, 1); }
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) {
return get_register_(command, ADDR_16_BIT, data, len, delay);
}
/** Read 1 data word from 16 bit i2c register.
* @param i2c register
* @param data reference to raw result
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) {
return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay);
}
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) {
return get_register_(i2c_register, ADDR_8_BIT, data, len, delay);
}
/** Read 1 data word from 8 bit i2c register.
* @param i2c register
* @param data reference to raw result
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) {
return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay);
}
/** Write a command to the i2c device.
* @param command i2c command to send
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); }
/** Write a command and one data word to the i2c device .
* @param command i2c command to send
* @param data argument for the i2c command
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); }
/** Write a command with arguments as words
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
* @param data vector<uint16> arguments for the i2c command
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, const std::vector<uint16_t> &data) {
return write_command_(i2c_register, sizeof(T), data.data(), data.size());
}
/** Write a command with arguments as words
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
* @param data arguments for the i2c command
* @param len number of arguments (words)
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, const uint16_t *data, uint8_t len) {
// limit to 8 or 16 bit only
static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2,
"only 8 or 16 bit command types are supported.");
return write_command_(i2c_register, CommandLen(sizeof(T)), data, len);
}
protected:
uint8_t crc_polynomial_{0x31u}; // default for sensirion
/** Write a command with arguments as words
* @param command i2c command to send can be uint8_t or uint16_t
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
* @param data arguments for the i2c command
* @param data_len number of arguments (words)
* @return true if reading succeded
*/
bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len);
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay);
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
* @param command i2c command to send
* @param data data word for which the crc8 checksum is calculated
* @param len number of arguments (words)
* @return 8 Bit CRC
*/
uint8_t sht_crc_(uint16_t data);
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
* @param command i2c command to send
* @param data1 high byte of data word
* @param data2 low byte of data word
* @return 8 Bit CRC
*/
uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); }
/** last error code from i2c operation
*/
i2c::ErrorCode last_error_;
};
} // namespace sensirion_common
} // namespace esphome