adding to local git repo
This commit is contained in:
commit
13bfb6676f
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
__pycache__/*
|
||||
__pycache__/*.*
|
||||
|
||||
0
ac_voltage_sensor/__init__.py
Normal file
0
ac_voltage_sensor/__init__.py
Normal file
73
ac_voltage_sensor/ac_voltage_sensor.cpp
Normal file
73
ac_voltage_sensor/ac_voltage_sensor.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "ac_voltage_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_voltage_sensor {
|
||||
|
||||
static const char *const TAG = "ac_voltage_sensor";
|
||||
|
||||
void AC_Voltage_Sensor::dump_config() {
|
||||
LOG_SENSOR("", "AC Voltage Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void AC_Voltage_Sensor::update() {
|
||||
// Update only starts the sampling phase, in loop() the actual sampling is happening.
|
||||
|
||||
// Request a high loop() execution interval during sampling phase.
|
||||
this->high_freq_.start();
|
||||
|
||||
// Set timeout for ending sampling phase
|
||||
this->set_timeout("read", this->sample_duration_, [this]() {
|
||||
this->is_sampling_ = false;
|
||||
this->high_freq_.stop();
|
||||
|
||||
if (this->num_samples_ == 0) {
|
||||
// Shouldn't happen, but let's not crash if it does.
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_;
|
||||
const float rms_dc = this->sample_sum_ / this->num_samples_;
|
||||
const float rms_ac_squared = rms_ac_dc_squared - rms_dc * rms_dc;
|
||||
float rms_ac = 0;
|
||||
if (rms_ac_squared > 0)
|
||||
rms_ac = std::sqrt(rms_ac_squared);
|
||||
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fV, DC: %.3fV after %" PRIu32 " different samples (%" PRIu32 " SPS)",
|
||||
this->name_.c_str(), rms_ac, rms_dc, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
||||
this->publish_state(rms_ac);
|
||||
});
|
||||
|
||||
// Set sampling values
|
||||
this->last_value_ = 0.0;
|
||||
this->num_samples_ = 0;
|
||||
this->sample_sum_ = 0.0f;
|
||||
this->sample_squared_sum_ = 0.0f;
|
||||
this->is_sampling_ = true;
|
||||
}
|
||||
|
||||
void AC_Voltage_Sensor::loop() {
|
||||
if (!this->is_sampling_)
|
||||
return;
|
||||
|
||||
// Perform a single sample
|
||||
float value = this->source_->sample();
|
||||
if (std::isnan(value))
|
||||
return;
|
||||
|
||||
// Assuming a sine wave, avoid requesting values faster than the ADC can provide them
|
||||
if (this->last_value_ == value)
|
||||
return;
|
||||
this->last_value_ = value;
|
||||
this->num_samples_++;
|
||||
this->sample_sum_ += value;
|
||||
this->sample_squared_sum_ += value * value;
|
||||
}
|
||||
|
||||
} // namespace ac_voltage_sensor
|
||||
} // namespace esphome
|
||||
52
ac_voltage_sensor/ac_voltage_sensor.h
Normal file
52
ac_voltage_sensor/ac_voltage_sensor.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_voltage_sensor {
|
||||
|
||||
class AC_Voltage_Sensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void update() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override {
|
||||
// After the base sensor has been initialized
|
||||
return setup_priority::DATA - 1.0f;
|
||||
}
|
||||
|
||||
void set_sample_duration(uint32_t sample_duration) { sample_duration_ = sample_duration; }
|
||||
void set_source(voltage_sampler::VoltageSampler *source) { source_ = source; }
|
||||
|
||||
protected:
|
||||
/// High Frequency loop() requester used during sampling phase.
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
/// Duration in ms of the sampling phase.
|
||||
uint32_t sample_duration_;
|
||||
/// The sampling source to read values from.
|
||||
voltage_sampler::VoltageSampler *source_;
|
||||
|
||||
/** The DC offset of the circuit is removed from the AC value, i.e. the value in dc_component_ is subtracted from the sampled value
|
||||
*
|
||||
* Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino
|
||||
*
|
||||
* The AC component is essentially the same as the calculating the Standard-Deviation,
|
||||
* which can be done by cumulating 3 values per sample:
|
||||
* 1) Number of samples
|
||||
* 2) Sum of samples
|
||||
* 3) Sum of sample squared
|
||||
* https://en.wikipedia.org/wiki/Root_mean_square
|
||||
*/
|
||||
float last_value_ = 0.0f;
|
||||
float sample_sum_ = 0.0f;
|
||||
float sample_squared_sum_ = 0.0f;
|
||||
uint32_t num_samples_ = 0;
|
||||
bool is_sampling_ = false;
|
||||
};
|
||||
|
||||
} // namespace ac_voltage_sensor
|
||||
} // namespace esphome
|
||||
45
ac_voltage_sensor/sensor.py
Normal file
45
ac_voltage_sensor/sensor.py
Normal file
@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_SENSOR,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
# based on ctclamp by jesserockz
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
CODEOWNERS = ["@stuurmcp"]
|
||||
|
||||
CONF_SAMPLE_DURATION = "sample_duration"
|
||||
|
||||
ac_voltage_sensor_ns = cg.esphome_ns.namespace("ac_voltage_sensor")
|
||||
AC_Voltage_Sensor = ac_voltage_sensor_ns.class_("AC_Voltage_Sensor", sensor.Sensor, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
AC_Voltage_Sensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
|
||||
cv.Optional(
|
||||
CONF_SAMPLE_DURATION, default="200ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_source(sens))
|
||||
cg.add(var.set_sample_duration(config[CONF_SAMPLE_DURATION]))
|
||||
42
ads1115_int/__init__.py
Normal file
42
ads1115_int/__init__.py
Normal file
@ -0,0 +1,42 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CONF_ALERT_RDY_PIN = "alert_rdy_pin"
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_int_ns = cg.esphome_ns.namespace("ads1115_int")
|
||||
ADS1115Component = ads1115_int_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||
CONF_INTERLEAVED_MODE = "interleaved_mode"
|
||||
CONF_ADS1115_ID = "ads1115_id"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
cv.Required(CONF_ALERT_RDY_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INTERLEAVED_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(None))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
alert_rdy = await cg.gpio_pin_expression(
|
||||
config.get(CONF_ALERT_RDY_PIN)
|
||||
)
|
||||
cg.add(var.set_alert_pin(alert_rdy))
|
||||
cg.add(var.set_interleaved_mode(config[CONF_INTERLEAVED_MODE]))
|
||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||
317
ads1115_int/ads1115_int.cpp
Normal file
317
ads1115_int/ads1115_int.cpp
Normal file
@ -0,0 +1,317 @@
|
||||
#include "ads1115_int.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_int {
|
||||
|
||||
static const char *const TAG = "ads1115_int";
|
||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
static const uint8_t ADS1115_REGISTER_LO_TRESH = 0x02;
|
||||
static const uint8_t ADS1115_REGISTER_HI_TRESH = 0x03;
|
||||
|
||||
void ADS1115Component::setup()
|
||||
{
|
||||
uint16_t value;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
||||
this->mark_failed("Could not read ADS1115 conversion register on setup.");
|
||||
return;
|
||||
}
|
||||
// 1. Create a binary semaphore
|
||||
data_ready_sem = xSemaphoreCreateBinary();
|
||||
this->alert_pin_->setup();
|
||||
this->alert_pin_->attach_interrupt(&ADS1115Component::isr_handler, this, gpio::INTERRUPT_FALLING_EDGE);
|
||||
// setup with default values
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
// Setup multiplexer
|
||||
// 0bx000xxxxxxxxxxxx
|
||||
config |= ADS1115_MULTIPLEXER_P0_N1 << 12;
|
||||
|
||||
// Setup Gain
|
||||
// 0bxxxx000xxxxxxxxx
|
||||
config |= ADS1115_GAIN_6P144 << 9;
|
||||
|
||||
if(!this->continuous_mode_) {
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
}
|
||||
// Set data rate - 860 samples per second
|
||||
// 0bxxxxxxxx100xxxxx
|
||||
config |= ADS1115_860SPS << 5;
|
||||
|
||||
// // Set comparator mode - hysteresis
|
||||
// // 0bxxxxxxxxxxx0xxxx
|
||||
// config |= 0b0000000000000000;
|
||||
//
|
||||
// // Set comparator polarity - active low
|
||||
// // 0bxxxxxxxxxxxx0xxx
|
||||
// config |= 0b0000000000000000;
|
||||
//
|
||||
// // Set comparator latch enabled - false
|
||||
// // 0bxxxxxxxxxxxxx0xx
|
||||
// config |= 0b0000000000000000;
|
||||
//
|
||||
// // Set comparator que mode - disabled
|
||||
// // 0bxxxxxxxxxxxxxx11
|
||||
// config |= 0b0000000000000011;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->mark_failed("ADS1115 config register write failed on setup.");
|
||||
return;
|
||||
}
|
||||
|
||||
this->prev_config_ = config;
|
||||
if(!this->set_data_ready_mode()) {
|
||||
this->mark_failed("ADS1115 data ready mode setup failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ISR function to handle interrupt from alert pin
|
||||
// MUST be fast and non-blocking
|
||||
void IRAM_ATTR ADS1115Component::isr_handler(ADS1115Component *arg)
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(arg->data_ready_sem, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR(); // Switch to the waiting task immediately
|
||||
}
|
||||
}
|
||||
|
||||
void ADS1115Component::loop()
|
||||
{
|
||||
// Check the semaphore (0 timeout means non-blocking)
|
||||
if (xSemaphoreTake(data_ready_sem, 0) == pdTRUE) {
|
||||
// Perform the I2C read here safely outside of ISR
|
||||
this->read_request_next();
|
||||
}
|
||||
}
|
||||
|
||||
// we read data for the current mux index, then set up the next conversion
|
||||
void ADS1115Component::read_request_next()
|
||||
{
|
||||
uint16_t config = 0x0000;
|
||||
// We do not support multiple ads1115 sharing the same alert/rdy pin, however we still read the ads1115 config register to get the multiplexer value. This allows us to also check the operational status (OS) bit.
|
||||
// If OS bit set, i.e. the conversion is done meaning we can read the data; this is valid only in single shot mode, in continuous mode we can read data without checking the status bit
|
||||
//ESP_LOGD(TAG, "Config: 0x%04X", this->prev_config_);
|
||||
//ESP_LOGD(TAG, "I2C bus frequency: %.1f kHz", bus_->bus_frequency_ / 1000.0f);
|
||||
if(this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) {
|
||||
//ESP_LOGD(TAG, "Config register: 0x%04X", config);
|
||||
int ads_mux = (config >> 12) & 0b111;
|
||||
bool conversion_done = (config >> 15) == 1 || this->continuous_mode_;
|
||||
//ESP_LOGD(TAG, "Current MUX setting according to ADS1115: %d", ads_mux);
|
||||
int ads_mux_index = get_mux_index(static_cast<ADS1115Multiplexer>(ads_mux));
|
||||
//ESP_LOGD(TAG, "Current MUX index according to ADS1115: %d", ads_mux_index);
|
||||
if (conversion_done && ads_mux_index != -1) {
|
||||
if(this->interleaved_mode_) {
|
||||
// in interleaved mode, we request the next conversion already here
|
||||
int next_index = next_mux_data_index(ads_mux_index);
|
||||
if(next_index != -1) {
|
||||
this->start_conversion(next_index);
|
||||
}
|
||||
}
|
||||
int16_t raw_value = 0;
|
||||
double v = this->read_data(raw_value, ads_mux_index);
|
||||
if (!std::isnan(v)) {
|
||||
auto sensor_ptr = this->muxdata_[ads_mux_index].sensor_ptr;
|
||||
ESP_LOGV(TAG, "'% -18s': Raw=% 6d %fV", sensor_ptr->get_name().c_str(), raw_value, v);
|
||||
sensor_ptr->publish_state(v);
|
||||
}
|
||||
if(!this->interleaved_mode_) {
|
||||
// we have read data for the current mux index, move to next
|
||||
int next_index = next_mux_data_index(ads_mux_index);
|
||||
if(next_index != -1) {
|
||||
this->start_conversion(next_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ADS1115Component::get_mux_index(ADS1115Multiplexer multiplexer)
|
||||
{
|
||||
for(int i=0; i<4; i++) {
|
||||
if(this->muxdata_[i].multiplexer == multiplexer) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // not found
|
||||
}
|
||||
|
||||
double ADS1115Component::read_data(int16_t& raw_value, int mux_index)
|
||||
{
|
||||
raw_value = 0;
|
||||
uint16_t raw_conversion;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
ADS1115Resolution resolution = this->muxdata_[mux_index].resolution;
|
||||
// Adjust for 12-bit resolution if needed
|
||||
if (resolution == ADS1015_12_BITS) {
|
||||
bool negative = (raw_conversion >> 15) == 1;
|
||||
|
||||
// shift raw_conversion as it's only 12-bits, left justified
|
||||
raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS);
|
||||
|
||||
// check if number was negative in order to keep the sign
|
||||
if (negative) {
|
||||
// the number was negative
|
||||
// 1) set the negative bit back
|
||||
raw_conversion |= 0x8000;
|
||||
// 2) reset the former (shifted) negative bit
|
||||
raw_conversion &= 0xF7FF;
|
||||
}
|
||||
}
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
raw_value = signed_conversion;
|
||||
double millivolts;
|
||||
double divider = (resolution == ADS1115_16_BITS) ? 32768.0f : 2048.0f;
|
||||
switch (this->muxdata_[mux_index].gain) {
|
||||
case ADS1115_GAIN_6P144:
|
||||
millivolts = (signed_conversion * 6144) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_4P096:
|
||||
millivolts = (signed_conversion * 4096) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_2P048:
|
||||
millivolts = (signed_conversion * 2048) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_1P024:
|
||||
millivolts = (signed_conversion * 1024) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P512:
|
||||
millivolts = (signed_conversion * 512) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P256:
|
||||
millivolts = (signed_conversion * 256) / divider;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return millivolts / 1e3f;
|
||||
}
|
||||
|
||||
// Method to set mux data for later use, i.e. when data is to be read
|
||||
int ADS1115Component::set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution, ADS1115Samplerate samplerate, sensor::Sensor *sensor_ptr)
|
||||
{
|
||||
int index = -1;
|
||||
for(int i=0; i<4; i++) {
|
||||
if(this->muxdata_[i].multiplexer == multiplexer) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
if(this->muxdata_[i].multiplexer == ADS1115_MULTIPLEXER_INVALID && index == -1) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if(index != -1) {
|
||||
this->muxdata_[index].multiplexer = multiplexer;
|
||||
this->muxdata_[index].gain = gain;
|
||||
this->muxdata_[index].resolution = resolution;
|
||||
this->muxdata_[index].samplerate = samplerate;
|
||||
this->muxdata_[index].sensor_ptr = sensor_ptr;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
bool ADS1115Component::set_data_ready_mode()
|
||||
{
|
||||
uint16_t config = this->prev_config_;
|
||||
// Set comparator que mode - assert after one conversion
|
||||
// 0bxxxxxxxxxxxxxx00
|
||||
config &= 0b1111111111111100;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
return false;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
if(!this->write_byte_16(ADS1115_REGISTER_HI_TRESH, 0x8000)) {
|
||||
return false;
|
||||
}
|
||||
if(!this->write_byte_16(ADS1115_REGISTER_LO_TRESH, 0x0000)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ADS1115Component::start_conversion(int mux_index)
|
||||
{
|
||||
auto& muxdata = this->muxdata_[mux_index];
|
||||
auto multiplexer = muxdata.multiplexer;
|
||||
if (multiplexer != ADS1115_MULTIPLEXER_INVALID) {
|
||||
auto gain = muxdata.gain;
|
||||
auto resolution = muxdata.resolution;
|
||||
auto samplerate = muxdata.samplerate;
|
||||
uint16_t config = this->prev_config_;
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
config &= 0b1000111111111111;
|
||||
config |= (multiplexer & 0b111) << 12;
|
||||
|
||||
// Gain
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
config &= 0b1111000111111111;
|
||||
config |= (gain & 0b111) << 9;
|
||||
|
||||
// Sample rate
|
||||
// 0bxxxxxxxxBBBxxxxx
|
||||
config &= 0b1111111100011111;
|
||||
config |= (samplerate & 0b111) << 5;
|
||||
|
||||
if(!this->continuous_mode_) {
|
||||
// Set single-shot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
}
|
||||
else {
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
}
|
||||
// Start conversion; this has no effect in continuous mode
|
||||
config |= 0b1000000000000000;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return false;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int ADS1115Component::next_mux_data_index(int start_index)
|
||||
{
|
||||
int result = start_index;
|
||||
do {
|
||||
result = (result + 1) % 4;
|
||||
if(this->muxdata_[result].multiplexer != ADS1115_MULTIPLEXER_INVALID) {
|
||||
return result;
|
||||
}
|
||||
} while(result != start_index);
|
||||
return -1; // no valid mux data found
|
||||
}
|
||||
|
||||
void ADS1115Component::dump_config()
|
||||
{
|
||||
ESP_LOGCONFIG(TAG, "ADS1115_INT:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
ESP_LOGCONFIG(TAG, " Interleaved mode: %s", YESNO(this->interleaved_mode_));
|
||||
ESP_LOGCONFIG(TAG, " Continuous mode: %s", YESNO(this->continuous_mode_));
|
||||
LOG_PIN(" ALERT Pin:", this->alert_pin_);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace ads1115_int
|
||||
} // namespace esphome
|
||||
93
ads1115_int/ads1115_int.h
Normal file
93
ads1115_int/ads1115_int.h
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h" // Ensure this header defines i2c::I2CDevice
|
||||
#include "esphome/core/component.h" // Ensure this header defines Component
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
#include <esphome/core/gpio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_int {
|
||||
|
||||
enum ADS1115Multiplexer {
|
||||
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
|
||||
ADS1115_MULTIPLEXER_P0_N3 = 0b001,
|
||||
ADS1115_MULTIPLEXER_P1_N3 = 0b010,
|
||||
ADS1115_MULTIPLEXER_P2_N3 = 0b011,
|
||||
ADS1115_MULTIPLEXER_P0_NG = 0b100,
|
||||
ADS1115_MULTIPLEXER_P1_NG = 0b101,
|
||||
ADS1115_MULTIPLEXER_P2_NG = 0b110,
|
||||
ADS1115_MULTIPLEXER_P3_NG = 0b111,
|
||||
ADS1115_MULTIPLEXER_INVALID = 0xFF
|
||||
};
|
||||
|
||||
enum ADS1115Gain {
|
||||
ADS1115_GAIN_6P144 = 0b000,
|
||||
ADS1115_GAIN_4P096 = 0b001,
|
||||
ADS1115_GAIN_2P048 = 0b010,
|
||||
ADS1115_GAIN_1P024 = 0b011,
|
||||
ADS1115_GAIN_0P512 = 0b100,
|
||||
ADS1115_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
enum ADS1115Resolution {
|
||||
ADS1115_16_BITS = 16,
|
||||
ADS1015_12_BITS = 12,
|
||||
};
|
||||
|
||||
enum ADS1115Samplerate {
|
||||
ADS1115_8SPS = 0b000,
|
||||
ADS1115_16SPS = 0b001,
|
||||
ADS1115_32SPS = 0b010,
|
||||
ADS1115_64SPS = 0b011,
|
||||
ADS1115_128SPS = 0b100,
|
||||
ADS1115_250SPS = 0b101,
|
||||
ADS1115_475SPS = 0b110,
|
||||
ADS1115_860SPS = 0b111
|
||||
};
|
||||
|
||||
// we will force single-shot mode for interrupt operation
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
private:
|
||||
struct mux_data_item {
|
||||
ADS1115Multiplexer multiplexer;
|
||||
ADS1115Gain gain;
|
||||
ADS1115Resolution resolution;
|
||||
ADS1115Samplerate samplerate;
|
||||
sensor::Sensor *sensor_ptr;
|
||||
mux_data_item() { multiplexer = ADS1115_MULTIPLEXER_INVALID; }
|
||||
};
|
||||
int mux_data_index_{0};
|
||||
mux_data_item muxdata_[4];
|
||||
int next_mux_data_index(int start_index);
|
||||
void read_request_next();
|
||||
int get_mux_index(ADS1115Multiplexer multiplexer);
|
||||
|
||||
public:
|
||||
// Semaphore handle
|
||||
SemaphoreHandle_t data_ready_sem = NULL;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
double read_data(int16_t& raw_value, int mux_index);
|
||||
bool start_conversion(int mux_index);
|
||||
void set_alert_pin(InternalGPIOPin *pin) { alert_pin_ = pin; }
|
||||
void set_interleaved_mode(bool interleaved) { this->interleaved_mode_ = interleaved; }
|
||||
void set_continuous_mode(bool continuous) { this->continuous_mode_ = continuous; }
|
||||
bool set_data_ready_mode();
|
||||
int set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution, ADS1115Samplerate samplerate, sensor::Sensor *sensor_ptr);
|
||||
|
||||
protected:
|
||||
bool interleaved_mode_{false};
|
||||
bool continuous_mode_{false};
|
||||
uint16_t prev_config_{0};
|
||||
InternalGPIOPin *alert_pin_;
|
||||
static void IRAM_ATTR isr_handler(ADS1115Component *arg);
|
||||
|
||||
};
|
||||
|
||||
} // namespace ads1115_int
|
||||
} // namespace esphome
|
||||
98
ads1115_int/sensor/__init__.py
Normal file
98
ads1115_int/sensor/__init__.py
Normal file
@ -0,0 +1,98 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor #, voltage_sampler
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
CONF_MULTIPLEXER,
|
||||
CONF_RESOLUTION,
|
||||
CONF_SAMPLE_RATE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_int_ns
|
||||
|
||||
#AUTO_LOAD = ["voltage_sampler"]
|
||||
DEPENDENCIES = ["ads1115_int"]
|
||||
|
||||
ADS1115Multiplexer = ads1115_int_ns.enum("ADS1115Multiplexer")
|
||||
MUX = {
|
||||
"A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
"A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
"A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
"A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
"A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
"A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
"A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
"A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = ads1115_int_ns.enum("ADS1115Gain")
|
||||
GAIN = {
|
||||
"6.144": ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
"4.096": ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
"2.048": ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
"1.024": ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
"0.512": ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
ADS1115Resolution = ads1115_int_ns.enum("ADS1115Resolution")
|
||||
RESOLUTION = {
|
||||
"16_BITS": ADS1115Resolution.ADS1115_16_BITS,
|
||||
"12_BITS": ADS1115Resolution.ADS1015_12_BITS,
|
||||
}
|
||||
|
||||
ADS1115Samplerate = ads1115_int_ns.enum("ADS1115Samplerate")
|
||||
SAMPLERATE = {
|
||||
"8": ADS1115Samplerate.ADS1115_8SPS,
|
||||
"16": ADS1115Samplerate.ADS1115_16SPS,
|
||||
"32": ADS1115Samplerate.ADS1115_32SPS,
|
||||
"64": ADS1115Samplerate.ADS1115_64SPS,
|
||||
"128": ADS1115Samplerate.ADS1115_128SPS,
|
||||
"250": ADS1115Samplerate.ADS1115_250SPS,
|
||||
"475": ADS1115Samplerate.ADS1115_475SPS,
|
||||
"860": ADS1115Samplerate.ADS1115_860SPS,
|
||||
}
|
||||
|
||||
ADS1115Sensor = ads1115_int_ns.class_(
|
||||
"ADS1115Sensor", sensor.Sensor, cg.Component #, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
ADS1115Sensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||
cv.Required(CONF_GAIN): cv.enum(GAIN, string=True),
|
||||
cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
|
||||
RESOLUTION, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="860"): cv.enum(
|
||||
SAMPLERATE, string=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_ADS1115_ID])
|
||||
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
cg.add(var.set_samplerate(config[CONF_SAMPLE_RATE]))
|
||||
28
ads1115_int/sensor/ads1115_int_sensor.cpp
Normal file
28
ads1115_int/sensor/ads1115_int_sensor.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "ads1115_int_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_int {
|
||||
|
||||
static const char *const TAG = "ads1115_int.sensor";
|
||||
|
||||
void ADS1115Sensor::loop()
|
||||
{
|
||||
if(this->first_reading_) {
|
||||
int mux_index = this->parent_->set_mux_data(this->multiplexer_, this->gain_, this->resolution_, this->samplerate_, this);
|
||||
this->first_reading_ = false;
|
||||
this->parent_->start_conversion(mux_index); // this will set off the first conversion; subsequent conversions will be triggered by the ISR
|
||||
}
|
||||
}
|
||||
|
||||
void ADS1115Sensor::dump_config() {
|
||||
LOG_SENSOR(" ", "ADS1115 Int Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_);
|
||||
ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_);
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
37
ads1115_int/sensor/ads1115_int_sensor.h
Normal file
37
ads1115_int/sensor/ads1115_int_sensor.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
//#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#include "../ads1115_int.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_int {
|
||||
|
||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||
class ADS1115Sensor : public sensor::Sensor,
|
||||
public Component,
|
||||
//public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1115Component> {
|
||||
public:
|
||||
void loop() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { this->gain_ = gain; }
|
||||
void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; }
|
||||
void set_samplerate(ADS1115Samplerate samplerate) { this->samplerate_ = samplerate; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
ADS1115Resolution resolution_;
|
||||
ADS1115Samplerate samplerate_;
|
||||
bool first_reading_{true};
|
||||
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
32
ads1115_pol/__init__.py
Normal file
32
ads1115_pol/__init__.py
Normal file
@ -0,0 +1,32 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_pol_ns = cg.esphome_ns.namespace("ads1115_pol")
|
||||
ADS1115Component = ads1115_pol_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||
CONF_ADS1115_ID = "ads1115_id"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(None))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||
223
ads1115_pol/ads1115_pol.cpp
Normal file
223
ads1115_pol/ads1115_pol.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
#include "ads1115_pol.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_pol {
|
||||
|
||||
static const char *const TAG = "ads1115_pol";
|
||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
void ADS1115Component::setup() {
|
||||
uint16_t value;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
||||
this->mark_failed(/*LOG_STR*/("Could not read ADS1115 conversion register on setup."));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
// Setup multiplexer
|
||||
// 0bx000xxxxxxxxxxxx
|
||||
config |= ADS1115_MULTIPLEXER_P0_N1 << 12;
|
||||
|
||||
// Setup Gain
|
||||
// 0bxxxx000xxxxxxxxx
|
||||
config |= ADS1115_GAIN_6P144 << 9;
|
||||
|
||||
if (this->continuous_mode_) {
|
||||
// Set continuous mode
|
||||
// 0bxxxxxxx0xxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
} else {
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
}
|
||||
|
||||
// Set data rate - 860 samples per second
|
||||
// 0bxxxxxxxx100xxxxx
|
||||
config |= ADS1115_860SPS << 5;
|
||||
|
||||
// Set comparator mode - hysteresis
|
||||
// 0bxxxxxxxxxxx0xxxx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator polarity - active low
|
||||
// 0bxxxxxxxxxxxx0xxx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator latch enabled - false
|
||||
// 0bxxxxxxxxxxxxx0xx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator que mode - disabled
|
||||
// 0bxxxxxxxxxxxxxx11
|
||||
config |= 0b0000000000000011;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->mark_failed(/*LOG_STR*/("Could not write to ADS1115 config register on setup."));
|
||||
return;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
}
|
||||
void ADS1115Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADS1115_pol:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
float ADS1115Component::request_measurement(int16_t& raw_value, ADS1115Multiplexer multiplexer, ADS1115Gain gain,
|
||||
ADS1115Resolution resolution, ADS1115Samplerate samplerate) {
|
||||
uint16_t config = this->prev_config_;
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
config &= 0b1000111111111111;
|
||||
config |= (multiplexer & 0b111) << 12;
|
||||
|
||||
// Gain
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
config &= 0b1111000111111111;
|
||||
config |= (gain & 0b111) << 9;
|
||||
|
||||
// Sample rate
|
||||
// 0bxxxxxxxxBBBxxxxx
|
||||
config &= 0b1111111100011111;
|
||||
config |= (samplerate & 0b111) << 5;
|
||||
|
||||
if (!this->continuous_mode_) {
|
||||
// Start conversion
|
||||
config |= 0b1000000000000000;
|
||||
}
|
||||
|
||||
if (!this->continuous_mode_ || this->prev_config_ != config) {
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
// Delay calculated as: ceil((1000/SPS)+.5)
|
||||
if (resolution == ADS1015_12_BITS) {
|
||||
switch (samplerate) {
|
||||
case ADS1115_8SPS:
|
||||
delay(9);
|
||||
break;
|
||||
case ADS1115_16SPS:
|
||||
delay(5);
|
||||
break;
|
||||
case ADS1115_32SPS:
|
||||
delay(3);
|
||||
break;
|
||||
case ADS1115_64SPS:
|
||||
case ADS1115_128SPS:
|
||||
delay(2);
|
||||
break;
|
||||
default:
|
||||
delay(1);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (samplerate) {
|
||||
case ADS1115_8SPS:
|
||||
delay(126); // NOLINT
|
||||
break;
|
||||
case ADS1115_16SPS:
|
||||
delay(63); // NOLINT
|
||||
break;
|
||||
case ADS1115_32SPS:
|
||||
delay(32);
|
||||
break;
|
||||
case ADS1115_64SPS:
|
||||
delay(17);
|
||||
break;
|
||||
case ADS1115_128SPS:
|
||||
delay(9);
|
||||
break;
|
||||
case ADS1115_250SPS:
|
||||
delay(5);
|
||||
break;
|
||||
case ADS1115_475SPS:
|
||||
delay(3);
|
||||
break;
|
||||
case ADS1115_860SPS:
|
||||
delay(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// in continuous mode, conversion will always be running, rely on the delay
|
||||
// to ensure conversion is taking place with the correct settings
|
||||
// can we use the rdy pin to trigger when a conversion is done?
|
||||
if (!this->continuous_mode_) {
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t raw_conversion;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
|
||||
if (resolution == ADS1015_12_BITS) {
|
||||
bool negative = (raw_conversion >> 15) == 1;
|
||||
|
||||
// shift raw_conversion as it's only 12-bits, left justified
|
||||
raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS);
|
||||
|
||||
// check if number was negative in order to keep the sign
|
||||
if (negative) {
|
||||
// the number was negative
|
||||
// 1) set the negative bit back
|
||||
raw_conversion |= 0x8000;
|
||||
// 2) reset the former (shifted) negative bit
|
||||
raw_conversion &= 0xF7FF;
|
||||
}
|
||||
}
|
||||
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
raw_value = signed_conversion;
|
||||
float millivolts;
|
||||
float divider = (resolution == ADS1115_16_BITS) ? 32768.0f : 2048.0f;
|
||||
switch (gain) {
|
||||
case ADS1115_GAIN_6P144:
|
||||
millivolts = (signed_conversion * 6144) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_4P096:
|
||||
millivolts = (signed_conversion * 4096) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_2P048:
|
||||
millivolts = (signed_conversion * 2048) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_1P024:
|
||||
millivolts = (signed_conversion * 1024) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P512:
|
||||
millivolts = (signed_conversion * 512) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P256:
|
||||
millivolts = (signed_conversion * 256) / divider;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return millivolts / 1e3f;
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
64
ads1115_pol/ads1115_pol.h
Normal file
64
ads1115_pol/ads1115_pol.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_pol {
|
||||
|
||||
enum ADS1115Multiplexer {
|
||||
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
|
||||
ADS1115_MULTIPLEXER_P0_N3 = 0b001,
|
||||
ADS1115_MULTIPLEXER_P1_N3 = 0b010,
|
||||
ADS1115_MULTIPLEXER_P2_N3 = 0b011,
|
||||
ADS1115_MULTIPLEXER_P0_NG = 0b100,
|
||||
ADS1115_MULTIPLEXER_P1_NG = 0b101,
|
||||
ADS1115_MULTIPLEXER_P2_NG = 0b110,
|
||||
ADS1115_MULTIPLEXER_P3_NG = 0b111,
|
||||
};
|
||||
|
||||
enum ADS1115Gain {
|
||||
ADS1115_GAIN_6P144 = 0b000,
|
||||
ADS1115_GAIN_4P096 = 0b001,
|
||||
ADS1115_GAIN_2P048 = 0b010,
|
||||
ADS1115_GAIN_1P024 = 0b011,
|
||||
ADS1115_GAIN_0P512 = 0b100,
|
||||
ADS1115_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
enum ADS1115Resolution {
|
||||
ADS1115_16_BITS = 16,
|
||||
ADS1015_12_BITS = 12,
|
||||
};
|
||||
|
||||
enum ADS1115Samplerate {
|
||||
ADS1115_8SPS = 0b000,
|
||||
ADS1115_16SPS = 0b001,
|
||||
ADS1115_32SPS = 0b010,
|
||||
ADS1115_64SPS = 0b011,
|
||||
ADS1115_128SPS = 0b100,
|
||||
ADS1115_250SPS = 0b101,
|
||||
ADS1115_475SPS = 0b110,
|
||||
ADS1115_860SPS = 0b111
|
||||
};
|
||||
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// HARDWARE_LATE setup priority
|
||||
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(int16_t& raw_value, ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution,
|
||||
ADS1115Samplerate samplerate);
|
||||
|
||||
protected:
|
||||
uint16_t prev_config_{0};
|
||||
bool continuous_mode_;
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
98
ads1115_pol/sensor/__init__.py
Normal file
98
ads1115_pol/sensor/__init__.py
Normal file
@ -0,0 +1,98 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
CONF_MULTIPLEXER,
|
||||
CONF_RESOLUTION,
|
||||
CONF_SAMPLE_RATE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_pol_ns
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
DEPENDENCIES = ["ads1115_pol"]
|
||||
|
||||
ADS1115Multiplexer = ads1115_pol_ns.enum("ADS1115Multiplexer")
|
||||
MUX = {
|
||||
"A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
"A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
"A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
"A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
"A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
"A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
"A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
"A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = ads1115_pol_ns.enum("ADS1115Gain")
|
||||
GAIN = {
|
||||
"6.144": ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
"4.096": ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
"2.048": ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
"1.024": ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
"0.512": ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
ADS1115Resolution = ads1115_pol_ns.enum("ADS1115Resolution")
|
||||
RESOLUTION = {
|
||||
"16_BITS": ADS1115Resolution.ADS1115_16_BITS,
|
||||
"12_BITS": ADS1115Resolution.ADS1015_12_BITS,
|
||||
}
|
||||
|
||||
ADS1115Samplerate = ads1115_pol_ns.enum("ADS1115Samplerate")
|
||||
SAMPLERATE = {
|
||||
"8": ADS1115Samplerate.ADS1115_8SPS,
|
||||
"16": ADS1115Samplerate.ADS1115_16SPS,
|
||||
"32": ADS1115Samplerate.ADS1115_32SPS,
|
||||
"64": ADS1115Samplerate.ADS1115_64SPS,
|
||||
"128": ADS1115Samplerate.ADS1115_128SPS,
|
||||
"250": ADS1115Samplerate.ADS1115_250SPS,
|
||||
"475": ADS1115Samplerate.ADS1115_475SPS,
|
||||
"860": ADS1115Samplerate.ADS1115_860SPS,
|
||||
}
|
||||
|
||||
ADS1115Sensor = ads1115_pol_ns.class_(
|
||||
"ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
ADS1115Sensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||
cv.Required(CONF_GAIN): cv.enum(GAIN, string=True),
|
||||
cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
|
||||
RESOLUTION, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="860"): cv.enum(
|
||||
SAMPLERATE, string=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_ADS1115_ID])
|
||||
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
cg.add(var.set_samplerate(config[CONF_SAMPLE_RATE]))
|
||||
36
ads1115_pol/sensor/ads1115_pol_sensor.cpp
Normal file
36
ads1115_pol/sensor/ads1115_pol_sensor.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "ads1115_pol_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_pol {
|
||||
|
||||
static const char *const TAG = "ads1115_pol.sensor";
|
||||
|
||||
float ADS1115Sensor::sample(int16_t& raw_value) {
|
||||
return this->parent_->request_measurement(raw_value, this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
|
||||
}
|
||||
|
||||
void ADS1115Sensor::update() {
|
||||
int16_t raw_value = -1000;
|
||||
float v = this->sample(raw_value);
|
||||
if (!std::isnan(v)) {
|
||||
// if(this->count_ % 10 == 0) {
|
||||
ESP_LOGI(TAG, "'% -18s': Raw=% 6d %fV", this->get_name().c_str(), raw_value, v);
|
||||
// }
|
||||
this->count_++;
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||
this->publish_state(v);
|
||||
}
|
||||
}
|
||||
|
||||
void ADS1115Sensor::dump_config() {
|
||||
LOG_SENSOR(" ", "ADS1115_pol Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_);
|
||||
ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_);
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
38
ads1115_pol/sensor/ads1115_pol_sensor.h
Normal file
38
ads1115_pol/sensor/ads1115_pol_sensor.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#include "../ads1115_pol.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115_pol {
|
||||
|
||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||
class ADS1115Sensor : public sensor::Sensor,
|
||||
public PollingComponent,
|
||||
//public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1115Component> {
|
||||
public:
|
||||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { this->gain_ = gain; }
|
||||
void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; }
|
||||
void set_samplerate(ADS1115Samplerate samplerate) { this->samplerate_ = samplerate; }
|
||||
float sample(int16_t& raw_value);
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
ADS1115Resolution resolution_;
|
||||
ADS1115Samplerate samplerate_;
|
||||
uint64_t count_{0};
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
80
ads131m08/README.md
Normal file
80
ads131m08/README.md
Normal file
@ -0,0 +1,80 @@
|
||||
# ESPHome components/ads131m08
|
||||
|
||||
This folder contains source code files and related resources for external ads131m08 component.
|
||||
|
||||
## Example .yaml config
|
||||
|
||||
---
|
||||
|
||||
```yaml
|
||||
substitutions:
|
||||
ESP32_S3_SPI0_SCK: GPIO12
|
||||
ESP32_S3_SPI0_MISO: GPIO13
|
||||
ESP32_S3_SPI0_MOSI: GPIO11
|
||||
ADC_SYNC_RESET_PIN: GPIO21
|
||||
ADC_CS_PIN: GPIO41
|
||||
ADC_DRDY_PIN: GPIO42
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: components # Path relative to this YAML file
|
||||
components: [ ads131m08 ]
|
||||
|
||||
spi:
|
||||
- id: spi_bus0
|
||||
clk_pin: ${ESP32_S3_SPI0_SCK}
|
||||
mosi_pin: ${ESP32_S3_SPI0_MOSI}
|
||||
miso_pin: ${ESP32_S3_SPI0_MISO}
|
||||
interface: hardware
|
||||
|
||||
ads131m08:
|
||||
id: highres_adc
|
||||
spi_id: spi_bus0
|
||||
cs_pin: ${ADC_CS_PIN}
|
||||
drdy_pin: ${ADC_DRDY_PIN}
|
||||
sync_reset_pin: ${ADC_SYNC_RESET_PIN}
|
||||
reference_voltage: 1.25
|
||||
data_rate: 10MHz
|
||||
clock_frequency: 8192kHz
|
||||
oversampling_ratio: 512
|
||||
|
||||
sensor:
|
||||
- platform: ads131m08
|
||||
ads131m08_id: highres_adc
|
||||
channel: 0
|
||||
id: ads_ch0
|
||||
gain: 1
|
||||
input_select: normal
|
||||
offset_calibration: 0
|
||||
gain_calibration: 0.986
|
||||
ac:
|
||||
name: "Channel 0 AC"
|
||||
id: ads_ch0_ac
|
||||
dc:
|
||||
name: "Channel 0 DC"
|
||||
id: ads_ch0_dc
|
||||
|
||||
- platform: ads131m08
|
||||
ads131m08_id: highres_adc
|
||||
channel: 1
|
||||
id: ads_ch1
|
||||
gain: 1
|
||||
input_select: normal
|
||||
offset_calibration: 0
|
||||
gain_calibration: 0.986
|
||||
ac:
|
||||
name: "Channel 1 AC"
|
||||
id: ads_ch1_ac
|
||||
accuracy_decimals: 6
|
||||
state_class: measurement
|
||||
device_class: voltage
|
||||
dc:
|
||||
name: "Channel 1 DC"
|
||||
id: ads_ch1_dc
|
||||
accuracy_decimals: 6
|
||||
state_class: measurement
|
||||
device_class: voltage
|
||||
```
|
||||
|
||||
For more information, visit the [ESPHome documentation](https://esphome.io/).
|
||||
80
ads131m08/__init__.py
Normal file
80
ads131m08/__init__.py
Normal file
@ -0,0 +1,80 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_REFERENCE_VOLTAGE
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_DRDY_PIN = "drdy_pin"
|
||||
CONF_SYNC_RESET_PIN = "sync_reset_pin"
|
||||
CONF_CLOCK_FREQUENCY = "clock_frequency"
|
||||
CONF_ADS131M08_ID = "ads131m08_id"
|
||||
CONF_OVERSAMPLING_RATIO = "oversampling_ratio"
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
ads131m08_ns = cg.esphome_ns.namespace("ads131m08")
|
||||
ADS131M08Hub = ads131m08_ns.class_("ADS131M08Hub", cg.Component, spi.SPIDevice)
|
||||
|
||||
OSR = ads131m08_ns.enum("ADS131M08_OVERSAMPLING_RATIO")
|
||||
ALLOWED_OSRS = {
|
||||
128: OSR.OSR_128,
|
||||
256: OSR.OSR_256,
|
||||
512: OSR.OSR_512,
|
||||
1024: OSR.OSR_1024,
|
||||
2048: OSR.OSR_2048,
|
||||
4096: OSR.OSR_4096,
|
||||
8192: OSR.OSR_8192,
|
||||
16256: OSR.OSR_16256,
|
||||
}
|
||||
|
||||
ALLOWED_CLOCK_FREQUENCIES = {
|
||||
2048000: 2048000,
|
||||
4096000: 4096000,
|
||||
8192000: 8192000,
|
||||
"2048kHz": 2048000,
|
||||
"4096kHz": 4096000,
|
||||
"8192kHz": 8192000,
|
||||
"2.048MHz": 2048000,
|
||||
"4.096MHz": 4096000,
|
||||
"8.192MHz": 8192000,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS131M08Hub),
|
||||
cv.Required(CONF_DRDY_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_SYNC_RESET_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_CLOCK_FREQUENCY, default="8192kHz"): cv.All(
|
||||
cv.frequency, cv.one_of(*ALLOWED_CLOCK_FREQUENCIES.keys())
|
||||
),
|
||||
cv.Optional(CONF_OVERSAMPLING_RATIO, default=1024): cv.enum(
|
||||
ALLOWED_OSRS, int=True
|
||||
),
|
||||
cv.Optional(CONF_REFERENCE_VOLTAGE, default=1.2): cv.float_range(
|
||||
min=1.1, max=1.3
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True, default_data_rate="10MHz"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
drdy = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
|
||||
if CONF_SYNC_RESET_PIN in config:
|
||||
sync_reset = await cg.gpio_pin_expression(config[CONF_SYNC_RESET_PIN])
|
||||
cg.add(var.set_sync_reset_pin(sync_reset))
|
||||
clock_frequency = ALLOWED_CLOCK_FREQUENCIES[config[CONF_CLOCK_FREQUENCY]]
|
||||
cg.add(var.set_clock_frequency(clock_frequency))
|
||||
osr = config[CONF_OVERSAMPLING_RATIO]
|
||||
cg.add(var.set_osr(osr))
|
||||
reference_voltage = config[CONF_REFERENCE_VOLTAGE]
|
||||
cg.add(var.set_reference_voltage(reference_voltage))
|
||||
cg.add(var.set_drdy_pin(drdy))
|
||||
1720
ads131m08/ads131m08.cpp
Normal file
1720
ads131m08/ads131m08.cpp
Normal file
File diff suppressed because it is too large
Load Diff
196
ads131m08/ads131m08.h
Normal file
196
ads131m08/ads131m08.h
Normal file
@ -0,0 +1,196 @@
|
||||
#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;
|
||||
static const int MAX_CHANNELS = 12; // for debugging
|
||||
static const int ADC_CHANNELS = 8;
|
||||
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
|
||||
|
||||
// #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
|
||||
|
||||
class ADS131M08Hub : public Component,
|
||||
public 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 | OSR_1024 | PM_HIGH_RESOLUTION;
|
||||
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_cfg16 = WLENGTH_16_BITS | DRDY_FMT_PULSE | TIMEOUT_ENABLED;
|
||||
static constexpr uint16_t reg_mode_cfg24 = WLENGTH_24_BITS | DRDY_FMT_PULSE | TIMEOUT_ENABLED;
|
||||
static constexpr uint16_t reg_mode_cfg32 = WLENGTH_32_BITS_MSB_SIGN_EXTEND | DRDY_FMT_PULSE | TIMEOUT_ENABLED;
|
||||
// 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;
|
||||
// Semaphore handles
|
||||
SemaphoreHandle_t data_ready_semhandle = NULL;
|
||||
SemaphoreHandle_t update_avg = NULL;
|
||||
|
||||
const 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();
|
||||
void txf_init();
|
||||
bool adc_initialize(uint8_t word_length);
|
||||
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);
|
||||
void set_global_chop(uint16_t global_chop);
|
||||
void 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]; }
|
||||
float get_average(uint8_t channel, bool read_ac);
|
||||
bool set_reg_osr();
|
||||
|
||||
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();
|
||||
std::pair<uint32_t, uint32_t> SET_IRAM read_multi();
|
||||
bool adc_lock(bool enable);
|
||||
bool adc_register_write(uint16_t address, uint16_t data);
|
||||
bool adc_register_write(uint16_t address, const uint16_str &data);
|
||||
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();
|
||||
void adc_hard_reset();
|
||||
void adc_sync();
|
||||
void write_byte(uint8_t byte);
|
||||
bool write(uint32_t data, uint8_t adcWordLength);
|
||||
uint8_t read_byte();
|
||||
void write_array(const spiframe &data);
|
||||
void SET_IRAM read_array(spiframe &buffer);
|
||||
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();
|
||||
|
||||
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.2 / 8388608.0}; // updated when word length changes
|
||||
const uint32_t sample_time_ = 40000; // 250 ms
|
||||
float sps_{0.0f};
|
||||
uint32_t num_samples_[ADC_CHANNELS];
|
||||
int64_t 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 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
|
||||
358
ads131m08/ads131m08_defs.h
Normal file
358
ads131m08/ads131m08_defs.h
Normal file
@ -0,0 +1,358 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace ads131m08 {
|
||||
|
||||
static constexpr size_t MAX_FRAME_SIZE =
|
||||
50; // in frame_words. This is an arbitrary limit, if frame is above this size something must have gone wrong
|
||||
|
||||
enum ADC_DRDY_STATE : uint8_t {
|
||||
DS_LOGIC_HIGH = 0, // DEFAULT
|
||||
DS_HI_Z = 1
|
||||
};
|
||||
|
||||
enum ADC_POWERMODE : uint16_t {
|
||||
PM_VERY_LOW_POWER = 0,
|
||||
PM_LOW_POWER = 1,
|
||||
PM_HIGH_RESOLUTION = 2, // DEFAULT
|
||||
PM_HIGH_RESOLUTION2 = 3,
|
||||
};
|
||||
|
||||
enum ADC_PGA_GAIN : uint8_t {
|
||||
PGA_1 = 0,
|
||||
PGA_2 = 1,
|
||||
PGA_4 = 2,
|
||||
PGA_8 = 3,
|
||||
PGA_16 = 4,
|
||||
PGA_32 = 5,
|
||||
PGA_64 = 6,
|
||||
PGA_128 = 7,
|
||||
PGA_INVALID
|
||||
};
|
||||
|
||||
enum ADC_INPUT_CHANNEL_MUX : uint8_t {
|
||||
ICM_AIN0P_AIN0N = 0, // DEFAULT
|
||||
ICM_INPUT_SHORTED = 1,
|
||||
ICM_POSITIVE_DC_TEST_SIGNAL = 2,
|
||||
ICM_NEGATIVE_DC_TEST_SIGNAL = 3,
|
||||
};
|
||||
|
||||
enum ADC_OVERSAMPLING_RATIO : uint16_t {
|
||||
OSR_128 = 0,
|
||||
OSR_256 = 1,
|
||||
OSR_512 = 2,
|
||||
OSR_1024 = 3, // default
|
||||
OSR_2048 = 4,
|
||||
OSR_4096 = 5,
|
||||
OSR_8192 = 6,
|
||||
OSR_16256 = 7
|
||||
};
|
||||
|
||||
enum ADC_WAIT_TIME : uint16_t {
|
||||
WT_128 = 856,
|
||||
WT_256 = 1112,
|
||||
WT_512 = 1624,
|
||||
WT_1024 = 2648,
|
||||
WT_2048 = 4696,
|
||||
WT_4096 = 8792,
|
||||
WT_8192 = 16984,
|
||||
WT_16384 = 33368
|
||||
};
|
||||
|
||||
// delays in microseconds
|
||||
enum ADC_DELAY {
|
||||
T_REGACQ = 5,
|
||||
|
||||
};
|
||||
|
||||
// MODE Register
|
||||
// enum ADC_RESET : uint16_t
|
||||
//{
|
||||
static constexpr uint16_t MODE_MASK_RESET_HAPPENED = ~0x0400; // DEFAULT
|
||||
static constexpr uint16_t MODE_RESET_HAPPENED = 0x0400;
|
||||
//};
|
||||
|
||||
enum ADC_CRC_TYPE : uint16_t {
|
||||
CRC_CCITT_16BIT = 0x0000, // DEFAULT
|
||||
CRC_ANSI_16BIT = 0x0800
|
||||
};
|
||||
|
||||
// enum ADC_WORD_LENGTH : uint16_t
|
||||
//{
|
||||
static constexpr uint16_t WLENGTH_16_BITS = 0x0000;
|
||||
static constexpr uint16_t WLENGTH_24_BITS = 0x0100; // DEFAULT
|
||||
static constexpr uint16_t WLENGTH_32_BITS_LSB_ZERO_PADDING = 0x0200;
|
||||
static constexpr uint16_t WLENGTH_32_BITS_MSB_SIGN_EXTEND = 0x0300;
|
||||
//};
|
||||
|
||||
enum ADC_TIMEOUT : uint16_t {
|
||||
TIMEOUT_DISABLED = 0x0000,
|
||||
TIMEOUT_ENABLED = 0x0010 // DEFAULT
|
||||
};
|
||||
|
||||
enum ADC_DRDY_SELECTION : uint16_t {
|
||||
DRDY_SEL_MOST_LAGGING = 0x0000, // DEFAULT
|
||||
DRDY_SEL_LOGICAL_OR = 0x0004,
|
||||
DRDY_SEL_MOST_LEADING_CHAN = 0x0008,
|
||||
DRDY_SEL_MOST_LEADING_CHAN2 = 0x000C
|
||||
};
|
||||
// enum ADC_DRDY_FORMAT : uint16_t
|
||||
//{
|
||||
static constexpr uint16_t DRDY_FMT_LEVEL = 0x0000; // Logic low (default)
|
||||
static constexpr uint16_t DRDY_FMT_PULSE = 0x0001; // Low pulse with a fixed duration
|
||||
//};
|
||||
// end of MODE Register
|
||||
|
||||
// Commands
|
||||
enum ADC_COMMANDS : uint16_t {
|
||||
CMD_NULL = 0x0000, // No operation; used to read STATUS register
|
||||
CMD_RESET = 0x0011, // RESET the device
|
||||
CMD_STANDBY = 0x0022, // Place the device into standby mode
|
||||
CMD_WAKEUP = 0x0033, // Wake up device from standby mode to conversion mode
|
||||
CMD_LOCK = 0x0555, // Lock the interface such that only the NULL, UNLOCK, and RREG commands are valid
|
||||
CMD_UNLOCK = 0x0655, // Unlock the interface after the interface is locked
|
||||
CMD_RREG = 0xA000, // Read register command base; number of registers to read added to lower byte; register address
|
||||
// to upper byte
|
||||
CMD_WREG = 0x6000, // Write register command base; number of registers to write added to lower byte; register address
|
||||
// to upper byte
|
||||
};
|
||||
|
||||
// Responses
|
||||
enum ADC_RESPONSES : uint16_t { RSP_RESET_OK = 0xFF28, RSP_RESET_NOK = 0x0011 };
|
||||
|
||||
// Used to generalise register config addresses
|
||||
enum REGRELADDR : uint16_t {
|
||||
REG_CHX_CFG,
|
||||
REG_CHX_OCAL_MSB,
|
||||
REG_CHX_OCAL_LSB,
|
||||
REG_CHX_GCAL_MSB,
|
||||
REG_CHX_GCAL_LSB,
|
||||
};
|
||||
|
||||
// enum ADC_REG : uint16_t {
|
||||
static constexpr uint16_t REG_ID = 0x00;
|
||||
static constexpr uint16_t REG_STATUS = 0x01;
|
||||
static constexpr uint16_t REG_MODE = 0x02;
|
||||
static constexpr uint16_t REG_CLOCK = 0x03;
|
||||
static constexpr uint16_t REG_GAIN1 = 0x04;
|
||||
static constexpr uint16_t REG_GAIN2 = 0x05;
|
||||
static constexpr uint16_t REG_CFG = 0x06;
|
||||
static constexpr uint16_t REG_THRSHLD_MSB = 0x07;
|
||||
static constexpr uint16_t REG_THRSHLD_LSB = 0x08;
|
||||
static constexpr uint16_t REG_CH0_CFG = 0x09;
|
||||
static constexpr uint16_t REG_CH0_OCAL_MSB = 0x0A;
|
||||
static constexpr uint16_t REG_CH0_OCAL_LSB = 0x0B;
|
||||
static constexpr uint16_t REG_CH0_GCAL_MSB = 0x0C;
|
||||
static constexpr uint16_t REG_CH0_GCAL_LSB = 0x0D;
|
||||
static constexpr uint16_t REG_CH1_CFG = 0x0E;
|
||||
static constexpr uint16_t REG_CH1_OCAL_MSB = 0x0F;
|
||||
static constexpr uint16_t REG_CH1_OCAL_LSB = 0x10;
|
||||
static constexpr uint16_t REG_CH1_GCAL_MSB = 0x11;
|
||||
static constexpr uint16_t REG_CH1_GCAL_LSB = 0x12;
|
||||
static constexpr uint16_t REG_CH2_CFG = 0x13;
|
||||
static constexpr uint16_t REG_CH2_OCAL_MSB = 0x14;
|
||||
static constexpr uint16_t REG_CH2_OCAL_LSB = 0x15;
|
||||
static constexpr uint16_t REG_CH2_GCAL_MSB = 0x16;
|
||||
static constexpr uint16_t REG_CH2_GCAL_LSB = 0x17;
|
||||
static constexpr uint16_t REG_CH3_CFG = 0x18;
|
||||
static constexpr uint16_t REG_CH3_OCAL_MSB = 0x19;
|
||||
static constexpr uint16_t REG_CH3_OCAL_LSB = 0x1A;
|
||||
static constexpr uint16_t REG_CH3_GCAL_MSB = 0x1B;
|
||||
static constexpr uint16_t REG_CH3_GCAL_LSB = 0x1C;
|
||||
static constexpr uint16_t REG_CH4_CFG = 0x1D;
|
||||
static constexpr uint16_t REG_CH4_OCAL_MSB = 0x1E;
|
||||
static constexpr uint16_t REG_CH4_OCAL_LSB = 0x1F;
|
||||
static constexpr uint16_t REG_CH4_GCAL_MSB = 0x20;
|
||||
static constexpr uint16_t REG_CH4_GCAL_LSB = 0x21;
|
||||
static constexpr uint16_t REG_CH5_CFG = 0x22;
|
||||
static constexpr uint16_t REG_CH5_OCAL_MSB = 0x23;
|
||||
static constexpr uint16_t REG_CH5_OCAL_LSB = 0x24;
|
||||
static constexpr uint16_t REG_CH5_GCAL_MSB = 0x25;
|
||||
static constexpr uint16_t REG_CH5_GCAL_LSB = 0x26;
|
||||
static constexpr uint16_t REG_CH6_CFG = 0x27;
|
||||
static constexpr uint16_t REG_CH6_OCAL_MSB = 0x28;
|
||||
static constexpr uint16_t REG_CH6_OCAL_LSB = 0x29;
|
||||
static constexpr uint16_t REG_CH6_GCAL_MSB = 0x2A;
|
||||
static constexpr uint16_t REG_CH6_GCAL_LSB = 0x2B;
|
||||
static constexpr uint16_t REG_CH7_CFG = 0x2C;
|
||||
static constexpr uint16_t REG_CH7_OCAL_MSB = 0x2D;
|
||||
static constexpr uint16_t REG_CH7_OCAL_LSB = 0x2E;
|
||||
static constexpr uint16_t REG_CH7_GCAL_MSB = 0x2F;
|
||||
static constexpr uint16_t REG_CH7_GCAL_LSB = 0x30;
|
||||
static constexpr uint16_t REGMAP_CRC = 0x3E;
|
||||
// };
|
||||
// Mask READ/WRITE_REG
|
||||
static constexpr uint16_t MASK_CMD_RW_REG = 0xE000;
|
||||
static constexpr uint16_t MASK_CMD_RW_REG_ADDRESS = 0x1F80;
|
||||
static constexpr uint16_t MASK_CMD_RW_REG_COUNT = 0x007F;
|
||||
static constexpr uint16_t MASK_CMD_RW_REG_RESP = 0xE000;
|
||||
|
||||
// WRITE_REG resp
|
||||
static constexpr uint16_t WREG_RESP = 0x4000; // mask first with MASK_CMD_RW_REG_RESP
|
||||
// READ_REG resp
|
||||
static constexpr uint16_t RREG_RESP = 0xE000; // mask first with MASK_CMD_RW_REG_RESP
|
||||
|
||||
// Mask Register STATUS
|
||||
static constexpr uint16_t MASK_STATUS_LOCK = 0x8000;
|
||||
static constexpr uint16_t MASK_STATUS_RESYNC = 0x4000;
|
||||
static constexpr uint16_t MASK_STATUS_REGMAP = 0x2000;
|
||||
static constexpr uint16_t MASK_STATUS_CRC_ERR = 0x1000;
|
||||
static constexpr uint16_t MASK_STATUS_CRC_TYPE = 0x0800;
|
||||
static constexpr uint16_t MASK_STATUS_RESET = 0x0400;
|
||||
static constexpr uint16_t MASK_STATUS_WLENGTH = 0x0300;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY7 = 0x0080;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY6 = 0x0040;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY5 = 0x0020;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY4 = 0x0010;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY3 = 0x0008;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY2 = 0x0004;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY1 = 0x0002;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY0 = 0x0001;
|
||||
static constexpr uint16_t MASK_STATUS_DRDY = 0x00FF;
|
||||
|
||||
// Mask Register MODE
|
||||
static constexpr uint16_t MASK_MODE_REG_CRC_EN = 0x2000;
|
||||
static constexpr uint16_t MASK_MODE_RX_CRC_EN = 0x1000;
|
||||
static constexpr uint16_t MASK_MODE_CRC_TYPE = 0x0800;
|
||||
static constexpr uint16_t MASK_MODE_RESET = 0x0400;
|
||||
static constexpr uint16_t MASK_MODE_WLENGTH = 0x0300;
|
||||
static constexpr uint16_t MASK_MODE_TIMEOUT = 0x0010;
|
||||
static constexpr uint16_t MASK_MODE_DRDY_SEL = 0x000C;
|
||||
static constexpr uint16_t MASK_MODE_DRDY_HiZ = 0x0002;
|
||||
static constexpr uint16_t MASK_MODE_DRDY_FMT = 0x0001;
|
||||
|
||||
// Mask Register CLOCK
|
||||
static constexpr uint16_t MASK_CLOCK_CH7 = 0x8000;
|
||||
static constexpr uint16_t MASK_CLOCK_CH6 = 0x4000;
|
||||
static constexpr uint16_t MASK_CLOCK_CH5 = 0x2000;
|
||||
static constexpr uint16_t MASK_CLOCK_CH4 = 0x1000;
|
||||
static constexpr uint16_t MASK_CLOCK_CH3 = 0x0800;
|
||||
static constexpr uint16_t MASK_CLOCK_CH2 = 0x0400;
|
||||
static constexpr uint16_t MASK_CLOCK_CH1 = 0x0200;
|
||||
static constexpr uint16_t MASK_CLOCK_CH0 = 0x0100;
|
||||
static constexpr uint16_t MASK_CLOCK_ALLCH = 0xFF00;
|
||||
static constexpr uint16_t MASK_CLOCK_ALLCH_OFF = 0x00FF;
|
||||
static constexpr uint16_t MASK_CLOCK_XTAL_DIS = 0x0080;
|
||||
static constexpr uint16_t MASK_CLOCK_EXTREF_EN = 0x0040;
|
||||
static constexpr uint16_t MASK_CLOCK_OSR = 0x001C;
|
||||
static constexpr uint16_t MASK_CLOCK_PWR = 0x0003;
|
||||
|
||||
static constexpr uint16_t MASK_CLOCK_ALL_CH_ENABLE = 0xFF00;
|
||||
// Mask Register GAIN
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN7 = 0x7000;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN6 = 0x0700;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN5 = 0x0070;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN4 = 0x0007;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN3 = 0x7000;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN2 = 0x0700;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN1 = 0x0070;
|
||||
static constexpr uint16_t MASK_GAIN_PGAGAIN0 = 0x0007;
|
||||
|
||||
// Mask Register CFG
|
||||
static constexpr uint16_t MASK_CFG_GC_DLY = 0x1E00;
|
||||
static constexpr uint16_t MASK_CFG_GC_EN = 0x0100;
|
||||
static constexpr uint16_t MASK_CFG_CD_ALLCH = 0x0080;
|
||||
static constexpr uint16_t MASK_CFG_CD_NUM = 0x0070;
|
||||
static constexpr uint16_t MASK_CFG_CD_LEN = 0x000E;
|
||||
static constexpr uint16_t MASK_CFG_CD_EN = 0x0001;
|
||||
|
||||
// Mask Register THRSHLD_MSB - dummy, for completeness
|
||||
static constexpr uint16_t MASK_THRSHLD_MSB_CD_TH_MSB = 0xFFFF;
|
||||
|
||||
// Mask Register THRSHLD_LSB
|
||||
static constexpr uint16_t MASK_THRSHLD_LSB_CD_TH_LSB = 0xFF00;
|
||||
static constexpr uint16_t MASK_THRSHLD_LSB_DCBLOCK = 0x000F;
|
||||
|
||||
// Mask Register CHX_CFG
|
||||
static constexpr uint16_t MASK_CHX_CFG_PHASE = 0xFFC0;
|
||||
static constexpr uint16_t MASK_CHX_CFG_DCBLKX_DIS = 0x0004;
|
||||
static constexpr uint16_t MASK_CHX_CFG_MUX = 0x0003;
|
||||
|
||||
// Mask Register CHX_OCAL_MSB - dummy, for completeness
|
||||
static constexpr uint16_t MASK_CHX_OCAL_MSB = 0xFFFF;
|
||||
|
||||
// Mask Register CHX_OCAL_LSB
|
||||
static constexpr uint16_t MASK_CHX_OCAL_LSB = 0xFF00;
|
||||
|
||||
// Mask Register CHX_GCAL_MSB
|
||||
static constexpr uint16_t MASK_CHX_GCAL_MSB = 0xFFFF;
|
||||
|
||||
// Mask Register CHX_GCAL_LSB
|
||||
static constexpr uint16_t MASK_CHX_GCAL_LSB = 0xFF00;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// Conversion modes
|
||||
static constexpr uint16_t CONVERSION_MODE_CONT = 0;
|
||||
static constexpr uint16_t CONVERSION_MODE_SINGLE = 1;
|
||||
|
||||
// Data Format
|
||||
static constexpr uint16_t DATA_FORMAT_TWO_COMPLEMENT = 0;
|
||||
static constexpr uint16_t DATA_FORMAT_BINARY = 1;
|
||||
|
||||
// Measure Mode
|
||||
static constexpr uint8_t MEASURE_UNIPOLAR = 1;
|
||||
static constexpr uint8_t MEASURE_BIPOLAR = 0;
|
||||
|
||||
// Clock Type
|
||||
static constexpr uint8_t CLOCK_EXTERNAL = 1;
|
||||
static constexpr uint8_t CLOCK_INTERNAL = 0;
|
||||
|
||||
// PGA Gain
|
||||
// static constexpr uint16_t PGA_GAIN_1 = 0;
|
||||
// static constexpr uint16_t PGA_GAIN_2 = 1;
|
||||
// static constexpr uint16_t PGA_GAIN_4 = 2;
|
||||
// static constexpr uint16_t PGA_GAIN_8 = 3;
|
||||
// static constexpr uint16_t PGA_GAIN_16 = 4;
|
||||
// static constexpr uint16_t PGA_GAIN_32 = 5;
|
||||
// static constexpr uint16_t PGA_GAIN_64 = 6;
|
||||
// static constexpr uint16_t PGA_GAIN_128 = 7;
|
||||
|
||||
// Input Filter
|
||||
static constexpr uint16_t FILTER_SYNC = 0;
|
||||
static constexpr uint16_t FILTER_FIR = 2;
|
||||
static constexpr uint16_t FILTER_FIR_IIR = 3;
|
||||
|
||||
// Data Mode
|
||||
static constexpr uint8_t DATA_MODE_24BITS = 0;
|
||||
static constexpr uint8_t DATA_MODE_32BITS = 1;
|
||||
|
||||
// Data Rate
|
||||
static constexpr uint8_t DATA_RATE_0 = 0;
|
||||
static constexpr uint8_t DATA_RATE_1 = 1;
|
||||
static constexpr uint8_t DATA_RATE_2 = 2;
|
||||
static constexpr uint8_t DATA_RATE_3 = 3;
|
||||
static constexpr uint8_t DATA_RATE_4 = 4;
|
||||
static constexpr uint8_t DATA_RATE_5 = 5;
|
||||
static constexpr uint8_t DATA_RATE_6 = 6;
|
||||
static constexpr uint8_t DATA_RATE_7 = 7;
|
||||
static constexpr uint8_t DATA_RATE_8 = 8;
|
||||
static constexpr uint8_t DATA_RATE_9 = 9;
|
||||
static constexpr uint8_t DATA_RATE_10 = 10;
|
||||
static constexpr uint8_t DATA_RATE_11 = 11;
|
||||
static constexpr uint8_t DATA_RATE_12 = 12;
|
||||
static constexpr uint8_t DATA_RATE_13 = 13;
|
||||
static constexpr uint8_t DATA_RATE_14 = 14;
|
||||
static constexpr uint8_t DATA_RATE_15 = 15;
|
||||
// Sync Mpdes
|
||||
static constexpr uint16_t SYNC_CONTINUOUS = 1;
|
||||
static constexpr uint16_t SYNC_PULSE = 0;
|
||||
|
||||
// DIO Config Mode
|
||||
static constexpr uint8_t DIO_OUTPUT = 1;
|
||||
static constexpr uint8_t DIO_INPUT = 0;
|
||||
|
||||
static constexpr uint8_t SPI_MASTER_DUMMY = 0xFF;
|
||||
static constexpr uint16_t SPI_MASTER_DUMMY16 = 0xFFFF;
|
||||
static constexpr uint32_t SPI_MASTER_DUMMY32 = 0xFFFFFFFF;
|
||||
// end of from datasheet
|
||||
|
||||
static constexpr size_t BYTE_BITLENGTH = 8; // uint8/int8
|
||||
static constexpr size_t WORD_BITLENGTH = 16; // uint16/int16
|
||||
static constexpr size_t DWORD_BITLENGTH = 32; // uint32/int32/float
|
||||
static constexpr size_t QWORD_BITLENGTH = 64; // uint64/int64/float64
|
||||
|
||||
} // namespace ads131m08
|
||||
} // namespace esphome
|
||||
104
ads131m08/git_errors.txt
Normal file
104
ads131m08/git_errors.txt
Normal file
@ -0,0 +1,104 @@
|
||||
ruff (legacy alias)......................................................Failed
|
||||
- hook id: ruff
|
||||
- exit code: 1
|
||||
- files were modified by this hook
|
||||
|
||||
F401 `esphome.const.CONF_ID` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||
--> esphome/components/ads131m08/sensor_rms/__init__.py:6:5
|
||||
|
|
||||
4 | from esphome.const import (
|
||||
5 | CONF_CHANNEL,
|
||||
6 | CONF_ID,
|
||||
| ^^^^^^^
|
||||
7 | CONF_NAME,
|
||||
8 | DEVICE_CLASS_VOLTAGE,
|
||||
|
|
||||
help: Use an explicit re-export: `CONF_ID as CONF_ID`
|
||||
|
||||
E402 Module level import not at top of file
|
||||
--> esphome/components/ads131m08/sensor_rms/__init__.py:20:1
|
||||
|
|
||||
18 | MAX_CHANNELS = 12
|
||||
19 |
|
||||
20 | from .. import CONF_ADS131M08_ID, ads131m08_ns
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
21 |
|
||||
22 | AUTO_LOAD = [
|
||||
|
|
||||
|
||||
Found 6 errors (4 fixed, 2 remaining).
|
||||
|
||||
ruff format..............................................................Failed
|
||||
- hook id: ruff-format
|
||||
- files were modified by this hook
|
||||
|
||||
3 files reformatted
|
||||
|
||||
flake8...................................................................Failed
|
||||
- hook id: flake8
|
||||
- exit code: 1
|
||||
|
||||
esphome/components/ads131m08/sensor_rms/__init__.py:4:1: F401 'esphome.const.CONF_ID' imported but unused
|
||||
esphome/components/ads131m08/sensor_rms/__init__.py:20:1: E402 module level import not at top of file
|
||||
|
||||
don't commit to branch...................................................Failed
|
||||
- hook id: no-commit-to-branch
|
||||
- exit code: 1
|
||||
fix end of files.........................................................Failed
|
||||
- hook id: end-of-file-fixer
|
||||
- exit code: 1
|
||||
- files were modified by this hook
|
||||
|
||||
Fixing esphome/components/ads131m08/README.md
|
||||
Fixing esphome/components/ads131m08/ads131m08.h
|
||||
Fixing esphome/components/ads131m08/uint_str.h
|
||||
|
||||
trim trailing whitespace.................................................Failed
|
||||
- hook id: trailing-whitespace
|
||||
- exit code: 1
|
||||
- files were modified by this hook
|
||||
|
||||
Fixing esphome/components/ads131m08/ads131m08_defs.h
|
||||
Fixing esphome/components/ads131m08/README.md
|
||||
Fixing esphome/components/ads131m08/sensor/ads131m08_sensor.cpp
|
||||
Fixing esphome/components/ads131m08/ads131m08.cpp
|
||||
Fixing esphome/components/ads131m08/ads131m08.h
|
||||
Fixing esphome/components/ads131m08/sensor/ads131m08_sensor.h
|
||||
Fixing esphome/components/ads131m08/uint_str.h
|
||||
|
||||
pyupgrade................................................................Passed
|
||||
yamllint.............................................(no files to check)Skipped
|
||||
clang-format.............................................................Failed
|
||||
- hook id: clang-format
|
||||
- files were modified by this hook
|
||||
pylint...................................................................Failed
|
||||
- hook id: pylint
|
||||
- exit code: 16
|
||||
|
||||
************* Module esphome.components.ads131m08.sensor_rms
|
||||
esphome/components/ads131m08/sensor_rms/__init__.py:20:0: C0413: Import "from .. import CONF_ADS131M08_ID, ads131m08_ns" should be placed at the top of the module (wrong-import-position)
|
||||
|
||||
Update clang-tidy hash...............................(no files to check)Skipped
|
||||
ci-custom................................................................Failed
|
||||
- hook id: ci-custom
|
||||
- exit code: 2
|
||||
|
||||
### File esphome/components/ads131m08/__init__.py
|
||||
|
||||
esphome/components/ads131m08/__init__.py:13:2: lint: Constant CONF_OSR does not match value oversampling_ratio! Please make sure the constant's name matches its value!
|
||||
|
||||
|
||||
### File esphome/components/ads131m08/sensor/__init__.py
|
||||
|
||||
esphome/components/ads131m08/sensor/__init__.py:14:2: lint: Constant CONF_OFFSET_CAL does not match value offset_calibration! Please make sure the constant's name matches its value!
|
||||
|
||||
esphome/components/ads131m08/sensor/__init__.py:16:2: lint: Constant CONF_GAIN_CAL does not match value gain_calibration! Please make sure the constant's name matches its value!
|
||||
|
||||
esphome/components/ads131m08/sensor/__init__.py:17:2: lint: Constant CONF_PHASE_CAL does not match value phase_calibration! Please make sure the constant's name matches its value!
|
||||
|
||||
esphome/components/ads131m08/sensor/__init__.py:18:2: lint: Constant CONF_MUX_INP does not match value input_select! Please make sure the constant's name matches its value!
|
||||
|
||||
|
||||
### File esphome/components/ads131m08/sensor_rms/__init__.py
|
||||
|
||||
esphome/components/ads131m08/sensor_rms/__init__.py:16:2: lint: Constant CONF_CALC_RMS does not match value read_rms! Please make sure the constant's name matches its value!
|
||||
91
ads131m08/sensor/__init__.py
Normal file
91
ads131m08/sensor/__init__.py
Normal file
@ -0,0 +1,91 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_ID, CONF_GAIN, CONF_CHANNEL
|
||||
from .. import CONF_ADS131M08_ID, ADS131M08Hub, ads131m08_ns
|
||||
from ..sensor_rms import CONFIG_SCHEMA_RMS_SENSORS, to_code_ac, to_code_dc
|
||||
|
||||
AUTO_LOAD = [
|
||||
"sensor", "voltage_sampler",
|
||||
]
|
||||
|
||||
CONF_OFFSET_CALIBRATION = "offset_calibration"
|
||||
CONF_GAIN_CALIBRATION = "gain_calibration"
|
||||
CONF_PHASE_CALIBRATION = "phase_calibration"
|
||||
CONF_INPUT_SELECT = "input_select"
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
MAX_CHANNELS = 12
|
||||
|
||||
DEPENDENCIES = ["ads131m08"]
|
||||
|
||||
GAIN = ads131m08_ns.enum("ADC_PGA_GAIN")
|
||||
ALLOWED_GAINS = {
|
||||
"1": GAIN.PGA_1,
|
||||
"2": GAIN.PGA_2,
|
||||
"4": GAIN.PGA_4,
|
||||
"8": GAIN.PGA_8,
|
||||
"16": GAIN.PGA_16,
|
||||
"32": GAIN.PGA_32,
|
||||
"64": GAIN.PGA_64,
|
||||
"128": GAIN.PGA_128,
|
||||
1: GAIN.PGA_1,
|
||||
2: GAIN.PGA_2,
|
||||
4: GAIN.PGA_4,
|
||||
8: GAIN.PGA_8,
|
||||
16: GAIN.PGA_16,
|
||||
32: GAIN.PGA_32,
|
||||
64: GAIN.PGA_64,
|
||||
128: GAIN.PGA_128,
|
||||
}
|
||||
|
||||
ADC_INPUT = ads131m08_ns.enum("ADC_INPUT_CHANNEL_MUX")
|
||||
ALLOWED_MUX_INP = {
|
||||
"normal": ADC_INPUT.ICM_AIN0P_AIN0N,
|
||||
"shorted" : ADC_INPUT.ICM_INPUT_SHORTED,
|
||||
"positive_dc" : ADC_INPUT.ICM_POSITIVE_DC_TEST_SIGNAL,
|
||||
"negative_dc" : ADC_INPUT.ICM_NEGATIVE_DC_TEST_SIGNAL,
|
||||
}
|
||||
|
||||
Channel = ads131m08_ns.class_(
|
||||
"Channel", sensor.Sensor, cg.Component, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA_RMS_SENSORS.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ADS131M08_ID): cv.use_id(ADS131M08Hub),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=MAX_CHANNELS),
|
||||
cv.Optional(CONF_GAIN, default=1): cv.enum(ALLOWED_GAINS, int=True),
|
||||
cv.Optional(CONF_OFFSET_CALIBRATION, default=0): cv.int_range(min=-8388608, max=8388607), # should use volts, but need to figure out conversion function first
|
||||
cv.Optional(CONF_GAIN_CALIBRATION, default=1): cv.float_range(min=0, max=2),
|
||||
cv.Optional(CONF_PHASE_CALIBRATION, default=0): cv.int_range(min=-512, max=511), # should use degrees, but need to figure out conversion function first
|
||||
cv.Optional(CONF_INPUT_SELECT, default='normal'): cv.enum(ALLOWED_MUX_INP, int=False),
|
||||
}
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Channel)
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
# we are using 3 sensors:
|
||||
# 1. channel_sensor: this represents 1 of the 8 ads131m08 channels and is used to program the adc channel gain, offset calibration, etc. This sensor publishes instantaneous sampled value
|
||||
# 2. dc_sensor: to publish averaged dc value
|
||||
# 3. ac_sensor: to publish rms ac value
|
||||
async def to_code(config):
|
||||
channel_sensor = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_parented(channel_sensor, config[CONF_ADS131M08_ID])
|
||||
channel = config[CONF_CHANNEL]
|
||||
cg.add(channel_sensor.set_channel_number(channel))
|
||||
gain = config[CONF_GAIN]
|
||||
cg.add(channel_sensor.set_gain(gain))
|
||||
mux_inp = config[CONF_INPUT_SELECT]
|
||||
cg.add(channel_sensor.set_mux_input(mux_inp))
|
||||
gain_cal = config[CONF_GAIN_CALIBRATION]
|
||||
cg.add(channel_sensor.set_gain_calibration(gain_cal))
|
||||
offset_cal = config[CONF_OFFSET_CALIBRATION]
|
||||
cg.add(channel_sensor.set_offset_calibration(offset_cal))
|
||||
phase_cal = config[CONF_PHASE_CALIBRATION]
|
||||
cg.add(channel_sensor.set_phase_calibration(phase_cal))
|
||||
await cg.register_component(channel_sensor, config)
|
||||
if dc_sensor := await to_code_dc(config):
|
||||
await cg.register_parented(dc_sensor, channel_sensor)
|
||||
if ac_sensor := await to_code_ac(config):
|
||||
await cg.register_parented(ac_sensor, channel_sensor)
|
||||
90
ads131m08/sensor/ads131m08_sensor.cpp
Normal file
90
ads131m08/sensor/ads131m08_sensor.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include "ads131m08_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads131m08 {
|
||||
|
||||
static const char *const TAG = "adsm131m08.sensor";
|
||||
|
||||
void Channel::loop()
|
||||
{
|
||||
if(this->first_reading_) {
|
||||
if(this->parent_ != nullptr) {
|
||||
this->parent_->set_channel_gain(this->channel_, this->gain_);
|
||||
this->parent_->set_channel_input_selection(this->channel_, this->input_);
|
||||
this->parent_->set_channel_phase_calibration(this->channel_, this->normalised_phase_calibration());
|
||||
this->parent_->set_channel_offset_calibration(this->channel_, this->normalised_offset_calibration());
|
||||
this->parent_->set_channel_gain_calibration(this->channel_, this->normalised_gain_calibration());
|
||||
this->parent_->set_dcblock_filter_disable(this->channel_, true);
|
||||
this->parent_->set_channel_enable(this->channel_, true);
|
||||
this->first_reading_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float Channel::sample()
|
||||
{
|
||||
return (this->parent_ == nullptr) ? NAN : this->parent_->get_sampled_value(this->channel_);
|
||||
}
|
||||
|
||||
// converts gain of 0.0 - 2.0 to the corresponding values that ADS131M08 expects
|
||||
uint32_t Channel::normalised_gain_calibration() const
|
||||
{
|
||||
uint32_t result = (uint32_t)lroundf(gain_cal_ * 8388608.0f);
|
||||
return result & 0xFFFFFF;
|
||||
}
|
||||
|
||||
uint32_t Channel::normalised_offset_calibration() const
|
||||
{
|
||||
uint32_t result = (uint32_t)offset_cal_;
|
||||
return result & 0xFFFFFF;
|
||||
}
|
||||
|
||||
int16_t Channel::normalised_phase_calibration() const
|
||||
{
|
||||
int16_t result = (int16_t)phase_cal_;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Channel::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Channel %" PRIu8 ":", this->channel_);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %" PRIu8, 1 << this->gain_);
|
||||
ESP_LOGCONFIG(TAG, " Gain calibration: %f", this->gain_cal_);
|
||||
ESP_LOGCONFIG(TAG, " Offset calibration: %" PRIu32, this->offset_cal_);
|
||||
ESP_LOGCONFIG(TAG, " Phase calibration: %" PRId32, this->phase_cal_);
|
||||
ESP_LOGCONFIG(TAG, " Input select: %s", (this->input_ == ICM_AIN0P_AIN0N) ? "normal" \
|
||||
: (this->input_ == ICM_INPUT_SHORTED) ? "shorted" \
|
||||
: (this->input_ == ICM_POSITIVE_DC_TEST_SIGNAL) ? "positive_dc" \
|
||||
: (this->input_ == ICM_NEGATIVE_DC_TEST_SIGNAL) ? "negative_dc" \
|
||||
: "invalid");
|
||||
}
|
||||
|
||||
|
||||
void RMS_Sensor::loop()
|
||||
{
|
||||
if(!this->set_calc_rms_) {
|
||||
if(this->parent_ != nullptr) {
|
||||
if(this->calc_rms_) {
|
||||
this->set_calc_rms_ = this->parent_->set_calc_rms(true);
|
||||
}
|
||||
else {
|
||||
this->set_calc_rms_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
float value = this->parent_->get_average(this->calc_rms_);
|
||||
publish_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
void RMS_Sensor::dump_config()
|
||||
{
|
||||
LOG_SENSOR(" ", "Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Calculate rms: %s", this->calc_rms_ ? "YES" : "NO");
|
||||
}
|
||||
|
||||
} // namespace ads131m08_acdc
|
||||
|
||||
} // namespace esphome
|
||||
63
ads131m08/sensor/ads131m08_sensor.h
Normal file
63
ads131m08/sensor/ads131m08_sensor.h
Normal file
@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#include "../ads131m08.h"
|
||||
|
||||
|
||||
namespace esphome {
|
||||
namespace ads131m08 {
|
||||
|
||||
class Channel : public sensor::Sensor,
|
||||
public Component,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS131M08Hub> {
|
||||
public:
|
||||
void loop() override;
|
||||
float sample() override;
|
||||
void set_gain(uint8_t value) { this->gain_ = value; }
|
||||
void dump_config() override;
|
||||
void set_channel_number(uint8_t channel) { this->channel_= channel; }
|
||||
void set_gain_calibration(float value) { this->gain_cal_ = value; }
|
||||
void set_offset_calibration(int value) { this->offset_cal_ = value; }
|
||||
void set_phase_calibration(int value) { this->phase_cal_ = value; }
|
||||
bool set_calc_rms(bool enable) { this->calc_rms_ = enable; return this->parent_ != nullptr ? this->parent_->set_measure_rms(this->channel_, enable) : false; }
|
||||
float get_average(bool read_ac) { return this->parent_->get_average(this->channel_, read_ac); }
|
||||
void set_mux_input(ADC_INPUT_CHANNEL_MUX value) { this->input_ = value; }
|
||||
uint32_t normalised_gain_calibration() const;
|
||||
uint32_t normalised_offset_calibration() const;
|
||||
int16_t normalised_phase_calibration() const;
|
||||
|
||||
protected:
|
||||
uint8_t channel_;
|
||||
int phase_cal_;
|
||||
int offset_cal_;
|
||||
float gain_cal_;
|
||||
bool calc_rms_;
|
||||
uint8_t gain_;
|
||||
ADC_INPUT_CHANNEL_MUX input_;
|
||||
bool first_reading_{true};
|
||||
|
||||
};
|
||||
|
||||
class RMS_Sensor : public sensor::Sensor,
|
||||
public Component,
|
||||
public Parented<ads131m08::Channel> {
|
||||
public:
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void set_calc_rms(bool enable) { this->calc_rms_ = enable; }
|
||||
|
||||
protected:
|
||||
bool calc_rms_{false};
|
||||
bool set_calc_rms_{false};
|
||||
};
|
||||
|
||||
|
||||
} // namespace ads131m08
|
||||
|
||||
} // namespace esphome
|
||||
75
ads131m08/sensor_rms/__init__.py
Normal file
75
ads131m08/sensor_rms/__init__.py
Normal file
@ -0,0 +1,75 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
ICON_CURRENT_AC,
|
||||
UNIT_VOLT,
|
||||
CONF_CHANNEL,
|
||||
CONF_NAME,
|
||||
)
|
||||
from .. import CONF_ADS131M08_ID, ads131m08_ns
|
||||
|
||||
CONF_AC = "ac_1"
|
||||
CONF_DC = "dc"
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
MAX_CHANNELS = 12
|
||||
AUTO_LOAD = ["sensor",]
|
||||
|
||||
DEPENDENCIES = ["ads131m08"]
|
||||
|
||||
ads131m08_acdc_ns = cg.esphome_ns.namespace("ads131m08_acdc")
|
||||
|
||||
RMS_SENSOR = ads131m08_ns.class_(
|
||||
"RMS_Sensor", sensor.Sensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA_RMS_SENSORS = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DC): sensor.sensor_schema(
|
||||
RMS_SENSOR,
|
||||
accuracy_decimals=6,
|
||||
icon=ICON_CURRENT_DC,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_AC): sensor.sensor_schema(
|
||||
RMS_SENSOR,
|
||||
accuracy_decimals=6,
|
||||
icon=ICON_CURRENT_AC,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
}
|
||||
),
|
||||
}
|
||||
). extend (cv.COMPONENT_SCHEMA)
|
||||
async def to_code_ac(config):
|
||||
hub = await cg.get_variable(config[CONF_ADS131M08_ID])
|
||||
channel = config[CONF_CHANNEL]
|
||||
if ac_config := config.get(CONF_AC):
|
||||
sens_ac = await sensor.new_sensor(ac_config)
|
||||
cg.add(sens_ac.set_calc_rms(True))
|
||||
cg.add(hub.register_sensor_ac(channel, sens_ac))
|
||||
await cg.register_component(sens_ac, config[CONF_AC])
|
||||
return sens_ac
|
||||
return None
|
||||
|
||||
async def to_code_dc(config):
|
||||
hub = await cg.get_variable(config[CONF_ADS131M08_ID])
|
||||
channel = config[CONF_CHANNEL]
|
||||
dc_config = config.get(CONF_DC)
|
||||
sens_dc = await sensor.new_sensor(dc_config)
|
||||
cg.add(sens_dc, config.get(CONF_DC))
|
||||
cg.add(hub.register_sensor_dc(channel, sens_dc))
|
||||
await cg.register_component(sens_dc, config[CONF_DC])
|
||||
return sens_dc
|
||||
28
ads131m08/uint_str.h
Normal file
28
ads131m08/uint_str.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace ads131m08 {
|
||||
|
||||
template<typename chartype> struct ci_char_traits : public std::char_traits<chartype> {
|
||||
static bool eq(chartype c1, chartype c2) { return c1 == c2; }
|
||||
static bool ne(chartype c1, chartype c2) { return c1 != c2; }
|
||||
static bool lt(chartype c1, chartype c2) { return c1 < c2; }
|
||||
static int compare(const chartype *a, const chartype *b, size_t _Count) {
|
||||
for (; 0 < _Count; --_Count, ++a, ++b) {
|
||||
if (*a == *b)
|
||||
continue;
|
||||
return (*a < *b ? -1 : +1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename chartype> class uint_str {
|
||||
public:
|
||||
typedef std::basic_string<chartype, ci_char_traits<chartype>, std::allocator<chartype> > Ty_string;
|
||||
};
|
||||
|
||||
} // namespace ads131m08
|
||||
} // namespace esphome
|
||||
183
canbus/__init__.py
Normal file
183
canbus/__init__.py
Normal file
@ -0,0 +1,183 @@
|
||||
import re
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
|
||||
from esphome.core import CORE, ID
|
||||
|
||||
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_CAN_ID = "can_id"
|
||||
CONF_CAN_ID_MASK = "can_id_mask"
|
||||
CONF_USE_EXTENDED_ID = "use_extended_id"
|
||||
CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request"
|
||||
CONF_CANBUS_ID = "canbus_id"
|
||||
CONF_BIT_RATE = "bit_rate"
|
||||
CONF_ON_FRAME = "on_frame"
|
||||
|
||||
|
||||
def validate_id(config):
|
||||
if CONF_CAN_ID in config:
|
||||
can_id = config[CONF_CAN_ID]
|
||||
id_ext = config[CONF_USE_EXTENDED_ID]
|
||||
if not id_ext and can_id > 0x7FF:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
return config
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode("utf-8")
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid(
|
||||
"data must either be a string wrapped in quotes or a list of bytes"
|
||||
)
|
||||
|
||||
|
||||
canbus_ns = cg.esphome_ns.namespace("canbus")
|
||||
CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component)
|
||||
CanbusTrigger = canbus_ns.class_(
|
||||
"CanbusTrigger",
|
||||
automation.Trigger.template(cg.std_vector.template(cg.uint8), cg.uint32),
|
||||
cg.Component,
|
||||
)
|
||||
CanSpeed = canbus_ns.enum("CAN_SPEED")
|
||||
|
||||
CAN_SPEEDS = {
|
||||
"1KBPS": CanSpeed.CAN_1KBPS,
|
||||
"5KBPS": CanSpeed.CAN_5KBPS,
|
||||
"10KBPS": CanSpeed.CAN_10KBPS,
|
||||
"12K5BPS": CanSpeed.CAN_12K5BPS,
|
||||
"16KBPS": CanSpeed.CAN_16KBPS,
|
||||
"20KBPS": CanSpeed.CAN_20KBPS,
|
||||
"25KBPS": CanSpeed.CAN_25KBPS,
|
||||
"31K25BPS": CanSpeed.CAN_31K25BPS,
|
||||
"33KBPS": CanSpeed.CAN_33KBPS,
|
||||
"40KBPS": CanSpeed.CAN_40KBPS,
|
||||
"50KBPS": CanSpeed.CAN_50KBPS,
|
||||
"80KBPS": CanSpeed.CAN_80KBPS,
|
||||
"83K3BPS": CanSpeed.CAN_83K3BPS,
|
||||
"95KBPS": CanSpeed.CAN_95KBPS,
|
||||
"100KBPS": CanSpeed.CAN_100KBPS,
|
||||
"125KBPS": CanSpeed.CAN_125KBPS,
|
||||
"200KBPS": CanSpeed.CAN_200KBPS,
|
||||
"250KBPS": CanSpeed.CAN_250KBPS,
|
||||
"500KBPS": CanSpeed.CAN_500KBPS,
|
||||
"800KBPS": CanSpeed.CAN_800KBPS,
|
||||
"1000KBPS": CanSpeed.CAN_1000KBPS,
|
||||
}
|
||||
|
||||
|
||||
def get_rate(value):
|
||||
match = re.match(r"(\d+)(?:K(\d+)?)?BPS", value, re.IGNORECASE)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid rate format: {value}")
|
||||
fraction = match.group(2) or "0"
|
||||
return int((float(match.group(1)) + float(f"0.{fraction}")) * 1000)
|
||||
|
||||
|
||||
CANBUS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CanbusComponent),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_CAN_ID_MASK, default=0x1FFFFFFF): cv.int_range(
|
||||
min=0, max=0x1FFFFFFF
|
||||
),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST): cv.boolean,
|
||||
},
|
||||
validate_id,
|
||||
),
|
||||
},
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
CANBUS_SCHEMA.add_extra(validate_id)
|
||||
|
||||
|
||||
async def setup_canbus_core_(var, config):
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
|
||||
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
|
||||
cg.add(var.set_bitrate(CAN_SPEEDS[config[CONF_BIT_RATE]]))
|
||||
|
||||
for conf in config.get(CONF_ON_FRAME, []):
|
||||
can_id = conf[CONF_CAN_ID]
|
||||
can_id_mask = conf[CONF_CAN_ID_MASK]
|
||||
ext_id = conf[CONF_USE_EXTENDED_ID]
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id
|
||||
)
|
||||
if CONF_REMOTE_TRANSMISSION_REQUEST in conf:
|
||||
cg.add(
|
||||
trigger.set_remote_transmission_request(
|
||||
conf[CONF_REMOTE_TRANSMISSION_REQUEST]
|
||||
)
|
||||
)
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[
|
||||
(cg.std_vector.template(cg.uint8), "x"),
|
||||
(cg.uint32, "can_id"),
|
||||
(cg.bool_, "remote_transmission_request"),
|
||||
],
|
||||
conf,
|
||||
)
|
||||
|
||||
|
||||
async def register_canbus(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.new_Pvariable(config[CONF_ID], var)
|
||||
await setup_canbus_core_(var, config)
|
||||
|
||||
|
||||
# Actions
|
||||
@automation.register_action(
|
||||
"canbus.send",
|
||||
canbus_ns.class_("CanbusSendAction", automation.Action),
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
validate_id,
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
|
||||
if can_id := config.get(CONF_CAN_ID):
|
||||
can_id = await cg.templatable(can_id, args, cg.uint32)
|
||||
cg.add(var.set_can_id(can_id))
|
||||
cg.add(var.set_use_extended_id(config[CONF_USE_EXTENDED_ID]))
|
||||
|
||||
cg.add(
|
||||
var.set_remote_transmission_request(config[CONF_REMOTE_TRANSMISSION_REQUEST])
|
||||
)
|
||||
|
||||
data = config[CONF_DATA]
|
||||
if cg.is_template(data):
|
||||
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
if isinstance(data, bytes):
|
||||
data = [int(x) for x in data]
|
||||
# Generate static array in flash to avoid RAM copy
|
||||
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
|
||||
cg.add(var.set_data_static(arr, len(data)))
|
||||
return var
|
||||
106
canbus/canbus.cpp
Normal file
106
canbus/canbus.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "canbus.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace canbus {
|
||||
|
||||
static const char *const TAG = "canbus";
|
||||
|
||||
void Canbus::setup() {
|
||||
if (!this->setup_internal()) {
|
||||
ESP_LOGE(TAG, "setup error!");
|
||||
this->mark_failed();
|
||||
}
|
||||
}
|
||||
|
||||
void Canbus::dump_config() {
|
||||
if (this->use_extended_id_) {
|
||||
ESP_LOGCONFIG(TAG, "config extended id=0x%08" PRIx32, this->can_id_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "config standard id=0x%03" PRIx32, this->can_id_);
|
||||
}
|
||||
}
|
||||
|
||||
canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const std::vector<uint8_t> &data) {
|
||||
struct CanFrame can_message;
|
||||
|
||||
uint8_t size = static_cast<uint8_t>(data.size());
|
||||
if (use_extended_id) {
|
||||
ESP_LOGD(TAG, "send extended id=0x%08" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request),
|
||||
size);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "send standard id=0x%03" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request),
|
||||
size);
|
||||
}
|
||||
if (size > CAN_MAX_DATA_LENGTH)
|
||||
size = CAN_MAX_DATA_LENGTH;
|
||||
can_message.can_data_length_code = size;
|
||||
can_message.can_id = can_id;
|
||||
can_message.use_extended_id = use_extended_id;
|
||||
can_message.remote_transmission_request = remote_transmission_request;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
can_message.data[i] = data[i];
|
||||
ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]);
|
||||
}
|
||||
|
||||
canbus::Error error = this->send_message(&can_message);
|
||||
if (error != canbus::ERROR_OK) {
|
||||
if (use_extended_id) {
|
||||
ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed with error %d!", can_id, error);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed with error %d!", can_id, error);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void Canbus::add_trigger(CanbusTrigger *trigger) {
|
||||
if (trigger->use_extended_id_) {
|
||||
ESP_LOGVV(TAG, "add trigger for extended canid=0x%08" PRIx32, trigger->can_id_);
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "add trigger for std canid=0x%03" PRIx32, trigger->can_id_);
|
||||
}
|
||||
this->triggers_.push_back(trigger);
|
||||
};
|
||||
|
||||
void Canbus::loop() {
|
||||
struct CanFrame can_message;
|
||||
// read all messages until queue is empty
|
||||
int message_counter = 0;
|
||||
while (this->read_message(&can_message) == canbus::ERROR_OK) {
|
||||
message_counter++;
|
||||
if (can_message.use_extended_id) {
|
||||
ESP_LOGD(TAG, "received can message (#%d) extended can_id=0x%" PRIx32 " size=%d", message_counter,
|
||||
can_message.can_id, can_message.can_data_length_code);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "received can message (#%d) std can_id=0x%" PRIx32 " size=%d", message_counter, can_message.can_id,
|
||||
can_message.can_data_length_code);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// show data received
|
||||
for (int i = 0; i < can_message.can_data_length_code; i++) {
|
||||
ESP_LOGV(TAG, " can_message.data[%d]=%02x", i, can_message.data[i]);
|
||||
data.push_back(can_message.data[i]);
|
||||
}
|
||||
|
||||
this->callback_manager_(can_message.can_id, can_message.use_extended_id, can_message.remote_transmission_request,
|
||||
data);
|
||||
|
||||
// fire all triggers
|
||||
for (auto *trigger : this->triggers_) {
|
||||
if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
|
||||
(trigger->use_extended_id_ == can_message.use_extended_id) &&
|
||||
(!trigger->remote_transmission_request_.has_value() ||
|
||||
trigger->remote_transmission_request_.value() == can_message.remote_transmission_request)) {
|
||||
trigger->trigger(data, can_message.can_id, can_message.remote_transmission_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace canbus
|
||||
} // namespace esphome
|
||||
184
canbus/canbus.h
Normal file
184
canbus/canbus.h
Normal file
@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace canbus {
|
||||
|
||||
enum Error : uint8_t {
|
||||
ERROR_OK = 0,
|
||||
ERROR_FAIL = 1,
|
||||
ERROR_ALLTXBUSY = 2,
|
||||
ERROR_FAILINIT = 3,
|
||||
ERROR_FAILTX = 4,
|
||||
ERROR_NOMSG = 5
|
||||
};
|
||||
|
||||
enum CanSpeed : uint8_t {
|
||||
CAN_1KBPS,
|
||||
CAN_5KBPS,
|
||||
CAN_10KBPS,
|
||||
CAN_12K5BPS,
|
||||
CAN_16KBPS,
|
||||
CAN_20KBPS,
|
||||
CAN_25KBPS,
|
||||
CAN_31K25BPS,
|
||||
CAN_33KBPS,
|
||||
CAN_40KBPS,
|
||||
CAN_50KBPS,
|
||||
CAN_80KBPS,
|
||||
CAN_83K3BPS,
|
||||
CAN_95KBPS,
|
||||
CAN_100KBPS,
|
||||
CAN_125KBPS,
|
||||
CAN_200KBPS,
|
||||
CAN_250KBPS,
|
||||
CAN_500KBPS,
|
||||
CAN_800KBPS,
|
||||
CAN_1000KBPS
|
||||
};
|
||||
|
||||
class CanbusTrigger;
|
||||
template<typename... Ts> class CanbusSendAction;
|
||||
|
||||
/* CAN payload length definitions according to ISO 11898-1 */
|
||||
static const uint8_t CAN_MAX_DATA_LENGTH = 8;
|
||||
|
||||
/*
|
||||
Can Frame describes a normative CAN Frame
|
||||
The RTR = Remote Transmission Request is implemented in every CAN controller but rarely used
|
||||
So currently the flag is passed to and from the hardware but currently ignored to the user application.
|
||||
*/
|
||||
struct CanFrame {
|
||||
bool use_extended_id = false;
|
||||
bool remote_transmission_request = false;
|
||||
uint32_t can_id; /* 29 or 11 bit CAN_ID */
|
||||
uint8_t can_data_length_code; /* frame payload length in byte (0 .. CAN_MAX_DATA_LENGTH) */
|
||||
uint8_t data[CAN_MAX_DATA_LENGTH] __attribute__((aligned(8)));
|
||||
};
|
||||
|
||||
class Canbus : public Component {
|
||||
public:
|
||||
Canbus(){};
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void loop() override;
|
||||
|
||||
canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const std::vector<uint8_t> &data);
|
||||
canbus::Error send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
|
||||
// for backwards compatibility only
|
||||
return this->send_data(can_id, use_extended_id, false, data);
|
||||
}
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
|
||||
|
||||
void add_trigger(CanbusTrigger *trigger);
|
||||
/**
|
||||
* Add a callback to be called when a CAN message is received. All received messages
|
||||
* are passed to the callback without filtering.
|
||||
*
|
||||
* The callback function receives:
|
||||
* - can_id of the received data
|
||||
* - extended_id True if the can_id is an extended id
|
||||
* - rtr If this is a remote transmission request
|
||||
* - data The message data
|
||||
*/
|
||||
void add_callback(
|
||||
std::function<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)> callback) {
|
||||
this->callback_manager_.add(std::move(callback));
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename... Ts> friend class CanbusSendAction;
|
||||
std::vector<CanbusTrigger *> triggers_{};
|
||||
uint32_t can_id_;
|
||||
bool use_extended_id_;
|
||||
CanSpeed bit_rate_;
|
||||
CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
|
||||
callback_manager_{};
|
||||
|
||||
virtual bool setup_internal() = 0;
|
||||
virtual Error send_message(struct CanFrame *frame) = 0;
|
||||
virtual Error read_message(struct CanFrame *frame) = 0;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
||||
public:
|
||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
|
||||
this->data_.func = func;
|
||||
this->len_ = -1; // Sentinel value indicates template mode
|
||||
}
|
||||
|
||||
// Store pointer to static data in flash (no RAM copy)
|
||||
void set_data_static(const uint8_t *data, size_t len) {
|
||||
this->data_.data = data;
|
||||
this->len_ = len; // Length >= 0 indicates static mode
|
||||
}
|
||||
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
|
||||
void set_remote_transmission_request(bool remote_transmission_request) {
|
||||
this->remote_transmission_request_ = remote_transmission_request;
|
||||
}
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||
auto use_extended_id =
|
||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
||||
std::vector<uint8_t> data;
|
||||
if (this->len_ >= 0) {
|
||||
// Static mode: copy from flash to vector
|
||||
data.assign(this->data_.data, this->data_.data + this->len_);
|
||||
} else {
|
||||
// Template mode: call function
|
||||
data = this->data_.func(x...);
|
||||
}
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data);
|
||||
}
|
||||
|
||||
protected:
|
||||
optional<uint32_t> can_id_{};
|
||||
optional<bool> use_extended_id_{};
|
||||
bool remote_transmission_request_{false};
|
||||
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||
union Data {
|
||||
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||
const uint8_t *data; // Pointer to static data in flash
|
||||
} data_;
|
||||
};
|
||||
|
||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||
friend class Canbus;
|
||||
|
||||
public:
|
||||
explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask,
|
||||
const bool use_extended_id)
|
||||
: parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){};
|
||||
|
||||
void set_remote_transmission_request(bool remote_transmission_request) {
|
||||
this->remote_transmission_request_ = remote_transmission_request;
|
||||
}
|
||||
|
||||
void setup() override { this->parent_->add_trigger(this); }
|
||||
|
||||
protected:
|
||||
Canbus *parent_;
|
||||
uint32_t can_id_;
|
||||
uint32_t can_id_mask_;
|
||||
bool use_extended_id_;
|
||||
optional<bool> remote_transmission_request_{};
|
||||
};
|
||||
|
||||
} // namespace canbus
|
||||
} // namespace esphome
|
||||
0
ds3231/__init__.py
Normal file
0
ds3231/__init__.py
Normal file
112
ds3231/ds3231.cpp
Normal file
112
ds3231/ds3231.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include "ds3231.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Datasheet:
|
||||
// - https://datasheets.maximintegrated.com/en/ds/DS3231.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace ds3231 {
|
||||
|
||||
static const char *const TAG = "ds3231";
|
||||
|
||||
void DS3231Component::setup() {
|
||||
if (!this->read_rtc_()) {
|
||||
this->mark_failed();
|
||||
}
|
||||
}
|
||||
|
||||
void DS3231Component::update() { this->read_time(); }
|
||||
|
||||
void DS3231Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DS3231:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
// ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
if (!this->read_bytes(0, this->ds3231_.raw, sizeof(this->ds3231_.raw))) {
|
||||
ESP_LOGE(TAG, "Can't read I2C data.");
|
||||
}
|
||||
}
|
||||
|
||||
float DS3231Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void DS3231Component::read_time() {
|
||||
if (!this->read_rtc_()) {
|
||||
return;
|
||||
}
|
||||
if (ds3231_.reg.ch) {
|
||||
ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
ESPTime rtc_time{
|
||||
.second = uint8_t(ds3231_.reg.second + 10 * ds3231_.reg.second_10),
|
||||
.minute = uint8_t(ds3231_.reg.minute + 10u * ds3231_.reg.minute_10),
|
||||
.hour = uint8_t(ds3231_.reg.hour + 10u * ds3231_.reg.hour_10),
|
||||
.day_of_week = uint8_t(ds3231_.reg.weekday),
|
||||
.day_of_month = uint8_t(ds3231_.reg.day + 10u * ds3231_.reg.day_10),
|
||||
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
|
||||
.month = uint8_t(ds3231_.reg.month + 10u * ds3231_.reg.month_10),
|
||||
.year = uint16_t(ds3231_.reg.year + 10u * ds3231_.reg.year_10 + 2000),
|
||||
.is_dst = false, // not used
|
||||
.timestamp = 0 // overwritten by recalc_timestamp_utc(false)
|
||||
|
||||
};
|
||||
rtc_time.recalc_timestamp_utc(false);
|
||||
if (!rtc_time.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
|
||||
}
|
||||
|
||||
void DS3231Component::write_time() {
|
||||
auto now = time::RealTimeClock::utcnow();
|
||||
if (!now.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
|
||||
return;
|
||||
}
|
||||
ds3231_.reg.year = (now.year - 2000) % 10;
|
||||
ds3231_.reg.year_10 = (now.year - 2000) / 10 % 10;
|
||||
ds3231_.reg.month = now.month % 10;
|
||||
ds3231_.reg.month_10 = now.month / 10;
|
||||
ds3231_.reg.day = now.day_of_month % 10;
|
||||
ds3231_.reg.day_10 = now.day_of_month / 10;
|
||||
ds3231_.reg.weekday = now.day_of_week;
|
||||
ds3231_.reg.hour = now.hour % 10;
|
||||
ds3231_.reg.hour_10 = now.hour / 10;
|
||||
ds3231_.reg.minute = now.minute % 10;
|
||||
ds3231_.reg.minute_10 = now.minute / 10;
|
||||
ds3231_.reg.second = now.second % 10;
|
||||
ds3231_.reg.second_10 = now.second / 10;
|
||||
ds3231_.reg.ch = false;
|
||||
// ESPHOME emits warning about synchronous mode; must check datasheet to see if this is a problem for us. Datasheet pg. 6 says "The DS3231 is designed to operate in a synchronous mode, where the timekeeping registers are updated at the end of each second. This ensures that the timekeeping registers always contain valid data and eliminates the need for software to handle potential timing issues when reading or writing the registers."
|
||||
this->write_rtc_();
|
||||
}
|
||||
|
||||
bool DS3231Component::read_rtc_() {
|
||||
if (!this->read_bytes(0, this->ds3231_.raw, sizeof(this->ds3231_.raw))) {
|
||||
ESP_LOGE(TAG, "Can't read I2C data.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Read %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u CH:%s RS:%0u SQWE:%s OUT:%s", ds3231_.reg.hour_10,
|
||||
ds3231_.reg.hour, ds3231_.reg.minute_10, ds3231_.reg.minute, ds3231_.reg.second_10, ds3231_.reg.second,
|
||||
ds3231_.reg.year_10, ds3231_.reg.year, ds3231_.reg.month_10, ds3231_.reg.month, ds3231_.reg.day_10,
|
||||
ds3231_.reg.day, ONOFF(ds3231_.reg.ch), ds3231_.reg.rs, ONOFF(ds3231_.reg.sqwe), ONOFF(ds3231_.reg.out));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DS3231Component::write_rtc_() {
|
||||
if (!this->write_bytes(0, this->ds3231_.raw, sizeof(this->ds3231_.raw))) {
|
||||
ESP_LOGE(TAG, "Can't write I2C data.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u CH:%s RS:%0u SQWE:%s OUT:%s", ds3231_.reg.hour_10,
|
||||
ds3231_.reg.hour, ds3231_.reg.minute_10, ds3231_.reg.minute, ds3231_.reg.second_10, ds3231_.reg.second,
|
||||
ds3231_.reg.year_10, ds3231_.reg.year, ds3231_.reg.month_10, ds3231_.reg.month, ds3231_.reg.day_10,
|
||||
ds3231_.reg.day, ONOFF(ds3231_.reg.ch), ds3231_.reg.rs, ONOFF(ds3231_.reg.sqwe), ONOFF(ds3231_.reg.out));
|
||||
return true;
|
||||
}
|
||||
} // namespace ds3231
|
||||
} // namespace esphome
|
||||
70
ds3231/ds3231.h
Normal file
70
ds3231/ds3231.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ds3231 {
|
||||
|
||||
class DS3231Component : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void read_time();
|
||||
void write_time();
|
||||
|
||||
protected:
|
||||
bool read_rtc_();
|
||||
bool write_rtc_();
|
||||
union DS3231Reg {
|
||||
struct {
|
||||
uint8_t second : 4;
|
||||
uint8_t second_10 : 3;
|
||||
bool ch : 1;
|
||||
|
||||
uint8_t minute : 4;
|
||||
uint8_t minute_10 : 3;
|
||||
uint8_t unused_1 : 1;
|
||||
|
||||
uint8_t hour : 4;
|
||||
uint8_t hour_10 : 2;
|
||||
uint8_t unused_2 : 2;
|
||||
|
||||
uint8_t weekday : 3;
|
||||
uint8_t unused_3 : 5;
|
||||
|
||||
uint8_t day : 4;
|
||||
uint8_t day_10 : 2;
|
||||
uint8_t unused_4 : 2;
|
||||
|
||||
uint8_t month : 4;
|
||||
uint8_t month_10 : 1;
|
||||
uint8_t unused_5 : 3;
|
||||
|
||||
uint8_t year : 4;
|
||||
uint8_t year_10 : 4;
|
||||
|
||||
uint8_t rs : 2;
|
||||
uint8_t unused_6 : 2;
|
||||
bool sqwe : 1;
|
||||
uint8_t unused_7 : 2;
|
||||
bool out : 1;
|
||||
} reg;
|
||||
mutable uint8_t raw[sizeof(reg)];
|
||||
} ds3231_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<DS3231Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<DS3231Component> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->read_time(); }
|
||||
};
|
||||
} // namespace ds3231
|
||||
} // namespace esphome
|
||||
58
ds3231/time.py
Normal file
58
ds3231/time.py
Normal file
@ -0,0 +1,58 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, time
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
# adapted from DS1307 code by badbadc0ffee
|
||||
CODEOWNERS = ["@stuurmcp"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
ds3231_ns = cg.esphome_ns.namespace("ds3231")
|
||||
DS3231Component = ds3231_ns.class_("DS3231Component", time.RealTimeClock, i2c.I2CDevice)
|
||||
WriteAction = ds3231_ns.class_("WriteAction", automation.Action)
|
||||
ReadAction = ds3231_ns.class_("ReadAction", automation.Action)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DS3231Component),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x68))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3231.write_time",
|
||||
WriteAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3231Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3231_write_time_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ds3231.read_time",
|
||||
ReadAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS3231Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def ds3231_read_time_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await time.register_time(var, config)
|
||||
711
mcp2515/mcp2515.cpp
Normal file
711
mcp2515/mcp2515.cpp
Normal file
@ -0,0 +1,711 @@
|
||||
#include "mcp2515.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp2515 {
|
||||
|
||||
static const char *const TAG = "mcp2515";
|
||||
|
||||
const struct MCP2515::TxBnRegs MCP2515::TXB[N_TXBUFFERS] = {{MCP_TXB0CTRL, MCP_TXB0SIDH, MCP_TXB0DATA},
|
||||
{MCP_TXB1CTRL, MCP_TXB1SIDH, MCP_TXB1DATA},
|
||||
{MCP_TXB2CTRL, MCP_TXB2SIDH, MCP_TXB2DATA}};
|
||||
|
||||
const struct MCP2515::RxBnRegs MCP2515::RXB[N_RXBUFFERS] = {{MCP_RXB0CTRL, MCP_RXB0SIDH, MCP_RXB0DATA, CANINTF_RX0IF},
|
||||
{MCP_RXB1CTRL, MCP_RXB1SIDH, MCP_RXB1DATA, CANINTF_RX1IF}};
|
||||
|
||||
bool MCP2515::setup_internal() {
|
||||
this->spi_setup();
|
||||
|
||||
if (this->reset_() != canbus::ERROR_OK)
|
||||
return false;
|
||||
if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
|
||||
return false;
|
||||
|
||||
// setup hardware filter RXF0 accepting all standard CAN IDs
|
||||
if (this->set_filter_(RXF::RXF0, false, 0) != canbus::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
if (this->set_filter_mask_(MASK::MASK0, false, 0) != canbus::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup hardware filter RXF1 accepting all extended CAN IDs
|
||||
if (this->set_filter_(RXF::RXF1, true, 0) != canbus::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
if (this->set_filter_mask_(MASK::MASK1, true, 0) != canbus::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->set_mode_(this->mcp_mode_) != canbus::ERROR_OK)
|
||||
return false;
|
||||
uint8_t err_flags = this->get_error_flags_();
|
||||
ESP_LOGD(TAG, "mcp2515 setup done, error_flags = %02X", err_flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::reset_() {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_RESET);
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "reset_()");
|
||||
delay(10);
|
||||
|
||||
ESP_LOGV(TAG, "reset() CLEAR ALL TXB registers");
|
||||
|
||||
uint8_t zeros[14];
|
||||
memset(zeros, 0, sizeof(zeros));
|
||||
set_registers_(MCP_TXB0CTRL, zeros, 14);
|
||||
set_registers_(MCP_TXB1CTRL, zeros, 14);
|
||||
set_registers_(MCP_TXB2CTRL, zeros, 14);
|
||||
ESP_LOGV(TAG, "reset() CLEARED TXB registers");
|
||||
|
||||
set_register_(MCP_RXB0CTRL, 0);
|
||||
set_register_(MCP_RXB1CTRL, 0);
|
||||
|
||||
set_register_(MCP_CANINTE, CANINTF_RX0IF | CANINTF_RX1IF | CANINTF_ERRIF | CANINTF_MERRF);
|
||||
|
||||
modify_register_(MCP_RXB0CTRL, RXB_CTRL_RXM_MASK | RXB_0_CTRL_BUKT, RXB_CTRL_RXM_STDEXT | RXB_0_CTRL_BUKT);
|
||||
modify_register_(MCP_RXB1CTRL, RXB_CTRL_RXM_MASK, RXB_CTRL_RXM_STDEXT);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
uint8_t MCP2515::read_register_(const REGISTER reg) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_READ);
|
||||
this->transfer_byte(reg);
|
||||
uint8_t ret = this->transfer_byte(0x00);
|
||||
this->disable();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MCP2515::read_registers_(const REGISTER reg, uint8_t values[], const uint8_t n) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_READ);
|
||||
this->transfer_byte(reg);
|
||||
// this->transfer_array(values, n);
|
||||
// mcp2515 has auto - increment of address - pointer
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
values[i] = this->transfer_byte(0x00);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP2515::set_register_(const REGISTER reg, const uint8_t value) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_WRITE);
|
||||
this->transfer_byte(reg);
|
||||
this->transfer_byte(value);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP2515::set_registers_(const REGISTER reg, uint8_t values[], const uint8_t n) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_WRITE);
|
||||
this->transfer_byte(reg);
|
||||
// this->transfer_array(values, n);
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
this->transfer_byte(values[i]);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void MCP2515::modify_register_(const REGISTER reg, const uint8_t mask, const uint8_t data) {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_BITMOD);
|
||||
this->transfer_byte(reg);
|
||||
this->transfer_byte(mask);
|
||||
this->transfer_byte(data);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
uint8_t MCP2515::get_status_() {
|
||||
this->enable();
|
||||
this->transfer_byte(INSTRUCTION_READ_STATUS);
|
||||
uint8_t i = this->transfer_byte(0x00);
|
||||
this->disable();
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode);
|
||||
|
||||
uint32_t start_time = millis();
|
||||
while (millis() - start_time < 10) {
|
||||
if ((read_register_(MCP_CANSTAT) & CANSTAT_OPMOD) == mode)
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
ESP_LOGE(TAG, "Failed to set mode");
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) {
|
||||
if (divisor == CLKOUT_DISABLE) {
|
||||
/* Turn off CLKEN */
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00);
|
||||
|
||||
/* Turn on CLKOUT for SOF */
|
||||
modify_register_(MCP_CNF3, CNF3_SOF, CNF3_SOF);
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
/* Set the prescaler (CLKPRE) */
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_CLKPRE, divisor);
|
||||
|
||||
/* Turn on CLKEN */
|
||||
modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, CANCTRL_CLKEN);
|
||||
|
||||
/* Turn off CLKOUT for SOF */
|
||||
modify_register_(MCP_CNF3, CNF3_SOF, 0x00);
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
void MCP2515::prepare_id_(uint8_t *buffer, const bool extended, const uint32_t id) {
|
||||
uint16_t canid = (uint16_t) (id & 0x0FFFF);
|
||||
|
||||
if (extended) {
|
||||
buffer[MCP_EID0] = (uint8_t) (canid & 0xFF);
|
||||
buffer[MCP_EID8] = (uint8_t) (canid >> 8);
|
||||
canid = (uint16_t) (id >> 16);
|
||||
buffer[MCP_SIDL] = (uint8_t) (canid & 0x03);
|
||||
buffer[MCP_SIDL] += (uint8_t) ((canid & 0x1C) << 3);
|
||||
buffer[MCP_SIDL] |= SIDL_EXIDE_MASK;
|
||||
buffer[MCP_SIDH] = (uint8_t) (canid >> 5);
|
||||
} else {
|
||||
buffer[MCP_SIDH] = (uint8_t) (canid >> 3);
|
||||
buffer[MCP_SIDL] = (uint8_t) ((canid & 0x07) << 5);
|
||||
buffer[MCP_EID0] = 0;
|
||||
buffer[MCP_EID8] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_filter_mask_(const MASK mask, const bool extended, const uint32_t ul_data) {
|
||||
canbus::Error res = set_mode_(CANCTRL_REQOP_CONFIG);
|
||||
if (res != canbus::ERROR_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t tbufdata[4];
|
||||
prepare_id_(tbufdata, extended, ul_data);
|
||||
|
||||
REGISTER reg;
|
||||
switch (mask) {
|
||||
case MASK0:
|
||||
reg = MCP_RXM0SIDH;
|
||||
break;
|
||||
case MASK1:
|
||||
reg = MCP_RXM1SIDH;
|
||||
break;
|
||||
default:
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
set_registers_(reg, tbufdata, 4);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_filter_(const RXF num, const bool extended, const uint32_t ul_data) {
|
||||
canbus::Error res = set_mode_(CANCTRL_REQOP_CONFIG);
|
||||
if (res != canbus::ERROR_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
REGISTER reg;
|
||||
|
||||
switch (num) {
|
||||
case RXF0:
|
||||
reg = MCP_RXF0SIDH;
|
||||
break;
|
||||
case RXF1:
|
||||
reg = MCP_RXF1SIDH;
|
||||
break;
|
||||
case RXF2:
|
||||
reg = MCP_RXF2SIDH;
|
||||
break;
|
||||
case RXF3:
|
||||
reg = MCP_RXF3SIDH;
|
||||
break;
|
||||
case RXF4:
|
||||
reg = MCP_RXF4SIDH;
|
||||
break;
|
||||
case RXF5:
|
||||
reg = MCP_RXF5SIDH;
|
||||
break;
|
||||
default:
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
uint8_t tbufdata[4];
|
||||
prepare_id_(tbufdata, extended, ul_data);
|
||||
set_registers_(reg, tbufdata, 4);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::send_message_(TXBn txbn, struct canbus::CanFrame *frame) {
|
||||
const struct TxBnRegs *txbuf = &TXB[txbn];
|
||||
|
||||
uint8_t data[13];
|
||||
|
||||
prepare_id_(data, frame->use_extended_id, frame->can_id);
|
||||
data[MCP_DLC] =
|
||||
frame->remote_transmission_request ? (frame->can_data_length_code | RTR_MASK) : frame->can_data_length_code;
|
||||
memcpy(&data[MCP_DATA], frame->data, frame->can_data_length_code);
|
||||
set_registers_(txbuf->SIDH, data, 5 + frame->can_data_length_code);
|
||||
modify_register_(txbuf->CTRL, TXB_TXREQ, TXB_TXREQ);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::send_message(struct canbus::CanFrame *frame) {
|
||||
if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
|
||||
return canbus::ERROR_FAILTX;
|
||||
}
|
||||
TXBn tx_buffers[N_TXBUFFERS] = {TXB0, TXB1, TXB2};
|
||||
|
||||
for (auto &tx_buffer : tx_buffers) {
|
||||
const struct TxBnRegs *txbuf = &TXB[tx_buffer];
|
||||
uint8_t ctrlval = read_register_(txbuf->CTRL);
|
||||
if ((ctrlval & TXB_TXREQ) == 0) {
|
||||
return send_message_(tx_buffer, frame);
|
||||
}
|
||||
}
|
||||
|
||||
return canbus::ERROR_ALLTXBUSY;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::read_message_(RXBn rxbn, struct canbus::CanFrame *frame) {
|
||||
const struct RxBnRegs *rxb = &RXB[rxbn];
|
||||
|
||||
uint8_t tbufdata[5];
|
||||
|
||||
read_registers_(rxb->SIDH, tbufdata, 5);
|
||||
|
||||
uint32_t id = (tbufdata[MCP_SIDH] << 3) + (tbufdata[MCP_SIDL] >> 5);
|
||||
bool use_extended_id = false;
|
||||
bool remote_transmission_request = false;
|
||||
|
||||
if ((tbufdata[MCP_SIDL] & SIDL_EXIDE_MASK) == SIDL_EXIDE_MASK) {
|
||||
id = (id << 2) + (tbufdata[MCP_SIDL] & 0x03);
|
||||
id = (id << 8) + tbufdata[MCP_EID8];
|
||||
id = (id << 8) + tbufdata[MCP_EID0];
|
||||
// id |= canbus::CAN_EFF_FLAG;
|
||||
use_extended_id = true;
|
||||
}
|
||||
|
||||
uint8_t dlc = (tbufdata[MCP_DLC] & DLC_MASK);
|
||||
if (dlc > canbus::CAN_MAX_DATA_LENGTH) {
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
uint8_t ctrl = read_register_(rxb->CTRL);
|
||||
if (ctrl & RXB_CTRL_RTR) {
|
||||
// id |= canbus::CAN_RTR_FLAG;
|
||||
remote_transmission_request = true;
|
||||
}
|
||||
|
||||
frame->can_id = id;
|
||||
frame->can_data_length_code = dlc;
|
||||
frame->use_extended_id = use_extended_id;
|
||||
frame->remote_transmission_request = remote_transmission_request;
|
||||
|
||||
read_registers_(rxb->DATA, frame->data, dlc);
|
||||
|
||||
modify_register_(MCP_CANINTF, rxb->CANINTF_RXnIF, 0);
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::read_message(struct canbus::CanFrame *frame) {
|
||||
canbus::Error rc;
|
||||
uint8_t stat = get_status_();
|
||||
|
||||
if (stat & STAT_RX0IF) {
|
||||
rc = read_message_(RXB0, frame);
|
||||
} else if (stat & STAT_RX1IF) {
|
||||
rc = read_message_(RXB1, frame);
|
||||
} else {
|
||||
rc = canbus::ERROR_NOMSG;
|
||||
}
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_DEBUG
|
||||
uint8_t err = get_error_flags_();
|
||||
// The receive flowchart in the datasheet says that if rollover is set (BUKT), RX1OVR flag will be set
|
||||
// once both buffers are full. However, the RX0OVR flag is actually set instead.
|
||||
// We can just check for both though because it doesn't break anything.
|
||||
if (err & (EFLG_RX0OVR | EFLG_RX1OVR)) {
|
||||
ESP_LOGD(TAG, "receive buffer overrun");
|
||||
clear_rx_n_ovr_flags_();
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool MCP2515::check_receive_() {
|
||||
uint8_t res = get_status_();
|
||||
return (res & STAT_RXIF_MASK) != 0;
|
||||
}
|
||||
|
||||
bool MCP2515::check_error_() {
|
||||
uint8_t eflg = get_error_flags_();
|
||||
return (eflg & EFLG_ERRORMASK) != 0;
|
||||
}
|
||||
|
||||
uint8_t MCP2515::get_error_flags_() { return read_register_(MCP_EFLG); }
|
||||
|
||||
void MCP2515::clear_rx_n_ovr_flags_() { modify_register_(MCP_EFLG, EFLG_RX0OVR | EFLG_RX1OVR, 0); }
|
||||
|
||||
uint8_t MCP2515::get_int_() { return read_register_(MCP_CANINTF); }
|
||||
|
||||
void MCP2515::clear_int_() { set_register_(MCP_CANINTF, 0); }
|
||||
|
||||
uint8_t MCP2515::get_int_mask_() { return read_register_(MCP_CANINTE); }
|
||||
|
||||
void MCP2515::clear_tx_int_() { modify_register_(MCP_CANINTF, (CANINTF_TX0IF | CANINTF_TX1IF | CANINTF_TX2IF), 0); }
|
||||
|
||||
void MCP2515::clear_rx_n_ovr_() {
|
||||
uint8_t eflg = get_error_flags_();
|
||||
if (eflg != 0) {
|
||||
clear_rx_n_ovr_flags_();
|
||||
clear_int_();
|
||||
// modify_register_(MCP_CANINTF, CANINTF_ERRIF, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MCP2515::clear_merr_() {
|
||||
// modify_register_(MCP_EFLG, EFLG_RX0OVR | EFLG_RX1OVR, 0);
|
||||
// clear_int_();
|
||||
modify_register_(MCP_CANINTF, CANINTF_MERRF, 0);
|
||||
}
|
||||
|
||||
void MCP2515::clear_errif_() {
|
||||
// modify_register_(MCP_EFLG, EFLG_RX0OVR | EFLG_RX1OVR, 0);
|
||||
// clear_int_();
|
||||
modify_register_(MCP_CANINTF, CANINTF_ERRIF, 0);
|
||||
}
|
||||
|
||||
canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed) { return this->set_bitrate_(can_speed, MCP_16MHZ); }
|
||||
|
||||
canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clock) {
|
||||
canbus::Error error = set_mode_(CANCTRL_REQOP_CONFIG);
|
||||
if (error != canbus::ERROR_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t set, cfg1, cfg2, cfg3;
|
||||
set = 1;
|
||||
switch (can_clock) {
|
||||
case (MCP_8MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_5KBPS): // 5KBPS
|
||||
cfg1 = MCP_8MHZ_5KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_5KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_5KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_10KBPS): // 10KBPS
|
||||
cfg1 = MCP_8MHZ_10KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_10KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_10KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_20KBPS): // 20KBPS
|
||||
cfg1 = MCP_8MHZ_20KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_20KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_20KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_31K25BPS): // 31.25KBPS
|
||||
cfg1 = MCP_8MHZ_31K25BPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_31K25BPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_31K25BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_33KBPS): // 33.333KBPS
|
||||
cfg1 = MCP_8MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_8MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_8MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_8MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_8MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_8MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_8MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_8MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_8MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_8MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_8MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_8MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case (MCP_12MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_5KBPS): // 5Kbps
|
||||
cfg1 = MCP_12MHZ_5KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_5KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_5KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_10KBPS): // 10Kbps
|
||||
cfg1 = MCP_12MHZ_10KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_10KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_10KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_20KBPS): // 20Kbps
|
||||
cfg1 = MCP_12MHZ_20KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_20KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_20KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_33KBPS): // 33.333Kbps
|
||||
cfg1 = MCP_12MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_12MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_12MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_12MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_12MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_12MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_12MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_12MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_12MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_12MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_12MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_12MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case (MCP_16MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_5KBPS): // 5Kbps
|
||||
cfg1 = MCP_16MHZ_5KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_5KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_5KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_10KBPS): // 10Kbps
|
||||
cfg1 = MCP_16MHZ_10KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_10KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_10KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_20KBPS): // 20Kbps
|
||||
cfg1 = MCP_16MHZ_20KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_20KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_20KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_33KBPS): // 33.333Kbps
|
||||
cfg1 = MCP_16MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_16MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_16MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_16MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_83K3BPS): // 83.333Kbps
|
||||
cfg1 = MCP_16MHZ_83K3BPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_83K3BPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_83K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_16MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_16MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_16MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_16MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_16MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_16MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case (MCP_20MHZ):
|
||||
switch (can_speed) {
|
||||
case (canbus::CAN_33KBPS): // 33.333Kbps
|
||||
cfg1 = MCP_20MHZ_33K3BPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_33K3BPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_33K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_40KBPS): // 40Kbps
|
||||
cfg1 = MCP_20MHZ_40KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_40KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_20MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_80KBPS): // 80Kbps
|
||||
cfg1 = MCP_20MHZ_80KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_80KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_80KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_83K3BPS): // 83.333Kbps
|
||||
cfg1 = MCP_20MHZ_83K3BPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_83K3BPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_83K3BPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_100KBPS): // 100Kbps
|
||||
cfg1 = MCP_20MHZ_100KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_100KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_100KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_125KBPS): // 125Kbps
|
||||
cfg1 = MCP_20MHZ_125KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_125KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_125KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_200KBPS): // 200Kbps
|
||||
cfg1 = MCP_20MHZ_200KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_200KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_200KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_250KBPS): // 250Kbps
|
||||
cfg1 = MCP_20MHZ_250KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_250KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_250KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_500KBPS): // 500Kbps
|
||||
cfg1 = MCP_20MHZ_500KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_500KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_500KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_1000KBPS): // 1Mbps
|
||||
cfg1 = MCP_20MHZ_1000KBPS_CFG1;
|
||||
cfg2 = MCP_20MHZ_1000KBPS_CFG2;
|
||||
cfg3 = MCP_20MHZ_1000KBPS_CFG3;
|
||||
break;
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
set = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (set) {
|
||||
set_register_(MCP_CNF1, cfg1); // NOLINT
|
||||
set_register_(MCP_CNF2, cfg2); // NOLINT
|
||||
set_register_(MCP_CNF3, cfg3); // NOLINT
|
||||
return canbus::ERROR_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid frequency/bitrate combination: %d/%d", can_clock, can_speed);
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
}
|
||||
} // namespace mcp2515
|
||||
} // namespace esphome
|
||||
112
mcp2515/mcp2515.h
Normal file
112
mcp2515/mcp2515.h
Normal file
@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/canbus/canbus.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "mcp2515_defs.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp2515 {
|
||||
static const uint32_t SPI_CLOCK = 10000000; // 10MHz
|
||||
|
||||
static const int N_TXBUFFERS = 3;
|
||||
static const int N_RXBUFFERS = 2;
|
||||
enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_12MHZ, MCP_8MHZ };
|
||||
enum MASK { MASK0, MASK1 };
|
||||
enum RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 };
|
||||
enum RXBn { RXB0 = 0, RXB1 = 1 };
|
||||
enum TXBn { TXB0 = 0, TXB1 = 1, TXB2 = 2 };
|
||||
|
||||
enum CanClkOut {
|
||||
CLKOUT_DISABLE = -1,
|
||||
CLKOUT_DIV1 = 0x0,
|
||||
CLKOUT_DIV2 = 0x1,
|
||||
CLKOUT_DIV4 = 0x2,
|
||||
CLKOUT_DIV8 = 0x3,
|
||||
};
|
||||
|
||||
enum CANINTF : uint8_t {
|
||||
CANINTF_RX0IF = 0x01,
|
||||
CANINTF_RX1IF = 0x02,
|
||||
CANINTF_TX0IF = 0x04,
|
||||
CANINTF_TX1IF = 0x08,
|
||||
CANINTF_TX2IF = 0x10,
|
||||
CANINTF_ERRIF = 0x20,
|
||||
CANINTF_WAKIF = 0x40,
|
||||
CANINTF_MERRF = 0x80
|
||||
};
|
||||
|
||||
enum EFLG : uint8_t {
|
||||
EFLG_RX1OVR = (1 << 7),
|
||||
EFLG_RX0OVR = (1 << 6),
|
||||
EFLG_TXBO = (1 << 5),
|
||||
EFLG_TXEP = (1 << 4),
|
||||
EFLG_RXEP = (1 << 3),
|
||||
EFLG_TXWAR = (1 << 2),
|
||||
EFLG_RXWAR = (1 << 1),
|
||||
EFLG_EWARN = (1 << 0)
|
||||
};
|
||||
|
||||
enum STAT : uint8_t { STAT_RX0IF = (1 << 0), STAT_RX1IF = (1 << 1) };
|
||||
|
||||
static const uint8_t STAT_RXIF_MASK = STAT_RX0IF | STAT_RX1IF;
|
||||
static const uint8_t EFLG_ERRORMASK = EFLG_RX1OVR | EFLG_RX0OVR | EFLG_TXBO | EFLG_TXEP | EFLG_RXEP;
|
||||
|
||||
class MCP2515 : public canbus::Canbus,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
MCP2515(){};
|
||||
void set_mcp_clock(CanClock clock) { this->mcp_clock_ = clock; };
|
||||
void set_mcp_mode(const CanctrlReqopMode mode) { this->mcp_mode_ = mode; }
|
||||
static const struct TxBnRegs {
|
||||
REGISTER CTRL;
|
||||
REGISTER SIDH;
|
||||
REGISTER DATA;
|
||||
} TXB[N_TXBUFFERS];
|
||||
|
||||
static const struct RxBnRegs {
|
||||
REGISTER CTRL;
|
||||
REGISTER SIDH;
|
||||
REGISTER DATA;
|
||||
CANINTF CANINTF_RXnIF;
|
||||
} RXB[N_RXBUFFERS];
|
||||
|
||||
protected:
|
||||
CanClock mcp_clock_{MCP_8MHZ};
|
||||
CanctrlReqopMode mcp_mode_ = CANCTRL_REQOP_NORMAL;
|
||||
bool setup_internal() override;
|
||||
canbus::Error set_mode_(CanctrlReqopMode mode);
|
||||
|
||||
uint8_t read_register_(REGISTER reg);
|
||||
void read_registers_(REGISTER reg, uint8_t values[], uint8_t n);
|
||||
void set_register_(REGISTER reg, uint8_t value);
|
||||
void set_registers_(REGISTER reg, uint8_t values[], uint8_t n);
|
||||
void modify_register_(REGISTER reg, uint8_t mask, uint8_t data);
|
||||
|
||||
void prepare_id_(uint8_t *buffer, bool extended, uint32_t id);
|
||||
canbus::Error reset_();
|
||||
canbus::Error set_clk_out_(CanClkOut divisor);
|
||||
canbus::Error set_bitrate_(canbus::CanSpeed can_speed);
|
||||
canbus::Error set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clock);
|
||||
canbus::Error set_filter_mask_(MASK mask, bool extended, uint32_t ul_data);
|
||||
canbus::Error set_filter_(RXF num, bool extended, uint32_t ul_data);
|
||||
canbus::Error send_message_(TXBn txbn, struct canbus::CanFrame *frame);
|
||||
canbus::Error send_message(struct canbus::CanFrame *frame) override;
|
||||
canbus::Error read_message_(RXBn rxbn, struct canbus::CanFrame *frame);
|
||||
canbus::Error read_message(struct canbus::CanFrame *frame) override;
|
||||
bool check_receive_();
|
||||
bool check_error_();
|
||||
uint8_t get_error_flags_();
|
||||
void clear_rx_n_ovr_flags_();
|
||||
uint8_t get_int_();
|
||||
uint8_t get_int_mask_();
|
||||
void clear_int_();
|
||||
void clear_tx_int_();
|
||||
uint8_t get_status_();
|
||||
void clear_rx_n_ovr_();
|
||||
void clear_merr_();
|
||||
void clear_errif_();
|
||||
};
|
||||
} // namespace mcp2515
|
||||
} // namespace esphome
|
||||
375
mcp2515/mcp2515_defs.h
Normal file
375
mcp2515/mcp2515_defs.h
Normal file
@ -0,0 +1,375 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp2515 {
|
||||
|
||||
static const uint8_t CANCTRL_REQOP = 0xE0;
|
||||
static const uint8_t CANCTRL_ABAT = 0x10;
|
||||
static const uint8_t CANCTRL_OSM = 0x08;
|
||||
static const uint8_t CANCTRL_CLKEN = 0x04;
|
||||
static const uint8_t CANCTRL_CLKPRE = 0x03;
|
||||
|
||||
enum CanctrlReqopMode : uint8_t {
|
||||
CANCTRL_REQOP_NORMAL = 0x00,
|
||||
CANCTRL_REQOP_SLEEP = 0x20,
|
||||
CANCTRL_REQOP_LOOPBACK = 0x40,
|
||||
CANCTRL_REQOP_LISTENONLY = 0x60,
|
||||
CANCTRL_REQOP_CONFIG = 0x80,
|
||||
CANCTRL_REQOP_POWERUP = 0xE0
|
||||
};
|
||||
|
||||
enum TxbNCtrl : uint8_t {
|
||||
TXB_ABTF = 0x40,
|
||||
TXB_MLOA = 0x20,
|
||||
TXB_TXERR = 0x10,
|
||||
TXB_TXREQ = 0x08,
|
||||
TXB_TXIE = 0x04,
|
||||
TXB_TXP = 0x03
|
||||
};
|
||||
|
||||
enum INSTRUCTION : uint8_t {
|
||||
INSTRUCTION_WRITE = 0x02,
|
||||
INSTRUCTION_READ = 0x03,
|
||||
INSTRUCTION_BITMOD = 0x05,
|
||||
INSTRUCTION_LOAD_TX0 = 0x40,
|
||||
INSTRUCTION_LOAD_TX1 = 0x42,
|
||||
INSTRUCTION_LOAD_TX2 = 0x44,
|
||||
INSTRUCTION_RTS_TX0 = 0x81,
|
||||
INSTRUCTION_RTS_TX1 = 0x82,
|
||||
INSTRUCTION_RTS_TX2 = 0x84,
|
||||
INSTRUCTION_RTS_ALL = 0x87,
|
||||
INSTRUCTION_READ_RX0 = 0x90,
|
||||
INSTRUCTION_READ_RX1 = 0x94,
|
||||
INSTRUCTION_READ_STATUS = 0xA0,
|
||||
INSTRUCTION_RX_STATUS = 0xB0,
|
||||
INSTRUCTION_RESET = 0xC0
|
||||
};
|
||||
|
||||
enum REGISTER : uint8_t {
|
||||
MCP_RXF0SIDH = 0x00,
|
||||
MCP_RXF0SIDL = 0x01,
|
||||
MCP_RXF0EID8 = 0x02,
|
||||
MCP_RXF0EID0 = 0x03,
|
||||
MCP_RXF1SIDH = 0x04,
|
||||
MCP_RXF1SIDL = 0x05,
|
||||
MCP_RXF1EID8 = 0x06,
|
||||
MCP_RXF1EID0 = 0x07,
|
||||
MCP_RXF2SIDH = 0x08,
|
||||
MCP_RXF2SIDL = 0x09,
|
||||
MCP_RXF2EID8 = 0x0A,
|
||||
MCP_RXF2EID0 = 0x0B,
|
||||
MCP_CANSTAT = 0x0E,
|
||||
MCP_CANCTRL = 0x0F,
|
||||
MCP_RXF3SIDH = 0x10,
|
||||
MCP_RXF3SIDL = 0x11,
|
||||
MCP_RXF3EID8 = 0x12,
|
||||
MCP_RXF3EID0 = 0x13,
|
||||
MCP_RXF4SIDH = 0x14,
|
||||
MCP_RXF4SIDL = 0x15,
|
||||
MCP_RXF4EID8 = 0x16,
|
||||
MCP_RXF4EID0 = 0x17,
|
||||
MCP_RXF5SIDH = 0x18,
|
||||
MCP_RXF5SIDL = 0x19,
|
||||
MCP_RXF5EID8 = 0x1A,
|
||||
MCP_RXF5EID0 = 0x1B,
|
||||
MCP_TEC = 0x1C,
|
||||
MCP_REC = 0x1D,
|
||||
MCP_RXM0SIDH = 0x20,
|
||||
MCP_RXM0SIDL = 0x21,
|
||||
MCP_RXM0EID8 = 0x22,
|
||||
MCP_RXM0EID0 = 0x23,
|
||||
MCP_RXM1SIDH = 0x24,
|
||||
MCP_RXM1SIDL = 0x25,
|
||||
MCP_RXM1EID8 = 0x26,
|
||||
MCP_RXM1EID0 = 0x27,
|
||||
MCP_CNF3 = 0x28,
|
||||
MCP_CNF2 = 0x29,
|
||||
MCP_CNF1 = 0x2A,
|
||||
MCP_CANINTE = 0x2B,
|
||||
MCP_CANINTF = 0x2C,
|
||||
MCP_EFLG = 0x2D,
|
||||
MCP_TXB0CTRL = 0x30,
|
||||
MCP_TXB0SIDH = 0x31,
|
||||
MCP_TXB0SIDL = 0x32,
|
||||
MCP_TXB0EID8 = 0x33,
|
||||
MCP_TXB0EID0 = 0x34,
|
||||
MCP_TXB0DLC = 0x35,
|
||||
MCP_TXB0DATA = 0x36,
|
||||
MCP_TXB1CTRL = 0x40,
|
||||
MCP_TXB1SIDH = 0x41,
|
||||
MCP_TXB1SIDL = 0x42,
|
||||
MCP_TXB1EID8 = 0x43,
|
||||
MCP_TXB1EID0 = 0x44,
|
||||
MCP_TXB1DLC = 0x45,
|
||||
MCP_TXB1DATA = 0x46,
|
||||
MCP_TXB2CTRL = 0x50,
|
||||
MCP_TXB2SIDH = 0x51,
|
||||
MCP_TXB2SIDL = 0x52,
|
||||
MCP_TXB2EID8 = 0x53,
|
||||
MCP_TXB2EID0 = 0x54,
|
||||
MCP_TXB2DLC = 0x55,
|
||||
MCP_TXB2DATA = 0x56,
|
||||
MCP_RXB0CTRL = 0x60,
|
||||
MCP_RXB0SIDH = 0x61,
|
||||
MCP_RXB0SIDL = 0x62,
|
||||
MCP_RXB0EID8 = 0x63,
|
||||
MCP_RXB0EID0 = 0x64,
|
||||
MCP_RXB0DLC = 0x65,
|
||||
MCP_RXB0DATA = 0x66,
|
||||
MCP_RXB1CTRL = 0x70,
|
||||
MCP_RXB1SIDH = 0x71,
|
||||
MCP_RXB1SIDL = 0x72,
|
||||
MCP_RXB1EID8 = 0x73,
|
||||
MCP_RXB1EID0 = 0x74,
|
||||
MCP_RXB1DLC = 0x75,
|
||||
MCP_RXB1DATA = 0x76
|
||||
};
|
||||
|
||||
static const uint8_t CANSTAT_OPMOD = 0xE0;
|
||||
static const uint8_t CANSTAT_ICOD = 0x0E;
|
||||
|
||||
static const uint8_t CNF3_SOF = 0x80;
|
||||
|
||||
// applies to RXBn_SIDL, TXBn_SIDL and RXFn_SIDL
|
||||
static const uint8_t SIDL_EXIDE_MASK = 0x08;
|
||||
|
||||
static const uint8_t DLC_MASK = 0x0F;
|
||||
static const uint8_t RTR_MASK = 0x40;
|
||||
|
||||
static const uint8_t RXB_CTRL_RXM_STD = 0x20;
|
||||
static const uint8_t RXB_CTRL_RXM_EXT = 0x40;
|
||||
static const uint8_t RXB_CTRL_RXM_STDEXT = 0x00;
|
||||
static const uint8_t RXB_CTRL_RXM_MASK = 0x60;
|
||||
static const uint8_t RXB_CTRL_RTR = 0x08;
|
||||
static const uint8_t RXB_0_CTRL_BUKT = 0x04;
|
||||
|
||||
static const uint8_t MCP_SIDH = 0;
|
||||
static const uint8_t MCP_SIDL = 1;
|
||||
static const uint8_t MCP_EID8 = 2;
|
||||
static const uint8_t MCP_EID0 = 3;
|
||||
static const uint8_t MCP_DLC = 4;
|
||||
static const uint8_t MCP_DATA = 5;
|
||||
|
||||
/*
|
||||
* Speed 8M
|
||||
*/
|
||||
static const uint8_t MCP_8MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_1000KBPS_CFG2 = 0x80;
|
||||
static const uint8_t MCP_8MHZ_1000KBPS_CFG3 = 0x80;
|
||||
|
||||
static const uint8_t MCP_8MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_500KBPS_CFG2 = 0x90;
|
||||
static const uint8_t MCP_8MHZ_500KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_8MHZ_250KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_250KBPS_CFG2 = 0xB1;
|
||||
static const uint8_t MCP_8MHZ_250KBPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_8MHZ_200KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_8MHZ_200KBPS_CFG2 = 0xB4;
|
||||
static const uint8_t MCP_8MHZ_200KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_8MHZ_125KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_8MHZ_125KBPS_CFG2 = 0xB1;
|
||||
static const uint8_t MCP_8MHZ_125KBPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_8MHZ_100KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_8MHZ_100KBPS_CFG2 = 0xB4;
|
||||
static const uint8_t MCP_8MHZ_100KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_8MHZ_80KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_8MHZ_80KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_80KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_50KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_8MHZ_50KBPS_CFG2 = 0xB4;
|
||||
static const uint8_t MCP_8MHZ_50KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_8MHZ_40KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_8MHZ_40KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_40KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_33K3BPS_CFG1 = 0x47;
|
||||
static const uint8_t MCP_8MHZ_33K3BPS_CFG2 = 0xE2;
|
||||
static const uint8_t MCP_8MHZ_33K3BPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_8MHZ_31K25BPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_8MHZ_31K25BPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_8MHZ_31K25BPS_CFG3 = 0x84;
|
||||
|
||||
static const uint8_t MCP_8MHZ_20KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_8MHZ_20KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_20KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_10KBPS_CFG1 = 0x0F;
|
||||
static const uint8_t MCP_8MHZ_10KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_10KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_8MHZ_5KBPS_CFG1 = 0x1F;
|
||||
static const uint8_t MCP_8MHZ_5KBPS_CFG2 = 0xBF;
|
||||
static const uint8_t MCP_8MHZ_5KBPS_CFG3 = 0x87;
|
||||
|
||||
/*
|
||||
* Speed 12M
|
||||
*/
|
||||
|
||||
static const uint8_t MCP_12MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_12MHZ_1000KBPS_CFG2 = 0x88;
|
||||
static const uint8_t MCP_12MHZ_1000KBPS_CFG3 = 0x81;
|
||||
|
||||
static const uint8_t MCP_12MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_12MHZ_500KBPS_CFG2 = 0x9B;
|
||||
static const uint8_t MCP_12MHZ_500KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_12MHZ_250KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_12MHZ_250KBPS_CFG2 = 0x9B;
|
||||
static const uint8_t MCP_12MHZ_250KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_12MHZ_200KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_12MHZ_200KBPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_12MHZ_200KBPS_CFG3 = 0x83;
|
||||
|
||||
static const uint8_t MCP_12MHZ_125KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_12MHZ_125KBPS_CFG2 = 0x9B;
|
||||
static const uint8_t MCP_12MHZ_125KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_12MHZ_100KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_12MHZ_100KBPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_12MHZ_100KBPS_CFG3 = 0x83;
|
||||
|
||||
static const uint8_t MCP_12MHZ_80KBPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_12MHZ_80KBPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_12MHZ_80KBPS_CFG3 = 0x83;
|
||||
|
||||
static const uint8_t MCP_12MHZ_50KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_12MHZ_50KBPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_12MHZ_50KBPS_CFG3 = 0x83;
|
||||
|
||||
static const uint8_t MCP_12MHZ_40KBPS_CFG1 = 0x09;
|
||||
static const uint8_t MCP_12MHZ_40KBPS_CFG2 = 0xA4;
|
||||
static const uint8_t MCP_12MHZ_40KBPS_CFG3 = 0x83;
|
||||
|
||||
static const uint8_t MCP_12MHZ_33K3BPS_CFG1 = 0x08;
|
||||
static const uint8_t MCP_12MHZ_33K3BPS_CFG2 = 0xB6;
|
||||
static const uint8_t MCP_12MHZ_33K3BPS_CFG3 = 0x84;
|
||||
|
||||
static const uint8_t MCP_12MHZ_20KBPS_CFG1 = 0x0E;
|
||||
static const uint8_t MCP_12MHZ_20KBPS_CFG2 = 0xB6;
|
||||
static const uint8_t MCP_12MHZ_20KBPS_CFG3 = 0x84;
|
||||
|
||||
static const uint8_t MCP_12MHZ_10KBPS_CFG1 = 0x31;
|
||||
static const uint8_t MCP_12MHZ_10KBPS_CFG2 = 0x9B;
|
||||
static const uint8_t MCP_12MHZ_10KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_12MHZ_5KBPS_CFG1 = 0x3B;
|
||||
static const uint8_t MCP_12MHZ_5KBPS_CFG2 = 0xB6;
|
||||
static const uint8_t MCP_12MHZ_5KBPS_CFG3 = 0x84;
|
||||
|
||||
/*
|
||||
* speed 16M
|
||||
*/
|
||||
static const uint8_t MCP_16MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_16MHZ_1000KBPS_CFG2 = 0xD0;
|
||||
static const uint8_t MCP_16MHZ_1000KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_16MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_16MHZ_500KBPS_CFG2 = 0xF0;
|
||||
static const uint8_t MCP_16MHZ_500KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_16MHZ_250KBPS_CFG1 = 0x41;
|
||||
static const uint8_t MCP_16MHZ_250KBPS_CFG2 = 0xF1;
|
||||
static const uint8_t MCP_16MHZ_250KBPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_16MHZ_200KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_16MHZ_200KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_16MHZ_200KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_125KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_125KBPS_CFG2 = 0xF0;
|
||||
static const uint8_t MCP_16MHZ_125KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_16MHZ_100KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_100KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_16MHZ_100KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_80KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_80KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_80KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_83K3BPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_16MHZ_83K3BPS_CFG2 = 0xBE;
|
||||
static const uint8_t MCP_16MHZ_83K3BPS_CFG3 = 0x07;
|
||||
|
||||
static const uint8_t MCP_16MHZ_50KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_16MHZ_50KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_16MHZ_50KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_40KBPS_CFG1 = 0x07;
|
||||
static const uint8_t MCP_16MHZ_40KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_40KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_33K3BPS_CFG1 = 0x4E;
|
||||
static const uint8_t MCP_16MHZ_33K3BPS_CFG2 = 0xF1;
|
||||
static const uint8_t MCP_16MHZ_33K3BPS_CFG3 = 0x85;
|
||||
|
||||
static const uint8_t MCP_16MHZ_20KBPS_CFG1 = 0x0F;
|
||||
static const uint8_t MCP_16MHZ_20KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_20KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_10KBPS_CFG1 = 0x1F;
|
||||
static const uint8_t MCP_16MHZ_10KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_10KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_16MHZ_5KBPS_CFG1 = 0x3F;
|
||||
static const uint8_t MCP_16MHZ_5KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_16MHZ_5KBPS_CFG3 = 0x87;
|
||||
|
||||
/*
|
||||
* speed 20M
|
||||
*/
|
||||
static const uint8_t MCP_20MHZ_1000KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_20MHZ_1000KBPS_CFG2 = 0xD9;
|
||||
static const uint8_t MCP_20MHZ_1000KBPS_CFG3 = 0x82;
|
||||
|
||||
static const uint8_t MCP_20MHZ_500KBPS_CFG1 = 0x00;
|
||||
static const uint8_t MCP_20MHZ_500KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_500KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_250KBPS_CFG1 = 0x41;
|
||||
static const uint8_t MCP_20MHZ_250KBPS_CFG2 = 0xFB;
|
||||
static const uint8_t MCP_20MHZ_250KBPS_CFG3 = 0x86;
|
||||
|
||||
static const uint8_t MCP_20MHZ_200KBPS_CFG1 = 0x01;
|
||||
static const uint8_t MCP_20MHZ_200KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_200KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_125KBPS_CFG1 = 0x03;
|
||||
static const uint8_t MCP_20MHZ_125KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_125KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_100KBPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_20MHZ_100KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_100KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_83K3BPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_20MHZ_83K3BPS_CFG2 = 0xFE;
|
||||
static const uint8_t MCP_20MHZ_83K3BPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_80KBPS_CFG1 = 0x04;
|
||||
static const uint8_t MCP_20MHZ_80KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_80KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_50KBPS_CFG1 = 0x09;
|
||||
static const uint8_t MCP_20MHZ_50KBPS_CFG2 = 0xFA;
|
||||
static const uint8_t MCP_20MHZ_50KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_40KBPS_CFG1 = 0x09;
|
||||
static const uint8_t MCP_20MHZ_40KBPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_40KBPS_CFG3 = 0x87;
|
||||
|
||||
static const uint8_t MCP_20MHZ_33K3BPS_CFG1 = 0x0B;
|
||||
static const uint8_t MCP_20MHZ_33K3BPS_CFG2 = 0xFF;
|
||||
static const uint8_t MCP_20MHZ_33K3BPS_CFG3 = 0x87;
|
||||
|
||||
} // namespace mcp2515
|
||||
} // namespace esphome
|
||||
1
rtq6056/__init__.py
Normal file
1
rtq6056/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@Sergio303", "@latonita"]
|
||||
167
rtq6056/rtq6056.cpp
Normal file
167
rtq6056/rtq6056.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include "rtq6056.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace rtq6056 {
|
||||
|
||||
static const char *const TAG = "rtq6056";
|
||||
|
||||
// | A1 | A0 | Address |
|
||||
// | GND | GND | 0x40 |
|
||||
// | GND | V_S+ | 0x41 |
|
||||
// | GND | SDA | 0x42 |
|
||||
// | GND | SCL | 0x43 |
|
||||
// | V_S+ | GND | 0x44 |
|
||||
// | V_S+ | V_S+ | 0x45 |
|
||||
// | V_S+ | SDA | 0x46 |
|
||||
// | V_S+ | SCL | 0x47 |
|
||||
// | SDA | GND | 0x48 |
|
||||
// | SDA | V_S+ | 0x49 |
|
||||
// | SDA | SDA | 0x4A |
|
||||
// | SDA | SCL | 0x4B |
|
||||
// | SCL | GND | 0x4C |
|
||||
// | SCL | V_S+ | 0x4D |
|
||||
// | SCL | SDA | 0x4E |
|
||||
// | SCL | SCL | 0x4F |
|
||||
|
||||
static const uint8_t RTQ6056_REGISTER_CONFIG = 0x00;
|
||||
static const uint8_t RTQ6056_REGISTER_SHUNT_VOLTAGE = 0x01;
|
||||
static const uint8_t RTQ6056_REGISTER_BUS_VOLTAGE = 0x02;
|
||||
static const uint8_t RTQ6056_REGISTER_POWER = 0x03;
|
||||
static const uint8_t RTQ6056_REGISTER_CURRENT = 0x04;
|
||||
static const uint8_t RTQ6056_REGISTER_CALIBRATION = 0x05;
|
||||
|
||||
static const uint16_t RTQ6056_ADC_TIMES[] = {140, 204, 332, 588, 1100, 2116, 4156, 8244};
|
||||
static const uint16_t RTQ6056_ADC_AVG_SAMPLES[] = {1, 4, 16, 64, 128, 256, 512, 1024};
|
||||
|
||||
void RTQ6056Component::setup() {
|
||||
ConfigurationRegister config;
|
||||
|
||||
config.reset = 1;
|
||||
if (!this->write_byte_16(RTQ6056_REGISTER_CONFIG, config.raw)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(1);
|
||||
|
||||
config.raw = 0;
|
||||
config.reserved = 0b100; // as per datasheet
|
||||
|
||||
// Averaging Mode AVG Bit Settings[11:9] (000 -> 1 sample, 001 -> 4 sample, 111 -> 1024 samples)
|
||||
config.avg_samples = this->adc_avg_samples_;
|
||||
|
||||
// Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms)
|
||||
config.bus_voltage_conversion_time = this->adc_time_voltage_;
|
||||
|
||||
// Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms)
|
||||
config.shunt_voltage_conversion_time = this->adc_time_current_;
|
||||
|
||||
// Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous)
|
||||
config.mode = 0b111;
|
||||
|
||||
if (!this->write_byte_16(RTQ6056_REGISTER_CONFIG, config.raw)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// lsb is multiplied by 1000000 to store it as an integer value
|
||||
uint32_t lsb = static_cast<uint32_t>(ceilf(this->max_current_a_ * 1000000.0f / 32768));
|
||||
|
||||
this->calibration_lsb_ = lsb;
|
||||
|
||||
auto calibration = uint32_t(0.00512 / (lsb * this->shunt_resistance_ohm_ / 1000000.0f));
|
||||
|
||||
ESP_LOGV(TAG, " Using LSB=%" PRIu32 " calibration=%" PRIu32, lsb, calibration);
|
||||
|
||||
if (!this->write_byte_16(RTQ6056_REGISTER_CALIBRATION, calibration)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void RTQ6056Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "RTQ6056:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" ADC Conversion Time Bus Voltage: %d\n"
|
||||
" ADC Conversion Time Shunt Voltage: %d\n"
|
||||
" ADC Averaging Samples: %d",
|
||||
RTQ6056_ADC_TIMES[this->adc_time_voltage_ & 0b111], RTQ6056_ADC_TIMES[this->adc_time_current_ & 0b111],
|
||||
RTQ6056_ADC_AVG_SAMPLES[this->adc_avg_samples_ & 0b111]);
|
||||
|
||||
LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Shunt Voltage", this->shunt_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
}
|
||||
|
||||
float RTQ6056Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void RTQ6056Component::update() {
|
||||
if (this->bus_voltage_sensor_ != nullptr) {
|
||||
uint16_t raw_bus_voltage;
|
||||
if (!this->read_byte_16(RTQ6056_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
// Convert for 2's compliment and signed value (though always positive)
|
||||
float bus_voltage_v = this->twos_complement_(raw_bus_voltage, 16);
|
||||
bus_voltage_v *= 0.00125f;
|
||||
this->bus_voltage_sensor_->publish_state(bus_voltage_v);
|
||||
}
|
||||
|
||||
if (this->shunt_voltage_sensor_ != nullptr) {
|
||||
uint16_t raw_shunt_voltage;
|
||||
if (!this->read_byte_16(RTQ6056_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
// Convert for 2's compliment and signed value
|
||||
float shunt_voltage_v = this->twos_complement_(raw_shunt_voltage, 16);
|
||||
shunt_voltage_v *= 0.0000025f;
|
||||
this->shunt_voltage_sensor_->publish_state(shunt_voltage_v);
|
||||
}
|
||||
|
||||
if (this->current_sensor_ != nullptr) {
|
||||
uint16_t raw_current;
|
||||
if (!this->read_byte_16(RTQ6056_REGISTER_CURRENT, &raw_current)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
// Convert for 2's compliment and signed value
|
||||
float current_ma = this->twos_complement_(raw_current, 16);
|
||||
current_ma *= (this->calibration_lsb_ / 1000.0f);
|
||||
this->current_sensor_->publish_state(current_ma / 1000.0f);
|
||||
}
|
||||
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
uint16_t raw_power;
|
||||
if (!this->read_byte_16(RTQ6056_REGISTER_POWER, &raw_power)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float power_mw = int16_t(raw_power) * (this->calibration_lsb_ * 25.0f / 1000.0f);
|
||||
this->power_sensor_->publish_state(power_mw / 1000.0f);
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
int32_t RTQ6056Component::twos_complement_(int32_t val, uint8_t bits) {
|
||||
if (val & ((uint32_t) 1 << (bits - 1))) {
|
||||
val -= (uint32_t) 1 << bits;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespace rtq6056
|
||||
} // namespace esphome
|
||||
78
rtq6056/rtq6056.h
Normal file
78
rtq6056/rtq6056.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace rtq6056 {
|
||||
|
||||
enum AdcTime : uint16_t {
|
||||
ADC_TIME_140US = 0,
|
||||
ADC_TIME_204US = 1,
|
||||
ADC_TIME_332US = 2,
|
||||
ADC_TIME_588US = 3,
|
||||
ADC_TIME_1100US = 4,
|
||||
ADC_TIME_2116US = 5,
|
||||
ADC_TIME_4156US = 6,
|
||||
ADC_TIME_8244US = 7
|
||||
};
|
||||
|
||||
enum AdcAvgSamples : uint16_t {
|
||||
ADC_AVG_SAMPLES_1 = 0,
|
||||
ADC_AVG_SAMPLES_4 = 1,
|
||||
ADC_AVG_SAMPLES_16 = 2,
|
||||
ADC_AVG_SAMPLES_64 = 3,
|
||||
ADC_AVG_SAMPLES_128 = 4,
|
||||
ADC_AVG_SAMPLES_256 = 5,
|
||||
ADC_AVG_SAMPLES_512 = 6,
|
||||
ADC_AVG_SAMPLES_1024 = 7
|
||||
};
|
||||
|
||||
union ConfigurationRegister {
|
||||
uint16_t raw;
|
||||
struct {
|
||||
uint16_t mode : 3;
|
||||
AdcTime shunt_voltage_conversion_time : 3;
|
||||
AdcTime bus_voltage_conversion_time : 3;
|
||||
AdcAvgSamples avg_samples : 3;
|
||||
uint16_t reserved : 3;
|
||||
uint16_t reset : 1;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
class RTQ6056Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; }
|
||||
void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; }
|
||||
void set_adc_time_voltage(AdcTime time) { adc_time_voltage_ = time; }
|
||||
void set_adc_time_current(AdcTime time) { adc_time_current_ = time; }
|
||||
void set_adc_avg_samples(AdcAvgSamples samples) { adc_avg_samples_ = samples; }
|
||||
|
||||
void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; }
|
||||
void set_shunt_voltage_sensor(sensor::Sensor *shunt_voltage_sensor) { shunt_voltage_sensor_ = shunt_voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
|
||||
protected:
|
||||
float shunt_resistance_ohm_;
|
||||
float max_current_a_;
|
||||
AdcTime adc_time_voltage_{AdcTime::ADC_TIME_1100US};
|
||||
AdcTime adc_time_current_{AdcTime::ADC_TIME_1100US};
|
||||
AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_4};
|
||||
uint32_t calibration_lsb_;
|
||||
sensor::Sensor *bus_voltage_sensor_{nullptr};
|
||||
sensor::Sensor *shunt_voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
|
||||
int32_t twos_complement_(int32_t val, uint8_t bits);
|
||||
};
|
||||
|
||||
} // namespace rtq6056
|
||||
} // namespace esphome
|
||||
147
rtq6056/sensor.py
Normal file
147
rtq6056/sensor.py
Normal file
@ -0,0 +1,147 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUS_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_ID,
|
||||
CONF_MAX_CURRENT,
|
||||
CONF_POWER,
|
||||
CONF_SHUNT_RESISTANCE,
|
||||
CONF_SHUNT_VOLTAGE,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_ADC_AVERAGING = "adc_averaging"
|
||||
CONF_ADC_TIME = "adc_time"
|
||||
|
||||
rtq6056_ns = cg.esphome_ns.namespace("rtq6056")
|
||||
RTQ6056Component = rtq6056_ns.class_(
|
||||
"RTQ6056Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
AdcTime = rtq6056_ns.enum("AdcTime")
|
||||
ADC_TIMES = {
|
||||
140: AdcTime.ADC_TIME_140US,
|
||||
204: AdcTime.ADC_TIME_204US,
|
||||
332: AdcTime.ADC_TIME_332US,
|
||||
588: AdcTime.ADC_TIME_588US,
|
||||
1100: AdcTime.ADC_TIME_1100US,
|
||||
2116: AdcTime.ADC_TIME_2116US,
|
||||
4156: AdcTime.ADC_TIME_4156US,
|
||||
8244: AdcTime.ADC_TIME_8244US,
|
||||
}
|
||||
|
||||
AdcAvgSamples = rtq6056_ns.enum("AdcAvgSamples")
|
||||
ADC_AVG_SAMPLES = {
|
||||
1: AdcAvgSamples.ADC_AVG_SAMPLES_1,
|
||||
4: AdcAvgSamples.ADC_AVG_SAMPLES_4,
|
||||
16: AdcAvgSamples.ADC_AVG_SAMPLES_16,
|
||||
64: AdcAvgSamples.ADC_AVG_SAMPLES_64,
|
||||
128: AdcAvgSamples.ADC_AVG_SAMPLES_128,
|
||||
256: AdcAvgSamples.ADC_AVG_SAMPLES_256,
|
||||
512: AdcAvgSamples.ADC_AVG_SAMPLES_512,
|
||||
1024: AdcAvgSamples.ADC_AVG_SAMPLES_1024,
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_time(value):
|
||||
value = cv.positive_time_period_microseconds(value).total_microseconds
|
||||
return cv.enum(ADC_TIMES, int=True)(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RTQ6056Component),
|
||||
cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(
|
||||
cv.resistance, cv.Range(min=0.0)
|
||||
),
|
||||
cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(
|
||||
cv.current, cv.Range(min=0.0)
|
||||
),
|
||||
cv.Optional(CONF_ADC_TIME, default="1100 us"): cv.Any(
|
||||
validate_adc_time,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): validate_adc_time,
|
||||
cv.Required(CONF_CURRENT): validate_adc_time,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_ADC_AVERAGING, default=4): cv.enum(
|
||||
ADC_AVG_SAMPLES, int=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE]))
|
||||
cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT]))
|
||||
|
||||
adc_time_config = config[CONF_ADC_TIME]
|
||||
if isinstance(adc_time_config, dict):
|
||||
cg.add(var.set_adc_time_voltage(adc_time_config[CONF_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_current(adc_time_config[CONF_CURRENT]))
|
||||
else:
|
||||
cg.add(var.set_adc_time_voltage(adc_time_config))
|
||||
cg.add(var.set_adc_time_current(adc_time_config))
|
||||
|
||||
cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING]))
|
||||
|
||||
if CONF_BUS_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE])
|
||||
cg.add(var.set_bus_voltage_sensor(sens))
|
||||
|
||||
if CONF_SHUNT_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_SHUNT_VOLTAGE])
|
||||
cg.add(var.set_shunt_voltage_sensor(sens))
|
||||
|
||||
if CONF_CURRENT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CURRENT])
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
|
||||
if CONF_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
32
tlc59208f_ext/__init__.py
Normal file
32
tlc59208f_ext/__init__.py
Normal file
@ -0,0 +1,32 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
tlc59208f_ext_ns = cg.esphome_ns.namespace("tlc59208f_ext")
|
||||
TLC59208FOutput = tlc59208f_ext_ns.class_("TLC59208FOutput", cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONF_RESET_PIN = "reset_pin"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TLC59208FOutput),
|
||||
cv.Required(CONF_RESET_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x20))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
reset_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset_pin))
|
||||
27
tlc59208f_ext/output.py
Normal file
27
tlc59208f_ext/output.py
Normal file
@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
|
||||
from . import TLC59208FOutput, tlc59208f_ext_ns
|
||||
|
||||
DEPENDENCIES = ["tlc59208f_ext"]
|
||||
|
||||
TLC59208FChannel = tlc59208f_ext_ns.class_("TLC59208FChannel", output.FloatOutput)
|
||||
CONF_TLC59208F_ID = "tlc59208f_id"
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(TLC59208FChannel),
|
||||
cv.GenerateID(CONF_TLC59208F_ID): cv.use_id(TLC59208FOutput),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_TLC59208F_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(paren.register_channel(var))
|
||||
await output.register_output(var, config)
|
||||
211
tlc59208f_ext/tlc59208f_output.cpp
Normal file
211
tlc59208f_ext/tlc59208f_output.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
#include "tlc59208f_output.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tlc59208f_ext {
|
||||
|
||||
static const char *const TAG = "tlc59208f_ext";
|
||||
|
||||
// * marks register defaults
|
||||
// 0*: Register auto increment disabled, 1: Register auto increment enabled
|
||||
const uint8_t TLC59208F_MODE1_AI2 = (1 << 7);
|
||||
// 0*: don't auto increment bit 1, 1: auto increment bit 1
|
||||
const uint8_t TLC59208F_MODE1_AI1 = (1 << 6);
|
||||
// 0*: don't auto increment bit 0, 1: auto increment bit 0
|
||||
const uint8_t TLC59208F_MODE1_AI0 = (1 << 5);
|
||||
// 0: normal mode, 1*: low power mode, osc off
|
||||
const uint8_t TLC59208F_MODE1_SLEEP = (1 << 4);
|
||||
// 0*: device doesn't respond to i2c bus sub-address 1, 1: responds
|
||||
const uint8_t TLC59208F_MODE1_SUB1 = (1 << 3);
|
||||
// 0*: device doesn't respond to i2c bus sub-address 2, 1: responds
|
||||
const uint8_t TLC59208F_MODE1_SUB2 = (1 << 2);
|
||||
// 0*: device doesn't respond to i2c bus sub-address 3, 1: responds
|
||||
const uint8_t TLC59208F_MODE1_SUB3 = (1 << 1);
|
||||
// 0: device doesn't respond to i2c all-call 3, 1*: responds to all-call
|
||||
const uint8_t TLC59208F_MODE1_ALLCALL = (1 << 0);
|
||||
|
||||
// 0*: Group dimming, 1: Group blinking
|
||||
const uint8_t TLC59208F_MODE2_DMBLNK = (1 << 5);
|
||||
// 0*: Output change on Stop command, 1: Output change on ACK
|
||||
const uint8_t TLC59208F_MODE2_OCH = (1 << 3);
|
||||
// 0*: WDT disabled, 1: WDT enabled
|
||||
const uint8_t TLC59208F_MODE2_WDTEN = (1 << 2);
|
||||
// WDT timeouts
|
||||
const uint8_t TLC59208F_MODE2_WDT_5MS = (0 << 0);
|
||||
const uint8_t TLC59208F_MODE2_WDT_15MS = (1 << 0);
|
||||
const uint8_t TLC59208F_MODE2_WDT_25MS = (2 << 0);
|
||||
const uint8_t TLC59208F_MODE2_WDT_35MS = (3 << 0);
|
||||
|
||||
// --- Special function ---
|
||||
// Call address to perform software reset, no devices will ACK
|
||||
const uint8_t TLC59208F_SWRST_ADDR = 0x96; //(0x4b 7-bit addr + ~W)
|
||||
const uint8_t TLC59208F_SWRST_SEQ[2] = {0xa5, 0x5a};
|
||||
|
||||
// --- Registers ---2
|
||||
// Mode register 1
|
||||
const uint8_t TLC59208F_REG_MODE1 = 0x00;
|
||||
// Mode register 2
|
||||
const uint8_t TLC59208F_REG_MODE2 = 0x01;
|
||||
// PWM0
|
||||
const uint8_t TLC59208F_REG_PWM0 = 0x02;
|
||||
// Group PWM
|
||||
const uint8_t TLC59208F_REG_GROUPPWM = 0x0a;
|
||||
// Group Freq
|
||||
const uint8_t TLC59208F_REG_GROUPFREQ = 0x0b;
|
||||
// LEDOUTx registers
|
||||
const uint8_t TLC59208F_REG_LEDOUT0 = 0x0c;
|
||||
const uint8_t TLC59208F_REG_LEDOUT1 = 0x0d;
|
||||
// Sub-address registers
|
||||
const uint8_t TLC59208F_REG_SUBADR1 = 0x0e; // default: 0x92 (8-bit addr)
|
||||
const uint8_t TLC59208F_REG_SUBADR2 = 0x0f; // default: 0x94 (8-bit addr)
|
||||
const uint8_t TLC59208F_REG_SUBADR3 = 0x10; // default: 0x98 (8-bit addr)
|
||||
// All call address register
|
||||
const uint8_t TLC59208F_REG_ALLCALLADR = 0x11; // default: 0xd0 (8-bit addr)
|
||||
|
||||
// --- Output modes ---
|
||||
static const uint8_t LDR_OFF = 0x00;
|
||||
static const uint8_t LDR_ON = 0x01;
|
||||
static const uint8_t LDR_PWM = 0x02;
|
||||
static const uint8_t LDR_GRPPWM = 0x03;
|
||||
|
||||
void TLC59208FOutput::setup() {
|
||||
ESP_LOGD(TAG, " Resetting all devices on the bus");
|
||||
this->hard_reset();
|
||||
// Software reset all devices on the bus
|
||||
auto errcode = this->bus_->write_readv(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, sizeof TLC59208F_SWRST_SEQ, nullptr, 0);
|
||||
if (errcode != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "RESET failed");
|
||||
this->mark_failed(LOG_STR("RESET failed"));
|
||||
return;
|
||||
}
|
||||
// Auto increment registers, and respond to all-call address
|
||||
if (!this->write_byte(TLC59208F_REG_MODE1, TLC59208F_MODE1_AI2 | TLC59208F_MODE1_ALLCALL)) {
|
||||
ESP_LOGE(TAG, "MODE1 failed");
|
||||
this->mark_failed(LOG_STR("MODE1 failed"));
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(TLC59208F_REG_MODE2, this->mode_)) {
|
||||
ESP_LOGE(TAG, "MODE2 failed");
|
||||
this->mark_failed(LOG_STR("MODE2 failed"));
|
||||
return;
|
||||
}
|
||||
// Set all 3 outputs to be individually controlled
|
||||
// TODO: think of a way to support group dimming
|
||||
if (!this->write_byte(TLC59208F_REG_LEDOUT0, (LDR_PWM << 6) | (LDR_PWM << 4) | (LDR_PWM << 2) | (LDR_PWM << 0))) {
|
||||
ESP_LOGE(TAG, "LEDOUT0 failed");
|
||||
this->mark_failed(LOG_STR("LEDOUT0 failed"));
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(TLC59208F_REG_LEDOUT1, (LDR_PWM << 6) | (LDR_PWM << 4) | (LDR_PWM << 2) | (LDR_PWM << 0))) {
|
||||
ESP_LOGE(TAG, "LEDOUT1 failed");
|
||||
this->mark_failed(LOG_STR("LEDOUT1 failed"));
|
||||
return;
|
||||
}
|
||||
delayMicroseconds(500);
|
||||
this->set_all_channels(255);
|
||||
this->loop();
|
||||
}
|
||||
|
||||
void TLC59208FOutput::hard_reset()
|
||||
{
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
ESP_LOGV(TAG, " Performing hard reset using reset pin");
|
||||
this->reset_pin_->digital_write(false);
|
||||
delayMicroseconds(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delayMicroseconds(500);
|
||||
}
|
||||
}
|
||||
|
||||
void TLC59208FOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"TLC59208F_EXT:\n"
|
||||
" Mode: 0x%02X",
|
||||
this->mode_);
|
||||
LOG_PIN(" RESET Pin:", this->reset_pin_);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Setting up TLC59208F failed!");
|
||||
}
|
||||
}
|
||||
|
||||
void TLC59208FOutput::test()
|
||||
{
|
||||
ESP_LOGD(TAG, "TLC59208F_EXT: Running light test");
|
||||
this->set_all_channels(255);
|
||||
this->update_ = true;
|
||||
this->loop();
|
||||
delay(200);
|
||||
this->set_all_channels(0);
|
||||
this->update_ = true;
|
||||
this->loop();
|
||||
}
|
||||
|
||||
void TLC59208FOutput::serial_test()
|
||||
{
|
||||
ESP_LOGD(TAG, "TLC59208F_EXT: Running serial test");
|
||||
for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) {
|
||||
this->set_channel_value_(channel, 255);
|
||||
this->update_ = true;
|
||||
this->loop();
|
||||
delay(20);
|
||||
this->set_channel_value_(channel, 0);
|
||||
}
|
||||
for(uint8_t channel = this->max_channel_; channel >= this->min_channel_; channel--) {
|
||||
this->set_channel_value_(channel, 255);
|
||||
this->update_ = true;
|
||||
this->loop();
|
||||
delay(20);
|
||||
this->set_channel_value_(channel, 0);
|
||||
}
|
||||
this->update_ = true;
|
||||
this->loop();
|
||||
}
|
||||
|
||||
void TLC59208FOutput::set_all_channels(uint8_t value)
|
||||
{
|
||||
for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) {
|
||||
this->set_channel_value_(channel, value);
|
||||
}
|
||||
this->update_ = true;
|
||||
}
|
||||
|
||||
void TLC59208FOutput::loop()
|
||||
{
|
||||
if (this->min_channel_ == 0xFF || !this->update_)
|
||||
return;
|
||||
|
||||
for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) {
|
||||
uint8_t pwm = this->pwm_amounts_[channel];
|
||||
ESP_LOGVV(TAG, "Channel %02u: pwm=%04u ", channel, pwm);
|
||||
|
||||
uint8_t reg = TLC59208F_REG_PWM0 + channel;
|
||||
if (!this->write_byte(reg, pwm)) {
|
||||
char buffer[128];
|
||||
snprintf(buffer, sizeof(buffer), "Writing pwm value %d to channel %d failed.", pwm, channel);
|
||||
this->status_set_warning(buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
void TLC59208FOutput::register_channel(TLC59208FChannel *channel) {
|
||||
auto c = channel->channel_;
|
||||
this->min_channel_ = std::min(this->min_channel_, c);
|
||||
this->max_channel_ = std::max(this->max_channel_, c);
|
||||
channel->set_parent(this);
|
||||
}
|
||||
|
||||
void TLC59208FChannel::write_state(float state) {
|
||||
const uint8_t max_duty = 255;
|
||||
const float duty_rounded = roundf(state * max_duty);
|
||||
auto duty = static_cast<uint8_t>(duty_rounded);
|
||||
this->parent_->set_channel_value_(this->channel_, duty);
|
||||
}
|
||||
|
||||
} // namespace tlc59208f_ext
|
||||
} // namespace esphome
|
||||
79
tlc59208f_ext/tlc59208f_output.h
Normal file
79
tlc59208f_ext/tlc59208f_output.h
Normal file
@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include <esphome/core/gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace tlc59208f_ext {
|
||||
|
||||
// 0*: Group dimming, 1: Group blinking
|
||||
extern const uint8_t TLC59208F_MODE2_DMBLNK;
|
||||
// 0*: Output change on Stop command, 1: Output change on ACK
|
||||
extern const uint8_t TLC59208F_MODE2_OCH;
|
||||
// 0*: WDT disabled, 1: WDT enabled
|
||||
extern const uint8_t TLC59208F_MODE2_WDTEN;
|
||||
// WDT timeouts
|
||||
extern const uint8_t TLC59208F_MODE2_WDT_5MS;
|
||||
extern const uint8_t TLC59208F_MODE2_WDT_15MS;
|
||||
extern const uint8_t TLC59208F_MODE2_WDT_25MS;
|
||||
extern const uint8_t TLC59208F_MODE2_WDT_35MS;
|
||||
|
||||
class TLC59208FOutput;
|
||||
|
||||
class TLC59208FChannel : public output::FloatOutput, public Parented<TLC59208FOutput> {
|
||||
public:
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
friend class TLC59208FOutput;
|
||||
|
||||
void write_state(float state) override;
|
||||
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
/// TLC59208F float output component.
|
||||
class TLC59208FOutput : public Component, public i2c::I2CDevice {
|
||||
private:
|
||||
void hard_reset();
|
||||
|
||||
public:
|
||||
TLC59208FOutput(uint8_t mode = TLC59208F_MODE2_OCH) : mode_(mode) {}
|
||||
|
||||
void register_channel(TLC59208FChannel *channel);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void set_reset_pin(InternalGPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
void loop() override;
|
||||
void test();
|
||||
void serial_test();
|
||||
void set_all_channels(uint8_t value);
|
||||
|
||||
protected:
|
||||
friend TLC59208FChannel;
|
||||
InternalGPIOPin *reset_pin_;
|
||||
|
||||
void set_channel_value_(uint8_t channel, uint8_t value) {
|
||||
if (this->pwm_amounts_[channel] != value)
|
||||
this->update_ = true;
|
||||
this->pwm_amounts_[channel] = value;
|
||||
}
|
||||
|
||||
|
||||
uint8_t mode_;
|
||||
|
||||
uint8_t min_channel_{0xFF};
|
||||
uint8_t max_channel_{0x00};
|
||||
uint8_t pwm_amounts_[256] = {
|
||||
0,
|
||||
};
|
||||
bool update_{true};
|
||||
};
|
||||
|
||||
} // namespace tlc59208f_ext
|
||||
} // namespace esphome
|
||||
Loading…
Reference in New Issue
Block a user