components/ads131m08/ads131m08.cpp

1764 lines
61 KiB
C++

#include "ads131m08.h"
// #include <chrono>
// #include <thread>
// #include "esphome/components/spi/spi.h"
#include "esphome/core/log.h"
#include <string>
#include <charconv>
#include <format>
#include "esp_system.h"
#include "spi_flash_mmap.h"
// using namespace esphome::spi;
namespace esphome {
namespace ads131m08 {
static const char *const TAG = "ads131m08";
static const char *const white_space = " \t\n\r\f\v"; // Defines whitespace
DRAM_ATTR static spiframe tx_frame(ADS131M08Hub::numFrameWords * 4, 0);
DRAM_ATTR static spiframe rx_frame(ADS131M08Hub::numFrameWords * 4, 0);
// ADS131M08Hub::ADS131M08Hub()
// : base_frame(40, 0)
//{
// }
void ADS131M08Hub::setup() {
for (int i = 0; i < ADC_CHANNELS; i++) {
// last_publish_time_[i] = 0;
num_samples_[i] = 0;
sample_sum_[i] = 0;
sample_squared_sum_[i] = 0;
}
// 1. Create a binary semaphore
this->data_ready_semhandle = xSemaphoreCreateBinary();
this->update_avg = xSemaphoreCreateMutex();
sync_reset_pin_->setup();
sync_reset_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT); // Set SYNC/RESET pin as output
sync_reset_pin_->digital_write(true);
// setup DRDY pin interrupt
this->drdy_pin_->setup();
this->drdy_pin_->pin_mode(esphome::gpio::FLAG_INPUT | esphome::gpio::FLAG_PULLUP); // external pull-up
this->spi_setup();
// Check if setup failed
if (this->is_failed()) {
ESP_LOGE(TAG, "SPI setup failed!");
this->mark_failed(LOG_STR("SPI setup failed."));
return;
}
// Send RESET command
bool reset_ok = adc_reset_retry();
// ESP_LOGW(TAG, "adc word length: %d", adc_word_length_);
if (!reset_ok) {
ESP_LOGE(TAG, "ADC reset failed!");
this->mark_failed(LOG_STR("Reset failed."));
this->adc_init_ = 0;
return;
}
if (!this->adc_initialize(DEFAULT_WORD_LENGTH)) {
ESP_LOGE(TAG, "ADC reset failed!");
this->mark_failed(LOG_STR("Initialisation failed."));
this->adc_init_ = 0;
return;
}
set_reg_osr();
if (!adc_lock(true)) {
ESP_LOGE(TAG, "ADC lock failed!");
this->mark_failed(LOG_STR("ADC lock failed."));
this->adc_init_ = 0;
return;
}
if (!adc_lock(false)) {
ESP_LOGE(TAG, "ADC unlock failed!");
this->mark_failed(LOG_STR("ADC unlock failed."));
this->adc_init_ = 0;
return;
}
this->drdy_pin_->attach_interrupt(&ADS131M08Hub::isr_handler, this, gpio::INTERRUPT_FALLING_EDGE);
// cs_ctr_=1000;
// SPIDevice::enable(); // leave CS low
// Request a high loop() execution interval
this->high_freq_.start();
}
void ADS131M08Hub::dump_config() {
ESP_LOGCONFIG(TAG, "ADS131M08:");
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DRDY Pin: ", this->drdy_pin_);
if (this->sync_reset_pin_ != nullptr) {
LOG_PIN(" SYNC/RESET Pin: ", this->sync_reset_pin_);
}
if (this->data_rate_ < 1000000) {
ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "kHz", this->data_rate_ / 1000);
} else {
ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "MHz", this->data_rate_ / 1000000);
}
if (this->clock_frequency_ < 1000000) {
ESP_LOGCONFIG(TAG, " Clock frequency: %.3fkHz", this->clock_frequency_ / 1000);
} else {
ESP_LOGCONFIG(TAG, " Clock frequency: %.3fMHz", this->clock_frequency_ / 1000000);
}
uint16_t osr = 1 << (7 + this->osr_);
if (osr == 16384) {
osr = 16256;
}
ESP_LOGCONFIG(TAG, " Oversampling ratio: %" PRId32, osr);
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
ESP_LOGCONFIG(TAG, " SPI Mode: %d", this->mode_);
ESP_LOGCONFIG(TAG, " Bit Order: %s",
this->bit_order_ == 1 ? "msb_first"
: this->bit_order_ == 0 ? "lsb_first"
: "unknown");
}
uint8_t ADS131M08Hub::update_adc_word_length() {
setup_frame(rx_frame, 2);
transfer_array(rx_frame); // write CMD_NULL to ADC to retrieve status
zero_fill(rx_frame);
transfer_array(rx_frame);
uint16_t status = rx_frame[0] << 8 | rx_frame[1];
// print_command_response_to_string(CMD_NULL, frame).c_str();
return update_adc_word_length(status);
}
uint8_t ADS131M08Hub::update_adc_word_length(uint16_t status) {
switch (status & MASK_STATUS_WLENGTH) {
case WLENGTH_16_BITS:
this->adc_word_length_ = 16;
break;
case WLENGTH_24_BITS:
this->adc_word_length_ = 24;
break;
case WLENGTH_32_BITS_LSB_ZERO_PADDING:
case WLENGTH_32_BITS_MSB_SIGN_EXTEND:
this->adc_word_length_ = 32;
break;
default:
ESP_LOGW(TAG, "Unknown word length: %u", adc_word_length_);
break;
}
update_conversion_factor();
return this->adc_word_length_;
}
float ADS131M08Hub::update_conversion_factor() {
switch (adc_word_length_) {
case 16:
this->conversion_factor_ = this->reference_voltage_ / 32768.0; // TODO: must double check this with a test
break;
default:
this->conversion_factor_ = this->reference_voltage_ / 8388608.0;
break;
}
return this->conversion_factor_;
}
bool ADS131M08Hub::adc_initialize(uint8_t word_length) {
this->adc_init_++;
update_adc_word_length();
if (!adc_register_write(REG_CLOCK, reg_clock_allch_off)) {
ESP_LOGE(TAG, "CLOCK register write / read to turn off all channels failed");
return false;
}
ESP_LOGV(TAG, "Turned off all channels so short frames can be written during config");
if (!adc_register_write(REG_MODE, MODE_MASK_RESET_HAPPENED & reg_mode_cfg24)) {
ESP_LOGE(TAG, "MODE register write / read to set word size to 24bits failed");
return false;
}
ESP_LOGV(TAG, "Written MODE register; Cleared the RESET flag, made DRDY active low pulse");
if (!adc_register_write(REG_THRSHLD_LSB, 0x09)) {
ESP_LOGE(TAG, "THRSHLD_LSB register write / read to set DBLOCK filter failed");
return false;
}
ESP_LOGV(TAG, "Written THRSHLD_LSB register; Set DCBLOCK filter to have a corner frequency of 622 mHz");
if (!adc_set_word_length(word_length))
return false;
// we leave should channels off, as the individual sensors should turn it on
if (!adc_register_write(REG_CLOCK, reg_clock_allch_off)) {
ESP_LOGE(TAG, "CLOCK register write / read to turn-off all channels failed");
return false;
}
if (!drdy_pin_->digital_read()) {
ESP_LOGE(TAG, "DRDY pin is low after initialization!");
return false;
}
this->adc_init_--;
return true;
}
bool ADS131M08Hub::adc_set_word_length(uint8_t word_length) {
uint16_t setting;
switch (word_length) {
case 32:
setting = reg_mode_cfg32;
break;
case 16:
setting = reg_mode_cfg16;
break;
default:
setting = reg_mode_cfg24;
break;
}
if (!adc_register_write_masked(REG_MODE, setting, reg_mode_cfg_mask, __LINE__)) {
ESP_LOGE(TAG, "MODE register write / read to set word size to %ubits failed", word_length);
return false;
}
ESP_LOGV(TAG, "Written MODE register; Made ADC word size %u bits. Re-wrote other set bits in MODE register",
word_length);
// update_adc_word_length();
return true;
}
// Implementing nested CS enable/disable handling in ADS131M08Hub to allow multiple transfers in a row without toggling
// CS between them, which is required for proper communication with the ADC according to the datasheet recommendations.
void ADS131M08Hub::enable(const char *txt) {
cs_ctr_++; // Increment counter on each enable call
// esph_log_i(TAG, "cs_ctr_e: %s %d", txt, cs_ctr_.load());
if (cs_ctr_ == 1) { // Only set CS low on the first enable call
SPIDevice::enable(); // Call the base class enable to set CS low
if (txt != nullptr)
esph_log_i(TAG, "enable: %s", txt);
}
}
void ADS131M08Hub::disable(const char *txt) {
// esph_log_i(TAG, "cs_ctr_d: %s %d", txt, cs_ctr.load());
cs_ctr_--; // Decrement counter on each disable call
if (cs_ctr_ == 0) { // Only set CS high when counter returns to 0
SPIDevice::disable(); // Call the base class disable to set CS high
if (txt != nullptr)
esph_log_i(TAG, "disable: %s", txt);
} else if (cs_ctr_ < 0) { // Sanity check to prevent counter from going negative
cs_ctr_ = 0;
}
}
// ISR function to handle interrupt from drdy pin
// MUST be fast and non-blocking
void ADS131M08Hub::isr_handler(ADS131M08Hub *arg) {
{
// InterruptLock lock;
if (arg != nullptr) {
arg->enable_loop_soon_any_context();
arg->isr_ctr_++;
// BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// xSemaphoreGiveFromISR(arg->data_ready_semhandle, &xHigherPriorityTaskWoken);
// if (xHigherPriorityTaskWoken == pdTRUE) {
// portYIELD_FROM_ISR(); // Switch to the waiting task immediately
// }
}
}
}
void ADS131M08Hub::loop()
{
// Check the semaphore (0 timeout means non-blocking)
//if (xSemaphoreTake(data_ready_semhandle, 0) == pdTRUE) {
if (this->adc_init_ == 0) {
float num_samples = 0;
float crc_errors = 0;
float iterations = 0;
float not_ready = 0;
float sps = 0;
{
CHIP_SELECTx
uint64_t start = micros();
this->txf_init(); // implementing datasheet recommended TXF init
//this->adc_sync();
if (rms_calc_req_) {
float_str result = read_multi();
num_samples = result[0];
crc_errors = result[1];
iterations = result[2];
not_ready = result[3];
sps = result[4];
uint64_t end = micros();
//update_averages(SAMPLE_TIME_SENSOR, 1, static_cast<float>(end - start) / 1000.0);
//update_averages(MAX_SAMPLES_SENSOR, 1, static_cast<float>(num_samples));
//update_averages(CRC_ERRORS_SENSOR, 1, static_cast<float>(crc_errors));
//uint16_t ctr = isr_ctr_;
//update_averages(ISR_COUNT_SENSOR, 1, static_cast<float>(ctr));
//update_averages(ITERATIONS_SENSOR, 1, static_cast<float>(iterations));
//update_averages(NOT_READY_SENSOR, 1, static_cast<float>(not_ready));
if(this->sensors_dc[SAMPLE_TIME_SENSOR] != nullptr)
this->sensors_dc[SAMPLE_TIME_SENSOR]->publish_state((end - start)/1000.0);
if(this->sensors_dc[MAX_SAMPLES_SENSOR] != nullptr)
this->sensors_dc[MAX_SAMPLES_SENSOR]->publish_state(num_samples);
if(this->sensors_dc[CRC_ERRORS_SENSOR] != nullptr)
this->sensors_dc[CRC_ERRORS_SENSOR]->publish_state(crc_errors);
if(this->sensors_dc[ISR_COUNT_SENSOR] != nullptr) {
uint16_t ctr = isr_ctr_;
this->sensors_dc[ISR_COUNT_SENSOR]->publish_state(ctr);
}
if(this->sensors_dc[ITERATIONS_SENSOR] != nullptr)
this->sensors_dc[ITERATIONS_SENSOR]->publish_state(iterations);
if(this->sensors_dc[NOT_READY_SENSOR] != nullptr)
this->sensors_dc[NOT_READY_SENSOR]->publish_state(not_ready);
if(this->sensors_dc[SPS_SENSOR] != nullptr)
this->sensors_dc[SPS_SENSOR]->publish_state(sps);
} else {
read_single();
}
isr_ctr_ = 0;
}
if (crc_errors > 30) {
ESP_LOGW(TAG, "High CRC error rate.");
int i = 4;
while(i-- > 0 && !adc_set_word_length(DEFAULT_WORD_LENGTH)); // for some reason the adc occasionally reverts to 24bits; this will
// reset the word length to what it should be
// update_adc_word_length();
// spiframe recoverframe(40, 0);
// read_array(recoverframe);
// ESP_LOGI(TAG, "WL: %u Frame: %s", this->adc_word_length_, frame_to_string(recoverframe).c_str());
}
// ESP_LOGW(TAG, "%llu ms (%llu us), max samples: %u", (end - start)/1000, (end - start), num_samples);
}
//}
}
/// @brief Set 16 bit frameword
/// @param frame
/// @param w_index
/// @param data uint16_t denotes command or CRC
/// @return
bool ADS131M08Hub::set_frame_word(spiframe &frame, int w_index, uint16_t data)
{
size_t word_nbytes = adc_word_length_ >> 3;
size_t frame_words = frame.size() / word_nbytes;
int b_index = w_index * word_nbytes;
if (w_index >= frame_words) {
if (w_index >= MAX_FRAME_SIZE) {
ESP_LOGE(TAG, "Word index of %d out of bounds", w_index);
// esp_crosscore_int_send_print_backtrace(xPortGetCoreID());
return false; // invalid - something has gone wrong
}
frame.resize(b_index + word_nbytes);
}
switch (adc_word_length_) {
// deliberate no breaks after first two cases
case 32:
frame[b_index + 3] = 0;
case 24:
frame[b_index + 2] = 0;
case 16:
frame[b_index + 1] = data & 0xFF;
frame[b_index] = (data >> 8) & 0xFF;
break;
default:
ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_);
break;
}
return true;
}
bool ADS131M08Hub::set_frame_word(spiframe &frame, int w_index, uint32_t data)
{
size_t word_nbytes = adc_word_length_ >> 3;
size_t frame_words = frame.size() / word_nbytes;
int b_index = w_index * word_nbytes;
if (w_index >= frame_words) {
if (w_index >= MAX_FRAME_SIZE) {
ESP_LOGE(TAG, "Word index of %d out of bounds", w_index);
// esp_crosscore_int_send_print_backtrace(xPortGetCoreID());
return false; // invalid - something has gone wrong
}
frame.resize(b_index + word_nbytes);
}
switch (adc_word_length_) {
// deliberate no breaks after first two cases
case 32:
frame[b_index++] = (data >> 24) & 0xFF;
case 24:
frame[b_index++] = (data >> 16) & 0xFF;
case 16:
frame[b_index++] = (data >> 8) & 0xFF;
frame[b_index] = data & 0xFF;
break;
default:
ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_);
break;
}
return true;
}
// index(i) = zero-reference, returns i'th word, LSB aligned
uint32_t ADS131M08Hub::get_unsigned_frame_word(const spiframe &frame, int w_index, bool force_16bits)
{
uint32_t result = 0;
size_t word_nbytes = adc_word_length_ >> 3;
int b_index = w_index * word_nbytes;
if (b_index > (frame.size() - word_nbytes)) {
return result; // illegal
}
switch (force_16bits ? 16 : adc_word_length_) {
// deliberate no breaks after first two cases
case 32:
result += frame[b_index++] << 24;
case 24:
result += frame[b_index++] << 16;
case 16:
result += frame[b_index++] << 8;
result += frame[b_index];
break;
default:
ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_);
break;
}
return result;
}
int32_t ADS131M08Hub::get_sign_ext_frame_word(const spiframe &frame, int w_index)
{
int32_t result = 0;
size_t word_nbytes = adc_word_length_ >> 3;
int b_index = w_index * word_nbytes;
if (b_index > (frame.size() - word_nbytes)) {
ESP_LOGW(TAG, "Word index of %d out of bounds", w_index);
return result; // invalid
}
switch (adc_word_length_) {
case 32:
result = (frame[b_index] << 24) | (frame[b_index + 1] << 16) | (frame[b_index + 2]) << 8 | frame[b_index + 3];
break;
case 24:
result = (frame[b_index] << 24) | (frame[b_index + 1] << 16) | (frame[b_index + 2] << 8);
result = result >> 8;
break;
case 16:
{
int16_t raw = (frame[b_index] << 8) | frame[b_index + 1];
result = static_cast<int16_t>(raw);
}
break;
default:
ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_);
break;
}
//if(w_index == 1 || w_index ==2)
//if(result > 7000000 || result < -7000000) {
//ESP_LOGD(TAG, "frame: %s", frame_to_string(rx_frame).c_str());
// ESP_LOGD(TAG, "frame_word%d: %s result: [0x%08X] %d", w_index, frame_words_to_string(frame, w_index, 1).c_str(), result, result);
//}
return result;
}
void ADS131M08Hub::adc_hard_reset() {
float cycle_micros = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds
uint32_t reset_pulse_duration = static_cast<uint32_t>(
1.05 * 2048 * cycle_micros); // 2048 clock cycles converted to microseconds, multiplied by 1.05 to ensure we meet
// the minimum pulse duration requirement
if (reset_pulse_duration < 1) {
reset_pulse_duration = 1; // set it to 1 microsecond
}
sync_reset_pin_->digital_write(false); // Pull SYNC/RESET pin low to reset the ADC
delay_microseconds_safe(reset_pulse_duration); // Hold reset for the calculated duration
sync_reset_pin_->digital_write(true); // Set SYNC/RESET pin high to allow normal operation
}
void ADS131M08Hub::adc_sync() {
float cycle_micros = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds
uint32_t sync_pulse_duration =
static_cast<uint32_t>(std::round(50 * cycle_micros)); // minimum is 1/clock freq, max = 2047/clock freq
if (sync_pulse_duration < 1) {
sync_pulse_duration = 1; // set it to 1 microsecond
}
sync_reset_pin_->digital_write(false); // Pull SYNC/RESET pin low to sync the ADC
delay_microseconds_safe(sync_pulse_duration); // Hold sync for the calculated duration
sync_reset_pin_->digital_write(true); // Set SYNC/RESET pin high to allow normal operation
}
// ********************** from datasheet ************************
void ADS131M08Hub::txf_init() {
CHIP_SELECT
setup_frame(rx_frame, numFrameWords);
if (firstRead) {
// Clear the ADC's 2-deep FIFO on the first read
this->transfer_array(rx_frame);
zero_fill(rx_frame);
this->transfer_array(rx_frame);
firstRead = false; // Clear the flag
}
// Send the nulls to the ADC to get the current ADC data
zero_fill(rx_frame);
this->transfer_array(rx_frame);
}
bool ADS131M08Hub::adc_reset_retry() {
adc_word_length_ = 24;
bool reset_ok = adc_soft_reset();
uint8_t retry_wordlengths[] = {32, 32, 24, 24, 16, 16};
int i = 0;
while (i < sizeof(retry_wordlengths) && !reset_ok) {
adc_word_length_ = retry_wordlengths[i];
// ESP_LOGW(TAG, "ADC word length: %d", adc_word_length_);
reset_ok = adc_soft_reset();
i++;
}
if (!reset_ok) {
adc_hard_reset();
delay_microseconds_safe(10000);
i = 0;
while (i < sizeof(retry_wordlengths) && !reset_ok) {
adc_word_length_ = retry_wordlengths[i];
// ESP_LOGW(TAG, "ADC word length: %d", adc_word_length_);
reset_ok = adc_soft_reset();
i++;
}
}
return reset_ok;
}
// int ADS131M08Hub::build_frame()
bool ADS131M08Hub::adc_soft_reset() {
int index = 0;
uint16_t reset_response;
uint16_t cmd = 0;
update_adc_word_length();
auto framelength = numFrameWords * (adc_word_length_ >> 3);
size_t frame_len;
// curly braces confine chip select scope
{
CHIP_SELECTx cmd = CMD_RESET;
setup_frame(rx_frame, 2);
set_frame_word(rx_frame, 0, cmd);
frame_len = add_crc(rx_frame);
rx_frame.resize(framelength);
index += frame_len;
for (; index < framelength; index++) {
rx_frame[index] = 0;
}
ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(rx_frame).c_str());
transfer_array(rx_frame);
}
delay_microseconds_safe(T_REGACQ);
update_adc_word_length(WLENGTH_24_BITS); // should be 24 after reset
setup_frame(rx_frame, numFrameWords);
{
CHIP_SELECTx
transfer_array(rx_frame);
reset_response = get_unsigned_frame_word(rx_frame, 0, true);
bool crc_ok = check_crc(rx_frame);
ESP_LOGVV(TAG, "Read Frame: %s CRC: %s", frame_to_string(rx_frame).c_str(), (crc_ok ? "OK" : "FAIL"));
}
bool result = reset_response == RSP_RESET_OK;
ESP_LOGV(TAG, "Reset response: [0x%04X] %s", reset_response, (result ? "OK" : "FAIL"));
return result;
}
// overwrites last (or second) frameword with CRC, therefore frame should be large enough for payload and crc
size_t ADS131M08Hub::add_crc(spiframe &frame) {
size_t frame_length = frame.size();
if (frame_length == 0 || adc_word_length_ == 0)
return 0;
size_t word_nbytes = adc_word_length_ >> 3;
size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any
size_t payload_len = word_nbytes * (frame_words - 1);
if (payload_len < word_nbytes)
return false;
uint16_t crc = get_crc(frame);
set_frame_word(frame, frame_words - 1, crc);
return frame_words * word_nbytes;
}
// CRC should be 16bits, MSB aligned in last word of frame
bool ADS131M08Hub::check_crc(const spiframe &frame) {
if (adc_word_length_ == 0)
return false;
size_t word_nbytes = adc_word_length_ >> 3;
int frame_length = frame.size();
size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any
size_t payload_len = word_nbytes * (frame_words - 1);
if (payload_len < word_nbytes)
return false;
uint16_t frame_crc = (frame[payload_len] << 8) + frame[payload_len + 1]; // Frames are MSB aligned, so is CRC
auto crc = get_crc(frame);
bool crc_ok = crc == frame_crc;
return crc_ok;
}
uint16_t ADS131M08Hub::read_frame_crc(const spiframe &frame) {
if (adc_word_length_ == 0)
return 0;
size_t word_nbytes = adc_word_length_ >> 3;
int frame_length = frame.size();
size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any
size_t payload_len = word_nbytes * (frame_words - 1);
if (payload_len < word_nbytes)
return 0;
return (frame[payload_len] << 8) + frame[payload_len + 1]; // Frames are MSB aligned, so is CRC
}
/*
CRC Parameters for ADS131M08
Polynomial: 0x1021 (x^16 + x^12 + x^5 + 1).
Initial Value (Seed): 0xFFFF.
Input Data: The calculation is performed over all bits in the SPI frame preceding the CRC word.
Output Format: The resulting 16-bit CRC is placed in the most significant 16 bits of the final 24-bit (or 32-bit) SPI
word; the remaining bits are typically padded with zeros.
*/
uint16_t ADS131M08Hub::get_crc(const spiframe &frame) {
size_t word_nbytes = adc_word_length_ >> 3;
size_t frame_words = frame.size() / word_nbytes; // integer division, we discard remainder if any
size_t payload_len = word_nbytes * (frame_words - 1);
uint16_t crc_result = crc(0xFFFF, frame[0]);
for (int j = 1; j < payload_len; j++) {
crc_result = crc(crc_result, frame[j]);
}
return crc_result;
}
uint16_t ADS131M08Hub::crc(uint16_t crc_register, uint8_t data) {
uint16_t xor_result, input_bit, crc_result;
crc_result = crc_register;
for (int i = 7; i >= 0; i--) {
input_bit = (data >> i) & 0x01;
xor_result = input_bit ^ (crc_result & 0x8000) >> 15;
crc_result = crc_result << 1;
if (xor_result)
crc_result = crc_result ^ 0x1021;
}
return crc_result;
}
void ADS131M08Hub::transfer_array(spiframe &frame) {
CHIP_SELECT
Base::transfer_array(frame.data(), (size_t)frame.size());
}
//void ADS131M08Hub::transfer_array(const spiframe &tx_frame, spiframe &rx_frame) {
// CHIP_SELECT
// auto frame_length = tx_frame.size();
// if (rx_frame.size() < frame_length)
// rx_frame.resize(frame_length);
// for (size_t i = 0; i < frame_length; i++) {
// rx_frame[i] = this->transfer_byte(tx_frame[i]);
// }
//}
bool ADS131M08Hub::set_measure_rms(uint8_t channel, bool enable) {
if (channel >= MAX_CHANNELS)
return false;
this->rms_enabled_[channel] = enable;
this->rms_calc_req_ = true;
return true;
}
void ADS131M08Hub::read_single() {
setup_frame(rx_frame, numFrameWords);
this->transfer_array(rx_frame);
bool crc_ok = check_crc(rx_frame);
uint16_t drdy_status = get_unsigned_frame_word(rx_frame, 0, true) & MASK_STATUS_DRDY;
// data_ready = crc_ok && (drdy_status != 0);
if (crc_ok) {
// sample channels
for (int i = 0; i < ADC_CHANNELS; i++) {
bool channel_ready = drdy_status & 1;
drdy_status = drdy_status >> 1;
if (channel_ready) {
int32_t raw = get_sign_ext_frame_word(rx_frame, i + 1);
float value = this->conversion_factor_ * raw;
this->sampled_values_[i] = value;
bool ac_sensor_exist = this->sensors_ac[i] != nullptr;
bool dc_sensor_exist = this->sensors_dc[i] != nullptr;
if (ac_sensor_exist || dc_sensor_exist) {
if (ac_sensor_exist)
this->sensors_ac[i]->publish_state(value);
if (dc_sensor_exist)
this->sensors_dc[i]->publish_state(value);
}
}
}
}
}
// do not like what the following procedure does at all (it blocks for +-40ms), but at the moment do not know how to get
// DRDY interrupt / ISR / loop setup to respond fast enough to read multiple frames with no or the minimum possible
// missed frames in quick succession idealy would want to read +- 200 24/32bit word frames in 2 AC 50/60Hz cycles to
// hopefully reliably calculate RMS value, phase offsets, etc. the best the read_multi procedure does is to read about
// 66x 16bit / 33x 32bit word frames in 40ms which is far from ideal Once we ge the ISR and loop setup to respond fast
// enough, the following procedure should be completely re-designed
float_str ADS131M08Hub::read_multi() {
int32_t raw;
float value;
float_str result(5, 0.0);
bool crc_ok, data_ready, ac_sensor_exist, dc_sensor_exist;
uint16_t drdy_status, status;
int i, iteration = 0;
uint32_t elapsed_time = 0;
uint32_t loop_start_time = micros();
int word_nbytes = adc_word_length_ >> 3;
int frame_length = numFrameWords * word_nbytes;
//spiframe frame(frame_length, 0);
tx_frame.resize(frame_length);
rx_frame.resize(frame_length);
// integrate for rms values over #iterations or sample_time depending on what comes first
for (int i = 0; i < ADC_CHANNELS; i++) {
// last_publish_time_[i] = 0;
num_samples_[i] = 0;
sample_sum_[i] = 0;
sample_squared_sum_[i] = 0;
}
while ((elapsed_time < this->sample_time_) && (iteration++ < 1000)) {
result[2] = iteration;
setup_frame(rx_frame, numFrameWords);
this->transfer_array(rx_frame);
//ESP_LOGD(TAG, "frame: %s", frame_to_string(rx_frame).c_str());
//ESP_LOGD(TAG, "iteration: %d, frame: %s", iteration, frame_to_string(rx_frame).c_str());
//ESP_LOGD(TAG, "frame_word%d: %s", 0, frame_words_to_string(rx_frame, 0, 1).c_str());
status = get_unsigned_frame_word(rx_frame, 0, true);
update_adc_word_length(status);
crc_ok = check_crc(rx_frame);
if (crc_ok) {
// skip frame immediately afte resync
if ((status & MASK_STATUS_RESYNC) == 0) {
drdy_status = status & MASK_STATUS_DRDY;
// sample channels
if(drdy_status == 0){
result[3]++;
}
for (i = 0; i < ADC_CHANNELS && drdy_status != 0; i++) {
data_ready = drdy_status & 1;
drdy_status = drdy_status >> 1;
if (data_ready) {
this->sampled_values_[i] = value;
ac_sensor_exist = this->sensors_ac[i] != nullptr;
dc_sensor_exist = this->sensors_dc[i] != nullptr;
if (ac_sensor_exist || dc_sensor_exist) {
raw = get_sign_ext_frame_word(rx_frame, i + 1);
value = this->conversion_factor_ * raw;
if (this->rms_enabled_[i]) {
(this->num_samples_[i])++;
(this->sample_sum_[i]) += value;
(this->sample_squared_sum_[i]) += value * value;
} else {
if(ac_sensor_exist)
this->sensors_ac[i]->publish_state(value);
if(dc_sensor_exist)
this->sensors_dc[i]->publish_state(value);
//update_averages(i, value, value);
}
}
}
}
}
} else {
result[1]++;
// ESP_LOGW(TAG, "iteration: %d, frame: %s CRC error", iteration, frame_to_string(frame).c_str());
}
elapsed_time = micros() - loop_start_time;
}
// publish integrated values
for (i = 0; i < ADC_CHANNELS; i++) {
if (this->rms_enabled_[i]) {
ac_sensor_exist = this->sensors_ac[i] != nullptr;
dc_sensor_exist = this->sensors_dc[i] != nullptr;
auto num_samples = this->num_samples_[i];
if (num_samples > result[0]) {
result[0] = num_samples;
}
if (num_samples == 0) {
// should not happen, but let's play safe and avoid dividing by zero
// ESP_LOGW(TAG, "Num samples: %d", num_samples);
//update_averages(i, NAN, NAN);
if(ac_sensor_exist)
this->sensors_ac[i]->publish_state(NAN);
if(dc_sensor_exist)
this->sensors_dc[i]->publish_state(NAN);
} else {
float sample_sum = sample_sum_[i];
float rms_dc = sample_sum / num_samples;
float rms_ac_squared = sample_squared_sum_[i] / num_samples - rms_dc * rms_dc;
float rms_ac = 0;
if (rms_ac_squared > 0) {
rms_ac = std::sqrtf(rms_ac_squared);
}
if(ac_sensor_exist)
this->sensors_ac[i]->publish_state(rms_ac);
if(dc_sensor_exist)
this->sensors_dc[i]->publish_state(rms_dc);
//update_averages(i, rms_ac, rms_dc);
}
this->num_samples_[i] = 0;
this->sample_sum_[i] = 0.0f;
this->sample_squared_sum_[i] = 0.0f;
}
}
this->sps_ = (1e6 * (float) result[0]) / elapsed_time;
result[4] = this->sps_;
return result;
}
/*
/// @brief Updates rolling averages for rms_ac and rms_dc. Window = 2
/// @param channel
/// @param rms_ac
/// @param rms_dc
/// @return
bool ADS131M08Hub::update_averages(uint8_t channel, float rms_ac, float rms_dc) {
if (update_avg == nullptr || channel >= MAX_CHANNELS) {
ESP_LOGW(TAG, "channel %d not ready", channel);
return false;
}
bool success = false;
bool ac_sensor_exist = this->sensors_ac[channel] != nullptr;
bool dc_sensor_exist = this->sensors_dc[channel] != nullptr;
if (!ac_sensor_exist && !dc_sensor_exist) {
//ESP_LOGW(TAG, "channel %d does not exist", channel);
return false;
}
// we treat update_state as a critical resource that must not be clobbered
if (xSemaphoreTake(update_avg, (TickType_t) 50) == pdTRUE) {
uint8_t update_state = update_state_[channel];
update_state_[channel] &= 0xF0;
if (ac_sensor_exist) {
// if this is the first value, then we just store it with no averaging
if ((update_state & 0x0F) == 0) {
avg_ac_[channel] = rms_ac;
update_state |= 0x01;
update_state_[channel] = update_state;
} else {
// if we have NAN (no samples), we store it, but also also treat next valid value as the first
if (rms_ac == NAN) {
avg_ac_[channel] = NAN;
} else {
ESP_LOGI(TAG, "Setting AC avg: %f %f", avg_ac_[channel], rms_ac);
avg_ac_[channel] = 0.5 * (avg_ac_[channel] + rms_ac);
update_state_[channel] = update_state;
}
}
}
update_state = update_state_[channel];
if (dc_sensor_exist) {
if ((update_state & 0xF0) == 0) {
avg_dc_[channel] = rms_dc;
update_state |= 0x10;
} else {
if (rms_dc == NAN) {
avg_dc_[channel] = NAN;
update_state &= 0x0F;
} else {
ESP_LOGI(TAG, "Setting DC avg: %f %f", avg_dc_[channel], rms_dc);
avg_dc_[channel] = 0.5 * (avg_dc_[channel] + rms_dc);
}
}
update_state_[channel] = update_state;
} else {
update_state_[channel] &= 0x0F;
}
success = true;
xSemaphoreGive(update_avg);
}
return success;
}
float ADS131M08Hub::get_average(uint8_t channel, bool read_ac) {
float result = NAN;
if (update_avg == nullptr || channel >= MAX_CHANNELS) {
return result;
}
if (update_avg != nullptr) {
if (xSemaphoreTake(update_avg, (TickType_t) 50) == pdTRUE) {
if (read_ac) {
// ESP_LOGI(TAG, "AC: update_state_[%u]: %02X", channel, update_state_[channel]);
if (update_state_[channel] & 0x0F) {
result = avg_ac_[channel];
update_state_[channel] &= 0xF0;
} else {
ESP_LOGW(TAG, "No AC data for channel %d", channel);
result = NAN;
}
} else {
// ESP_LOGI(TAG, "DC: update_state_[%u]: %02X", channel, update_state_[channel]);
if (update_state_[channel] & 0xF0) {
result = avg_dc_[channel];
update_state_[channel] &= 0x0F;
} else {
ESP_LOGW(TAG, "No DC data for channel %d", channel);
result = NAN;
}
}
xSemaphoreGive(update_avg);
}
}
return result;
}
*/
bool ADS131M08Hub::adc_lock(bool enable) {
setup_frame(rx_frame, 2);
uint16_t command = CMD_UNLOCK;
if (enable) {
command = CMD_LOCK;
}
set_frame_word(rx_frame, 0, command);
add_crc(rx_frame);
transfer_array(rx_frame);
delay_microseconds_safe(T_REGACQ);
zero_fill(rx_frame);
transfer_array(rx_frame);
auto ack = get_unsigned_frame_word(rx_frame, 0, true);
return ack == command;
}
/// @brief
/// @param address - register address
/// @param value - mask is used to remove unwanted bits before adding to value read from register
/// @param mask - register contents read from adc is masked with inverse of this
/// @param line - temporary / for debugging
/// @return success flag
bool ADS131M08Hub::adc_register_write_masked(uint8_t address, uint16_t value, uint16_t mask, int line) {
uint16_str reg_contents = adc_register_read(address, 1);
if (reg_contents.empty()) {
return false;
}
// ESP_LOGW(TAG, "1: reg contents: 0x%04X, address: 0x%02X, value: 0x%04X, mask: 0x%04X, caller: %d",
// reg_contents[0],address, value,mask, line );
reg_contents[0] = (reg_contents[0] & ~mask) | (value & mask);
// ESP_LOGW(TAG, "After applying mask - reg contents: 0x%04X, caller: %d", reg_contents[0], line );
return adc_register_write(address, reg_contents);
}
/// @brief
/// @param start_address - first register address
/// @param values - mask is used to remove unwanted bits before adding to value read from register
/// @param masks - register contents read from adc is masked with inverse of this
/// @param line - temporary / for debugging
/// @return success flag
bool ADS131M08Hub::adc_register_write_masked(uint8_t start_address, const uint16_str &values, const uint16_str &masks,
int line) {
uint8_t nregs = values.size();
uint8_t nmasks = masks.size();
if (nregs != nmasks) {
ESP_LOGW(TAG,
"Number register values to write (%d) are different than the corresponding number of masks (%d). Register "
"write aborted.",
nregs, nmasks);
return false;
}
uint16_str reg_contents = adc_register_read(start_address, nregs);
if (reg_contents.empty()) {
ESP_LOGW(TAG, "No contents read from adc. Expected %d values. Register write aborted.", nregs);
return false;
}
for (int i = 0; i < nregs; i++) {
// ESP_LOGW(TAG, "Before change %d: reg contents: 0x%04X, address: 0x%02X, value: 0x%04X, mask: 0x%04X, caller: %d",
// i, reg_contents[i], start_address + i, values[i], masks[i], line);
reg_contents[i] = (reg_contents[i] & ~masks[i]) | (values[i] & masks[i]);
// ESP_LOGW(TAG, "After applying mask - reg contents: 0x%04X, caller: %d", reg_contents[i], line);
}
return adc_register_write(start_address, reg_contents);
}
bool ADS131M08Hub::adc_register_write(uint16_t address, uint16_t data) {
uint16_str arg;
arg += data;
return adc_register_write(address, arg);
}
/*
//https://github.com/espressif/esp-idf/blob/v6.0.1/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c
//Allocate memory for the pixel buffers
for (int i = 0; i < 2; i++) {
lines[i] = spi_bus_dma_memory_alloc(LCD_HOST, 320 * PARALLEL_LINES * sizeof(uint16_t), mem_cap);
assert(lines[i] != NULL);
}
*/
bool ADS131M08Hub::adc_register_write(uint16_t start_address, const uint16_str &data) {
int nregs = data.size();
if (nregs == 0) {
return false; // invalid
}
int wreg_nwords = 2 + nregs; // ensure room for crc
int rreg_resp_framelength = 2 + (nregs == 1 ? -1 : nregs); // add room for CRC if nregs > 1
//spiframe frame(wreg_nwords, 0);
//spiframe rxframe(wreg_nwords, 0);
uint16_t addr_regcnt_mask = (start_address << 7) | ((nregs - 1) & MASK_CMD_RW_REG_COUNT);
uint16_t wreg_addr_opcode = CMD_WREG | addr_regcnt_mask; // Combine WREG command, start_address and register count
uint16_t rreg_addr_opcode = CMD_RREG | addr_regcnt_mask; // Combine RREG command, start_address and register count
bool has_mode_reg =
(start_address == REG_MODE) || ((start_address < REG_MODE) && ((start_address + nregs) > REG_MODE));
setup_frame(rx_frame, wreg_nwords);
{
CHIP_SELECT
set_frame_word(rx_frame, 0, wreg_addr_opcode);
for (int i = 0; i < nregs; i++) {
set_frame_word(rx_frame, i + 1, data[i]);
}
add_crc(rx_frame);
//ESP_LOGD(TAG, "%s\n", rwreg_command_frame_to_string(rx_frame).c_str());
//ESP_LOGD(TAG, "Send Frame: %s", frame_to_string(rx_frame).c_str());
transfer_array(rx_frame); // WREG
}
if (has_mode_reg) {
update_adc_word_length();
}
delay_microseconds_safe(T_REGACQ);
auto rxdata = adc_register_read(start_address, nregs);
bool verified = !rxdata.empty();
for (int i = 0; i < rxdata.size() && verified; i++) {
verified = rxdata[i] == data[i];
if (!verified) {
ESP_LOGE(TAG, "Write \'%s\' register failed: tx data: 0x%04X, rx data: 0x%04X", reg_addr_to_string(start_address + i).c_str(), data[i], rxdata[i]);
// ESP_LOGE(TAG, "Write: %s", reg_data_to_string(start_address + i, data[i]).c_str());
// ESP_LOGE(TAG, "Ack : %s", reg_data_to_string(start_address + i, rxdata[i]).c_str());
}
}
delay_microseconds_safe(T_REGACQ);
return verified;
}
// recommended not to read more than 20 registers at a time
uint16_str ADS131M08Hub::adc_register_read(uint8_t address, uint8_t nregs) {
uint16_str result;
if (nregs == 0) {
return result; // invalid
}
if (nregs > MAX_FRAME_SIZE - 2) {
// We are limited to 8 registers, otherwise we will exceed our static DMA / SPI buffer size
ESP_LOGE(TAG, "Trying to read %d registers. This exceeds maximum register read count of %d!", nregs, MAX_FRAME_SIZE - 2);
return result; // invalid
}
int rreg_nwords = 2; // ensure room for crc
int response_nwords = 2 + (nregs == 1 ? -1 : nregs); // add room for CRC if nregs > 1
uint16_t rreg_addr_opcode =
CMD_RREG | (address << 7) |
((nregs - 1) & MASK_CMD_RW_REG_COUNT); // Combine RREG command, address and register count
{
CHIP_SELECT
setup_frame(rx_frame, rreg_nwords);
set_frame_word(rx_frame, 0, rreg_addr_opcode);
add_crc(rx_frame);
transfer_array(rx_frame); // send RREG
}
ESP_LOGVV(TAG, "%s\n", rwreg_command_frame_to_string(rx_frame).c_str());
ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(rx_frame).c_str());
delay_microseconds_safe(T_REGACQ);
{
CHIP_SELECT
setup_frame(rx_frame, response_nwords);
transfer_array(rx_frame);
}
ESP_LOGVV(TAG, "Recv Frame: %s", frame_to_string(rx_frame).c_str());
if (nregs > 1) {
bool crc_ok = check_crc(rx_frame);
if (!crc_ok) {
ESP_LOGW(TAG, "CRC failed reading ADC registers!");
result.clear();
return result; // invalid
}
}
result.resize(nregs);
int offset = 0;
uint16_t rreg_ack = rreg_addr_opcode; // not really an ack, but since we don't get an ack when reading 1 register we
// pretend this is the ack
if (nregs > 1) {
offset = 1;
rreg_ack = get_unsigned_frame_word(rx_frame, 0, true);
}
// uint16_t rreg_ack = (nregs == 1) ? rreg_addr_opcode : get_unsigned_frame_word(rx_frame, 0, true);
nregs = (rreg_ack & MASK_CMD_RW_REG_COUNT) + 1;
uint16_t start_addr = (rreg_ack & MASK_CMD_RW_REG_ADDRESS) >> 7;
for (int i = 0; i < nregs; i++) {
result[i] = get_unsigned_frame_word(rx_frame, i + offset, true);
}
print_command_response_to_string(rreg_addr_opcode, rx_frame);
delay_microseconds_safe(1);
return result;
}
bool ADS131M08Hub::set_reg_osr() {
uint16_t osr = 3; // default
if (this->osr_ <= 16256 && this->osr_ >= 128) {
if (this->osr_ == 16256)
osr = 7;
else {
osr = 0;
auto tmp = this->osr_ / 128;
while (tmp >>= 1)
osr++;
}
}
return adc_register_write_masked(REG_CLOCK, osr << 2, MASK_CLOCK_OSR, __LINE__);
}
bool ADS131M08Hub::set_channel_enable(uint8_t channel, bool enable) {
if (channel >= ADC_CHANNELS)
return false;
uint16_t enable_mask = MASK_CLOCK_CH0 << channel;
uint16_t reg_value = (enable) ? enable_mask : 0;
// ESP_LOGD(TAG, "adc_register_write_masked(REG_CLOCK=%02X, reg_value=%04X, enable_mask=%04X, __LINE__)", REG_CLOCK,
// reg_value, enable_mask);
return adc_register_write_masked(REG_CLOCK, reg_value, enable_mask, __LINE__);
}
bool ADS131M08Hub::set_channel_gain(uint8_t channel, uint8_t gain) {
if (channel >= ADC_CHANNELS)
return false;
uint16_t reg = channel < 4 ? REG_GAIN1 : REG_GAIN2;
uint8_t shift = (channel % 4) << 2;
uint16_t mask = MASK_GAIN_PGAGAIN0 << shift;
uint16_t value = gain << shift;
// ESP_LOGW(TAG, "Ch%d: Set Gain: reg: %02X pga: %04X mask: %04X ", channel, reg, value, mask);
return adc_register_write_masked(reg, value, mask, __LINE__);
}
void ADS131M08Hub::set_global_chop(uint16_t global_chop) {
adc_register_write_masked(REG_CFG, global_chop << 8, MASK_CFG_GC_EN, __LINE__);
}
void ADS131M08Hub::set_global_chop_delay(uint16_t delay) {
adc_register_write_masked(REG_CFG, delay << 9, MASK_CFG_GC_DLY, __LINE__);
}
bool ADS131M08Hub::set_channel_phase_calibration(uint8_t channel, int16_t phase_offset) {
if (channel > 7)
return false;
uint16_t reg_addr = 5 * channel + REG_CH0_CFG;
if (phase_offset < -512 || phase_offset > 511)
return false;
return adc_register_write_masked(reg_addr, (phase_offset << 6), MASK_CHX_CFG_PHASE, __LINE__);
}
bool ADS131M08Hub::set_dcblock_filter_disable(uint8_t channel, bool disable) {
if (channel > 7)
return false;
uint16_t reg_addr = 5 * channel + REG_CH0_CFG;
uint16_t reg_value = (disable) ? MASK_CHX_CFG_DCBLKX_DIS : 0;
return adc_register_write_masked(reg_addr, reg_value, MASK_CHX_CFG_DCBLKX_DIS, __LINE__);
}
bool ADS131M08Hub::set_channel_input_selection(uint8_t channel, ADC_INPUT_CHANNEL_MUX input) {
if (channel > 7)
return false;
uint16_t reg_addr = 5 * channel + REG_CH0_CFG;
return adc_register_write_masked(reg_addr, input, MASK_CHX_CFG_MUX, __LINE__);
}
bool ADS131M08Hub::set_channel_offset_calibration(uint8_t channel, int32_t offset) {
if (channel > 7)
return false;
if (offset < -8388608 || offset > 8388607)
return false;
uint16_t MSB = offset >> 8;
uint16_t LSB = offset << 8;
uint16_t reg_addr = 5 * channel + REG_CH0_OCAL_MSB;
return adc_register_write_masked(reg_addr, {MSB, LSB}, {MASK_CHX_OCAL_MSB, MASK_CHX_OCAL_LSB}, __LINE__);
}
bool ADS131M08Hub::set_channel_gain_calibration(uint8_t channel, uint32_t gain) {
if (channel > 7)
return false;
if (gain > 16777215)
return false;
uint16_t MSB = gain >> 8;
uint8_t LSB = gain;
uint16_t reg_addr = 5 * channel + REG_CH0_GCAL_MSB;
return adc_register_write_masked(reg_addr, {MSB, LSB}, {MASK_CHX_GCAL_MSB, MASK_CHX_GCAL_LSB}, __LINE__);
}
// for debug only - remove references, declarations and definitions before submitting for production
std::string ADS131M08Hub::frame_words_to_string(const spiframe &frame, int index, int count) {
std::string str;
char buffer[20];
const std::string::size_type new_cap(768);
str.reserve(new_cap);
int word_nbytes = adc_word_length_ >> 3;
int frame_nwords = frame.size() / word_nbytes;
if(index >= frame_nwords)
return "";
for (int w = index; w < (index + count) && w < frame_nwords; w++) {
for (int i = 0; i < word_nbytes; i++) {
snprintf(buffer, sizeof(buffer), " %02X", frame[w * word_nbytes + i]);
str += buffer;
}
}
return str;
}
std::string ADS131M08Hub::frame_to_string(const spiframe &frame) {
std::string str;
char buffer[20];
const std::string::size_type new_cap(768);
str.reserve(new_cap);
for (int i = 0; i < frame.size(); i++) {
snprintf(buffer, sizeof(buffer), " %02X", frame[i]);
str += buffer;
}
return str;
}
std::string ADS131M08Hub::conversion_frame_to_string(const spiframe &frame) {
std::string str;
char buffer[20];
const std::string::size_type new_cap(768);
str.reserve(new_cap);
int word_nbytes = adc_word_length_ >> 3;
int frame_nwords = frame.size() / word_nbytes;
uint16_t drdy_status = get_unsigned_frame_word(frame, 0, true) & MASK_STATUS_DRDY;
// init i = 0 to also print status word
for (int i = 1; i < frame_nwords - 1; i++) {
if (i == 0) {
for (int j = 0; j < word_nbytes; j++) {
snprintf(buffer, sizeof(buffer), "%02X", frame[j]);
str += buffer;
}
} else {
if (drdy_status & 1) {
str += std::format(" CH{}:", i - 1);
for (int j = 0; j < word_nbytes; j++) {
snprintf(buffer, sizeof(buffer), "%02X", frame[i * word_nbytes + j]);
str += buffer;
}
}
drdy_status = drdy_status >> 1;
}
}
return str;
}
std::string ADS131M08Hub::rwreg_command_frame_to_string(const spiframe &frame) {
char buffer[100];
std::string str;
str.reserve(512);
uint16_t cmdadr = get_unsigned_frame_word(frame, 0, true);
uint16_t address = (cmdadr & MASK_CMD_RW_REG_ADDRESS) >> 7;
int nregs = 1 + (cmdadr & MASK_CMD_RW_REG_COUNT);
str = command_to_string(cmdadr);
uint16_t command = (cmdadr & MASK_CMD_RW_REG) ? (cmdadr & MASK_CMD_RW_REG) : cmdadr;
if (command == CMD_RREG) {
str += " : regs ";
for (int i = 0; i < nregs; i++) {
str += reg_addr_to_string(address + i);
str += (i < (nregs - 1) ? ", " : "");
}
}
if (command == CMD_WREG) {
str += ", data:";
for (int i = 0; i < nregs; i++) {
uint16_t data = get_unsigned_frame_word(frame, i + 1, true);
snprintf(buffer, sizeof(buffer), " 0x%04X", data);
str += buffer;
}
for (int i = 0; i < nregs; i++) {
uint16_t data = get_unsigned_frame_word(frame, i + 1, true);
str += "\n ";
str += reg_data_to_string(address + i, data);
str += (i < (nregs - 1) ? ", " : "");
}
}
return str;
}
std::string ADS131M08Hub::reg_addr_to_string(int address)
{
auto str = reg_data_to_string(address, 0, true);
str.erase(str.find_last_not_of(white_space) + 1);
return str;
}
std::string ADS131M08Hub::reg_data_to_string(int address, uint16_t data, bool nameonly) {
std::string str;
char buffer[100];
str.reserve(120);
switch (address) {
case REG_ID:
str += std::format("{:<10}", "ID");
if (nameonly)
break;
snprintf(buffer, sizeof(buffer), ": %d ", (data >> 8) & 0x0F);
str += buffer;
break;
case REG_STATUS:
str += std::format("{:<10}", "STATUS");
if (nameonly)
break;
str += ":";
str += status_to_string(data);
break;
case REG_MODE:
str += std::format("{:<10}", "MODE");
if (nameonly)
break;
str += ":";
if ((data & MASK_MODE_REG_CRC_EN) != 0)
str += " REGMAP_CRC_EN /";
if ((data & MASK_MODE_RX_CRC_EN) != 0)
str += " SPI_IN_CRC_EN /";
if ((data & MASK_MODE_TIMEOUT) != 0)
str += " SPI_TIMEOUT_EN /";
str += " CRC=";
str += ((data & MASK_MODE_CRC_TYPE) != 0) ? "ANSI /" : "CCITT /";
if ((data & MASK_MODE_RESET) != 0)
str += " RESET /";
str += " WL=";
if ((data & MASK_MODE_WLENGTH) == WLENGTH_16_BITS)
str += "16";
if ((data & MASK_MODE_WLENGTH) == WLENGTH_24_BITS)
str += "24";
if ((data & MASK_MODE_WLENGTH) == WLENGTH_32_BITS_LSB_ZERO_PADDING)
str += "32zp";
if ((data & MASK_MODE_WLENGTH) == WLENGTH_32_BITS_MSB_SIGN_EXTEND)
str += "32se";
str += " / DRDY SEL=";
if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_MOST_LAGGING)
str += "MOST_LAGGING";
if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_LOGICAL_OR)
str += "OR_OF_CHANLS";
if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_MOST_LEADING_CHAN)
str += "MOST_LEADING";
if ((data & MASK_MODE_DRDY_SEL) == DRDY_SEL_MOST_LEADING_CHAN2)
str += "MOST_LEADING";
str += " / DRDY IDLE=";
str += ((data & MASK_MODE_DRDY_HiZ) != 0) ? "Hi_IMP" : "LOGIC_HI";
str += " / DRDY ACTV=";
str += ((data & MASK_MODE_DRDY_FMT) != 0) ? "LOW_FIX_DUR" : "LOGIC_LOW";
break;
case REG_CLOCK:
str += std::format("{:<10}", "CLOCK");
if (nameonly)
break;
str += ": CH ENABLED=";
str += ((data & MASK_CLOCK_CH7) != 0) ? "7" : "_";
str += ((data & MASK_CLOCK_CH6) != 0) ? "6" : "_";
str += ((data & MASK_CLOCK_CH5) != 0) ? "5" : "_";
str += ((data & MASK_CLOCK_CH4) != 0) ? "4" : "_";
str += ((data & MASK_CLOCK_CH3) != 0) ? "3" : "_";
str += ((data & MASK_CLOCK_CH2) != 0) ? "2" : "_";
str += ((data & MASK_CLOCK_CH1) != 0) ? "1" : "_";
str += ((data & MASK_CLOCK_CH0) != 0) ? "0" : "_";
str += " / XTAL OSC=";
str += ((data & MASK_CLOCK_XTAL_DIS) != 0) ? "DIS" : "EN";
str += " / EXT VREF=";
str += ((data & MASK_CLOCK_EXTREF_EN) != 0) ? "EN" : "DIS";
{
uint16_t osr = 1 << (7 + ((data & MASK_CLOCK_OSR) >> 2));
if (osr == 16384) {
osr = 16256;
}
snprintf(buffer, sizeof(buffer), " / OSR=%d", osr);
str += buffer;
}
str += " / POWER=";
if ((data & MASK_CLOCK_PWR) == PM_VERY_LOW_POWER)
str += "VERY_LOW";
if ((data & MASK_CLOCK_PWR) == PM_LOW_POWER)
str += "VERY_LOW";
if ((data & MASK_CLOCK_PWR) == PM_HIGH_RESOLUTION)
str += "HIGH_RES";
if ((data & MASK_CLOCK_PWR) == PM_HIGH_RESOLUTION2)
str += "HIGH_RES";
break;
case REG_GAIN1: {
str += std::format("{:<10}", "GAIN1");
if (nameonly)
break;
auto gain = 1 << ((data & MASK_GAIN_PGAGAIN3) >> 24);
snprintf(buffer, sizeof(buffer), ": Ch3=%d /", gain);
str += buffer;
gain = 1 << ((data & MASK_GAIN_PGAGAIN2) >> 16);
snprintf(buffer, sizeof(buffer), " Ch2=%d /", gain);
str += buffer;
gain = 1 << ((data & MASK_GAIN_PGAGAIN1) >> 8);
snprintf(buffer, sizeof(buffer), " Ch1=%d /", gain);
str += buffer;
gain = 1 << (data & MASK_GAIN_PGAGAIN0);
snprintf(buffer, sizeof(buffer), " Ch0=%d", gain);
str += buffer;
break;
}
case REG_GAIN2: {
str += std::format("{:<10}", "GAIN2");
if (nameonly)
break;
auto gain = 1 << ((data & MASK_GAIN_PGAGAIN7) >> 24);
snprintf(buffer, sizeof(buffer), ": Ch7=%d /", gain);
str += buffer;
gain = 1 << ((data & MASK_GAIN_PGAGAIN6) >> 16);
snprintf(buffer, sizeof(buffer), " Ch6=%d /", gain);
str += buffer;
gain = 1 << ((data & MASK_GAIN_PGAGAIN5) >> 8);
snprintf(buffer, sizeof(buffer), " Ch5=%d /", gain);
str += buffer;
gain = 1 << (data & MASK_GAIN_PGAGAIN4);
snprintf(buffer, sizeof(buffer), " Ch4=%d", gain);
str += buffer;
break;
}
case REG_CFG: {
str += std::format("{:<10}", "CFG");
if (nameonly)
break;
str += ": GC_EN=";
str += ((data & MASK_CFG_GC_EN) != 0) ? "Y /" : "N /";
auto gc_dly = 1 << (1 + ((data & MASK_CFG_GC_DLY) >> 9));
snprintf(buffer, sizeof(buffer), " GC DELAY= %d", gc_dly);
str += buffer;
str += " / CUR DET= [";
str += ((data & MASK_CFG_CD_EN) != 0) ? "Y] " : "N] ";
str += ((data & MASK_CFG_CD_ALLCH) != 0) ? "ALL_CH" : "ANY_CH";
auto cd_num = 1 << ((data & MASK_CFG_CD_NUM) >> 4);
snprintf(buffer, sizeof(buffer), " / CD_NUM= %d", cd_num);
str += buffer;
auto cd_len = (data & MASK_CFG_CD_LEN) >> 1;
switch (cd_len) {
case 0:
cd_len = 128;
break;
case 1:
cd_len = 256;
break;
case 2:
cd_len = 512;
break;
case 3:
cd_len = 768;
break;
case 4:
cd_len = 1280;
break;
case 5:
cd_len = 1792;
break;
case 6:
cd_len = 2560;
break;
case 7:
cd_len = 3584;
break;
default:
cd_len = 0; // illegal case
break;
}
snprintf(buffer, sizeof(buffer), " / CD_LEN= %d", cd_len);
str += buffer;
break;
}
case REG_THRSHLD_MSB:
str += std::format("{:<10}", "TH_MSB");
if (nameonly)
break;
snprintf(buffer, sizeof(buffer), ": 0x%04X", data);
str += buffer;
break;
case REG_THRSHLD_LSB:
str += std::format("{:<10}", "TH_LSB");
if (nameonly)
break;
snprintf(buffer, sizeof(buffer), ": 0x%04X / ", (data & MASK_THRSHLD_LSB_CD_TH_LSB) >> 8);
str += buffer;
snprintf(buffer, sizeof(buffer), " DCBLOCK: 0x%04X", data & MASK_THRSHLD_LSB_DCBLOCK);
str += buffer;
break;
case REGMAP_CRC:
str += std::format("{:<10}", std::format("REGMAP_CRC[{:#02}]", address));
if (nameonly)
break;
snprintf(buffer, sizeof(buffer), " value: 0x%04X", data);
str += buffer;
default:
if (address >= REG_CH0_CFG && address <= REG_CH7_GCAL_LSB) {
str += reg_config_to_string(address, data, nameonly);
break;
}
}
return str;
}
std::string ADS131M08Hub::reg_config_to_string(int address, uint16_t data, bool nameonly) {
std::string str;
str.reserve(256);
int ch_num = (address - REG_CH0_CFG) / 5;
int gen_addr = (address - REG_CH0_CFG) % 5;
std::string ch_str = std::format("CH{}_", ch_num);
switch (gen_addr) {
case REG_CHX_CFG:
str += std::format("{:<10}", std::format("{}CFG", ch_str));
if (nameonly)
break;
str += std::format(": PH_DEL={0:}", (data & MASK_CHX_CFG_PHASE) >> 6);
str += " DCBLOCK_EN=";
str += ((data & MASK_CHX_CFG_DCBLKX_DIS) != 0) ? "N /" : "Y /";
str += std::format("{}INPUT=", ch_str);
if ((data & MASK_CHX_CFG_MUX) == ICM_AIN0P_AIN0N)
str += "AIN0P and AIN0N";
if ((data & MASK_CHX_CFG_MUX) == ICM_INPUT_SHORTED)
str += "ADC inputs shorted";
if ((data & MASK_CHX_CFG_MUX) == ICM_POSITIVE_DC_TEST_SIGNAL)
str += "Positive DC test signal";
if ((data & MASK_CHX_CFG_MUX) == ICM_NEGATIVE_DC_TEST_SIGNAL)
str += "Negative DC test signal";
break;
case REG_CHX_OCAL_MSB:
str += std::format("{:<13}", std::format("{}OCAL_MSB", ch_str));
if (nameonly)
break;
str += std::format(": {:#04x}", data);
break;
case REG_CHX_OCAL_LSB:
str += std::format("{:<13}", std::format("{}OCAL_LSB", ch_str));
if (nameonly)
break;
str += std::format(": {:#04x}", (data & MASK_CHX_OCAL_LSB) >> 8);
break;
case REG_CHX_GCAL_MSB:
str += std::format("{:<13}", std::format("{}GCAL_MSB", ch_str));
if (nameonly)
break;
str += std::format(": {:#04x}", data);
break;
case REG_CHX_GCAL_LSB:
str += std::format("{:<13}", std::format("{}GCAL_LSB", ch_str));
if (nameonly)
break;
str += std::format(": {:#04x}", (data & MASK_CHX_GCAL_LSB) >> 8);
break;
}
return str;
}
std::string ADS131M08Hub::command_to_string(uint16_t cmdadr) {
std::string str;
uint16_t reg_addr, nregs;
char buffer[40];
const std::string::size_type new_cap(256);
str.reserve(new_cap);
snprintf(buffer, sizeof(buffer), "0x%04X", cmdadr);
uint16_t cmd_sw = (cmdadr & MASK_CMD_RW_REG) ? (cmdadr & MASK_CMD_RW_REG) : cmdadr;
switch (cmd_sw) {
case CMD_NULL:
str = "NULL [";
str += buffer;
str += "]";
break;
case CMD_RESET:
str = "RESET [";
str += buffer;
str += "]";
break;
case CMD_STANDBY:
str = "STANDBY [";
str += buffer;
str += "]";
break;
case CMD_WAKEUP:
str = "WAKEUP [";
str += buffer;
str += "]";
break;
case CMD_LOCK:
str = "LOCK [";
str += buffer;
str += "]";
break;
case CMD_UNLOCK:
str = "UNLOCK [";
str += buffer;
str += "]";
break;
case CMD_RREG:
str = "RREG [";
str += buffer;
str += "]: ";
nregs = cmdadr & MASK_CMD_RW_REG_COUNT;
nregs++;
reg_addr = (cmdadr & MASK_CMD_RW_REG_ADDRESS) >> 7;
snprintf(buffer, sizeof(buffer), "#regs:%d, start_addr:%d ", nregs, reg_addr);
str += buffer;
break;
case CMD_WREG:
str = "WREG [";
str += buffer;
str += "]: ";
nregs = cmdadr & MASK_CMD_RW_REG_COUNT;
nregs++;
reg_addr = (cmdadr & MASK_CMD_RW_REG_ADDRESS) >> 7;
snprintf(buffer, sizeof(buffer), "#regs:%d, start_addr:%d ", nregs, reg_addr);
str += buffer;
break;
default:
str = "UNKNOWN command [";
str += buffer;
str += "]";
break;
}
return str;
}
// we do not return strings from this function as its length could exceed maximum length that log print can handle
void ADS131M08Hub::print_command_response_to_string(uint16_t cmdadr_sent, const spiframe &frame) {
std::string str;
uint16_t nregs, start_addr, respcmd;
char buffer[40];
str.reserve(256);
uint16_t response = frame[0] << 8 | frame[1];
snprintf(buffer, sizeof(buffer), " [0x%04X]: ", response);
std::string resp_txt(buffer);
uint16_t command = (cmdadr_sent & MASK_CMD_RW_REG) ? (cmdadr_sent & MASK_CMD_RW_REG) : cmdadr_sent;
switch (command) {
case CMD_NULL:
str = "STATUS" + resp_txt;
str += status_to_string(response);
ESP_LOGD(TAG, "%s", str.c_str());
break;
case CMD_RESET:
str = "RESET RESPONSE" + resp_txt;
switch (response) {
case RSP_RESET_OK:
str += "RESET SUCCESS";
break;
case RSP_RESET_NOK:
str += "RESET NOT COMPLETE";
break;
default:
str += "UNKNOWN RESET RESPONSE";
break;
}
ESP_LOGD(TAG, "%s", str.c_str());
break;
case CMD_STANDBY:
str = "STANDBY RESPONSE" + resp_txt;
if (response == CMD_STANDBY) {
str += "IN STANDBY";
} else {
str += "UNKNOWN";
}
ESP_LOGD(TAG, "%s", str.c_str());
break;
case CMD_WAKEUP:
str = "WAKEUP RESPONSE" + resp_txt;
if (response == CMD_WAKEUP) {
str += "EXITED STANDBY";
} else {
str += "UNKNOWN";
}
ESP_LOGD(TAG, "%s", str.c_str());
break;
case CMD_LOCK:
str = "LOCK RESPONSE " + resp_txt;
if (response == CMD_LOCK) {
str += "LOCKED";
} else {
str += "UNKNOWN";
}
ESP_LOGD(TAG, "%s", str.c_str());
break;
case CMD_UNLOCK:
str = "UNLOCK RESPONSE" + resp_txt;
if (response == CMD_UNLOCK) {
str += "UNLOCKED";
} else {
str += "UNKNOWN";
}
ESP_LOGD(TAG, "%s", str.c_str());
break;
case CMD_RREG: {
int offset = 0;
str = "RREG RESPONSE" + resp_txt;
nregs = (cmdadr_sent & MASK_CMD_RW_REG_COUNT) + 1;
uint16_t rreg_ack = cmdadr_sent; // not really and ack, but since we don't get an ack when reading 1 register we
// pretend this is the ack
if (nregs > 1) {
offset = 1;
rreg_ack = get_unsigned_frame_word(frame, 0, true);
;
nregs = (rreg_ack & MASK_CMD_RW_REG_COUNT) +
1; // should not change here, but just in case adc decided to sent different number of registers
}
start_addr = (rreg_ack & MASK_CMD_RW_REG_ADDRESS) >> 7;
snprintf(buffer, sizeof(buffer), "#regs:%d, start_addr:%d ", nregs, start_addr);
ESP_LOGVV(TAG, "%s", buffer);
if (nregs == 1) {
start_addr = (cmdadr_sent & MASK_CMD_RW_REG_ADDRESS) >> 7;
str = reg_data_to_string(start_addr, response);
ESP_LOGVV(TAG, "%s", str.c_str());
} else {
respcmd = response & MASK_CMD_RW_REG_RESP;
if (respcmd == RREG_RESP) {
for (int i = 0; i < nregs; i++) {
uint16_t rxdata = get_unsigned_frame_word(frame, i + offset, true);
str = reg_data_to_string(start_addr + i, rxdata);
ESP_LOGVV(TAG, "%s", str.c_str());
}
} else {
str = "WARNING: invalid response to RREG. Should be Exxxx or Fxxxx";
ESP_LOGW(TAG, "%s", str.c_str());
}
}
} break;
case CMD_WREG:
str = "WREG RESPONSE" + resp_txt;
respcmd = response & MASK_CMD_RW_REG_RESP;
if (respcmd == WREG_RESP) {
nregs = response & MASK_CMD_RW_REG_COUNT;
start_addr = (response & MASK_CMD_RW_REG_ADDRESS) >> 7;
snprintf(buffer, sizeof(buffer), "#regs:%d, start-addr:%d ", nregs + 1, start_addr);
str += buffer;
ESP_LOGVV(TAG, "%s", str.c_str());
} else {
str += "WARNING: invalid response to WREG. Should be 4xxxx or 5xxxx";
ESP_LOGW(TAG, "%s", str.c_str());
}
break;
default:
str = "Response to UNKNOWN command [";
str += buffer;
str += "]";
ESP_LOGW(TAG, "%s", str.c_str());
break;
}
}
std::string ADS131M08Hub::status_to_string(uint16_t response) {
std::string str;
str.reserve(90);
if ((response & MASK_STATUS_LOCK) != 0)
str += " LOCK /";
if ((response & MASK_STATUS_RESYNC) != 0)
str += " RESYNC /";
if ((response & MASK_STATUS_REGMAP) != 0)
str += " REGMAP /";
if ((response & MASK_STATUS_CRC_ERR) != 0)
str += " CRC_ERR /";
str += " CRC=";
str += ((response & MASK_STATUS_CRC_TYPE) != 0) ? "ANSI /" : "CCITT /";
if ((response & MASK_STATUS_RESET) != 0)
str += " RESET /";
str += " WL=";
if ((response & MASK_STATUS_WLENGTH) == WLENGTH_16_BITS)
str += "16";
if ((response & MASK_STATUS_WLENGTH) == WLENGTH_24_BITS)
str += "24";
if ((response & MASK_STATUS_WLENGTH) == WLENGTH_32_BITS_LSB_ZERO_PADDING)
str += "32zp";
if ((response & MASK_STATUS_WLENGTH) == WLENGTH_32_BITS_MSB_SIGN_EXTEND)
str += "32se";
str += " / DRDY=";
str += ((response & MASK_STATUS_DRDY7) != 0) ? "7" : "_";
str += ((response & MASK_STATUS_DRDY6) != 0) ? "6" : "_";
str += ((response & MASK_STATUS_DRDY5) != 0) ? "5" : "_";
str += ((response & MASK_STATUS_DRDY4) != 0) ? "4" : "_";
str += ((response & MASK_STATUS_DRDY3) != 0) ? "3" : "_";
str += ((response & MASK_STATUS_DRDY2) != 0) ? "2" : "_";
str += ((response & MASK_STATUS_DRDY1) != 0) ? "1" : "_";
str += ((response & MASK_STATUS_DRDY0) != 0) ? "0" : "_";
return str;
}
// end of for debug only section
} // namespace ads131m08
} // namespace esphome