#include "ads131m08.h" //#include //#include //#include "esphome/components/spi/spi.h" #include "esphome/core/log.h" #include #include #include #include "esp_system.h" #include "spi_flash_mmap.h" //using namespace esphome::spi; namespace esphome { namespace ads131m08 { static const char *const TAG = "ads131m08"; 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(); 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(32)) { 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 } 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"); } void ADS131M08Hub::set_channel_gain(uint8_t channel, ADS131M08_PGA_GAIN gain) { if(channel < ADC_CHANNELS) { set_channel_pga(channel, gain); } } 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(); switch(status & MASK_STATUS_WLENGTH) { case WLENGTH_16_BITS: this->adc_word_length_ = 16; this->conversion_factor_ = this->reference_voltage_/32768.0; // TODO: must double check this with a test break; case WLENGTH_24_BITS: this->adc_word_length_ = 24; this->conversion_factor_ = this->reference_voltage_/8388608.0; break; default: this->adc_word_length_ = 32; this->conversion_factor_ = this->reference_voltage_/8388608.0; break; } return this->adc_word_length_; } 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; } //update_adc_word_length(); 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; } //ESP_LOGV(TAG, "Turned on all ADC channels; Re-wrote defaults for other bits in CLOCK register"); 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->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; { 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_) { std::pair result = read_multi(); num_samples = result.first; crc_errors = result.second; uint64_t end = micros(); if(this->sensors_ac[SAMPLE_TIME_SENSOR] != nullptr) this->sensors_ac[SAMPLE_TIME_SENSOR]->publish_state(static_cast(end - start)/1000.0); if(this->sensors_ac[MAX_SAMPLES_SENSOR] != nullptr) this->sensors_ac[MAX_SAMPLES_SENSOR]->publish_state(static_cast(num_samples)); if(this->sensors_ac[CRC_ERRORS_SENSOR] != nullptr) this->sensors_ac[CRC_ERRORS_SENSOR]->publish_state(static_cast(crc_errors)); uint16_t ctr = isr_ctr_; ESP_LOGI(TAG, "WL: %u isr_ctr_: %u", this->adc_word_length_, ctr); } else { read_single(); } isr_ctr_ = 0; } if (crc_errors > 30) { ESP_LOGW(TAG, "High CRC error rate."); adc_set_word_length(this->adc_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; //ESP_LOGD(TAG, "b_index: %d, word_nbytes: %d, frame words: %d, w_index: %d, frame size: %d", b_index, word_nbytes, frame_words, w_index, frame.size()); 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 } //int oldsize = frame.size(); frame.resize(b_index + word_nbytes); //ESP_LOGD(TAG, "Resized frame from %d, to %d.", oldsize, frame.size()); } 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)) { //ESP_LOGE(TAG, "Word index of %d out of bounds. b_index: %d, frame_size: %d, Caller: %s Line: %d", w_index, b_index, frame.size(), caller.c_str(), line); //esp_crosscore_int_send_print_backtrace(xPortGetCoreID()); 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 } uint32_t raw = 0; switch(adc_word_length_) { case 32: raw = (frame[b_index] << 24) | (frame[b_index + 1] << 16) | (frame[b_index + 2]) << 8 | frame[b_index + 3]; result = static_cast(raw); break; case 24: raw = (frame[b_index] << 16) | (frame[b_index + 1] << 8) | frame[b_index + 2]; if (raw & 0x800000) result = static_cast(raw | 0xFF000000); // Sign extend else result = static_cast(raw); break; case 16: raw = ((frame[b_index] << 8) | frame[b_index + 1]); result = static_cast(raw); break; default: ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); break; } return result; } //{ // // Read STATUS register to check if data is ready for the specified channel // // STATUS register is the first word in the SPI frame, so we can read it directly // uint16_t status; // this->enable(); // this->transfer_byte(0x00); // Send dummy byte to read STATUS register // status = this->transfer_byte(0x00); // Read upper byte of STATUS // status <<= 8; // status |= this->transfer_byte(0x00); // Read lower byte of STATUS // this->disable(); // // Check the corresponding bit in the STATUS register for the specified channel // // Assuming each channel corresponds to a specific bit in the STATUS register, e.g. bit 0 for channel 0, bit 1 for channel 1, etc. // return (status & (1 << channel)) != 0; // Return true if data is ready for the specified channel //} void ADS131M08Hub::adc_hard_reset() { float one_cycle = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds uint32_t reset_pulse_duration = static_cast(1.05 * 2048 * one_cycle); // 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 one_cycle = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds uint32_t sync_pulse_duration = static_cast(std::round(50 * one_cycle)); // 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++; } return reset_ok; } //int ADS131M08Hub::build_frame() bool ADS131M08Hub::adc_soft_reset() { int index = 0; uint16_t reset_response; uint16_t cmd = 0; // we assume 24 bit wordlength if adc_word_length_is illegal //if(adc_word_length_ != 32 && adc_word_length_ != 24 && adc_word_length_ != 16) { // adc_word_length_ = 24; //} 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); index += 2; for(; index < framelength; index++) { frame[index] = 0; } frame_len = add_crc(frame, crc_pos::crc_after_firstword); write_array(frame); ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str()); } delay_microseconds_safe(T_REGACQ); { CHIP_SELECTx read_array(frame); reset_response = get_unsigned_frame_word(frame, 0, true); ESP_LOGVV(TAG, "Read Frame: %s", frame_to_string(frame).c_str()); } ESP_LOGVV(TAG, "reset_response: 0x%04X", reset_response); if (reset_response == RSP_RESET_OK) { return true; } return false; } // overwrites last (or second) frameword with CRC, therefore frame should be large enough for payload and crc size_t ADS131M08Hub::add_crc(spiframe& frame, crc_pos crcpos) { size_t effective_frame_length; size_t frame_length = frame.size(); if (frame_length == 0 || adc_word_length_ == 0) return 0; size_t word_nbytes = adc_word_length_ >> 3; if (crcpos == crc_pos::crc_after_firstword) { effective_frame_length = word_nbytes * 2; } else { effective_frame_length = frame_length; } if (effective_frame_length > frame_length) { if (frame.capacity() >= effective_frame_length) { frame.resize(effective_frame_length); if (frame.size() < effective_frame_length) { return 0; } frame_length = effective_frame_length; } else { return 0; } } size_t frame_words = effective_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 for (size_t i = 0; i < frame.size(); i++) { frame[i] = this->transfer_byte(0x00); // Send dummy byte to read data } } 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]); } } // ********************** end of from datasheet ************************ void ADS131M08Hub::set_measure_rms(uint8_t channel, bool enable) { if(channel < ADC_CHANNELS) { ESP_LOGW(TAG, "set_measure_rms[%d] = %s", channel, enable ? "enabled" : "disabled"); this->rms_enabled_[channel] = enable; this->rms_calc_req_ = 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); this->sampled_values_[i] = this->conversion_factor_ * raw; float value = this->conversion_factor_ * raw; if(ac_sensor_exist) this->sensors_ac[i]->publish_state(value); if(dc_sensor_exist) this->sensors_dc[i]->publish_state(value); } } } } std::pair ADS131M08Hub::read_multi() { int32_t raw; float value; std::pair result; result.first = 0; result.second = 0; bool crc_ok, data_ready, ac_sensor_exist, dc_sensor_exist; uint16_t drdy_status, status; int i, max_iters = 250; // 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); // 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_ && max_iters-- > 0) { this->read_array(frame); //ESP_LOGD(TAG, "max_iters: %d, frame: %s", max_iters, frame_to_string(frame).c_str()); crc_ok = check_crc(frame); if (crc_ok) { status = get_unsigned_frame_word(frame, 0, true); // skip frame immediately afte resync if ((status & MASK_STATUS_RESYNC) == 0) { drdy_status = status & MASK_STATUS_DRDY; // sample channels 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(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]) += static_cast(raw); (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); } } } } } else { result.second++; //ESP_LOGW(TAG, "max_iters: %d, frame: %s CRC error", max_iters, 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.first) { result.first = num_samples; } if (num_samples == 0) { // should never happen, but let's play safe and avoid dividing by zero if(ac_sensor_exist) this->sensors_ac[i]->publish_state(NAN); if(dc_sensor_exist) this->sensors_dc[i]->publish_state(NAN); } else { int64_t sample_sum = sample_sum_[i]; float rms_dc = this->conversion_factor_ * static_cast(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; if(ac_sensor_exist) this->sensors_ac[i]->publish_state(rms_ac); if(dc_sensor_exist) this->sensors_dc[i]->publish_state(rms_dc); } this->num_samples_[i] = 0; this->sample_sum_[i] = 0.0f; this->sample_squared_sum_[i] = 0.0f; } } 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, crc_after_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); } 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)); { CHIP_SELECT set_frame_word(frame, 0, wreg_addr_opcode); for(int i = 0; i < nregs; i++) { set_frame_word(frame, i + 1, data[i]); } add_crc(frame, crc_after_frame); ESP_LOGVV(TAG, "%s\n", rwreg_command_frame_to_string(frame).c_str()); transfer_array(frame, rxframe); // WREG } ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(frame).c_str()); 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 register failed: tx data= 0x%04X, rx data= 0x%04X", data[i] ,rxdata[i]); } } 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, crc_after_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) { //auto crc = get_crc(frame); //auto frame_crc = read_frame_crc(frame); //ESP_LOGW(TAG, "CRC calc: 0x%04X, CRC recv: 0x%04X", crc, frame_crc); 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 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);; } //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; } int8_t ADS131M08Hub::is_data_ready_soft(uint8_t channel) { switch (channel) { case 0: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY0); case 1: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY1); case 2: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY2); case 3: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY3); case 4: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY4); case 5: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY5); case 6: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY6); case 7: return (readRegister(REG_STATUS) & MASK_STATUS_DRDY7); default: return -1; // Invalid channel } } bool ADS131M08Hub::is_reset_status() { return (readRegister(REG_STATUS) & MASK_STATUS_RESET); } bool ADS131M08Hub::is_lock_spi() { return (readRegister(REG_STATUS) & MASK_STATUS_LOCK); } bool ADS131M08Hub::set_drdy_format(uint8_t drdy_format) { if (drdy_format > 1) { return false; } return adc_register_write_masked(REG_MODE, drdy_format, MASK_MODE_DRDY_FMT, __LINE__); } bool ADS131M08Hub::set_drdy_idle_state(uint8_t drdy_state) { if (drdy_state > 1) { return false; } return adc_register_write_masked(REG_MODE, drdy_state < 1, MASK_MODE_DRDY_HiZ, __LINE__); } bool ADS131M08Hub::set_power_mode(uint8_t power_mode) { if (power_mode > 3) { return false; } return adc_register_write_masked(REG_CLOCK, power_mode, MASK_CLOCK_PWR, __LINE__); } 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__); } //void ADS131M08Hub::set_full_scale(uint8_t channel, float scale) //{ // if (channel > 7) { // return; // } // // this->full_scale.ch[channel].f = scale; // //} // //float ADS131M08Hub::get_full_scale(uint8_t channel) //{ // if (channel > 7) { // return 0.0; // } // // return this->full_scale.ch[channel].f; // //} bool ADS131M08Hub::set_channel_enable(uint8_t channel, bool enable) { if (channel > 7) 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__); //return true; } bool ADS131M08Hub::set_channel_pga(uint8_t channel, ADS131M08_PGA_GAIN pga) { uint16_t pgaCode = (uint16_t) pga; switch (channel) { case 0: if(!adc_register_write_masked(REG_GAIN1, pgaCode, MASK_GAIN_PGAGAIN0, __LINE__)) return false; this->pgaGain[0] = pga; return true; case 1: if(!adc_register_write_masked(REG_GAIN1, pgaCode << 4, MASK_GAIN_PGAGAIN1, __LINE__)) return false; this->pgaGain[1] = pga; return true; case 2: if(!adc_register_write_masked(REG_GAIN1, pgaCode << 8, MASK_GAIN_PGAGAIN2, __LINE__)) return false; this->pgaGain[2] = pga; return true; case 3: if(!adc_register_write_masked(REG_GAIN1, pgaCode << 12, MASK_GAIN_PGAGAIN3, __LINE__)) return false; this->pgaGain[3] = pga; return true; case 4: if(!adc_register_write_masked(REG_GAIN2, pgaCode, MASK_GAIN_PGAGAIN4, __LINE__)) return false; this->pgaGain[4] = pga; return true; case 5: if(!adc_register_write_masked(REG_GAIN2, pgaCode << 4, MASK_GAIN_PGAGAIN5, __LINE__)) return false; this->pgaGain[5] = pga; return true; case 6: if(!adc_register_write_masked(REG_GAIN2, pgaCode << 8, MASK_GAIN_PGAGAIN6, __LINE__)) return false; this->pgaGain[6] = pga; return true; case 7: if(!adc_register_write_masked(REG_GAIN2, pgaCode << 12, MASK_GAIN_PGAGAIN7, __LINE__)) return false; this->pgaGain[7] = pga; return true; default: return false; // Invalid channel } return false; } ADS131M08_PGA_GAIN ADS131M08Hub::get_channel_pga(uint8_t channel) { if(channel > 7) { return ADS131M08_PGA_GAIN::PGA_INVALID; } return pgaGain[channel]; } 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, ADS131M08_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__); } //bool ADS131M08Hub::is_data_ready() //{ // if (digitalRead(drdyPin) == HIGH) // { // return false; // } // return true; //} uint16_t ADS131M08Hub::get_id() { return readRegister(REG_ID); } uint16_t ADS131M08Hub::get_mode_reg() { return readRegister(REG_MODE); } uint16_t ADS131M08Hub::get_clock_reg() { return readRegister(REG_CLOCK); } uint16_t ADS131M08Hub::get_cfg_reg() { return readRegister(REG_CFG); } //AdcOutput ADS131M08Hub::read_adc_raw() //{ // uint8_t x = 0; // uint8_t x2 = 0; // uint8_t x3 = 0; // int32_t aux; // AdcOutput res; // // digitalWrite(csPin, LOW); // delayMicroseconds(1); // // x = spi.transfer(0x00); // x2 = spi.transfer(0x00); // spi.transfer(0x00); // // this->resultRaw.status = ((x << 8) | x2); // // for(int i = 0; i<8; i++) // { // x = spi.transfer(0x00); // x2 = spi.transfer(0x00); // x3 = spi.transfer(0x00); // // aux = (((x << 16) | (x2 << 8) | x3) & 0x00FFFFFF); // if (aux > 0x7FFFFF) // { // this->resultRaw.ch[i].i = ((~(aux)&0x00FFFFFF) + 1) * -1; // } // else // { // this->resultRaw.ch[i].i = aux; // } // } // // delayMicroseconds(1); // digitalWrite(csPin, HIGH); // // return this->resultRaw; //} //float ADS131M08Hub::scale_result(uint8_t num) //{ // if( num >= 8) { // return 0.0; // } // // return this->resultFloat.ch[num].f = (float)(this->resultRaw.ch[num].i * rawToVolts * this->full_scale.ch[num].f); //} //AdcOutput ADS131M08Hub::scale_result() //{ // // update status // this->resultFloat.status = this->resultRaw.status; // // Scale all channels // for(int i = 0; i<8; i++) // { // this->scaleResult(i); // } // // return this->resultFloat; //} // //AdcOutput ADS131M08Hub::read_adc_float() //{ // this->readAdcRaw(); // return this->scaleResult(); //} // end of from tpcorrea // for debug only - remove references, declarations and definitions before submitting for production 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; const char* ws = " \t\n\r\f\v"; // Defines whitespace if (command == CMD_RREG) { str += " : regs "; for (int i = 0; i < nregs; i++) { str += reg_addr_to_string(address + i); str.erase(str.find_last_not_of(ws) + 1); 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.erase(str.find_last_not_of(ws) + 1); str += (i < (nregs - 1) ? ", " : ""); } } return str; } std::string ADS131M08Hub::reg_addr_to_string(int address) { return reg_data_to_string(address, 0, true); } 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 REG_CH0_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH0_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH0_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH0_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH0_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH1_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH1_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH1_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH1_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH1_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH2_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH2_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH2_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH2_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH2_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH3_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH3_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH3_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH3_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH3_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH4_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH4_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH4_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH4_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH4_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH5_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH5_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH5_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH5_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH5_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH6_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH6_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH6_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH6_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH6_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH7_CFG: str += reg_config_to_string(address, data, nameonly); break; case REG_CH7_OCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH7_OCAL_LSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH7_GCAL_MSB: str += reg_config_to_string(address, data, nameonly); break; case REG_CH7_GCAL_LSB: str += reg_config_to_string(address, data, nameonly); 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; } 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