mirror of
https://github.com/esphome/esphome.git
synced 2024-11-23 23:48:11 +01:00
support for KP18058 / KP18068 LED dimming drivers
support for KP18058 / KP18068 LED dimming drivers
This commit is contained in:
parent
ee3ee3a63b
commit
dca6fd8407
7 changed files with 716 additions and 0 deletions
38
esphome/components/kp18058/__init__.py
Normal file
38
esphome/components/kp18058/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CLOCK_PIN,
|
||||||
|
CONF_DATA_PIN,
|
||||||
|
CONF_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
CONF_CW_CURRENT = "cw_current"
|
||||||
|
CONF_RGB_CURRENT = "rgb_current"
|
||||||
|
|
||||||
|
KP18058_ns = cg.esphome_ns.namespace("kp18058")
|
||||||
|
KP18058 = KP18058_ns.class_("kp18058", cg.Component)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(KP18058),
|
||||||
|
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_CW_CURRENT, default=5): cv.int_range(min=0, max=31),
|
||||||
|
cv.Optional(CONF_RGB_CURRENT, default=5): cv.int_range(min=0, max=31),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_cw_current(config[CONF_CW_CURRENT]))
|
||||||
|
cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT]))
|
||||||
|
|
||||||
|
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
|
||||||
|
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||||
|
cg.add(var.set_i2c_pins(data,clock))
|
119
esphome/components/kp18058/kp18058.cpp
Normal file
119
esphome/components/kp18058/kp18058.cpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#include "kp18058.h"
|
||||||
|
#include "message.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kp18058 {
|
||||||
|
|
||||||
|
static const char *const TAG = "kp18058";
|
||||||
|
static const uint8_t I2C_MAX_RETRY = 3;
|
||||||
|
#define BIT_CHECK(PIN, N) !!((PIN & (1 << N)))
|
||||||
|
|
||||||
|
uint8_t GetParityBit(uint8_t b) {
|
||||||
|
uint8_t sum = 0;
|
||||||
|
for (int i = 1; i < 8; i++) {
|
||||||
|
if (BIT_CHECK(b, i)) {
|
||||||
|
sum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum % 2; // 0 for even, 1 for odd
|
||||||
|
}
|
||||||
|
|
||||||
|
kp18058::kp18058() : i2c_ready_(false), max_cw_current_(0), max_rgb_current_(0) {
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
channels_[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kp18058::setup() {
|
||||||
|
i2c_.setup();
|
||||||
|
i2c_ready_ = i2c_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void kp18058::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "KP18058 LED Driver:");
|
||||||
|
LOG_PIN(" Data Pin: ", i2c_.get_data_pin());
|
||||||
|
LOG_PIN(" Clock Pin: ", i2c_.get_clock_pin());
|
||||||
|
ESP_LOGCONFIG(TAG, " I2C Communication %s", i2c_ready_ ? "Initialized": "FAILED");
|
||||||
|
ESP_LOGCONFIG(TAG, " CW max current: %.1f", this->max_cw_current_);
|
||||||
|
ESP_LOGCONFIG(TAG, " RGB max current: %.1f", this->max_rgb_current_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kp18058::program_led_driver() {
|
||||||
|
|
||||||
|
if (!i2c_ready_) {
|
||||||
|
ESP_LOGI(TAG, "Reestablishing communication with KP18058.");
|
||||||
|
i2c_ready_ = i2c_.reset();
|
||||||
|
if (!i2c_ready_) {
|
||||||
|
ESP_LOGE(TAG, "KP18058 I2C Reset failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if All channels are zero or nullptr
|
||||||
|
auto areAllChannelsZero = [this]() {
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
if (channels_[i] != nullptr && channels_[i]->get_value() > 0) {
|
||||||
|
// If any channel is non-zero, return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the settings union
|
||||||
|
KP18058_Settings settings{};
|
||||||
|
|
||||||
|
settings.address_identification = 1;
|
||||||
|
settings.working_mode = areAllChannelsZero() ? STANDBY_MODE : RGBCW_MODE;
|
||||||
|
// Set byte address start. valid values are 0 - 13
|
||||||
|
// In this message always all bytes are transmited (starting from 0)
|
||||||
|
settings.start_byte_address = 0x00;
|
||||||
|
|
||||||
|
// Set Line Compensation Mechanism
|
||||||
|
settings.line_compensation_enable = LC_DISABLE;
|
||||||
|
settings.line_comp_threshold = LC_THRESHOLD_260V;
|
||||||
|
settings.line_comp_slope = LC_SLOPE_10_PERCENT;
|
||||||
|
settings.rc_filter_enable = RC_FILTER_DISABLE;
|
||||||
|
|
||||||
|
// Set max current values
|
||||||
|
settings.max_current_out4_5 = static_cast<uint8_t>(max_cw_current_/2.5) & 0x1F;
|
||||||
|
settings.max_current_out1_3 = static_cast<uint8_t>(max_rgb_current_/2.5) & 0x1F;
|
||||||
|
|
||||||
|
// set dimming method for RGB channels and chop dimming frequency
|
||||||
|
settings.chop_dimming_out1_3 = ANALOG_DIMMING;
|
||||||
|
settings.chop_dimming_frequency = CD_FREQUENCY_500HZ;
|
||||||
|
|
||||||
|
// Set grayscale values for each output channel
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
uint16_t useVal = (channels_[i] != nullptr) ? channels_[i]->get_value() : 0;
|
||||||
|
settings.channels[i].upper_grayscale = (useVal >> 5) & 0x1F;
|
||||||
|
settings.channels[i].lower_grayscale = useVal & 0x1F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate parity bits for each byte
|
||||||
|
for (int i = 0; i < sizeof(KP18058_Settings); ++i) {
|
||||||
|
settings.bytes[i] |= GetParityBit(settings.bytes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the I2C message
|
||||||
|
i2c_.start();
|
||||||
|
for (int i = 0; i < sizeof(KP18058_Settings); i++) {
|
||||||
|
// on error try to repeat the byte transmission I2C_MAX_RETRY times
|
||||||
|
bool write_succeeded;
|
||||||
|
for (int attempt = 0; attempt < I2C_MAX_RETRY; attempt++) {
|
||||||
|
write_succeeded = i2c_.write_byte(settings.bytes[i]);
|
||||||
|
if (write_succeeded) break;
|
||||||
|
}
|
||||||
|
// if all tries failed break and stop sending the rest of the frame bytes
|
||||||
|
if (!write_succeeded) {
|
||||||
|
ESP_LOGE(TAG, "Failed to write byte %02d (0x%02X) after %d attempts", i, settings.bytes[i], I2C_MAX_RETRY);
|
||||||
|
i2c_ready_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i2c_.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace kp18058
|
||||||
|
} // namespace esphome
|
123
esphome/components/kp18058/kp18058.h
Normal file
123
esphome/components/kp18058/kp18058.h
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/gpio.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/output/float_output.h"
|
||||||
|
|
||||||
|
#include "softi2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kp18058 {
|
||||||
|
|
||||||
|
// KP18058 main component for controlling LED drivers over soft I2C
|
||||||
|
class kp18058 : public Component {
|
||||||
|
public:
|
||||||
|
kp18058();
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the configuration details, including pin and communication status.
|
||||||
|
* Outputs diagnostic information to ESPHome logs.
|
||||||
|
*/
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the I2C data and clock pins for the soft I2C driver.
|
||||||
|
*
|
||||||
|
* @param data_pin Pointer to GPIOPin for data line.
|
||||||
|
* @param clock_pin Pointer to GPIOPin for clock line.
|
||||||
|
*/
|
||||||
|
void set_i2c_pins(GPIOPin *data_pin, GPIOPin *clock_pin) { i2c_.set_pins(data_pin, clock_pin); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns an output channel to the kp18058 driver for LED control.
|
||||||
|
*
|
||||||
|
* @param channel The output channel to assign (1-5).
|
||||||
|
* @param output Pointer to the kp18058_output instance for this channel.
|
||||||
|
*/
|
||||||
|
void set_output_channel(uint8_t channel, class kp18058_output *output) { channels_[channel - 1] = output; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum current for the CW (cold-white) channels.
|
||||||
|
*
|
||||||
|
* @param max_cw_current Maximum allowed current for CW channels (in mA).
|
||||||
|
*/
|
||||||
|
void set_cw_current(float max_cw_current) { max_cw_current_ = max_cw_current; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum current for RGB channels.
|
||||||
|
*
|
||||||
|
* @param max_rgb_current Maximum allowed current for RGB channels (in mA).
|
||||||
|
*/
|
||||||
|
void set_rgb_current(float max_rgb_current) { max_rgb_current_ = max_rgb_current; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Programs the LED driver by sending I2C commands based on current channel settings.
|
||||||
|
* Ensures the driver is configured according to active channel values.
|
||||||
|
*/
|
||||||
|
void program_led_driver();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float max_cw_current_;
|
||||||
|
float max_rgb_current_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class kp18058_output *channels_[5];
|
||||||
|
class softI2C i2c_;
|
||||||
|
bool i2c_ready_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// class represents an output channel for the KP18058 LED driver
|
||||||
|
class kp18058_output : public output::FloatOutput {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructor for the kp18058_output class.
|
||||||
|
* Initializes the channel with default values.
|
||||||
|
*/
|
||||||
|
kp18058_output() : parent_(nullptr), value_(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parent kp18058 driver instance for this output channel.
|
||||||
|
*
|
||||||
|
* @param parent Pointer to the parent kp18058 instance.
|
||||||
|
*/
|
||||||
|
void set_parent(kp18058 *parent) { parent_ = parent; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current grayscale value for this output channel.
|
||||||
|
*
|
||||||
|
* @return 10-bit grayscale value (0-1023) representing channel intensity.
|
||||||
|
*/
|
||||||
|
uint16_t get_value() { return this->value_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Overrides the write_state function to control LED brightness.
|
||||||
|
*
|
||||||
|
* @param state Float value representing desired brightness (0.0 to 1.0).
|
||||||
|
* Converts state to a 10-bit grayscale integer (0-1023) and triggers
|
||||||
|
* the parent class to program the LED driver.
|
||||||
|
*/
|
||||||
|
void write_state(float state) override {
|
||||||
|
if (state >= 1)
|
||||||
|
state = 1;
|
||||||
|
else if (state <= 0)
|
||||||
|
state = 0;
|
||||||
|
|
||||||
|
// Convert brightness state (0.0 - 1.0) to 10-bit value (0 - 1023).
|
||||||
|
value_ = static_cast<uint16_t>(roundf(state * 1023));
|
||||||
|
|
||||||
|
// Request parent to reprogram the LED driver with updated brightness values.
|
||||||
|
this->parent_->program_led_driver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10-bit grayscale value representing intensity (0-1023) of the output.
|
||||||
|
uint16_t value_;
|
||||||
|
|
||||||
|
// Pointer to the parent kp18058 driver class for this output channel.
|
||||||
|
kp18058 *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kp18058
|
||||||
|
} // namespace esphome
|
157
esphome/components/kp18058/message.h
Normal file
157
esphome/components/kp18058/message.h
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kp18058 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for device working modes.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
STANDBY_MODE = 0b00,
|
||||||
|
RGB_MODE = 0b01,
|
||||||
|
CW_MODE = 0b10,
|
||||||
|
RGBCW_MODE = 0b11
|
||||||
|
} WorkingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for Line Compensation Mode.
|
||||||
|
*
|
||||||
|
* Controls whether line compensation is enabled or disabled.
|
||||||
|
* When the input voltage exceeds the voltage threshold
|
||||||
|
* the Line compensation decreases the current linearly with the defined slope
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
LC_DISABLE = 0b0,
|
||||||
|
LC_ENABLE = 0b1
|
||||||
|
} LCMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for Line Compensation Start Threshold.
|
||||||
|
*
|
||||||
|
* Defines the input voltage at which line compensation activates. The slope of compensation
|
||||||
|
* changes depending on voltage range and on the Compensation Slope parameter:
|
||||||
|
* - For low thresholds (140V to 175V): Compensation slope parameter is defined for 15V increment.
|
||||||
|
* - For high thresholds (260V to 330V): Compensation slope parameter is defined for 30V increment.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
// Low voltage thresholds (15V increments)
|
||||||
|
LC_THRESHOLD_140V = 0b0000,
|
||||||
|
LC_THRESHOLD_145V = 0b0001,
|
||||||
|
LC_THRESHOLD_150V = 0b0010,
|
||||||
|
LC_THRESHOLD_155V = 0b0011,
|
||||||
|
LC_THRESHOLD_160V = 0b0100,
|
||||||
|
LC_THRESHOLD_165V = 0b0101,
|
||||||
|
LC_THRESHOLD_170V = 0b0110,
|
||||||
|
LC_THRESHOLD_175V = 0b0111,
|
||||||
|
|
||||||
|
// High voltage thresholds (30V increments)
|
||||||
|
LC_THRESHOLD_260V = 0b1000,
|
||||||
|
LC_THRESHOLD_270V = 0b1001,
|
||||||
|
LC_THRESHOLD_280V = 0b1010,
|
||||||
|
LC_THRESHOLD_290V = 0b1011,
|
||||||
|
LC_THRESHOLD_300V = 0b1100,
|
||||||
|
LC_THRESHOLD_310V = 0b1101,
|
||||||
|
LC_THRESHOLD_320V = 0b1110,
|
||||||
|
LC_THRESHOLD_330V = 0b1111
|
||||||
|
} LCThreshold;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for Line Compensation Slope Settings
|
||||||
|
*
|
||||||
|
* Defines the slope used to decrease the current when line compensation is enabled.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
LC_SLOPE_7_5_PERCENT = 0b00, /**< current decreases 7.5% */
|
||||||
|
LC_SLOPE_10_PERCENT = 0b01, /**< current decreases 10% */
|
||||||
|
LC_SLOPE_12_5_PERCENT = 0b10, /**< current decreases 12.5% */
|
||||||
|
LC_SLOPE_15_PERCENT = 0b11 /**< current decreases 15% */
|
||||||
|
} LCSlope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #brief Enumeration for RC Filter Settings
|
||||||
|
*
|
||||||
|
* Controls whether RC filtering is enabled for line compensation calculations.
|
||||||
|
* When enabled, it takes an average input voltage; otherwise, it uses the instantaneous input value.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
RC_FILTER_ENABLE = 0b0,
|
||||||
|
RC_FILTER_DISABLE = 0b1
|
||||||
|
} RCFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for Dimming Modes for RGB Channels (1-3)
|
||||||
|
*
|
||||||
|
* Specifies whether analog dimming or chop dimming is used for RGB channels.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
ANALOG_DIMMING = 0b0, /**< Analog dimming mode, adjusts LED current amplitude. */
|
||||||
|
CHOP_DIMMING = 0b1 /**< Chop dimming mode, adjusts LED current duty cycle. */
|
||||||
|
} DimmingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration for Chop Dimming Frequency Settings
|
||||||
|
* Defines the frequency used for chop dimming mode. */
|
||||||
|
typedef enum {
|
||||||
|
CD_FREQUENCY_4KHZ = 0b00,
|
||||||
|
CD_FREQUENCY_2KHZ = 0b01,
|
||||||
|
CD_FREQUENCY_1KHZ = 0b10,
|
||||||
|
CD_FREQUENCY_500HZ = 0b11
|
||||||
|
} CDFrequency;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union representing the structure of the I2C message
|
||||||
|
* for configuring the KP18058 LED driver settings.
|
||||||
|
*/
|
||||||
|
typedef union {
|
||||||
|
|
||||||
|
// Access the settings as a structure
|
||||||
|
struct {
|
||||||
|
// Byte 0
|
||||||
|
uint8_t byte0_parity_bit : 1;
|
||||||
|
uint8_t start_byte_address : 4;
|
||||||
|
WorkingMode working_mode : 2;
|
||||||
|
uint8_t address_identification : 1;
|
||||||
|
|
||||||
|
// Byte 1
|
||||||
|
uint8_t byte1_parity_bit : 1;
|
||||||
|
LCSlope line_comp_slope : 2;
|
||||||
|
LCThreshold line_comp_threshold : 4;
|
||||||
|
LCMode line_compensation_enable : 1;
|
||||||
|
|
||||||
|
// Byte 2
|
||||||
|
uint8_t byte2_parity_bit : 1;
|
||||||
|
uint8_t max_current_out1_3 : 5;
|
||||||
|
CDFrequency chop_dimming_frequency : 2;
|
||||||
|
|
||||||
|
// Byte 3
|
||||||
|
uint8_t byte3_parity_bit : 1;
|
||||||
|
uint8_t max_current_out4_5 : 5;
|
||||||
|
RCFilter rc_filter_enable : 1;
|
||||||
|
DimmingMode chop_dimming_out1_3 : 1;
|
||||||
|
|
||||||
|
// Channel array for OUT1 to OUT5 grayscale data
|
||||||
|
struct {
|
||||||
|
uint8_t upper_parity : 1;
|
||||||
|
uint8_t upper_grayscale : 5; /**< upper 5 bits of the channel value */
|
||||||
|
uint8_t upper_reserved : 2;
|
||||||
|
|
||||||
|
uint8_t lower_parity : 1;
|
||||||
|
uint8_t lower_grayscale : 5; /**< lower 5 bits of the channel value */
|
||||||
|
uint8_t lower_reserved : 2;
|
||||||
|
} channels[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Access the settings as a byte array
|
||||||
|
uint8_t bytes[14];
|
||||||
|
|
||||||
|
} KP18058_Settings;
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// Ensures that KP18058_Settings size is exactly 14 bytes during compilation
|
||||||
|
static_assert(sizeof(KP18058_Settings) == 14, "Size of KP18058_Settings must be exactly 14 bytes");
|
||||||
|
|
||||||
|
} // namespace kp18058
|
||||||
|
} // namespace esphome
|
51
esphome/components/kp18058/output.py
Normal file
51
esphome/components/kp18058/output.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import output
|
||||||
|
from esphome.const import CONF_ID, CONF_CHANNEL
|
||||||
|
from . import KP18058
|
||||||
|
|
||||||
|
KP18058_ns = cg.esphome_ns.namespace("kp18058")
|
||||||
|
DriverOutput = KP18058_ns.class_("kp18058_output", output.FloatOutput)
|
||||||
|
|
||||||
|
CONF_KP18058_ID = "kp18058_id"
|
||||||
|
|
||||||
|
# Storage for outputs, to be validated at the end
|
||||||
|
_output_registry = {}
|
||||||
|
|
||||||
|
def validate_unique_channels(config):
|
||||||
|
|
||||||
|
kp18058_id = str(config[CONF_KP18058_ID])
|
||||||
|
channel = config[CONF_CHANNEL]
|
||||||
|
|
||||||
|
if kp18058_id not in _output_registry:
|
||||||
|
_output_registry[kp18058_id] = set()
|
||||||
|
|
||||||
|
if channel in _output_registry[kp18058_id]:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Channel {channel} is already used for kp18058 component with id {kp18058_id}. Each output must have a unique channel."
|
||||||
|
)
|
||||||
|
|
||||||
|
_output_registry[kp18058_id].add(channel)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(DriverOutput),
|
||||||
|
cv.GenerateID(CONF_KP18058_ID): cv.use_id(KP18058),
|
||||||
|
cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=5),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
validate_unique_channels,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
parent = await cg.get_variable(config[CONF_KP18058_ID])
|
||||||
|
|
||||||
|
cg.add(var.set_parent(parent))
|
||||||
|
cg.add(parent.set_output_channel(config[CONF_CHANNEL], var))
|
||||||
|
|
||||||
|
await output.register_output(var, config)
|
101
esphome/components/kp18058/softi2c.cpp
Normal file
101
esphome/components/kp18058/softi2c.cpp
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
#include "softi2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kp18058 {
|
||||||
|
|
||||||
|
// Hold time is 250 ns
|
||||||
|
static const uint8_t SOFT_I2C_CLOCK_TIME = 50;
|
||||||
|
|
||||||
|
void ns_sleep(int ns_delay) {
|
||||||
|
// Create a delay by executing NOP instructions in a loop.
|
||||||
|
for (volatile int i = 0; i < ns_delay; i++)
|
||||||
|
__asm__("nop");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool softI2C::reset() {
|
||||||
|
// Ensure SDA is released (high) to avoid conflict during reset
|
||||||
|
set_high(data_pin_);
|
||||||
|
|
||||||
|
// Clock SCL up to 9 times to clear any stuck data
|
||||||
|
for (int i = 0; i < 9; ++i) {
|
||||||
|
set_high(clock_pin_);
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME / 2);
|
||||||
|
set_low(clock_pin_);
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME / 2);
|
||||||
|
|
||||||
|
// Check if SDA is released (high) by the device during clocking
|
||||||
|
if (data_pin_->digital_read()) {
|
||||||
|
break; // SDA released, bus should be free now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final check: If SDA is still low, reset failed
|
||||||
|
if (!data_pin_->digital_read()) {
|
||||||
|
return false; // SDA stuck low, reset unsuccessful
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a start-stop signals during intialisation
|
||||||
|
// in order to prevent the chip from misjudging
|
||||||
|
// the Start signal and causing data errors
|
||||||
|
start();
|
||||||
|
stop();
|
||||||
|
|
||||||
|
// Final verification: both SDA and SCL should be high (bus idle)
|
||||||
|
return (data_pin_->digital_read()) && (clock_pin_->digital_read());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool softI2C::write_byte(uint8_t value) {
|
||||||
|
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
|
||||||
|
for (uint8_t curr = 0x80; curr != 0; curr >>= 1) {
|
||||||
|
if (curr & value) {
|
||||||
|
set_high(data_pin_);
|
||||||
|
} else {
|
||||||
|
set_low(data_pin_);
|
||||||
|
}
|
||||||
|
set_high(clock_pin_);
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME);
|
||||||
|
set_low(clock_pin_);
|
||||||
|
// Data is written to the register on the falling edge of SCL
|
||||||
|
// it needs to be valid through at least HOLD TIME
|
||||||
|
// waiting half a cycle assuming it is longer than HOLD_TIME
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every time the transmission of 8bit data (one byte)
|
||||||
|
// is completed, in the ninth SCL, KP18058 internally
|
||||||
|
// generates a response signal ACK
|
||||||
|
bool ack_received;
|
||||||
|
set_high(data_pin_);
|
||||||
|
set_high(clock_pin_);
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
ack_received = !data_pin_->digital_read();
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
set_low(clock_pin_);
|
||||||
|
|
||||||
|
return ack_received;
|
||||||
|
}
|
||||||
|
|
||||||
|
void softI2C::start() {
|
||||||
|
set_low(data_pin_);
|
||||||
|
// It needs to be valid through at least HOLD TIME
|
||||||
|
// Waiting half a cycle. Assuming it is longer than HOLD_TIME
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
set_low(clock_pin_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void softI2C::stop() {
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
set_low(data_pin_);
|
||||||
|
// It needs to be valid through at least HOLD TIME
|
||||||
|
// Waiting half a cycle. Assuming it is longer than HOLD_TIME
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
set_high(clock_pin_);
|
||||||
|
ns_sleep(SOFT_I2C_CLOCK_TIME/2);
|
||||||
|
set_high(data_pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace kp18058
|
||||||
|
} // namespace esphome
|
127
esphome/components/kp18058/softi2c.h
Normal file
127
esphome/components/kp18058/softi2c.h
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/gpio.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace kp18058 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Class to implement a software I2C protocol.
|
||||||
|
*
|
||||||
|
* This class allows communication with I2C devices using GPIO pins
|
||||||
|
* to simulate I2C communication through software control.
|
||||||
|
*/
|
||||||
|
class softI2C {
|
||||||
|
public:
|
||||||
|
softI2C() : data_pin_(nullptr), clock_pin_(nullptr) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets up the I2C pins.
|
||||||
|
*
|
||||||
|
* This method initializes the data and clock pin objects by calling
|
||||||
|
* their respective setup methods.
|
||||||
|
*/
|
||||||
|
void setup() {
|
||||||
|
data_pin_->setup();
|
||||||
|
clock_pin_->setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the GPIO pins for data and clock.
|
||||||
|
*
|
||||||
|
* This method associates the provided GPIO pins with the I2C instance.
|
||||||
|
*
|
||||||
|
* @param data_pin Pointer to the GPIOPin object for the data line (SDA).
|
||||||
|
* @param clock_pin Pointer to the GPIOPin object for the clock line (SCL).
|
||||||
|
*/
|
||||||
|
void set_pins(GPIOPin *data_pin, GPIOPin *clock_pin) {
|
||||||
|
data_pin_ = data_pin;
|
||||||
|
clock_pin_ = clock_pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the data pin associated with this I2C instance.
|
||||||
|
*
|
||||||
|
* @return Pointer to the GPIOPin object for the data line (SDA).
|
||||||
|
*/
|
||||||
|
GPIOPin* get_data_pin() const { return data_pin_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the clock pin associated with this I2C instance.
|
||||||
|
*
|
||||||
|
* @return Pointer to the GPIOPin object for the clock line (SCL).
|
||||||
|
*/
|
||||||
|
GPIOPin* get_clock_pin() const { return clock_pin_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the I2C bus and checks for device readiness.
|
||||||
|
*
|
||||||
|
* This method generates clock pulses to clear any stuck data on the bus,
|
||||||
|
* checks the state of the data line (SDA), and sends start-stop signals
|
||||||
|
* to ensure proper initialization.
|
||||||
|
*
|
||||||
|
* @return true if the reset was successful and the bus is free.
|
||||||
|
* @return false if the reset failed (SDA remains low).
|
||||||
|
*/
|
||||||
|
bool reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initiates the start condition for I2C communication.
|
||||||
|
*
|
||||||
|
* This method pulls the data line (SDA) low while the clock line (SCL)
|
||||||
|
* is high, signaling the start of communication to the slave device.
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initiates the stop condition for I2C communication.
|
||||||
|
*
|
||||||
|
* This method releases the data line (SDA) after setting the clock line (SCL) high,
|
||||||
|
* signaling the end of communication to the slave device.
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Writes a byte of data to the I2C bus.
|
||||||
|
*
|
||||||
|
* This method sends a byte by shifting each bit to the data line (SDA).
|
||||||
|
* After the byte is sent, it checks for an acknowledgment (ACK) from the slave device.
|
||||||
|
*
|
||||||
|
* @param value The byte value to be sent to the I2C bus.
|
||||||
|
* @return true if an acknowledgment (ACK) is received from the slave.
|
||||||
|
* @return false if no acknowledgment (NACK) is received.
|
||||||
|
*/
|
||||||
|
bool write_byte(uint8_t value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the specified GPIO pin low.
|
||||||
|
*
|
||||||
|
* This method configures the pin as an output and writes a low value (false) to it.
|
||||||
|
*
|
||||||
|
* @param pin Pointer to the GPIOPin object representing the pin to set low.
|
||||||
|
*/
|
||||||
|
void set_low(GPIOPin *pin) {
|
||||||
|
pin->pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
pin->digital_write(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the specified GPIO pin high.
|
||||||
|
*
|
||||||
|
* This method configures the pin as an input with a pull-up resistor.
|
||||||
|
*
|
||||||
|
* @param pin Pointer to the GPIOPin object representing the pin to set high.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void set_high(GPIOPin *pin) {
|
||||||
|
pin->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPIOPin *data_pin_; ///< Pointer to the GPIOPin object for the data line (SDA).
|
||||||
|
GPIOPin *clock_pin_; ///< Pointer to the GPIOPin object for the clock line (SCL).
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kp18058
|
||||||
|
} // namespace esphome
|
Loading…
Reference in a new issue