245 lines
10 KiB
C++
245 lines
10 KiB
C++
#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 <string>
|
|
#include <charconv>
|
|
#include <format>
|
|
|
|
#include "esphome/components/spi/spi.h"
|
|
#include "esphome/components/sensor/sensor.h"
|
|
//
|
|
// #include <vector>
|
|
// #include <esphome/core/gpio.h>
|
|
// #include <freertos/FreeRTOS.h>
|
|
// #include <freertos/semphr.h>
|
|
// #include <atomic>
|
|
|
|
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<uint8_t> spiframe;
|
|
typedef uint_str<uint16_t>::Ty_string uint16_str; // we want to use the stack to pass small arrays
|
|
typedef uint_str<float>::Ty_string float_str; // we want to use the stack to pass small arrays
|
|
|
|
class ADS131M08Hub : public Component,
|
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING,
|
|
spi::DATA_RATE_8MHZ> {
|
|
|
|
using Base = spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_8MHZ>;
|
|
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<int> adc_init_{0};
|
|
std::atomic<int> cs_ctr_{
|
|
0}; // Counter to track nested CS enable/disable calls for proper handling of multiple transfers in a row
|
|
std::atomic<uint16_t> 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
|