1778 lines
62 KiB
C++
1778 lines
62 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
|
|
|
|
//TODO: RREG might be longer than allocated frame size. work out max length and update following accordingly
|
|
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() {
|
|
int word_nbytes = this->adc_word_length_ >> 3;
|
|
spiframe frame(2 * word_nbytes, 0); // prepare with CMD_NULL; always cater for crc
|
|
write_array(frame); // write CMD_NULL to ADC to retrieve status
|
|
read_array(frame);
|
|
uint16_t status = frame[0] << 8 | 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) {
|
|
uint32_t num_samples = 0;
|
|
uint32_t crc_errors = 0;
|
|
uint32_t iterations = 0;
|
|
uint32_t not_ready = 0;
|
|
{
|
|
CHIP_SELECTx
|
|
// Perform the read here safely outside of ISR
|
|
uint64_t start = micros();
|
|
// this->txf_init(); // implementing datasheet recommended TXF init in ISR
|
|
this->adc_sync();
|
|
if (rms_calc_req_) {
|
|
uint16_str result = read_multi();
|
|
num_samples = result[0];
|
|
crc_errors = result[1];
|
|
iterations = result[2];
|
|
not_ready = result[3];
|
|
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_ac[SAMPLE_TIME_SENSOR] != nullptr)
|
|
// this->sensors_ac[SAMPLE_TIME_SENSOR]->publish_state(static_cast<float>(end - start)/1000.0);
|
|
// if(this->sensors_ac[MAX_SAMPLES_SENSOR] != nullptr)
|
|
// this->sensors_ac[MAX_SAMPLES_SENSOR]->publish_state(static_cast<float>(num_samples));
|
|
// if(this->sensors_ac[CRC_ERRORS_SENSOR] != nullptr)
|
|
// this->sensors_ac[CRC_ERRORS_SENSOR]->publish_state(static_cast<float>(crc_errors));
|
|
// if(this->sensors_ac[ISR_COUNT_SENSOR] != nullptr) {
|
|
// uint16_t ctr = isr_ctr_;
|
|
// this->sensors_ac[ISR_COUNT_SENSOR]->publish_state(static_cast<float>(ctr));
|
|
// }
|
|
} 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
|
|
if (firstRead) {
|
|
// Clear the ADC's 2-deep FIFO on the first read
|
|
for (int i = 0; i < numFrameWords * adc_word_length_; i++) {
|
|
this->write_byte(0);
|
|
}
|
|
for (int i = 0; i < numFrameWords; i++) {
|
|
this->read_byte(); // should perhaps read dwords instead of bytes, but we just want to clear the FIFO so it
|
|
// doesn't matter much?
|
|
}
|
|
firstRead = false; // Clear the flag
|
|
// we will do this later, after sorting out basic communication
|
|
// DMA.enable(); // Let the DMA start sending ADC data to memory
|
|
}
|
|
// Send the dummy data to the ADC to get the ADC data
|
|
for (int i = 0; i < numFrameWords * adc_word_length_; i++) {
|
|
this->write_byte(0);
|
|
}
|
|
}
|
|
|
|
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();
|
|
size_t word_nbytes = adc_word_length_ >> 3;
|
|
int framelength = numFrameWords * word_nbytes;
|
|
spiframe frame(framelength, 0);
|
|
size_t frame_len;
|
|
// curly braces confine chip select scope
|
|
{
|
|
CHIP_SELECTx cmd = CMD_RESET;
|
|
set_frame_word(frame, 0, cmd);
|
|
frame.resize(2 * word_nbytes);
|
|
frame_len = add_crc(frame);
|
|
frame.resize(framelength);
|
|
index += frame_len;
|
|
for (; index < framelength; index++) {
|
|
frame[index] = 0;
|
|
}
|
|
write_array(frame);
|
|
ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str());
|
|
}
|
|
delay_microseconds_safe(T_REGACQ);
|
|
update_adc_word_length(WLENGTH_24_BITS); // should be 24 after reset
|
|
word_nbytes = adc_word_length_ >> 3;
|
|
frame.resize(numFrameWords * word_nbytes);
|
|
{
|
|
CHIP_SELECTx read_array(frame);
|
|
reset_response = get_unsigned_frame_word(frame, 0, true);
|
|
bool crc_ok = check_crc(frame);
|
|
ESP_LOGVV(TAG, "Read Frame: %s CRC: %s", frame_to_string(frame).c_str(), (crc_ok ? "OK" : "FAIL"));
|
|
}
|
|
bool result = reset_response == RSP_RESET_OK;
|
|
ESP_LOGVV(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::write_byte(uint8_t byte) {
|
|
CHIP_SELECT
|
|
this->transfer_byte(byte);
|
|
}
|
|
|
|
uint8_t ADS131M08Hub::read_byte() {
|
|
CHIP_SELECT
|
|
uint8_t result = this->transfer_byte(0x00); // Send dummy byte to read data
|
|
return result;
|
|
}
|
|
|
|
void ADS131M08Hub::read_array(spiframe &frame) {
|
|
CHIP_SELECT
|
|
this->transfer_array(frame.data(), frame.size());
|
|
}
|
|
|
|
void ADS131M08Hub::write_array(const spiframe &frame) {
|
|
CHIP_SELECT
|
|
for (size_t i = 0; i < frame.size(); i++) {
|
|
this->transfer_byte(frame[i]);
|
|
}
|
|
}
|
|
|
|
//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() {
|
|
int word_nbytes = adc_word_length_ >> 3;
|
|
int frame_length = numFrameWords * word_nbytes;
|
|
spiframe frame(frame_length, 0);
|
|
this->read_array(frame);
|
|
bool crc_ok = check_crc(frame);
|
|
uint16_t drdy_status = get_unsigned_frame_word(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;
|
|
bool ac_sensor_exist = this->sensors_ac[i] != nullptr;
|
|
bool dc_sensor_exist = this->sensors_dc[i] != nullptr;
|
|
if (channel_ready && (ac_sensor_exist || dc_sensor_exist)) {
|
|
int32_t raw = get_sign_ext_frame_word(frame, i + 1);
|
|
float value = this->conversion_factor_ * raw;
|
|
this->sampled_values_[i] = value;
|
|
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
|
|
uint16_str ADS131M08Hub::read_multi() {
|
|
int32_t raw;
|
|
float value;
|
|
uint16_str result(4, 0);
|
|
bool crc_ok, data_ready, ac_sensor_exist, dc_sensor_exist;
|
|
uint16_t drdy_status, status;
|
|
int i, iteration = 0; // going above 255 will result in overflows
|
|
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;
|
|
std::fill(rx_frame.begin(), rx_frame.end(), 0);
|
|
this->read_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;
|
|
ac_sensor_exist = this->sensors_ac[i] != nullptr;
|
|
dc_sensor_exist = this->sensors_dc[i] != nullptr;
|
|
if (data_ready && (ac_sensor_exist || dc_sensor_exist)) {
|
|
raw = get_sign_ext_frame_word(rx_frame, i + 1);
|
|
value = this->conversion_factor_ * raw;
|
|
this->sampled_values_[i] = value;
|
|
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);
|
|
}
|
|
this->sps_ = (1e6 * (float) num_samples) / elapsed_time;
|
|
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;
|
|
}
|
|
}
|
|
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) {
|
|
int word_nbytes = adc_word_length_ >> 3;
|
|
spiframe frame(word_nbytes * 2, 0);
|
|
uint16_t command = CMD_UNLOCK;
|
|
if (enable) {
|
|
command = CMD_LOCK;
|
|
}
|
|
set_frame_word(frame, 0, command);
|
|
add_crc(frame);
|
|
write_array(frame);
|
|
delay_microseconds_safe(T_REGACQ);
|
|
read_array(frame);
|
|
auto ack = get_unsigned_frame_word(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 word_nbytes = adc_word_length_ >> 3;
|
|
int wreg_framelength = word_nbytes * (2 + nregs); // ensure room for crc
|
|
int rreg_resp_framelength = word_nbytes * (2 + (nregs == 1 ? -1 : nregs)); // add room for CRC if nregs > 1
|
|
//spiframe frame(wreg_framelength, 0);
|
|
//spiframe rxframe(wreg_framelength, 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));
|
|
tx_frame.resize(wreg_framelength);
|
|
rx_frame.resize(wreg_framelength);
|
|
{
|
|
CHIP_SELECT set_frame_word(tx_frame, 0, wreg_addr_opcode);
|
|
for (int i = 0; i < nregs; i++) {
|
|
set_frame_word(tx_frame, i + 1, data[i]);
|
|
}
|
|
add_crc(tx_frame);
|
|
//ESP_LOGD(TAG, "%s\n", rwreg_command_frame_to_string(tx_frame).c_str());
|
|
//ESP_LOGD(TAG, "Send Frame: %s", frame_to_string(tx_frame).c_str());
|
|
transfer_array(tx_frame.data(), tx_frame.size()); // 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 > 31) {
|
|
// We are limited to 31 registers, otherwise we will ran into DMA / SPI buffer problems
|
|
ESP_LOGE(TAG, "Trying to read %d registers. This exceeds maximum register read count of 31!", nregs);
|
|
return result; // invalid
|
|
}
|
|
int word_nbytes = adc_word_length_ >> 3;
|
|
int request_framelength = 2 * word_nbytes; // ensure room for crc
|
|
int response_framelength = word_nbytes * (2 + (nregs == 1 ? -1 : nregs)); // add room for CRC if nregs > 1
|
|
spiframe frame(response_framelength, 0);
|
|
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
|
|
frame.resize(request_framelength);
|
|
set_frame_word(frame, 0, rreg_addr_opcode);
|
|
add_crc(frame);
|
|
write_array(frame); // send RREG
|
|
}
|
|
ESP_LOGVV(TAG, "%s\n", rwreg_command_frame_to_string(frame).c_str());
|
|
ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str());
|
|
delay_microseconds_safe(T_REGACQ);
|
|
{
|
|
CHIP_SELECT
|
|
frame.resize(response_framelength);
|
|
read_array(frame);
|
|
}
|
|
ESP_LOGVV(TAG, "Recv Frame: %s", frame_to_string(frame).c_str());
|
|
if (nregs > 1) {
|
|
bool crc_ok = check_crc(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(frame, 0, true);
|
|
}
|
|
// uint16_t rreg_ack = (nregs == 1) ? rreg_addr_opcode : get_unsigned_frame_word(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(frame, i + offset, true);
|
|
}
|
|
print_command_response_to_string(rreg_addr_opcode, 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
|