#pragma once // using external voltage reference #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/optional.h" #include "ads131m08_defs.h" #include "uint_str.h" #include #include #include #include "esphome/components/spi/spi.h" #include "esphome/components/sensor/sensor.h" // // #include // #include // #include // #include // #include namespace esphome { namespace ads131m08 { static const uint8_t DEFAULT_WORD_LENGTH = 32; // we use 32 bit to allow for DMA transfers static const int ADC_CHANNELS = 8; static const int MAX_CHANNELS = 15; // for debugging static const int SAMPLE_TIME_SENSOR = 8; // for debugging static const int MAX_SAMPLES_SENSOR = 9; // for debugging static const int CRC_ERRORS_SENSOR = 10; // for debugging static const int ISR_COUNT_SENSOR = 11; // for debugging static const int ITERATIONS_SENSOR = 12; // for debugging static const int NOT_READY_SENSOR = 13; // for debugging static const int SPS_SENSOR = 14; // for debugging // #define SET_IRAM IRAM_ATTR #define SET_IRAM typedef std::vector spiframe; typedef uint_str::Ty_string uint16_str; // we want to use the stack to pass small arrays typedef uint_str::Ty_string float_str; // we want to use the stack to pass small arrays class ADS131M08Hub : public Component, public spi::SPIDevice { using Base = spi::SPIDevice; friend class ads131m08_select; public: // register config values used static constexpr uint16_t reg_clock_cfg = MASK_CLOCK_EXTREF_EN | PM_HIGH_RESOLUTION; // OSR must be added static constexpr uint16_t reg_clock_allch_on = MASK_CLOCK_ALLCH | reg_clock_cfg; static constexpr uint16_t reg_clock_allch_off = ~MASK_CLOCK_ALLCH & reg_clock_cfg; static constexpr uint16_t reg_mode_cfg = DRDY_FMT_PULSE | TIMEOUT_ENABLED | CRC_REC_ENABLE | CRC_REG_ENABLE; static constexpr uint16_t reg_mode_cfg16 = WLENGTH_16_BITS | reg_mode_cfg; static constexpr uint16_t reg_mode_cfg24 = WLENGTH_24_BITS | reg_mode_cfg; static constexpr uint16_t reg_mode_cfg32 = WLENGTH_32_BITS_MSB_SIGN_EXTEND | reg_mode_cfg; // masks for the above static constexpr uint16_t reg_clock_cfg_mask = MASK_CLOCK_EXTREF_EN | MASK_CLOCK_OSR | MASK_CLOCK_PWR; static constexpr uint16_t reg_clock_allch_mask = MASK_CLOCK_ALLCH_OFF | reg_clock_cfg_mask; static constexpr uint16_t reg_mode_cfg_mask = MASK_MODE_WLENGTH | MASK_MODE_DRDY_FMT | MASK_MODE_TIMEOUT | MASK_MODE_RX_CRC_EN | MASK_MODE_REG_CRC_EN; struct adc_channel_settings { uint16_t ch_cfg; uint16_t ch_ocal_msb; uint16_t ch_ocal_lsb; uint16_t ch_gcal_msb; uint16_t ch_gcal_lsb; }; struct adc_register_settings { uint16_t mode; uint16_t clock; uint16_t gain1; uint16_t gain2; uint16_t cfg; uint16_t threshold_msb; uint16_t threshold_lsb; adc_channel_settings chan[8]; //uint16_t regmap_crc; }; union regmap { adc_register_settings reg; uint16_t data[sizeof(adc_register_settings)/2]; }; // Semaphore handles SemaphoreHandle_t data_ready_semhandle = NULL; SemaphoreHandle_t update_avg = NULL; static constexpr int numFrameWords = 10; // Number of words in a full ADS131M08 SPI frame bool firstRead = true; // Flag to tell us if we are reading ADC data for the first time // signed long adcData; // Location where DMA will store ADC data in memory, length defined elsewhere // ADS131M08Hub(); regmap settings; void txf_init(); bool adc_initialize(uint8_t word_length); bool adc_restore_registers(); bool adc_backup_registers(); bool adc_set_word_length(uint8_t word_length); void setup() override; void loop() override; void set_drdy_pin(InternalGPIOPin *pin) { drdy_pin_ = pin; } void set_sync_reset_pin(InternalGPIOPin *pin) { sync_reset_pin_ = pin; } void set_clock_frequency(float clock_frequency) { this->clock_frequency_ = clock_frequency; } void register_sensor_ac(int channel, sensor::Sensor *s) { sensors_ac[channel] = s; } void register_sensor_dc(int channel, sensor::Sensor *s) { sensors_dc[channel] = s; } void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } void set_osr(uint16_t osr) { this->osr_ = osr; } void dump_config() override; bool set_channel_enable(uint8_t channel, bool enable); bool set_global_chop(uint16_t global_chop); bool set_global_chop_delay(uint16_t delay); bool set_channel_input_selection(uint8_t channel, ADC_INPUT_CHANNEL_MUX input); bool set_channel_offset_calibration(uint8_t channel, int32_t offset); bool set_channel_gain_calibration(uint8_t channel, uint32_t gain); bool set_channel_phase_calibration(uint8_t channel, int16_t phase_offset); bool set_dcblock_filter_disable(uint8_t channel, bool disable); bool set_channel_gain(uint8_t channel, uint8_t gain); bool set_measure_rms(uint8_t channel, bool enable); float get_sampled_value(uint8_t channel) { return sampled_values_[channel]; } uint32_t get_filter_settling_time(); //float get_average(uint8_t channel, bool read_ac); bool set_reg_osr(); uint16_t convert_value_to_adc_osr(uint16_t osr_value); uint16_t convert_adc_osr_to_value(uint16_t adc_osr); uint32_t convert_adc_clock_cycles_to_micros(uint32_t cycles); protected: HighFrequencyLoopRequester high_freq_; static void IRAM_ATTR isr_handler(ADS131M08Hub *arg); float reference_voltage_; float clock_frequency_; InternalGPIOPin *drdy_pin_; InternalGPIOPin *sync_reset_pin_ = {nullptr}; sensor::Sensor *sensors_ac[MAX_CHANNELS] = {nullptr}; sensor::Sensor *sensors_dc[MAX_CHANNELS] = {nullptr}; float sampled_values_[ADC_CHANNELS]; bool rms_enabled_[MAX_CHANNELS]; bool rms_calc_req_{false}; uint16_t osr_{3}; uint8_t update_adc_word_length(); uint8_t update_adc_word_length(uint16_t status); void SET_IRAM read_single(); float_str SET_IRAM read_multi(); bool adc_lock(bool enable); bool adc_register_write(uint16_t address, uint16_t data, int line, bool backup_date = true); bool adc_register_write(uint16_t address, const uint16_str &data, int line, bool backup_date = true); uint16_str adc_register_read(uint8_t address, uint8_t nregs); bool adc_register_write_masked(uint8_t address, uint16_t value, uint16_t mask, int line); bool adc_register_write_masked(uint8_t start_address, const uint16_str &values, const uint16_str &masks, int line); bool adc_reset_retry(); bool adc_soft_reset(); uint32_t adc_hard_reset(); uint32_t adc_sync(); void SET_IRAM transfer_array(spiframe &frame); //void transfer_array(const spiframe &data_out, spiframe &data_in); uint16_t SET_IRAM get_crc(const spiframe &frame); bool SET_IRAM check_crc(const spiframe &frame_with_crc); size_t SET_IRAM add_crc(spiframe &frame); uint16_t SET_IRAM read_frame_crc(const spiframe &frame); uint16_t SET_IRAM crc(uint16_t crc_register, uint8_t data); bool SET_IRAM set_frame_word(spiframe &frame, int w_index, uint16_t data); // write 16 bit data to first two bytes of word bool SET_IRAM set_frame_word(spiframe &frame, int w_index, uint32_t data); uint32_t SET_IRAM get_unsigned_frame_word(const spiframe &frame, int w_index, bool force_16bits = false); int32_t SET_IRAM get_sign_ext_frame_word(const spiframe &frame, int w_index); //bool update_averages(uint8_t channel, float rms_ac, float rms_dc); std::atomic adc_init_{0}; std::atomic cs_ctr_{ 0}; // Counter to track nested CS enable/disable calls for proper handling of multiple transfers in a row std::atomic isr_ctr_{0}; void enable(const char *txt); void disable(const char *txt); struct chipselect { ADS131M08Hub *parent_{nullptr}; const char *txt_; chipselect(ADS131M08Hub *parent, const char *txt = nullptr) { parent_ = parent; if (parent_ != nullptr) txt_ = txt; parent_->enable(txt_); } ~chipselect() { if (parent_ != nullptr) { parent_->disable(txt_); parent_ = nullptr; } } }; float update_conversion_factor(); //DRAM_ATTR static spiframe rx_frame; inline void zero_fill(spiframe& frame) { std::fill(frame.begin(), frame.end(), 0); } inline size_t setup_frame(spiframe& frame, uint8_t word_count) { int word_nbytes = this->adc_word_length_ >> 3; frame.resize(word_nbytes * word_count); zero_fill(frame); return frame.size(); } private: uint8_t update_state_[MAX_CHANNELS] = {0, 0, 0, 0, 0, 0, 0, 0}; uint8_t adc_word_length_{24}; float conversion_factor_{1.25 / 8388608.0}; // updated when word length changes const uint32_t sample_time_ = 40000; // 40 ms float sps_{0.0f}; uint32_t num_samples_[ADC_CHANNELS]; float sample_sum_[ADC_CHANNELS]; float sample_squared_sum_[ADC_CHANNELS]; float avg_dc_[MAX_CHANNELS]; float avg_ac_[MAX_CHANNELS]; // for debug only std::string frame_to_string(const spiframe &frame); std::string frame_words_to_string(const spiframe &frame, int index, int count); std::string conversion_frame_to_string(const spiframe &frame); std::string command_to_string(uint16_t cmdadr); void print_command_response_to_string(uint16_t cmdadr_sent, const spiframe &frame); std::string rwreg_command_frame_to_string(const spiframe &frame); std::string status_to_string(uint16_t response); std::string reg_data_to_string(int address, uint16_t data, bool nameonly = false); std::string reg_addr_to_string(int address); std::string reg_config_to_string(int address, uint16_t data, bool nameonly = false); // temp int first_read_data{0}; }; #ifndef CHIP_SELECT #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #define CHIP_SELECT volatile chipselect cs(this, __FUNCTION__); #else #define CHIP_SELECT volatile chipselect cs(this); #endif #define CHIP_SELECTx volatile chipselect cs(this); #endif } // namespace ads131m08 } // namespace esphome