#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" # include "driver/spi_master.h" // using namespace esphome::spi; /* //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); } */ namespace esphome { namespace ads131m08 { static const char *const TAG = "ads131m08"; static const char *const white_space = " \t\n\r\f\v"; // Defines whitespace DRAM_ATTR static spiframe tx_frame(ADS131M08Hub::numFrameWords * 4, 0); DRAM_ATTR static spiframe rx_frame(ADS131M08Hub::numFrameWords * 4, 0); // ADS131M08Hub::ADS131M08Hub() // : base_frame(40, 0) //{ // } void ADS131M08Hub::setup() { for (int i = 0; i < ADC_CHANNELS; i++) { // last_publish_time_[i] = 0; num_samples_[i] = 0; sample_sum_[i] = 0; sample_squared_sum_[i] = 0; } // 1. Create a binary semaphore this->data_ready_semhandle = xSemaphoreCreateBinary(); this->update_avg = xSemaphoreCreateMutex(); sync_reset_pin_->setup(); sync_reset_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT); // Set SYNC/RESET pin as output sync_reset_pin_->digital_write(true); // setup DRDY pin interrupt this->drdy_pin_->setup(); this->drdy_pin_->pin_mode(esphome::gpio::FLAG_INPUT | esphome::gpio::FLAG_PULLUP); // external pull-up this->spi_setup(); // Check if setup failed if (this->is_failed()) { ESP_LOGE(TAG, "SPI setup failed!"); this->mark_failed(LOG_STR("SPI setup failed.")); return; } // Send RESET command bool reset_ok = adc_reset_retry(); // ESP_LOGW(TAG, "adc word length: %d", adc_word_length_); if (!reset_ok) { ESP_LOGE(TAG, "ADC reset failed!"); this->mark_failed(LOG_STR("Reset failed.")); this->adc_init_ = 0; return; } if (!this->adc_initialize(DEFAULT_WORD_LENGTH)) { ESP_LOGE(TAG, "ADC reset failed!"); this->mark_failed(LOG_STR("Initialisation failed.")); this->adc_init_ = 0; return; } set_reg_osr(); if (!adc_lock(true)) { ESP_LOGE(TAG, "ADC lock failed!"); this->mark_failed(LOG_STR("ADC lock failed.")); this->adc_init_ = 0; return; } if (!adc_lock(false)) { ESP_LOGE(TAG, "ADC unlock failed!"); this->mark_failed(LOG_STR("ADC unlock failed.")); this->adc_init_ = 0; return; } if (!adc_backup_registers()) { ESP_LOGE(TAG, "Backing up ADC registers failed!"); this->mark_failed(LOG_STR("ADC register backup 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 = convert_adc_osr_to_value(this->osr_); ESP_LOGCONFIG(TAG, " Oversampling ratio: %" PRId32, osr); ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); ESP_LOGCONFIG(TAG, " SPI Mode: %d", this->mode_); ESP_LOGCONFIG(TAG, " Bit Order: %s", this->bit_order_ == 1 ? "msb_first" : this->bit_order_ == 0 ? "lsb_first" : "unknown"); } uint8_t ADS131M08Hub::update_adc_word_length() { setup_frame(rx_frame, 2); transfer_array(rx_frame); // write CMD_NULL to ADC to retrieve status zero_fill(rx_frame); transfer_array(rx_frame); uint16_t status = rx_frame[0] << 8 | rx_frame[1]; // print_command_response_to_string(CMD_NULL, frame).c_str(); return update_adc_word_length(status); } uint8_t ADS131M08Hub::update_adc_word_length(uint16_t status) { switch (status & MASK_STATUS_WLENGTH) { case WLENGTH_16_BITS: this->adc_word_length_ = 16; break; case WLENGTH_24_BITS: this->adc_word_length_ = 24; break; case WLENGTH_32_BITS_LSB_ZERO_PADDING: case WLENGTH_32_BITS_MSB_SIGN_EXTEND: this->adc_word_length_ = 32; break; default: ESP_LOGW(TAG, "Unknown word length: %u", adc_word_length_); break; } update_conversion_factor(); return this->adc_word_length_; } float ADS131M08Hub::update_conversion_factor() { switch (adc_word_length_) { case 16: this->conversion_factor_ = this->reference_voltage_ / 32768.0; // TODO: must double check this with a test break; default: this->conversion_factor_ = this->reference_voltage_ / 8388608.0; break; } return this->conversion_factor_; } bool ADS131M08Hub::adc_initialize(uint8_t word_length) { this->adc_init_++; update_adc_word_length(); if (!adc_register_write(REG_CLOCK, (reg_clock_allch_off | this->osr_ << 2), __LINE__)) { 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, __LINE__)) { 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, __LINE__)) { 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 | this->osr_ << 2), __LINE__)) { 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_backup_registers() { bool success = true; // we will employ this later, for now we just return success int j = 0; int store_nwords = sizeof(settings.data) / sizeof(settings.data[0]); while(success && (j < store_nwords)) { auto reg_data = adc_register_read(j + REG_MODE, MAX_FRAME_SIZE - 2); int nregs_read = reg_data.size(); int i = 0; while((i < nregs_read) && (j < store_nwords)) { settings.data[j++] = reg_data[i++]; } } return success; } bool ADS131M08Hub::adc_restore_registers() { uint16_str stored_regs(MAX_FRAME_SIZE - 2, 0); bool success = true; int j = 0; int store_nwords = sizeof(settings.data) / sizeof(settings.data[0]); success = adc_register_write(j + REG_MODE, settings.data[j], __LINE__, false); if(!success) { ESP_LOGD(TAG, "restoring %s reg: %04X failed", reg_addr_to_string(j + REG_MODE).c_str(), settings.data[j]); } j++; while(success && (j < store_nwords)) { int i = 0; stored_regs.resize(MAX_FRAME_SIZE - 2); while((i < MAX_FRAME_SIZE - 2) && (j < store_nwords)) { ESP_LOGD(TAG, "restoring %s reg: %04X", reg_addr_to_string(j + REG_MODE).c_str(), stored_regs[i]); stored_regs[i++] = settings.data[j++]; } stored_regs.resize(i); success = adc_register_write(j + REG_MODE - i, stored_regs, __LINE__, false); //ESP_LOGD(TAG, "start_addr: %02X i: %02X", j + REG_MODE - , i); } return success; } bool ADS131M08Hub::adc_set_word_length(uint8_t word_length) { uint16_t value; switch (word_length) { case 32: value = reg_mode_cfg32; break; case 16: value = reg_mode_cfg16; break; default: value = reg_mode_cfg24; break; } uint16_t mask = reg_mode_cfg_mask; //ESP_LOGD(TAG, "WL: %d stored value: %04X / %04X new value: %04X masked value %04X mask: %04X", word_length, this->settings.reg.mode, this->settings.data[REG_MODE-2], value, value & mask, mask ); if (!adc_register_write_masked(REG_MODE, value, mask, __LINE__)) { ESP_LOGE(TAG, "MODE register write / read to set word size to %ubits failed", word_length); return false; } ESP_LOGV(TAG, "Written MODE register; Made ADC word size %u bits. Re-wrote other set bits in MODE register", word_length); // update_adc_word_length(); return true; } // Implementing nested CS enable/disable handling in ADS131M08Hub to allow multiple transfers in a row without toggling // CS between them, which is required for proper communication with the ADC according to the datasheet recommendations. void ADS131M08Hub::enable(const char *txt) { cs_ctr_++; // Increment counter on each enable call // esph_log_i(TAG, "cs_ctr_e: %s %d", txt, cs_ctr_.load()); if (cs_ctr_ == 1) { // Only set CS low on the first enable call SPIDevice::enable(); // Call the base class enable to set CS low if (txt != nullptr) esph_log_i(TAG, "enable: %s", txt); } } void ADS131M08Hub::disable(const char *txt) { // esph_log_i(TAG, "cs_ctr_d: %s %d", txt, cs_ctr.load()); cs_ctr_--; // Decrement counter on each disable call if (cs_ctr_ == 0) { // Only set CS high when counter returns to 0 SPIDevice::disable(); // Call the base class disable to set CS high if (txt != nullptr) esph_log_i(TAG, "disable: %s", txt); } else if (cs_ctr_ < 0) { // Sanity check to prevent counter from going negative cs_ctr_ = 0; } } // ISR function to handle interrupt from drdy pin // MUST be fast and non-blocking void ADS131M08Hub::isr_handler(ADS131M08Hub *arg) { { // InterruptLock lock; if (arg != nullptr) { arg->enable_loop_soon_any_context(); arg->isr_ctr_++; // BaseType_t xHigherPriorityTaskWoken = pdFALSE; // xSemaphoreGiveFromISR(arg->data_ready_semhandle, &xHigherPriorityTaskWoken); // if (xHigherPriorityTaskWoken == pdTRUE) { // portYIELD_FROM_ISR(); // Switch to the waiting task immediately // } } } } void ADS131M08Hub::loop() { // Check the semaphore (0 timeout means non-blocking) //if (xSemaphoreTake(data_ready_semhandle, 0) == pdTRUE) { if (this->adc_init_ == 0) { float num_samples = 0; float crc_errors = 0; float iterations = 0; float not_ready = 0; float sps = 0; { CHIP_SELECTx uint64_t start = micros(); ///this->txf_init(); // implementing datasheet recommended TXF init this->adc_sync(); int freq = 0; //if(delegate_ != nullptr){ // esp_err_t ret = spi_device_get_actual_freq((SPIDelegateHw)delegate_->handle_, &freq); // if (ret != ESP_OK) { // ESP_LOGE(TAG, "SPI get actual frequency failed!"); // } // ESP_LOGD(TAG, "spi_freq: %u", freq); //} if (rms_calc_req_) { float_str result = read_multi(); num_samples = result[0]; crc_errors = result[1]; iterations = result[2]; not_ready = result[3]; sps = result[4]; uint64_t end = micros(); //update_averages(SAMPLE_TIME_SENSOR, 1, static_cast(end - start) / 1000.0); //update_averages(MAX_SAMPLES_SENSOR, 1, static_cast(num_samples)); //update_averages(CRC_ERRORS_SENSOR, 1, static_cast(crc_errors)); //uint16_t ctr = isr_ctr_; //update_averages(ISR_COUNT_SENSOR, 1, static_cast(ctr)); //update_averages(ITERATIONS_SENSOR, 1, static_cast(iterations)); //update_averages(NOT_READY_SENSOR, 1, static_cast(not_ready)); if(this->sensors_dc[SAMPLE_TIME_SENSOR] != nullptr) this->sensors_dc[SAMPLE_TIME_SENSOR]->publish_state((end - start)/1000.0); if(this->sensors_dc[MAX_SAMPLES_SENSOR] != nullptr) this->sensors_dc[MAX_SAMPLES_SENSOR]->publish_state(num_samples); if(this->sensors_dc[CRC_ERRORS_SENSOR] != nullptr) this->sensors_dc[CRC_ERRORS_SENSOR]->publish_state(crc_errors); if(this->sensors_dc[ISR_COUNT_SENSOR] != nullptr) { uint16_t ctr = isr_ctr_; this->sensors_dc[ISR_COUNT_SENSOR]->publish_state(ctr); } if(this->sensors_dc[ITERATIONS_SENSOR] != nullptr) this->sensors_dc[ITERATIONS_SENSOR]->publish_state(iterations); if(this->sensors_dc[NOT_READY_SENSOR] != nullptr) this->sensors_dc[NOT_READY_SENSOR]->publish_state(not_ready); if(this->sensors_dc[SPS_SENSOR] != nullptr) this->sensors_dc[SPS_SENSOR]->publish_state(sps); } else { read_single(); } isr_ctr_ = 0; } if (crc_errors > 30) { ESP_LOGW(TAG, "High CRC error rate."); int i = 4; while(i-- > 0 && !adc_set_word_length(DEFAULT_WORD_LENGTH)); // for some reason the adc occasionally reverts to 24bits; this will // reset the word length to what it should be // update_adc_word_length(); // spiframe recoverframe(40, 0); // read_array(recoverframe); // ESP_LOGI(TAG, "WL: %u Frame: %s", this->adc_word_length_, frame_to_string(recoverframe).c_str()); } // ESP_LOGW(TAG, "%llu ms (%llu us), max samples: %u", (end - start)/1000, (end - start), num_samples); } //} } /// @brief Set 16 bit frameword /// @param frame /// @param w_index /// @param data uint16_t denotes command or CRC /// @return bool ADS131M08Hub::set_frame_word(spiframe &frame, int w_index, uint16_t data) { size_t word_nbytes = adc_word_length_ >> 3; size_t frame_words = frame.size() / word_nbytes; int b_index = w_index * word_nbytes; if (w_index >= frame_words) { if (w_index >= MAX_FRAME_SIZE) { ESP_LOGE(TAG, "Word index of %d out of bounds", w_index); // esp_crosscore_int_send_print_backtrace(xPortGetCoreID()); return false; // invalid - something has gone wrong } frame.resize(b_index + word_nbytes); } switch (adc_word_length_) { // deliberate no breaks after first two cases case 32: frame[b_index + 3] = 0; case 24: frame[b_index + 2] = 0; case 16: frame[b_index + 1] = data & 0xFF; frame[b_index] = (data >> 8) & 0xFF; break; default: ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); break; } return true; } bool ADS131M08Hub::set_frame_word(spiframe &frame, int w_index, uint32_t data) { size_t word_nbytes = adc_word_length_ >> 3; size_t frame_words = frame.size() / word_nbytes; int b_index = w_index * word_nbytes; if (w_index >= frame_words) { if (w_index >= MAX_FRAME_SIZE) { ESP_LOGE(TAG, "Word index of %d out of bounds", w_index); // esp_crosscore_int_send_print_backtrace(xPortGetCoreID()); return false; // invalid - something has gone wrong } frame.resize(b_index + word_nbytes); } switch (adc_word_length_) { // deliberate no breaks after first two cases case 32: frame[b_index++] = (data >> 24) & 0xFF; case 24: frame[b_index++] = (data >> 16) & 0xFF; case 16: frame[b_index++] = (data >> 8) & 0xFF; frame[b_index] = data & 0xFF; break; default: ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); break; } return true; } // index(i) = zero-reference, returns i'th word, LSB aligned uint32_t ADS131M08Hub::get_unsigned_frame_word(const spiframe &frame, int w_index, bool force_16bits) { uint32_t result = 0; size_t word_nbytes = adc_word_length_ >> 3; int b_index = w_index * word_nbytes; if (b_index > (frame.size() - word_nbytes)) { return result; // illegal } switch (force_16bits ? 16 : adc_word_length_) { // deliberate no breaks after first two cases case 32: result += frame[b_index++] << 24; case 24: result += frame[b_index++] << 16; case 16: result += frame[b_index++] << 8; result += frame[b_index]; break; default: ESP_LOGW(TAG, "Invalid adc word length of %d", adc_word_length_); break; } return result; } int32_t ADS131M08Hub::get_sign_ext_frame_word(const spiframe &frame, int w_index) { int32_t result = 0; size_t word_nbytes = adc_word_length_ >> 3; int b_index = w_index * word_nbytes; if (b_index > (frame.size() - word_nbytes)) { ESP_LOGW(TAG, "Word index of %d out of bounds", w_index); return result; // invalid } switch (adc_word_length_) { case 32: result = (frame[b_index] << 24) | (frame[b_index + 1] << 16) | (frame[b_index + 2]) << 8 | frame[b_index + 3]; break; case 24: result = (frame[b_index] << 24) | (frame[b_index + 1] << 16) | (frame[b_index + 2] << 8); result = result >> 8; break; case 16: { int16_t raw = (frame[b_index] << 8) | frame[b_index + 1]; result = static_cast(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; } uint32_t ADS131M08Hub::adc_hard_reset() { uint32_t pulse_duration = convert_adc_clock_cycles_to_micros(2150); sync_reset_pin_->digital_write(false); // Pull SYNC/RESET pin low to reset the ADC delay_microseconds_safe(pulse_duration); // Hold reset for the calculated duration sync_reset_pin_->digital_write(true); // Set SYNC/RESET pin high to allow normal operation return convert_adc_clock_cycles_to_micros(T_SETTLE_OSR1024); } uint32_t ADS131M08Hub::adc_sync() { uint32_t pulse_duration = convert_adc_clock_cycles_to_micros(50); sync_reset_pin_->digital_write(false); // Pull SYNC/RESET pin low to sync the ADC delay_microseconds_safe(pulse_duration); // Hold sync for the calculated duration sync_reset_pin_->digital_write(true); // Set SYNC/RESET pin high to allow normal operation return get_filter_settling_time(); } uint32_t ADS131M08Hub::get_filter_settling_time() { uint16_t cycles = T_SETTLE_OSR16384; switch(this->osr_) { case OSR_128: cycles = T_SETTLE_OSR128; break; case OSR_256: cycles = T_SETTLE_OSR256; break; case OSR_512: cycles = T_SETTLE_OSR512; break; case OSR_1024: cycles = T_SETTLE_OSR1024; break; case OSR_2048: cycles = T_SETTLE_OSR2048; break; case OSR_4096: cycles = T_SETTLE_OSR4096; break; case OSR_8192: cycles = T_SETTLE_OSR8192; break; } return convert_adc_clock_cycles_to_micros(cycles); } uint32_t ADS131M08Hub::convert_adc_clock_cycles_to_micros(uint32_t cycles) { float cycle_micros = 1000000 / this->clock_frequency_; // one cycle duration in micro seconds return 1 + static_cast(std::round(cycle_micros * cycles)); } // ********************** from datasheet ************************ void ADS131M08Hub::txf_init() { CHIP_SELECT setup_frame(rx_frame, numFrameWords); if (firstRead) { // Clear the ADC's 2-deep FIFO on the first read this->transfer_array(rx_frame); zero_fill(rx_frame); this->transfer_array(rx_frame); firstRead = false; // Clear the flag } // Send the nulls to the ADC to get the current ADC data zero_fill(rx_frame); this->transfer_array(rx_frame); } bool ADS131M08Hub::adc_reset_retry() { adc_word_length_ = 24; bool reset_ok = adc_soft_reset(); uint8_t retry_wordlengths[] = {32, 32, 24, 24, 16, 16}; int i = 0; while (i < sizeof(retry_wordlengths) && !reset_ok) { adc_word_length_ = retry_wordlengths[i]; ESP_LOGW(TAG, "ADC word length: %d", adc_word_length_); reset_ok = adc_soft_reset(); i++; } if (!reset_ok) { adc_hard_reset(); for(int i = 0; i < 4; i++) { //zero_fill(rx_frame); setup_frame(rx_frame, numFrameWords); transfer_array(rx_frame); delay_microseconds_safe(T_REGACQ); } //delay_microseconds_safe(10000); i = 0; while (i < sizeof(retry_wordlengths) && !reset_ok) { adc_word_length_ = retry_wordlengths[i]; ESP_LOGW(TAG, "ADC word length: %d", adc_word_length_); reset_ok = adc_soft_reset(); i++; } } return reset_ok; } // int ADS131M08Hub::build_frame() bool ADS131M08Hub::adc_soft_reset() { int index = 0; uint16_t reset_response; uint16_t cmd = 0; update_adc_word_length(); auto framelength = numFrameWords * (adc_word_length_ >> 3); size_t frame_len; // curly braces confine chip select scope { CHIP_SELECTx cmd = CMD_RESET; setup_frame(rx_frame, 2); set_frame_word(rx_frame, 0, cmd); frame_len = add_crc(rx_frame); rx_frame.resize(framelength); index += frame_len; for (; index < framelength; index++) { rx_frame[index] = 0; } ESP_LOGVV(TAG, "Sent Frame: %s", frame_to_string(rx_frame).c_str()); transfer_array(rx_frame); } delay_microseconds_safe(T_REGACQ); update_adc_word_length(WLENGTH_24_BITS); // should be 24 after reset setup_frame(rx_frame, numFrameWords); { CHIP_SELECTx transfer_array(rx_frame); reset_response = get_unsigned_frame_word(rx_frame, 0, true); bool crc_ok = check_crc(rx_frame); ESP_LOGVV(TAG, "Read Frame: %s CRC: %s", frame_to_string(rx_frame).c_str(), (crc_ok ? "OK" : "FAIL")); } bool result = reset_response == RSP_RESET_OK; ESP_LOGV(TAG, "Reset response: [0x%04X] %s", reset_response, (result ? "OK" : "FAIL")); return result; } // overwrites last (or second) frameword with CRC, therefore frame should be large enough for payload and crc size_t ADS131M08Hub::add_crc(spiframe &frame) { size_t frame_length = frame.size(); if (frame_length == 0 || adc_word_length_ == 0) return 0; size_t word_nbytes = adc_word_length_ >> 3; size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any size_t payload_len = word_nbytes * (frame_words - 1); if (payload_len < word_nbytes) return false; uint16_t crc = get_crc(frame); set_frame_word(frame, frame_words - 1, crc); return frame_words * word_nbytes; } // CRC should be 16bits, MSB aligned in last word of frame bool ADS131M08Hub::check_crc(const spiframe &frame) { if (adc_word_length_ == 0) return false; size_t word_nbytes = adc_word_length_ >> 3; int frame_length = frame.size(); size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any size_t payload_len = word_nbytes * (frame_words - 1); if (payload_len < word_nbytes) return false; uint16_t frame_crc = (frame[payload_len] << 8) + frame[payload_len + 1]; // Frames are MSB aligned, so is CRC auto crc = get_crc(frame); bool crc_ok = crc == frame_crc; return crc_ok; } uint16_t ADS131M08Hub::read_frame_crc(const spiframe &frame) { if (adc_word_length_ == 0) return 0; size_t word_nbytes = adc_word_length_ >> 3; int frame_length = frame.size(); size_t frame_words = frame_length / word_nbytes; // integer division, we discard remainder if any size_t payload_len = word_nbytes * (frame_words - 1); if (payload_len < word_nbytes) return 0; return (frame[payload_len] << 8) + frame[payload_len + 1]; // Frames are MSB aligned, so is CRC } /* CRC Parameters for ADS131M08 Polynomial: 0x1021 (x^16 + x^12 + x^5 + 1). Initial Value (Seed): 0xFFFF. Input Data: The calculation is performed over all bits in the SPI frame preceding the CRC word. Output Format: The resulting 16-bit CRC is placed in the most significant 16 bits of the final 24-bit (or 32-bit) SPI word; the remaining bits are typically padded with zeros. */ uint16_t ADS131M08Hub::get_crc(const spiframe &frame) { size_t word_nbytes = adc_word_length_ >> 3; size_t frame_words = frame.size() / word_nbytes; // integer division, we discard remainder if any size_t payload_len = word_nbytes * (frame_words - 1); uint16_t crc_result = crc(0xFFFF, frame[0]); for (int j = 1; j < payload_len; j++) { crc_result = crc(crc_result, frame[j]); } return crc_result; } uint16_t ADS131M08Hub::crc(uint16_t crc_register, uint8_t data) { uint16_t xor_result, input_bit, crc_result; crc_result = crc_register; for (int i = 7; i >= 0; i--) { input_bit = (data >> i) & 0x01; xor_result = input_bit ^ (crc_result & 0x8000) >> 15; crc_result = crc_result << 1; if (xor_result) crc_result = crc_result ^ 0x1021; } return crc_result; } void ADS131M08Hub::transfer_array(spiframe &frame) { CHIP_SELECT Base::transfer_array(frame.data(), (size_t)frame.size()); } //void ADS131M08Hub::transfer_array(const spiframe &tx_frame, spiframe &rx_frame) { // CHIP_SELECT // auto frame_length = tx_frame.size(); // if (rx_frame.size() < frame_length) // rx_frame.resize(frame_length); // for (size_t i = 0; i < frame_length; i++) { // rx_frame[i] = this->transfer_byte(tx_frame[i]); // } //} bool ADS131M08Hub::set_measure_rms(uint8_t channel, bool enable) { if (channel >= MAX_CHANNELS) return false; this->rms_enabled_[channel] = enable; this->rms_calc_req_ = true; return true; } void ADS131M08Hub::read_single() { setup_frame(rx_frame, numFrameWords); this->transfer_array(rx_frame); bool crc_ok = check_crc(rx_frame); uint16_t drdy_status = get_unsigned_frame_word(rx_frame, 0, true) & MASK_STATUS_DRDY; // data_ready = crc_ok && (drdy_status != 0); if (crc_ok) { // sample channels for (int i = 0; i < ADC_CHANNELS; i++) { bool channel_ready = drdy_status & 1; drdy_status = drdy_status >> 1; if (channel_ready) { int32_t raw = get_sign_ext_frame_word(rx_frame, i + 1); float value = this->conversion_factor_ * raw; this->sampled_values_[i] = value; bool ac_sensor_exist = this->sensors_ac[i] != nullptr; bool dc_sensor_exist = this->sensors_dc[i] != nullptr; if (ac_sensor_exist || dc_sensor_exist) { if (ac_sensor_exist) this->sensors_ac[i]->publish_state(value); if (dc_sensor_exist) this->sensors_dc[i]->publish_state(value); } } } } } // do not like what the following procedure does at all (it blocks for +-40ms), but at the moment do not know how to get // DRDY interrupt / ISR / loop setup to respond fast enough to read multiple frames with no or the minimum possible // missed frames in quick succession idealy would want to read +- 200 24/32bit word frames in 2 AC 50/60Hz cycles to // hopefully reliably calculate RMS value, phase offsets, etc. the best the read_multi procedure does is to read about // 66x 16bit / 33x 32bit word frames in 40ms which is far from ideal Once we ge the ISR and loop setup to respond fast // enough, the following procedure should be completely re-designed float_str ADS131M08Hub::read_multi() { int32_t raw; float value; float_str result(5, 0.0); bool crc_ok, data_ready, ac_sensor_exist, dc_sensor_exist; uint16_t drdy_status, status; int i, iteration = 0; uint32_t elapsed_time = 0; uint32_t sample_time = this->sample_time_; 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; } uint32_t settling_end_time = loop_start_time; while ((elapsed_time < sample_time) && (iteration++ < 1000)) { result[2] = iteration; setup_frame(rx_frame, numFrameWords); this->transfer_array(rx_frame); //ESP_LOGD(TAG, "frame: %s", frame_to_string(rx_frame).c_str()); //ESP_LOGD(TAG, "iteration: %d, frame: %s", iteration, frame_to_string(rx_frame).c_str()); //ESP_LOGD(TAG, "frame_word%d: %s", 0, frame_words_to_string(rx_frame, 0, 1).c_str()); status = get_unsigned_frame_word(rx_frame, 0, true); update_adc_word_length(status); if(status & MASK_STATUS_RESET) { ESP_LOGI(TAG,"Reset frame: %s", frame_to_string(rx_frame).c_str()); ESP_LOGW(TAG, "ADC reset detected. Trying recover"); if (!adc_register_write(REG_MODE, MODE_MASK_RESET_HAPPENED & reg_mode_cfg32, __LINE__)) { ESP_LOGE(TAG, "MODE register write / read to set word size to 32bits failed"); return result; } this->set_timeout("reset detected", 250, [this, result]() { ESP_LOGE(TAG, "ADC reset detected. Trying to restore registers"); if(this->adc_restore_registers()) { ESP_LOGW(TAG, "Mid data-read reset detected; restored registers"); //delay_microseconds_safe(get_filter_settling_time() + 50); txf_init(); return result; } ESP_LOGE(TAG, "ADC reset detected. Unable to recover"); //this->mark_failed(LOG_STR("ADC reset detected. Recovery failed")); return result; }); //if(adc_reset_retry()) { // if(adc_restore_registers()) { // ESP_LOGW(TAG, "Mid data-read reset detected; restored registers"); // return result; // } //} //ESP_LOGE(TAG, "ADC reset detected. Unable to recover"); //this->mark_failed(LOG_STR("ADC reset detected. Recovery failed")); //ESP_LOGE(TAG, "ADC reset detected"); return result; } crc_ok = check_crc(rx_frame); if (crc_ok) { if(status & MASK_STATUS_RESYNC) { settling_end_time = elapsed_time + get_filter_settling_time() + 50; // add 50us for good measure } if(elapsed_time > settling_end_time) { if ((status & MASK_STATUS_RESYNC) == 0) { drdy_status = status & MASK_STATUS_DRDY; // sample channels if(drdy_status == 0){ result[3]++; } for (i = 0; i < ADC_CHANNELS && drdy_status != 0; i++) { data_ready = drdy_status & 1; drdy_status = drdy_status >> 1; if (data_ready) { this->sampled_values_[i] = value; ac_sensor_exist = this->sensors_ac[i] != nullptr; dc_sensor_exist = this->sensors_dc[i] != nullptr; if (ac_sensor_exist || dc_sensor_exist) { raw = get_sign_ext_frame_word(rx_frame, i + 1); value = this->conversion_factor_ * raw; if (this->rms_enabled_[i]) { (this->num_samples_[i])++; (this->sample_sum_[i]) += value; (this->sample_squared_sum_[i]) += value * value; } else { if(ac_sensor_exist) this->sensors_ac[i]->publish_state(value); if(dc_sensor_exist) this->sensors_dc[i]->publish_state(value); //update_averages(i, value, value); } } } } } } } else { result[1]++; // ESP_LOGW(TAG, "iteration: %d, frame: %s CRC error", iteration, frame_to_string(frame).c_str()); } elapsed_time = micros() - loop_start_time; } // publish integrated values for (i = 0; i < ADC_CHANNELS; i++) { if (this->rms_enabled_[i]) { ac_sensor_exist = this->sensors_ac[i] != nullptr; dc_sensor_exist = this->sensors_dc[i] != nullptr; auto num_samples = this->num_samples_[i]; if (num_samples > result[0]) { result[0] = num_samples; } if (num_samples == 0) { // should not happen, but let's play safe and avoid dividing by zero // ESP_LOGW(TAG, "Num samples: %d", num_samples); //update_averages(i, NAN, NAN); if(ac_sensor_exist) this->sensors_ac[i]->publish_state(NAN); if(dc_sensor_exist) this->sensors_dc[i]->publish_state(NAN); } else { float sample_sum = sample_sum_[i]; float rms_dc = sample_sum / num_samples; float rms_ac_squared = sample_squared_sum_[i] / num_samples - rms_dc * rms_dc; float rms_ac = 0; if (rms_ac_squared > 0) { rms_ac = std::sqrtf(rms_ac_squared); } if(ac_sensor_exist) this->sensors_ac[i]->publish_state(rms_ac); if(dc_sensor_exist) this->sensors_dc[i]->publish_state(rms_dc); //update_averages(i, rms_ac, rms_dc); } this->num_samples_[i] = 0; this->sample_sum_[i] = 0.0f; this->sample_squared_sum_[i] = 0.0f; } } this->sps_ = (1e6 * (float) result[0]) / elapsed_time; result[4] = this->sps_; return result; } /* /// @brief Updates rolling averages for rms_ac and rms_dc. Window = 2 /// @param channel /// @param rms_ac /// @param rms_dc /// @return bool ADS131M08Hub::update_averages(uint8_t channel, float rms_ac, float rms_dc) { if (update_avg == nullptr || channel >= MAX_CHANNELS) { ESP_LOGW(TAG, "channel %d not ready", channel); return false; } bool success = false; bool ac_sensor_exist = this->sensors_ac[channel] != nullptr; bool dc_sensor_exist = this->sensors_dc[channel] != nullptr; if (!ac_sensor_exist && !dc_sensor_exist) { //ESP_LOGW(TAG, "channel %d does not exist", channel); return false; } // we treat update_state as a critical resource that must not be clobbered if (xSemaphoreTake(update_avg, (TickType_t) 50) == pdTRUE) { uint8_t update_state = update_state_[channel]; update_state_[channel] &= 0xF0; if (ac_sensor_exist) { // if this is the first value, then we just store it with no averaging if ((update_state & 0x0F) == 0) { avg_ac_[channel] = rms_ac; update_state |= 0x01; update_state_[channel] = update_state; } else { // if we have NAN (no samples), we store it, but also also treat next valid value as the first if (rms_ac == NAN) { avg_ac_[channel] = NAN; } else { ESP_LOGI(TAG, "Setting AC avg: %f %f", avg_ac_[channel], rms_ac); avg_ac_[channel] = 0.5 * (avg_ac_[channel] + rms_ac); update_state_[channel] = update_state; } } } update_state = update_state_[channel]; if (dc_sensor_exist) { if ((update_state & 0xF0) == 0) { avg_dc_[channel] = rms_dc; update_state |= 0x10; } else { if (rms_dc == NAN) { avg_dc_[channel] = NAN; update_state &= 0x0F; } else { ESP_LOGI(TAG, "Setting DC avg: %f %f", avg_dc_[channel], rms_dc); avg_dc_[channel] = 0.5 * (avg_dc_[channel] + rms_dc); } } update_state_[channel] = update_state; } else { update_state_[channel] &= 0x0F; } success = true; xSemaphoreGive(update_avg); } return success; } float ADS131M08Hub::get_average(uint8_t channel, bool read_ac) { float result = NAN; if (update_avg == nullptr || channel >= MAX_CHANNELS) { return result; } if (update_avg != nullptr) { if (xSemaphoreTake(update_avg, (TickType_t) 50) == pdTRUE) { if (read_ac) { // ESP_LOGI(TAG, "AC: update_state_[%u]: %02X", channel, update_state_[channel]); if (update_state_[channel] & 0x0F) { result = avg_ac_[channel]; update_state_[channel] &= 0xF0; } else { ESP_LOGW(TAG, "No AC data for channel %d", channel); result = NAN; } } else { // ESP_LOGI(TAG, "DC: update_state_[%u]: %02X", channel, update_state_[channel]); if (update_state_[channel] & 0xF0) { result = avg_dc_[channel]; update_state_[channel] &= 0x0F; } else { ESP_LOGW(TAG, "No DC data for channel %d", channel); result = NAN; } } xSemaphoreGive(update_avg); } } return result; } */ bool ADS131M08Hub::adc_lock(bool enable) { setup_frame(rx_frame, 2); uint16_t command = CMD_UNLOCK; if (enable) { command = CMD_LOCK; } set_frame_word(rx_frame, 0, command); add_crc(rx_frame); transfer_array(rx_frame); delay_microseconds_safe(T_REGACQ); zero_fill(rx_frame); transfer_array(rx_frame); auto ack = get_unsigned_frame_word(rx_frame, 0, true); return ack == command; } /// @brief /// @param address - register address /// @param value - mask is used to remove unwanted bits before adding to value read from register /// @param mask - register contents read from adc is masked with inverse of this /// @param line - temporary / for debugging /// @return success flag bool ADS131M08Hub::adc_register_write_masked(uint8_t address, uint16_t value, uint16_t mask, int line) { uint16_str reg_contents = adc_register_read(address, 1); if (reg_contents.empty()) { return false; } // ESP_LOGW(TAG, "1: reg contents: 0x%04X, address: 0x%02X, value: 0x%04X, mask: 0x%04X, caller: %d", // reg_contents[0],address, value,mask, line ); auto new_value = (reg_contents[0] & ~mask) | (value & mask); if(new_value == reg_contents[0]) { //ESP_LOGW(TAG, "No updt: %-13s: newv %04X old regval %04X mskd %04X Line: %d", reg_addr_to_string(address).c_str(), value, reg_contents[0], new_value, line); int bup_index = address - REG_MODE; //ESP_LOGW(TAG, "Storing: %-13s: data %04X stored val %04X Line %d", reg_addr_to_string(address).c_str(), value, settings.data[bup_index], line); // we backup data irrespective settings.data[bup_index] = value; return true; // no need for an adc reg update } reg_contents[0] = new_value; return adc_register_write(address, reg_contents, line); } /// @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; } bool update_required = 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); auto new_value = (reg_contents[i] & ~masks[i]) | (values[i] & masks[i]); if(reg_contents[i] != new_value) update_required = true; reg_contents[i] = new_value; } if(!update_required) { //ESP_LOGW(TAG, "2: No update needed. start reg: %02X first data: %04X Line: %d", start_address, values[0], line); // we backup data irrespective int bup_index = start_address - REG_MODE; for(auto i = values.begin(); i != values.end(); i++) { //ESP_LOGW(TAG, "Storing: %-13s: data %04X stored val: %04X Line: %d", reg_addr_to_string(bup_index + REG_MODE).c_str(), *i, settings.data[bup_index], line); settings.data[bup_index++] = *i; } return true; } return adc_register_write(start_address, reg_contents, line); } bool ADS131M08Hub::adc_register_write(uint16_t address, uint16_t data, int line, bool backup_data) { uint16_str arg; arg += data; return adc_register_write(address, arg, line, backup_data); } bool ADS131M08Hub::adc_register_write(uint16_t start_address, const uint16_str &data, int line, bool backup_data) { int nregs = data.size(); if (nregs == 0) { return false; // invalid } if(backup_data) { int bup_index = start_address - REG_MODE; for(auto i = data.begin(); i != data.end(); i++) { //ESP_LOGW(TAG, "Storing: %-13s: data %04X stored val: %04X Line: %d", reg_addr_to_string(bup_index + REG_MODE).c_str(), *i, settings.data[bup_index], line); settings.data[bup_index++] = *i; } } else { int bup_index = start_address - REG_MODE; for(auto i = data.begin(); i != data.end(); i++) { ESP_LOGD(TAG, "Restoring: %-13s: data %04X stored val: %04X Line: %d", reg_addr_to_string(bup_index + REG_MODE).c_str(), *i, settings.data[bup_index], line); bup_index++; } } int wreg_nwords = 2 + nregs; // ensure room for crc int rreg_resp_framelength = 2 + (nregs == 1 ? -1 : nregs); // add room for CRC if nregs > 1 //spiframe frame(wreg_nwords, 0); //spiframe rxframe(wreg_nwords, 0); uint16_t addr_regcnt_mask = (start_address << 7) | ((nregs - 1) & MASK_CMD_RW_REG_COUNT); uint16_t wreg_addr_opcode = CMD_WREG | addr_regcnt_mask; // Combine WREG command, start_address and register count uint16_t rreg_addr_opcode = CMD_RREG | addr_regcnt_mask; // Combine RREG command, start_address and register count bool has_mode_reg = (start_address == REG_MODE) || ((start_address < REG_MODE) && ((start_address + nregs) > REG_MODE)); setup_frame(rx_frame, wreg_nwords); { CHIP_SELECT set_frame_word(rx_frame, 0, wreg_addr_opcode); for (int i = 0; i < nregs; i++) { set_frame_word(rx_frame, i + 1, data[i]); } add_crc(rx_frame); ESP_LOGD(TAG, "%s\n", rwreg_command_frame_to_string(rx_frame).c_str()); //ESP_LOGD(TAG, "Send Frame: %s", frame_to_string(rx_frame).c_str()); transfer_array(rx_frame); // WREG } if (has_mode_reg) { update_adc_word_length(); } delay_microseconds_safe(T_REGACQ); auto rxdata = adc_register_read(start_address, nregs); bool verified = !rxdata.empty(); for (int i = 0; i < rxdata.size() && verified; i++) { verified = rxdata[i] == data[i]; if (!verified) { ESP_LOGE(TAG, "Write \'%s\' register failed: tx data: 0x%04X, rx data: 0x%04X", reg_addr_to_string(start_address + i).c_str(), data[i], rxdata[i]); // ESP_LOGE(TAG, "Write: %s", reg_data_to_string(start_address + i, data[i]).c_str()); ESP_LOGE(TAG, "Ack : %s", reg_data_to_string(start_address + i, rxdata[i]).c_str()); } } delay_microseconds_safe(T_REGACQ); return verified; } // recommended not to read more than 8 registers at a time uint16_str ADS131M08Hub::adc_register_read(uint8_t address, uint8_t nregs) { uint16_str result; if (nregs == 0) { return result; // invalid } if (nregs > MAX_FRAME_SIZE - 2) { // We are limited to 8 registers, otherwise we will exceed our static DMA / SPI buffer size ESP_LOGE(TAG, "Trying to read %d registers. This exceeds maximum register read count of %d!", nregs, MAX_FRAME_SIZE - 2); return result; // invalid } int rreg_nwords = 2; // ensure room for crc int response_nwords = 2 + (nregs == 1 ? -1 : nregs); // add room for CRC if nregs > 1 uint16_t rreg_addr_opcode = CMD_RREG | (address << 7) | ((nregs - 1) & MASK_CMD_RW_REG_COUNT); // Combine RREG command, address and register count { CHIP_SELECT setup_frame(rx_frame, rreg_nwords); set_frame_word(rx_frame, 0, rreg_addr_opcode); add_crc(rx_frame); ESP_LOGVV(TAG, "%s\n", rwreg_command_frame_to_string(rx_frame).c_str()); ESP_LOGVV(TAG, "Sending Frame: %s", frame_to_string(rx_frame).c_str()); transfer_array(rx_frame); // send RREG } delay_microseconds_safe(T_REGACQ); { CHIP_SELECT setup_frame(rx_frame, response_nwords); transfer_array(rx_frame); } ESP_LOGVV(TAG, "Recv Frame: %s", frame_to_string(rx_frame).c_str()); if (nregs > 1) { bool crc_ok = check_crc(rx_frame); if (!crc_ok) { ESP_LOGV(TAG, "Recv Frame: %s", frame_to_string(rx_frame).c_str()); ESP_LOGW(TAG, "CRC failed reading ADC registers!"); result.clear(); return result; // invalid } } result.resize(nregs); int offset = 0; uint16_t rreg_ack = rreg_addr_opcode; // not really an ack, but since we don't get an ack when reading 1 register we // pretend this is the ack if (nregs > 1) { offset = 1; rreg_ack = get_unsigned_frame_word(rx_frame, 0, true); } nregs = (rreg_ack & MASK_CMD_RW_REG_COUNT) + 1; uint16_t start_addr = (rreg_ack & MASK_CMD_RW_REG_ADDRESS) >> 7; for (int i = 0; i < nregs; i++) { result[i] = get_unsigned_frame_word(rx_frame, i + offset, true); } print_command_response_to_string(rreg_addr_opcode, rx_frame); delay_microseconds_safe(1); return result; } bool ADS131M08Hub::set_reg_osr() { uint16_t mask = MASK_CLOCK_OSR; uint16_t value = this->osr_ << 2; return adc_register_write_masked(REG_CLOCK, value, mask, __LINE__); } uint16_t ADS131M08Hub::convert_value_to_adc_osr(uint16_t osr_value) { uint16_t adc_osr = OSR_1024; // default if (osr_value <= 16256 && osr_value >= 128) { if (osr_value == 16256) adc_osr = OSR_16256; else { adc_osr = OSR_128; auto tmp = osr_value / 128; while (tmp >>= 1) adc_osr++; } } return adc_osr; } uint16_t ADS131M08Hub::convert_adc_osr_to_value(uint16_t adc_osr) { uint16_t osr_value = 1 << (7 + adc_osr); if (osr_value == 16384) { osr_value = 16256; } return osr_value; } bool ADS131M08Hub::set_channel_enable(uint8_t channel, bool enable) { if (channel >= ADC_CHANNELS) return false; uint16_t mask = MASK_CLOCK_CH0 << channel; uint16_t value = (enable) ? mask : 0; return adc_register_write_masked(REG_CLOCK, value, 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; return adc_register_write_masked(reg, value, mask, __LINE__); } bool ADS131M08Hub::set_global_chop(uint16_t global_chop) { uint16_t mask = MASK_CFG_GC_EN; uint16_t value = global_chop << 8; //this->settings.reg.cfg = value & mask; return adc_register_write_masked(REG_CFG, value, mask, __LINE__); } bool ADS131M08Hub::set_global_chop_delay(uint16_t delay) { uint16_t mask = MASK_CFG_GC_DLY; uint16_t value = delay << 9; return adc_register_write_masked(REG_CFG, value, mask, __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; uint16_t mask = MASK_CHX_CFG_PHASE; uint16_t value = phase_offset << 6; return adc_register_write_masked(reg_addr, value, mask, __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 mask = MASK_CHX_CFG_DCBLKX_DIS; uint16_t value = (disable) ? mask : 0; //ESP_LOGI(TAG, "Setting DC BLOCK of Ch%d reg:%02X to %s [%04X]", channel, reg_addr, (disable ? "disable" : "enable"), value); return adc_register_write_masked(reg_addr, value, mask, __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; uint16_t mask = MASK_CHX_CFG_MUX; uint16_t value = input; return adc_register_write_masked(reg_addr, value, mask, __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; uint16_str masks = {MASK_CHX_OCAL_MSB, MASK_CHX_OCAL_LSB}; uint16_str values = {MSB, LSB}; return adc_register_write_masked(reg_addr, values, masks, __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; uint16_str masks = {MASK_CHX_GCAL_MSB, MASK_CHX_GCAL_LSB}; uint16_str values = {MSB, LSB}; return adc_register_write_masked(reg_addr, values, masks, __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; //} uint16_t osr = convert_adc_osr_to_value((data & MASK_CLOCK_OSR) >> 2); 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