Add the WeiKai SPI/I2C UART/IO Expander components to esphome (#5218)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Jean Louis-Guerin 2024-04-24 03:21:44 +02:00 committed by GitHub
parent f9ce35c894
commit f8cdb087fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 2874 additions and 0 deletions

View file

@ -400,10 +400,21 @@ esphome/components/wake_on_lan/* @willwill2will54
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
esphome/components/weikai_i2c/* @DrCoolZic
esphome/components/weikai_spi/* @DrCoolZic
esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wk2132_i2c/* @DrCoolZic
esphome/components/wk2132_spi/* @DrCoolZic
esphome/components/wk2168_i2c/* @DrCoolZic
esphome/components/wk2168_spi/* @DrCoolZic
esphome/components/wk2204_i2c/* @DrCoolZic
esphome/components/wk2204_spi/* @DrCoolZic
esphome/components/wk2212_i2c/* @DrCoolZic
esphome/components/wk2212_spi/* @DrCoolZic
esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier

View file

@ -0,0 +1,108 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import (
CONF_BAUD_RATE,
CONF_CHANNEL,
CONF_ID,
CONF_INPUT,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
)
CODEOWNERS = ["@DrCoolZic"]
AUTO_LOAD = ["uart"]
MULTI_CONF = True
CONF_STOP_BITS = "stop_bits"
CONF_PARITY = "parity"
CONF_CRYSTAL = "crystal"
CONF_UART = "uart"
CONF_TEST_MODE = "test_mode"
weikai_ns = cg.esphome_ns.namespace("weikai")
WeikaiComponent = weikai_ns.class_("WeikaiComponent", cg.Component)
WeikaiChannel = weikai_ns.class_("WeikaiChannel", uart.UARTComponent)
def check_channel_max(value, max):
channel_uniq = []
channel_dup = []
for x in value[CONF_UART]:
if x[CONF_CHANNEL] > max - 1:
raise cv.Invalid(f"Invalid channel number: {x[CONF_CHANNEL]}")
if x[CONF_CHANNEL] not in channel_uniq:
channel_uniq.append(x[CONF_CHANNEL])
else:
channel_dup.append(x[CONF_CHANNEL])
if len(channel_dup) > 0:
raise cv.Invalid(f"Duplicate channel list: {channel_dup}")
return value
def check_channel_max_4(value):
return check_channel_max(value, 4)
def check_channel_max_2(value):
return check_channel_max(value, 2)
WKBASE_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(WeikaiComponent),
cv.Optional(CONF_CRYSTAL, default=14745600): cv.int_,
cv.Optional(CONF_TEST_MODE, default=0): cv.int_,
cv.Required(CONF_UART): cv.ensure_list(
{
cv.Required(CONF_ID): cv.declare_id(WeikaiChannel),
cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=3),
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
uart.UART_PARITY_OPTIONS, upper=True
),
}
),
}
).extend(cv.COMPONENT_SCHEMA)
async def register_weikai(var, config):
"""Register an weikai device with the given config."""
cg.add(var.set_crystal(config[CONF_CRYSTAL]))
cg.add(var.set_test_mode(config[CONF_TEST_MODE]))
await cg.register_component(var, config)
for uart_elem in config[CONF_UART]:
chan = cg.new_Pvariable(uart_elem[CONF_ID])
cg.add(chan.set_channel_name(str(uart_elem[CONF_ID])))
cg.add(chan.set_parent(var))
cg.add(chan.set_channel(uart_elem[CONF_CHANNEL]))
cg.add(chan.set_baud_rate(uart_elem[CONF_BAUD_RATE]))
cg.add(chan.set_stop_bits(uart_elem[CONF_STOP_BITS]))
cg.add(chan.set_parity(uart_elem[CONF_PARITY]))
def validate_pin_mode(value):
"""Checks input/output mode inconsistency"""
if not (value[CONF_MODE][CONF_INPUT] or value[CONF_MODE][CONF_OUTPUT]):
raise cv.Invalid("Mode must be either input or output")
if value[CONF_MODE][CONF_INPUT] and value[CONF_MODE][CONF_OUTPUT]:
raise cv.Invalid("Mode must be either input or output")
return value
WEIKAI_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7),
cv.Optional(CONF_MODE, default={}): cv.All(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
},
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
)

View file

@ -0,0 +1,615 @@
/// @file weikai.cpp
/// @brief WeiKai component family - classes implementation
/// @date Last Modified: 2024/04/06 15:13:11
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai.h"
namespace esphome {
namespace weikai {
/*! @mainpage Weikai source code documentation
This documentation provides information about the implementation of the family of WeiKai Components in ESPHome.
Here is the class diagram related to Weikai family of components:
@image html weikai_class.png
@section WKRingBuffer_ The WKRingBuffer template class
The WKRingBuffer template class has it names implies implement a simple ring buffer helper class. This straightforward
container implements FIFO functionality, enabling bytes to be pushed into one side and popped from the other in the
order of entry. Implementation is classic and therefore not described in any details.
@section WeikaiRegister_ The WeikaiRegister class
The WeikaiRegister helper class creates objects that act as proxies to the device registers.
@details This is an abstract virtual class (interface) that provides all the necessary access to registers while hiding
the actual implementation. The access to the registers can be made through an I²C bus in for example for wk2168_i2c
component or through a SPI bus for example in the case of the wk2168_spi component. Derived classes will actually
performs the specific bus operations.
@section WeikaiRegisterI2C_ WeikaiRegisterI2C
The weikai_i2c::WeikaiRegisterI2C class implements the virtual methods of the WeikaiRegister class for an I2C bus.
@section WeikaiRegisterSPI_ WeikaiRegisterSPI
The weikai_spi::WeikaiRegisterSPI class implements the virtual methods of the WeikaiRegister class for an SPI bus.
@section WeikaiComponent_ The WeikaiComponent class
The WeikaiComponent class stores the information global to a WeiKai family component and provides methods to set/access
this information. It also serves as a container for WeikaiChannel instances. This is done by maintaining an array of
references these WeikaiChannel instances. This class derives from the esphome::Component classes. This class override
esphome::Component::loop() method to facilitate the seamless transfer of accumulated bytes from the receive
FIFO into the ring buffer. This process ensures quick access to the stored bytes, enhancing the overall efficiency of
the component.
@section WeikaiComponentI2C_ WeikaiComponentI2C
The weikai_i2c::WeikaiComponentI2C class implements the virtual methods of the WeikaiComponent class for an I2C bus.
@section WeikaiComponentSPI_ WeikaiComponentSPI
The weikai_spi::WeikaiComponentSPI class implements the virtual methods of the WeikaiComponent class for an SPI bus.
@section WeikaiGPIOPin_ WeikaiGPIOPin class
The WeikaiGPIOPin class is an helper class to expose the GPIO pins of WK family components as if they were internal
GPIO pins. It also provides the setup() and dump_summary() methods.
@section WeikaiChannel_ The WeikaiChannel class
The WeikaiChannel class is used to implement all the virtual methods of the ESPHome uart::UARTComponent class. An
individual instance of this class is created for each UART channel. It has a link back to the WeikaiComponent object it
belongs to. This class derives from the uart::UARTComponent class. It collaborates through an aggregation with
WeikaiComponent. This implies that WeikaiComponent acts as a container, housing several WeikaiChannel instances.
Furthermore, the WeikaiChannel class derives from the ESPHome uart::UARTComponent class, it also has an association
relationship with the WKRingBuffer and WeikaiRegister helper classes. Consequently, when a WeikaiChannel instance is
destroyed, the associated WKRingBuffer instance is also destroyed.
*/
static const char *const TAG = "weikai";
/// @brief convert an int to binary representation as C++ std::string
/// @param val integer to convert
/// @return a std::string
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
/// Convert std::string to C string
#define I2S2CS(val) (i2s(val).c_str())
/// @brief measure the time elapsed between two calls
/// @param last_time time of the previous call
/// @return the elapsed time in milliseconds
uint32_t elapsed_ms(uint32_t &last_time) {
uint32_t e = millis() - last_time;
last_time = millis();
return e;
};
/// @brief Converts the parity enum value to a C string
/// @param parity enum
/// @return the string
const char *p2s(uart::UARTParityOptions parity) {
using namespace uart;
switch (parity) {
case UART_CONFIG_PARITY_NONE:
return "NONE";
case UART_CONFIG_PARITY_EVEN:
return "EVEN";
case UART_CONFIG_PARITY_ODD:
return "ODD";
default:
return "UNKNOWN";
}
}
/// @brief Display a buffer in hexadecimal format (32 hex values / line) for debug
void print_buffer(const uint8_t *data, size_t length) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < length; i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]);
if (i % 32 == 31) {
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
if (length % 32) {
// null terminate if incomplete line
hex_buffer[3 * (length % 32) + 2] = 0;
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER",
"SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"};
static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL",
"TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"};
// method to print a register value as text: used in the log messages ...
const char *reg_to_str(int reg, bool page1) {
if (reg == WKREG_GPDAT) {
return "GPDAT";
} else if (reg == WKREG_GPDIR) {
return "GPDIR";
} else {
return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F];
}
}
enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO
///////////////////////////////////////////////////////////////////////////////
// The WeikaiRegister methods
///////////////////////////////////////////////////////////////////////////////
WeikaiRegister &WeikaiRegister::operator=(uint8_t value) {
write_reg(value);
return *this;
}
WeikaiRegister &WeikaiRegister::operator&=(uint8_t value) {
value &= read_reg();
write_reg(value);
return *this;
}
WeikaiRegister &WeikaiRegister::operator|=(uint8_t value) {
value |= read_reg();
write_reg(value);
return *this;
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiComponent methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiComponent::loop() {
if ((this->component_state_ & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP)
return;
// If there are some bytes in the receive FIFO we transfers them to the ring buffers
size_t transferred = 0;
for (auto *child : this->children_) {
// we look if some characters has been received in the fifo
transferred += child->xfer_fifo_to_buffer_();
}
if (transferred > 0) {
ESP_LOGV(TAG, "we transferred %d bytes from fifo to buffer...", transferred);
}
#ifdef TEST_COMPONENT
static uint32_t loop_time = 0;
static uint32_t loop_count = 0;
uint32_t time = 0;
if (test_mode_ == 1) { // test component in loopback
ESP_LOGI(TAG, "Component loop %" PRIu32 " for %s : %" PRIu32 " ms since last call ...", loop_count++,
this->get_name(), millis() - loop_time);
loop_time = millis();
char message[64];
elapsed_ms(time); // set time to now
for (int i = 0; i < this->children_.size(); i++) {
if (i != ((loop_count - 1) % this->children_.size())) // we do only one per loop
continue;
snprintf(message, sizeof(message), "%s:%s", this->get_name(), children_[i]->get_channel_name());
children_[i]->uart_send_test_(message);
uint32_t const start_time = millis();
while (children_[i]->tx_fifo_is_not_empty_()) { // wait until buffer empty
if (millis() - start_time > 1500) {
ESP_LOGE(TAG, "timeout while flushing - %d bytes left in buffer...", children_[i]->tx_in_fifo_());
break;
}
yield(); // reschedule our thread to avoid blocking
}
bool status = children_[i]->uart_receive_test_(message);
ESP_LOGI(TAG, "Test %s => send/received %u bytes %s - execution time %" PRIu32 " ms...", message,
RING_BUFFER_SIZE, status ? "correctly" : "with error", elapsed_ms(time));
}
}
if (this->test_mode_ == 2) { // test component in echo mode
for (auto *child : this->children_) {
uint8_t data = 0;
if (child->available()) {
child->read_byte(&data);
ESP_LOGI(TAG, "echo mode: read -> send %02X", data);
child->write_byte(data);
}
}
}
if (test_mode_ == 3) {
test_gpio_input_();
}
if (test_mode_ == 4) {
test_gpio_output_();
}
#endif
}
#if defined(TEST_COMPONENT)
void WeikaiComponent::test_gpio_input_() {
static bool init_input{false};
static uint8_t state{0};
uint8_t value;
if (!init_input) {
init_input = true;
// set all pins in input mode
this->reg(WKREG_GPDIR, 0) = 0x00;
ESP_LOGI(TAG, "initializing all pins to input mode");
state = this->reg(WKREG_GPDAT, 0);
ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, I2S2CS(state));
}
value = this->reg(WKREG_GPDAT, 0);
if (value != state) {
ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, I2S2CS(value));
state = value;
}
}
void WeikaiComponent::test_gpio_output_() {
static bool init_output{false};
static uint8_t state{0};
if (!init_output) {
init_output = true;
// set all pins in output mode
this->reg(WKREG_GPDIR, 0) = 0xFF;
ESP_LOGI(TAG, "initializing all pins to output mode");
this->reg(WKREG_GPDAT, 0) = state;
ESP_LOGI(TAG, "setting all outputs to 0");
}
state = ~state;
this->reg(WKREG_GPDAT, 0) = state;
ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, I2S2CS(state));
delay(100); // NOLINT
}
#endif
///////////////////////////////////////////////////////////////////////////////
// The WeikaiGPIOPin methods
///////////////////////////////////////////////////////////////////////////////
bool WeikaiComponent::read_pin_val_(uint8_t pin) {
this->input_state_ = this->reg(WKREG_GPDAT, 0);
ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin), I2S2CS(input_state_));
return this->input_state_ & (1 << pin);
}
void WeikaiComponent::write_pin_val_(uint8_t pin, bool value) {
if (value) {
this->output_state_ |= (1 << pin);
} else {
this->output_state_ &= ~(1 << pin);
}
ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value), I2S2CS(this->output_state_));
this->reg(WKREG_GPDAT, 0) = this->output_state_;
}
void WeikaiComponent::set_pin_direction_(uint8_t pin, gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
this->pin_config_ &= ~(1 << pin); // clear bit (input mode)
} else {
if (flags == gpio::FLAG_OUTPUT) {
this->pin_config_ |= 1 << pin; // set bit (output mode)
} else {
ESP_LOGE(TAG, "pin %d direction invalid", pin);
}
}
ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, I2S2CS(this->pin_config_));
this->reg(WKREG_GPDIR, 0) = this->pin_config_; // TODO check ~
}
void WeikaiGPIOPin::setup() {
ESP_LOGCONFIG(TAG, "Setting GPIO pin %d mode to %s", this->pin_,
flags_ == gpio::FLAG_INPUT ? "Input"
: this->flags_ == gpio::FLAG_OUTPUT ? "Output"
: "NOT SPECIFIED");
// ESP_LOGCONFIG(TAG, "Setting GPIO pins mode to '%s' %02X", I2S2CS(this->flags_), this->flags_);
this->pin_mode(this->flags_);
}
std::string WeikaiGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via WeiKai %s", this->pin_, this->parent_->get_name());
return buffer;
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiChannel methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiChannel::setup_channel() {
ESP_LOGCONFIG(TAG, " Setting up UART %s:%s ...", this->parent_->get_name(), this->get_channel_name());
// we enable transmit and receive on this channel
if (this->check_channel_down()) {
ESP_LOGCONFIG(TAG, " Error channel %s not working...", this->get_channel_name());
}
this->reset_fifo_();
this->receive_buffer_.clear();
this->set_line_param_();
this->set_baudrate_();
}
void WeikaiChannel::dump_channel() {
ESP_LOGCONFIG(TAG, " UART %s ...", this->get_channel_name());
ESP_LOGCONFIG(TAG, " Baud rate: %" PRIu32 " Bd", this->baud_rate_);
ESP_LOGCONFIG(TAG, " Data bits: %u", this->data_bits_);
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
ESP_LOGCONFIG(TAG, " Parity: %s", p2s(this->parity_));
}
void WeikaiChannel::reset_fifo_() {
// enable transmission and reception
this->reg(WKREG_SCR) = SCR_RXEN | SCR_TXEN;
// we reset and enable transmit and receive FIFO
this->reg(WKREG_FCR) = FCR_TFEN | FCR_RFEN | FCR_TFRST | FCR_RFRST;
}
void WeikaiChannel::set_line_param_() {
this->data_bits_ = 8; // always equal to 8 for WeiKai (cant be changed)
uint8_t lcr = 0;
if (this->stop_bits_ == 2)
lcr |= LCR_STPL;
switch (this->parity_) { // parity selection settings
case uart::UART_CONFIG_PARITY_ODD:
lcr |= (LCR_PAEN | LCR_PAR_ODD);
break;
case uart::UART_CONFIG_PARITY_EVEN:
lcr |= (LCR_PAEN | LCR_PAR_EVEN);
break;
default:
break; // no parity 000x
}
this->reg(WKREG_LCR) = lcr; // write LCR
ESP_LOGV(TAG, " line config: %d data_bits, %d stop_bits, parity %s register [%s]", this->data_bits_,
this->stop_bits_, p2s(this->parity_), I2S2CS(lcr));
}
void WeikaiChannel::set_baudrate_() {
if (this->baud_rate_ > this->parent_->crystal_ / 16) {
baud_rate_ = this->parent_->crystal_ / 16;
ESP_LOGE(TAG, " Requested baudrate too high for crystal=%" PRIu32 " Hz. Has been reduced to %" PRIu32 " Bd",
this->parent_->crystal_, this->baud_rate_);
};
uint16_t const val_int = this->parent_->crystal_ / (this->baud_rate_ * 16) - 1;
uint16_t val_dec = (this->parent_->crystal_ % (this->baud_rate_ * 16)) / (this->baud_rate_ * 16);
uint8_t const baud_high = (uint8_t) (val_int >> 8);
uint8_t const baud_low = (uint8_t) (val_int & 0xFF);
while (val_dec > 0x0A)
val_dec /= 0x0A;
uint8_t const baud_dec = (uint8_t) (val_dec);
this->parent_->page1_ = true; // switch to page 1
this->reg(WKREG_SPAGE) = 1;
this->reg(WKREG_BRH) = baud_high;
this->reg(WKREG_BRL) = baud_low;
this->reg(WKREG_BRD) = baud_dec;
this->parent_->page1_ = false; // switch back to page 0
this->reg(WKREG_SPAGE) = 0;
ESP_LOGV(TAG, " Crystal=%d baudrate=%d => registers [%d %d %d]", this->parent_->crystal_, this->baud_rate_,
baud_high, baud_low, baud_dec);
}
inline bool WeikaiChannel::tx_fifo_is_not_empty_() { return this->reg(WKREG_FSR) & FSR_TFDAT; }
size_t WeikaiChannel::tx_in_fifo_() {
size_t tfcnt = this->reg(WKREG_TFCNT);
if (tfcnt == 0) {
uint8_t const fsr = this->reg(WKREG_FSR);
if (fsr & FSR_TFFULL) {
ESP_LOGVV(TAG, "tx FIFO full FSR=%s", I2S2CS(fsr));
tfcnt = FIFO_SIZE;
}
}
ESP_LOGVV(TAG, "tx FIFO contains %d bytes", tfcnt);
return tfcnt;
}
size_t WeikaiChannel::rx_in_fifo_() {
size_t available = this->reg(WKREG_RFCNT);
uint8_t const fsr = this->reg(WKREG_FSR);
if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) {
if (fsr & FSR_RFOE)
ESP_LOGE(TAG, "Receive data overflow FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFLB)
ESP_LOGE(TAG, "Receive line break FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFFE)
ESP_LOGE(TAG, "Receive frame error FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFPE)
ESP_LOGE(TAG, "Receive parity error FSR=%s", I2S2CS(fsr));
}
if ((available == 0) && (fsr & FSR_RFDAT)) {
// here we should be very careful because we can have something like this:
// - at time t0 we read RFCNT=0 because nothing yet received
// - at time t0+delta we might read FIFO not empty because one byte has just been received
// - so to be sure we need to do another read of RFCNT and if it is still zero -> buffer full
available = this->reg(WKREG_RFCNT);
if (available == 0) { // still zero ?
ESP_LOGV(TAG, "rx FIFO is full FSR=%s", I2S2CS(fsr));
available = FIFO_SIZE;
}
}
ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, I2S2CS(fsr));
return available;
}
bool WeikaiChannel::check_channel_down() {
// to check if we channel is up we write to the LCR W/R register
// note that this will put a break on the tx line for few ms
WeikaiRegister &lcr = this->reg(WKREG_LCR);
lcr = 0x3F;
uint8_t val = lcr;
if (val != 0x3F) {
ESP_LOGE(TAG, "R/W of register failed expected 0x3F received 0x%02X", val);
return true;
}
lcr = 0;
val = lcr;
if (val != 0x00) {
ESP_LOGE(TAG, "R/W of register failed expected 0x00 received 0x%02X", val);
return true;
}
return false;
}
bool WeikaiChannel::peek_byte(uint8_t *buffer) {
auto available = this->receive_buffer_.count();
if (!available)
xfer_fifo_to_buffer_();
return this->receive_buffer_.peek(*buffer);
}
int WeikaiChannel::available() {
size_t available = this->receive_buffer_.count();
if (!available)
available = xfer_fifo_to_buffer_();
return available;
}
bool WeikaiChannel::read_array(uint8_t *buffer, size_t length) {
bool status = true;
auto available = this->receive_buffer_.count();
if (length > available) {
ESP_LOGW(TAG, "read_array: buffer underflow requested %d bytes only %d bytes available...", length, available);
length = available;
status = false;
}
// retrieve the bytes from ring buffer
for (size_t i = 0; i < length; i++) {
this->receive_buffer_.pop(buffer[i]);
}
ESP_LOGVV(TAG, "read_array(ch=%d buffer[0]=%02X, length=%d): status %s", this->channel_, *buffer, length,
status ? "OK" : "ERROR");
return status;
}
void WeikaiChannel::write_array(const uint8_t *buffer, size_t length) {
if (length > XFER_MAX_SIZE) {
ESP_LOGE(TAG, "Write_array: invalid call - requested %d bytes but max size %d ...", length, XFER_MAX_SIZE);
length = XFER_MAX_SIZE;
}
this->reg(0).write_fifo(const_cast<uint8_t *>(buffer), length);
}
void WeikaiChannel::flush() {
uint32_t const start_time = millis();
while (this->tx_fifo_is_not_empty_()) { // wait until buffer empty
if (millis() - start_time > 200) {
ESP_LOGW(TAG, "WARNING flush timeout - still %d bytes not sent after 200 ms...", this->tx_in_fifo_());
return;
}
yield(); // reschedule our thread to avoid blocking
}
}
size_t WeikaiChannel::xfer_fifo_to_buffer_() {
size_t to_transfer;
size_t free;
while ((to_transfer = this->rx_in_fifo_()) && (free = this->receive_buffer_.free())) {
// while bytes in fifo and some room in the buffer we transfer
if (to_transfer > XFER_MAX_SIZE)
to_transfer = XFER_MAX_SIZE; // we can only do so much
if (to_transfer > free)
to_transfer = free; // we'll do the rest next time
if (to_transfer) {
uint8_t data[to_transfer];
this->reg(0).read_fifo(data, to_transfer);
for (size_t i = 0; i < to_transfer; i++)
this->receive_buffer_.push(data[i]);
}
} // while work to do
return to_transfer;
}
///
// TEST COMPONENT
//
#ifdef TEST_COMPONENT
/// @addtogroup test_ Test component information
/// @{
/// @brief An increment "Functor" (i.e. a class object that acts like a method with state!)
///
/// Functors are objects that can be treated as though they are a function or function pointer.
class Increment {
public:
/// @brief constructor: initialize current value to 0
Increment() : i_(0) {}
/// @brief overload of the parenthesis operator.
/// Returns the current value and auto increment it
/// @return the current value.
uint8_t operator()() { return i_++; }
private:
uint8_t i_;
};
/// @brief Hex converter to print/display a buffer in hexadecimal format (32 hex values / line).
/// @param buffer contains the values to display
void print_buffer(std::vector<uint8_t> buffer) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < buffer.size(); i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", buffer[i]);
if (i % 32 == 31)
ESP_LOGI(TAG, " %s", hex_buffer);
}
if (buffer.size() % 32) {
// null terminate if incomplete line
hex_buffer[3 * (buffer.size() % 32) + 1] = 0;
ESP_LOGI(TAG, " %s", hex_buffer);
}
}
/// @brief test the write_array method
void WeikaiChannel::uart_send_test_(char *message) {
auto start_exec = micros();
std::vector<uint8_t> output_buffer(XFER_MAX_SIZE);
generate(output_buffer.begin(), output_buffer.end(), Increment()); // fill with incrementing number
size_t to_send = RING_BUFFER_SIZE;
while (to_send) {
this->write_array(&output_buffer[0], XFER_MAX_SIZE); // we send the buffer
this->flush();
to_send -= XFER_MAX_SIZE;
}
ESP_LOGV(TAG, "%s => sent %d bytes - exec time %d µs ...", message, RING_BUFFER_SIZE, micros() - start_exec);
}
/// @brief test read_array method
bool WeikaiChannel::uart_receive_test_(char *message) {
auto start_exec = micros();
bool status = true;
size_t received = 0;
std::vector<uint8_t> buffer(RING_BUFFER_SIZE);
// we wait until we have received all the bytes
uint32_t const start_time = millis();
status = true;
while (received < RING_BUFFER_SIZE) {
while (XFER_MAX_SIZE > this->available()) {
this->xfer_fifo_to_buffer_();
if (millis() - start_time > 1500) {
ESP_LOGE(TAG, "uart_receive_test_() timeout: only %d bytes received...", this->available());
break;
}
yield(); // reschedule our thread to avoid blocking
}
status = this->read_array(&buffer[received], XFER_MAX_SIZE) && status;
received += XFER_MAX_SIZE;
}
uint8_t peek_value = 0;
this->peek_byte(&peek_value);
if (peek_value != 0) {
ESP_LOGE(TAG, "Peek first byte value error...");
status = false;
}
for (size_t i = 0; i < RING_BUFFER_SIZE; i++) {
if (buffer[i] != i % XFER_MAX_SIZE) {
ESP_LOGE(TAG, "Read buffer contains error...b=%x i=%x", buffer[i], i % XFER_MAX_SIZE);
print_buffer(buffer);
status = false;
break;
}
}
ESP_LOGV(TAG, "%s => received %d bytes status %s - exec time %d µs ...", message, received, status ? "OK" : "ERROR",
micros() - start_exec);
return status;
}
/// @}
#endif
} // namespace weikai
} // namespace esphome

View file

@ -0,0 +1,443 @@
/// @file weikai.h
/// @author DrCoolZic
/// @brief WeiKai component family - classes declaration
/// @date Last Modified: 2024/04/06 14:44:17
/// @details The classes declared in this file can be used by the Weikai family
/// of UART and GPIO expander components. As of today it provides support for
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
#pragma once
#include <bitset>
#include <memory>
#include <cinttypes>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "wk_reg_def.h"
/// When the TEST_COMPONENT flag is defined we include some auto-test methods. Used to test the software during
/// development but can also be used in situ to test if the component is working correctly. For release we do
/// not set it by default but you can set it by using the following lines in you configuration file:
/// @code
/// esphome:
/// platformio_options:
/// build_flags:
/// - -DTEST_COMPONENT
/// @endcode
// #define TEST_COMPONENT
namespace esphome {
namespace weikai {
/// @brief XFER_MAX_SIZE defines the maximum number of bytes allowed during one transfer.
#if defined(I2C_BUFFER_LENGTH)
constexpr size_t XFER_MAX_SIZE = I2C_BUFFER_LENGTH;
#else
constexpr size_t XFER_MAX_SIZE = 128;
#endif
/// @brief size of the internal WeiKai FIFO
constexpr size_t FIFO_SIZE = 256;
/// @brief size of the ring buffer set to size of the FIFO
constexpr size_t RING_BUFFER_SIZE = FIFO_SIZE;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief This is an helper class that provides a simple ring buffers that works as a FIFO
/// @details This ring buffer is used to buffer the bytes received in the FIFO of the Weika device. The best way to read
/// characters from the device FIFO, is to first check how many bytes were received and then read them all at once.
/// Unfortunately in all the code I have reviewed the characters are read one by one in a while loop by checking if
/// bytes are available then reading the byte until no more byte available. This is pretty inefficient for two reasons:
/// - Fist you need to perform a test for each byte to read
/// - and second you call the read byte method for each character.
/// .
/// Assuming you need to read 100 bytes that results into 200 calls. This is to compare to 2 calls (one to find the
/// number of bytes available plus one to read all the bytes) in the best case! If the registers you read are located on
/// the micro-controller this is acceptable because the registers can be accessed fast. But when the registers are
/// located on a remote device accessing them requires several cycles on a slow bus. As it it not possible to fix this
/// problem by asking users to rewrite their code, I have implemented this ring buffer that store the bytes received
/// locally.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename T, size_t SIZE> class WKRingBuffer {
public:
/// @brief pushes an item at the tail of the fifo
/// @param item item to push
/// @return true if item has been pushed, false il item could not pushed (buffer full)
bool push(const T item) {
if (is_full())
return false;
this->rb_[this->head_] = item;
this->head_ = (this->head_ + 1) % SIZE;
this->count_++;
return true;
}
/// @brief return and remove the item at head of the fifo
/// @param item item read
/// @return true if an item has been retrieved, false il no item available (buffer empty)
bool pop(T &item) {
if (is_empty())
return false;
item = this->rb_[this->tail_];
this->tail_ = (this->tail_ + 1) % SIZE;
this->count_--;
return true;
}
/// @brief return the value of the item at fifo's head without removing it
/// @param item pointer to item to return
/// @return true if item has been retrieved, false il no item available (buffer empty)
bool peek(T &item) {
if (is_empty())
return false;
item = this->rb_[this->tail_];
return true;
}
/// @brief test is the Ring Buffer is empty ?
/// @return true if empty
inline bool is_empty() { return (this->count_ == 0); }
/// @brief test is the ring buffer is full ?
/// @return true if full
inline bool is_full() { return (this->count_ == SIZE); }
/// @brief return the number of item in the ring buffer
/// @return the number of items
inline size_t count() { return this->count_; }
/// @brief returns the number of free positions in the buffer
/// @return how many items can be added
inline size_t free() { return SIZE - this->count_; }
/// @brief clear the buffer content
inline void clear() { this->head_ = this->tail_ = this->count_ = 0; }
protected:
std::array<T, SIZE> rb_{0}; ///< the ring buffer
int tail_{0}; ///< position of the next element to read
int head_{0}; ///< position of the next element to write
size_t count_{0}; ///< count number of element in the buffer
};
class WeikaiComponent;
// class WeikaiComponentSPI;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief WeikaiRegister objects acts as proxies to access remote register independently of the bus type.
/// @details This is an abstract interface class that provides many operations to access to registers while hiding
/// the actual implementation. This allow to accesses the registers in the Weikai component abstract class independently
/// of the actual bus (I2C, SPI). The derived classes will actually implements the specific bus operations dependant of
/// the bus used.
/// @n typical usage of WeikaiRegister:
/// @code
/// WeikaiRegister reg_X {&WeikaiComponent, ADDR_REGISTER_X, CHANNEL_NUM} // declaration
/// reg_X |= 0x01; // set bit 0 of the weikai register
/// reg_X &= ~0x01; // reset bit 0 of the weikai register
/// reg_X = 10; // Set the value of weikai register
/// uint val = reg_X; // get the value of weikai register
/// @endcode
class WeikaiRegister {
public:
/// @brief WeikaiRegister constructor.
/// @param comp our parent WeikaiComponent
/// @param reg address of the register
/// @param channel the channel of this register
WeikaiRegister(WeikaiComponent *const comp, uint8_t reg, uint8_t channel)
: comp_(comp), register_(reg), channel_(channel) {}
virtual ~WeikaiRegister() {}
/// @brief overloads the = operator. This is used to set a value into the weikai register
/// @param value to be set
/// @return this object
WeikaiRegister &operator=(uint8_t value);
/// @brief overloads the compound &= operator. This is often used to reset bits in the weikai register
/// @param value performs an & operation with value and store the result
/// @return this object
WeikaiRegister &operator&=(uint8_t value);
/// @brief overloads the compound |= operator. This is often used to set bits in the weikai register
/// @param value performs an | operation with value and store the result
/// @return this object
WeikaiRegister &operator|=(uint8_t value);
/// @brief cast operator that returns the content of the weikai register
operator uint8_t() const { return read_reg(); }
/// @brief reads the register
/// @return the value read from the register
virtual uint8_t read_reg() const = 0;
/// @brief writes the register
/// @param value to write in the register
virtual void write_reg(uint8_t value) = 0;
/// @brief read an array of bytes from the receiver fifo
/// @param data pointer to data buffer
/// @param length number of bytes to read
virtual void read_fifo(uint8_t *data, size_t length) const = 0;
/// @brief write an array of bytes to the transmitter fifo
/// @param data pointer to data buffer
/// @param length number of bytes to write
virtual void write_fifo(uint8_t *data, size_t length) = 0;
WeikaiComponent *const comp_; ///< pointer to our parent (aggregation)
uint8_t register_; ///< address of the register
uint8_t channel_; ///< channel for this register
};
class WeikaiChannel; // forward declaration
////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiComponent class stores the information global to the WeiKai component
/// and provides methods to set/access this information. It is also the container of
/// the WeikaiChannel children objects. This class is derived from esphome::Component
/// class.
////////////////////////////////////////////////////////////////////////////////////
class WeikaiComponent : public Component {
public:
/// @brief virtual destructor
virtual ~WeikaiComponent() {}
/// @brief store crystal frequency
/// @param crystal frequency
void set_crystal(uint32_t crystal) { this->crystal_ = crystal; }
/// @brief store if the component is in test mode
/// @param test_mode 0=normal mode any other values mean component in test mode
void set_test_mode(int test_mode) { this->test_mode_ = test_mode; }
/// @brief store the name for the component
/// @param name the name as defined by the python code generator
void set_name(std::string name) { this->name_ = std::move(name); }
/// @brief Get the name of the component
/// @return the name
const char *get_name() { return this->name_.c_str(); }
/// @brief override the Component loop()
void loop() override;
bool page1() { return page1_; }
/// @brief Factory method to create a Register object
/// @param reg address of the register
/// @param channel channel associated with this register
/// @return a reference to WeikaiRegister
virtual WeikaiRegister &reg(uint8_t reg, uint8_t channel) = 0;
protected:
friend class WeikaiChannel;
/// @brief Get the priority of the component
/// @return the priority
/// @details The priority is set below setup_priority::BUS because we use
/// the spi/i2c busses (which has a priority of BUS) to communicate and the WeiKai
/// therefore it is seen by our client almost as if it was a bus.
float get_setup_priority() const override { return setup_priority::BUS - 0.1F; }
friend class WeikaiGPIOPin;
/// Helper method to read the value of a pin.
bool read_pin_val_(uint8_t pin);
/// Helper method to write the value of a pin.
void write_pin_val_(uint8_t pin, bool value);
/// Helper method to set the pin mode of a pin.
void set_pin_direction_(uint8_t pin, gpio::Flags flags);
#ifdef TEST_COMPONENT
/// @defgroup test_ Test component information
/// @brief Contains information about the auto-tests of the component
/// @{
void test_gpio_input_();
void test_gpio_output_();
/// @}
#endif
uint8_t pin_config_{0x00}; ///< pin config mask: 1 means OUTPUT, 0 means INPUT
uint8_t output_state_{0x00}; ///< output state: 1 means HIGH, 0 means LOW
uint8_t input_state_{0x00}; ///< input pin states: 1 means HIGH, 0 means LOW
uint32_t crystal_; ///< crystal value;
int test_mode_; ///< test mode value (0 -> no tests)
bool page1_{false}; ///< set to true when in "page1 mode"
std::vector<WeikaiChannel *> children_{}; ///< the list of WeikaiChannel UART children
std::string name_; ///< name of entity
};
///////////////////////////////////////////////////////////////////////////////
/// @brief Helper class to expose a WeiKai family IO pin as an internal GPIO pin.
///////////////////////////////////////////////////////////////////////////////
class WeikaiGPIOPin : public GPIOPin {
public:
void set_parent(WeikaiComponent *parent) { this->parent_ = parent; }
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
void setup() override;
std::string dump_summary() const override;
void pin_mode(gpio::Flags flags) override { this->parent_->set_pin_direction_(this->pin_, flags); }
bool digital_read() override { return this->parent_->read_pin_val_(this->pin_) != this->inverted_; }
void digital_write(bool value) override { this->parent_->write_pin_val_(this->pin_, value != this->inverted_); }
protected:
WeikaiComponent *parent_{nullptr};
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiChannel class is used to implement all the virtual methods of the ESPHome
/// uart::UARTComponent virtual class. This class is common to the different members of the Weikai
/// components family and therefore avoid code duplication.
///////////////////////////////////////////////////////////////////////////////////////////////////
class WeikaiChannel : public uart::UARTComponent {
public:
/// @brief We belongs to this WeikaiComponent
/// @param parent pointer to the component we belongs to
void set_parent(WeikaiComponent *parent) {
this->parent_ = parent;
this->parent_->children_.push_back(this); // add ourself to the list (vector)
}
/// @brief Sets the channel number
/// @param channel number
void set_channel(uint8_t channel) { this->channel_ = channel; }
/// @brief The name as generated by the Python code generator
/// @param name of the channel
void set_channel_name(std::string name) { this->name_ = std::move(name); }
/// @brief Get the channel name
/// @return the name
const char *get_channel_name() { return this->name_.c_str(); }
/// @brief Setup the channel
void virtual setup_channel();
/// @brief dump channel information
void virtual dump_channel();
/// @brief Factory method to create a WeikaiRegister proxy object
/// @param reg address of the register
/// @return a reference to WeikaiRegister
WeikaiRegister &reg(uint8_t reg) { return this->parent_->reg(reg, channel_); }
//
// we implements/overrides the virtual class from UARTComponent
//
/// @brief Writes a specified number of bytes to a serial port
/// @param buffer pointer to the buffer
/// @param length number of bytes to write
/// @details This method sends 'length' characters from the buffer to the serial line. Unfortunately (unlike the
/// Arduino equivalent) this method does not return any flag and therefore it is not possible to know if any/all bytes
/// have been transmitted correctly. Another problem is that it is not possible to know ahead of time how many bytes
/// we can safely send as there is no tx_available() method provided! To avoid overrun when using the write method you
/// can use the flush() method to wait until the transmit fifo is empty.
/// @n Typical usage could be:
/// @code
/// // ...
/// uint8_t buffer[128];
/// // ...
/// write_array(&buffer, length);
/// flush();
/// // ...
/// @endcode
void write_array(const uint8_t *buffer, size_t length) override;
/// @brief Reads a specified number of bytes from a serial port
/// @param buffer buffer to store the bytes
/// @param length number of bytes to read
/// @return true if succeed, false otherwise
/// @details Typical usage:
/// @code
/// // ...
/// auto length = available();
/// uint8_t buffer[128];
/// if (length > 0) {
/// auto status = read_array(&buffer, length)
/// // test status ...
/// }
/// @endcode
bool read_array(uint8_t *buffer, size_t length) override;
/// @brief Reads the first byte in FIFO without removing it
/// @param buffer pointer to the byte
/// @return true if succeed reading one byte, false if no character available
/// @details This method returns the next byte from receiving buffer without removing it from the internal fifo. It
/// returns true if a character is available and has been read, false otherwise.\n
bool peek_byte(uint8_t *buffer) override;
/// @brief Returns the number of bytes in the receive buffer
/// @return the number of bytes available in the receiver fifo
int available() override;
/// @brief Flush the output fifo.
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
/// to complete. (Prior to Arduino 1.0, this the method was removing any buffered incoming serial data.). ** Therefore
/// we wait until all bytes are gone with a timeout of 100 ms
void flush() override;
protected:
friend class WeikaiComponent;
/// @brief this cannot happen with external uart therefore we do nothing
void check_logger_conflict() override {}
/// @brief reset the weikai internal FIFO
void reset_fifo_();
/// @brief set the line parameters
void set_line_param_();
/// @brief set the baud rate
void set_baudrate_();
/// @brief Returns the number of bytes in the receive fifo
/// @return the number of bytes in the fifo
size_t rx_in_fifo_();
/// @brief Returns the number of bytes in the transmit fifo
/// @return the number of bytes in the fifo
size_t tx_in_fifo_();
/// @brief test if transmit buffer is not empty in the status register (optimization)
/// @return true if not emptygroup test_
bool tx_fifo_is_not_empty_();
/// @brief transfer bytes from the weikai internal FIFO to the buffer (if any)
/// @return number of bytes transferred
size_t xfer_fifo_to_buffer_();
/// @brief check if channel is alive
/// @return true if OK
bool virtual check_channel_down();
#ifdef TEST_COMPONENT
/// @ingroup test_
/// @{
/// @brief Test the write_array() method
/// @param message to display
void uart_send_test_(char *message);
/// @brief Test the read_array() method
/// @param message to display
/// @return true if success
bool uart_receive_test_(char *message);
/// @}
#endif
/// @brief the buffer where we store temporarily the bytes received
WKRingBuffer<uint8_t, RING_BUFFER_SIZE> receive_buffer_;
WeikaiComponent *parent_; ///< our WK2168component parent
uint8_t channel_; ///< our Channel number
uint8_t data_; ///< a one byte buffer for register read storage
std::string name_; ///< name of the entity
};
} // namespace weikai
} // namespace esphome

View file

@ -0,0 +1,304 @@
/// @file wk_reg_def.h
/// @author DrCoolZic
/// @brief WeiKai component family - registers' definition
/// @date Last Modified: 2024/02/18 15:49:18
#pragma once
namespace esphome {
namespace weikai {
////////////////////////////////////////////////////////////////////////////////////////
/// Definition of the WeiKai registers
////////////////////////////////////////////////////////////////////////////////////////
/// @defgroup wk2168_gr_ WeiKai Global Registers
/// This section groups all **Global Registers**: these registers are global to the
/// the WeiKai chip (i.e. independent of the UART channel used)
/// @note only registers and parameters used have been fully documented
/// @{
/// @brief Global Control Register - 00 0000
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | M0 | M1 | RSV | C4EN | C3EN | C2EN | C1EN | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GENA = 0x00;
/// @brief Channel 4 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C4EN = 1 << 3;
/// @brief Channel 3 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C3EN = 1 << 2;
/// @brief Channel 2 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C2EN = 1 << 1;
/// @brief Channel 1 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C1EN = 1 << 0;
/// @brief Global Reset Register - 00 0001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | C4SLEEP| C3SLEEP| C2SLEEP| C1SLEEP| C4RST | C3RST | C2RST | C1RST | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | W1/R0 | W1/R0 | W1/R0 | W1/R0 | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GRST = 0x01;
/// @brief Channel 4 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C4RST = 1 << 3;
/// @brief Channel 3 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C3RST = 1 << 2;
/// @brief Channel 2 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C2RST = 1 << 1;
/// @brief Channel 1 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C1RST = 1 << 0;
/// @brief Global Master channel control register (not used) - 000010
constexpr uint8_t WKREG_GMUT = 0x02;
/// Global interrupt register (not used) - 01 0000
constexpr uint8_t WKREG_GIER = 0x10;
/// Global interrupt flag register (not used) 01 0001
constexpr uint8_t WKREG_GIFR = 0x11;
/// @brief Global GPIO direction register - 10 0001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | PD7 | PD6 | PD5 | PD4 | PD3 | PD2 | PD1 | PD0 | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GPDIR = 0x21;
/// @brief Global GPIO data register - 11 0001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | PV7 | PV6 | PV5 | PV4 | PV3 | PV2 | PV1 | PV0 | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GPDAT = 0x31;
/// @}
/// @defgroup WeiKai_cr_ WeiKai Channel Registers
/// @brief Definition of the register linked to a particular channel
/// @details This topic groups all the **Channel Registers**: these registers are specific
/// to the a specific channel i.e. each channel has its own set of registers
/// @note only registers and parameters used have been documented
/// @{
/// @defgroup cr_p0 Channel registers when SPAGE=0
/// @brief Definition of the register linked to a particular channel when SPAGE=0
/// @details The channel registers are further splitted into two groups.
/// This first group is defined when the Global register WKREG_SPAGE is 0
/// @{
/// @brief Global Page register c0/c1 0011
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RSV | PAGE | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | R | R | R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_SPAGE = 0x03;
/// @brief Serial Control Register - c0/c1 0100
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RSV | SLPEN | TXEN | RXEN | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | R | R/W | R/W | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_SCR = 0x04;
/// @brief transmission control (0: enable, 1: disable)
constexpr uint8_t SCR_TXEN = 1 << 1;
/// @brief receiving control (0: enable, 1: disable)
constexpr uint8_t SCR_RXEN = 1 << 0;
/// @brief Line Configuration Register - c0/c1 0101
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RSV | BREAK | IREN | PAEN | PARITY | STPL | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_LCR = 0x05;
/// @brief Parity enable (0: no check, 1: check)
constexpr uint8_t LCR_PAEN = 1 << 3;
/// @brief Parity force 0
constexpr uint8_t LCR_PAR_F0 = 0 << 1;
/// @brief Parity odd
constexpr uint8_t LCR_PAR_ODD = 1 << 1;
/// @brief Parity even
constexpr uint8_t LCR_PAR_EVEN = 2 << 1;
/// @brief Parity force 1
constexpr uint8_t LCR_PAR_F1 = 3 << 1;
/// @brief Stop length (0: 1 bit, 1: 2 bits)
constexpr uint8_t LCR_STPL = 1 << 0;
/// @brief FIFO Control Register - c0/c1 0110
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | TFTRIG | RFTRIG | TFEN | RFEN | TFRST | RFRST | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_FCR = 0x06;
/// @brief Transmitter FIFO enable
constexpr uint8_t FCR_TFEN = 1 << 3;
/// @brief Receiver FIFO enable
constexpr uint8_t FCR_RFEN = 1 << 2;
/// @brief Transmitter FIFO reset
constexpr uint8_t FCR_TFRST = 1 << 1;
/// @brief Receiver FIFO reset
constexpr uint8_t FCR_RFRST = 1 << 0;
/// @brief Serial Interrupt Enable Register (not used) - c0/c1 0111
constexpr uint8_t WKREG_SIER = 0x07;
/// @brief Serial Interrupt Flag Register (not used) - c0/c1 1000
constexpr uint8_t WKREG_SIFR = 0x08;
/// @brief Transmitter FIFO Count - c0/c1 1001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | NUMBER OF DATA IN TRANSMITTER FIFO |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_TFCNT = 0x09;
/// @brief Receiver FIFO count - c0/c1 1010
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | NUMBER OF DATA IN RECEIVER FIFO |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_RFCNT = 0x0A;
/// @brief FIFO Status Register - c0/c1 1011
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RFOE | RFLB | RFFE | RFPE | RFDAT | TFDAT | TFFULL | TBUSY | name
/// -------------------------------------------------------------------------
/// | R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
/// @warning The received buffer can hold 256 bytes. However, as the RFCNT reg
/// is 8 bits, if we have 256 byte in the register this is reported as 0 ! Therefore
/// RFCNT=0 can indicate that there are 0 **or** 256 bytes in the buffer. If we
/// have RFDAT = 1 and RFCNT = 0 it should be interpreted as 256 bytes in the FIFO.
/// @note Note that in case of overflow the RFOE goes to one **but** as soon as you read
/// the FSR this bit is cleared. Therefore Overflow can be read only once.
/// @n The same problem applies to the transmit buffer but here we have to check the
/// TFFULL flag. So if TFFULL is set and TFCNT is 0 this should be interpreted as 256
constexpr uint8_t WKREG_FSR = 0x0B;
/// @brief Receiver FIFO Overflow Error (0: no OE, 1: OE)
constexpr uint8_t FSR_RFOE = 1 << 7;
/// @brief Receiver FIFO Line Break (0: no LB, 1: LB)
constexpr uint8_t FSR_RFLB = 1 << 6;
/// @brief Receiver FIFO Frame Error (0: no FE, 1: FE)
constexpr uint8_t FSR_RFFE = 1 << 5;
/// @brief Receiver Parity Error (0: no PE, 1: PE)
constexpr uint8_t FSR_RFPE = 1 << 4;
/// @brief Receiver FIFO count (0: empty, 1: not empty)
constexpr uint8_t FSR_RFDAT = 1 << 3;
/// @brief Transmitter FIFO count (0: empty, 1: not empty)
constexpr uint8_t FSR_TFDAT = 1 << 2;
/// @brief Transmitter FIFO full (0: not full, 1: full)
constexpr uint8_t FSR_TFFULL = 1 << 1;
/// @brief Transmitter busy (0 nothing to transmit, 1: transmitter busy sending)
constexpr uint8_t FSR_TBUSY = 1 << 0;
/// @brief Line Status Register (not used - using FIFO)
constexpr uint8_t WKREG_LSR = 0x0C;
/// @brief FIFO Data Register (not used - does not seems to work)
constexpr uint8_t WKREG_FDAT = 0x0D;
/// @}
/// @defgroup cr_p1 Channel registers for SPAGE=1
/// @brief Definition of the register linked to a particular channel when SPAGE=1
/// @details The channel registers are further splitted into two groups.
/// This second group is defined when the Global register WKREG_SPAGE is 1
/// @{
/// @brief Baud rate configuration register: high byte - c0/c1 0100
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | High byte of the baud rate |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_BRH = 0x04;
/// @brief Baud rate configuration register: low byte - c0/c1 0101
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | Low byte of the baud rate |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_BRL = 0x05;
/// @brief Baud rate configuration register decimal part - c0/c1 0110
constexpr uint8_t WKREG_BRD = 0x06;
/// @brief Receive FIFO Interrupt trigger configuration (not used) - c0/c1 0111
constexpr uint8_t WKREG_RFI = 0x07;
/// @brief Transmit FIFO interrupt trigger configuration (not used) - c0/c1 1000
constexpr uint8_t WKREG_TFI = 0x08;
/// @}
/// @}
} // namespace weikai
} // namespace esphome

View file

@ -0,0 +1 @@
CODEOWNERS = ["@DrCoolZic"]

View file

@ -0,0 +1,177 @@
/// @file weikai_i2c.cpp
/// @brief WeiKai component family - classes implementation
/// @date Last Modified: 2024/04/06 14:43:31
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai_i2c.h"
namespace esphome {
namespace weikai_i2c {
static const char *const TAG = "weikai_i2c";
/// @brief Display a buffer in hexadecimal format (32 hex values / line).
void print_buffer(const uint8_t *data, size_t length) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < length; i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]);
if (i % 32 == 31) {
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
if (length % 32) {
// null terminate if incomplete line
hex_buffer[3 * (length % 32) + 2] = 0;
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER",
"SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"};
static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL",
"TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"};
using namespace weikai;
// method to print a register value as text: used in the log messages ...
const char *reg_to_str(int reg, bool page1) {
if (reg == WKREG_GPDAT) {
return "GPDAT";
} else if (reg == WKREG_GPDIR) {
return "GPDIR";
} else {
return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F];
}
}
enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO
/// @brief Computes the I²C bus's address used to access the component
/// @param base_address the base address of the component - set by the A1 A0 pins
/// @param channel (0-3) the UART channel
/// @param fifo (0-1) 0 = access to internal register, 1 = direct access to fifo
/// @return the i2c address to use
inline uint8_t i2c_address(uint8_t base_address, uint8_t channel, RegType fifo) {
// the address of the device is:
// +----+----+----+----+----+----+----+----+
// | 0 | A1 | A0 | 1 | 0 | C1 | C0 | F |
// +----+----+----+----+----+----+----+----+
// where:
// - A1,A0 is the address read from A1,A0 switch
// - C1,C0 is the channel number (in practice only 00 or 01)
// - F is: 0 when accessing register, one when accessing FIFO
uint8_t const addr = base_address | channel << 1 | fifo << 0;
return addr;
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiRegisterI2C methods
///////////////////////////////////////////////////////////////////////////////
uint8_t WeikaiRegisterI2C::read_reg() const {
uint8_t value = 0x00;
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, REG);
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->read_register(this->register_, &value, 1);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
ESP_LOGVV(TAG, "WeikaiRegisterI2C::read_reg() @%02X reg=%s ch=%u I2C_code:%d, buf=%02X", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WeikaiRegisterI2C::read_reg() @%02X reg=%s ch=%u I2C_code:%d, buf=%02X", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
}
return value;
}
void WeikaiRegisterI2C::read_fifo(uint8_t *data, size_t length) const {
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, FIFO);
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->read(data, length);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WeikaiRegisterI2C::read_fifo() @%02X ch=%d I2C_code:%d len=%d buffer", address, this->channel_,
(int) error, length);
print_buffer(data, length);
#endif
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WeikaiRegisterI2C::read_fifo() @%02X reg=N/A ch=%d I2C_code:%d len=%d buf=%02X...", address,
this->channel_, (int) error, length, data[0]);
}
}
void WeikaiRegisterI2C::write_reg(uint8_t value) {
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, REG); // update the i2c bus
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->write_register(this->register_, &value, 1);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
ESP_LOGVV(TAG, "WK2168Reg::write_reg() @%02X reg=%s ch=%d I2C_code:%d buf=%02X", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WK2168Reg::write_reg() @%02X reg=%s ch=%d I2C_code:%d buf=%d", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
}
}
void WeikaiRegisterI2C::write_fifo(uint8_t *data, size_t length) {
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, FIFO); // set fifo flag
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->write(data, length);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WK2168Reg::write_fifo() @%02X ch=%d I2C_code:%d len=%d buffer", address, this->channel_,
(int) error, length);
print_buffer(data, length);
#endif
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WK2168Reg::write_fifo() @%02X reg=N/A, ch=%d I2C_code:%d len=%d, buf=%02X...", address,
this->channel_, (int) error, length, data[0]);
}
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiComponentI2C methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiComponentI2C::setup() {
// before any manipulation we store the address to base_address_ for future use
this->base_address_ = this->address_;
ESP_LOGCONFIG(TAG, "Setting up wk2168_i2c: %s with %d UARTs at @%02X ...", this->get_name(), this->children_.size(),
this->base_address_);
// enable all channels
this->reg(WKREG_GENA, 0) = GENA_C1EN | GENA_C2EN | GENA_C3EN | GENA_C4EN;
// reset all channels
this->reg(WKREG_GRST, 0) = GRST_C1RST | GRST_C2RST | GRST_C3RST | GRST_C4RST;
// initialize the spage register to page 0
this->reg(WKREG_SPAGE, 0) = 0;
this->page1_ = false;
// we setup our children channels
for (auto *child : this->children_) {
child->setup_channel();
}
}
void WeikaiComponentI2C::dump_config() {
ESP_LOGCONFIG(TAG, "Initialization of %s with %d UARTs completed", this->get_name(), this->children_.size());
ESP_LOGCONFIG(TAG, " Crystal: %" PRIu32, this->crystal_);
if (test_mode_)
ESP_LOGCONFIG(TAG, " Test mode: %d", test_mode_);
ESP_LOGCONFIG(TAG, " Transfer buffer size: %d", XFER_MAX_SIZE);
this->address_ = this->base_address_; // we restore the base_address before display (less confusing)
LOG_I2C_DEVICE(this);
for (auto *child : this->children_) {
child->dump_channel();
}
}
} // namespace weikai_i2c
} // namespace esphome

View file

@ -0,0 +1,61 @@
/// @file weikai_i2c.h
/// @author DrCoolZic
/// @brief WeiKai component family - classes declaration
/// @date Last Modified: 2024/03/01 13:31:57
/// @details The classes declared in this file can be used by the Weikai family
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
#pragma once
#include <bitset>
#include <memory>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/weikai/weikai.h"
namespace esphome {
namespace weikai_i2c {
class WeikaiComponentI2C;
// using namespace weikai;
////////////////////////////////////////////////////////////////////////////////////
/// @brief WeikaiRegisterI2C objects acts as proxies to access remote register through an I2C Bus
////////////////////////////////////////////////////////////////////////////////////
class WeikaiRegisterI2C : public weikai::WeikaiRegister {
public:
uint8_t read_reg() const override;
void write_reg(uint8_t value) override;
void read_fifo(uint8_t *data, size_t length) const override;
void write_fifo(uint8_t *data, size_t length) override;
protected:
friend WeikaiComponentI2C;
WeikaiRegisterI2C(weikai::WeikaiComponent *const comp, uint8_t reg, uint8_t channel)
: weikai::WeikaiRegister(comp, reg, channel) {}
};
////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiComponentI2C class stores the information to the WeiKai component
/// connected through an I2C bus.
////////////////////////////////////////////////////////////////////////////////////
class WeikaiComponentI2C : public weikai::WeikaiComponent, public i2c::I2CDevice {
public:
weikai::WeikaiRegister &reg(uint8_t reg, uint8_t channel) override {
reg_i2c_.register_ = reg;
reg_i2c_.channel_ = channel;
return reg_i2c_;
}
//
// override Component methods
//
void setup() override;
void dump_config() override;
uint8_t base_address_; ///< base address of I2C device
WeikaiRegisterI2C reg_i2c_{this, 0, 0}; ///< init to this component
};
} // namespace weikai_i2c
} // namespace esphome

View file

@ -0,0 +1 @@
CODEOWNERS = ["@DrCoolZic"]

View file

@ -0,0 +1,189 @@
/// @file weikai_spi.cpp
/// @brief WeiKai component family - classes implementation
/// @date Last Modified: 2024/04/06 14:46:09
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai_spi.h"
namespace esphome {
namespace weikai_spi {
using namespace weikai;
static const char *const TAG = "weikai_spi";
/// @brief convert an int to binary representation as C++ std::string
/// @param val integer to convert
/// @return a std::string
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
/// Convert std::string to C string
#define I2S2CS(val) (i2s(val).c_str())
/// @brief measure the time elapsed between two calls
/// @param last_time time of the previous call
/// @return the elapsed time in microseconds
uint32_t elapsed_ms(uint32_t &last_time) {
uint32_t e = millis() - last_time;
last_time = millis();
return e;
};
/// @brief Converts the parity enum value to a C string
/// @param parity enum
/// @return the string
const char *p2s(uart::UARTParityOptions parity) {
using namespace uart;
switch (parity) {
case UART_CONFIG_PARITY_NONE:
return "NONE";
case UART_CONFIG_PARITY_EVEN:
return "EVEN";
case UART_CONFIG_PARITY_ODD:
return "ODD";
default:
return "UNKNOWN";
}
}
/// @brief Display a buffer in hexadecimal format (32 hex values / line).
void print_buffer(const uint8_t *data, size_t length) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < length; i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]);
if (i % 32 == 31) {
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
if (length % 32) {
// null terminate if incomplete line
hex_buffer[3 * (length % 32) + 2] = 0;
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER",
"SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"};
static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL",
"TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"};
// method to print a register value as text: used in the log messages ...
const char *reg_to_str(int reg, bool page1) {
if (reg == WKREG_GPDAT) {
return "GPDAT";
} else if (reg == WKREG_GPDIR) {
return "GPDIR";
} else {
return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F];
}
}
enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO
enum CmdType { WRITE_CMD = 0, READ_CMD = 1 }; ///< Read or Write transfer
/// @brief Computes the SPI command byte
/// @param transfer_type read or write command
/// @param reg (0-15) the address of the register
/// @param channel (0-3) the UART channel
/// @param fifo (0-1) 0 = access to internal register, 1 = direct access to fifo
/// @return the spi command byte
/// @details
/// +------+------+------+------+------+------+------+------+
/// | FIFO | R/W | C1-C0 | A3-A0 |
/// +------+------+-------------+---------------------------+
/// FIFO: 0 = register, 1 = FIFO
/// R/W: 0 = write, 1 = read
/// C1-C0: Channel (0-1)
/// A3-A0: Address (0-F)
inline static uint8_t cmd_byte(RegType fifo, CmdType transfer_type, uint8_t channel, uint8_t reg) {
return (fifo << 7 | transfer_type << 6 | channel << 4 | reg << 0);
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiRegisterSPI methods
///////////////////////////////////////////////////////////////////////////////
uint8_t WeikaiRegisterSPI::read_reg() const {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t cmd = cmd_byte(REG, READ_CMD, this->channel_, this->register_);
spi_comp->enable();
spi_comp->write_byte(cmd);
uint8_t val = spi_comp->read_byte();
spi_comp->disable();
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(cmd), cmd,
reg_to_str(this->register_, this->comp_->page1()), this->channel_, val);
return val;
}
void WeikaiRegisterSPI::read_fifo(uint8_t *data, size_t length) const {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t cmd = cmd_byte(FIFO, READ_CMD, this->channel_, this->register_);
spi_comp->enable();
spi_comp->write_byte(cmd);
spi_comp->read_array(data, length);
spi_comp->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
length);
print_buffer(data, length);
#endif
}
void WeikaiRegisterSPI::write_reg(uint8_t value) {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t buf[2]{cmd_byte(REG, WRITE_CMD, this->channel_, this->register_), value};
spi_comp->enable();
spi_comp->write_array(buf, 2);
spi_comp->disable();
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(buf[0]), buf[0],
reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]);
}
void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t cmd = cmd_byte(FIFO, WRITE_CMD, this->channel_, this->register_);
spi_comp->enable();
spi_comp->write_byte(cmd);
spi_comp->write_array(data, length);
spi_comp->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
length);
print_buffer(data, length);
#endif
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiComponentSPI methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiComponentSPI::setup() {
using namespace weikai;
ESP_LOGCONFIG(TAG, "Setting up wk2168_spi: %s with %d UARTs...", this->get_name(), this->children_.size());
this->spi_setup();
// enable all channels
this->reg(WKREG_GENA, 0) = GENA_C1EN | GENA_C2EN | GENA_C3EN | GENA_C4EN;
// reset all channels
this->reg(WKREG_GRST, 0) = GRST_C1RST | GRST_C2RST | GRST_C3RST | GRST_C4RST;
// initialize the spage register to page 0
this->reg(WKREG_SPAGE, 0) = 0;
this->page1_ = false;
// we setup our children channels
for (auto *child : this->children_) {
child->setup_channel();
}
}
void WeikaiComponentSPI::dump_config() {
ESP_LOGCONFIG(TAG, "Initialization of %s with %d UARTs completed", this->get_name(), this->children_.size());
ESP_LOGCONFIG(TAG, " Crystal: %" PRIu32 "", this->crystal_);
if (test_mode_)
ESP_LOGCONFIG(TAG, " Test mode: %d", test_mode_);
ESP_LOGCONFIG(TAG, " Transfer buffer size: %d", XFER_MAX_SIZE);
LOG_PIN(" CS Pin: ", this->cs_);
for (auto *child : this->children_) {
child->dump_channel();
}
}
} // namespace weikai_spi
} // namespace esphome

View file

@ -0,0 +1,54 @@
/// @file weikai.h
/// @author DrCoolZic
/// @brief WeiKai component family - classes declaration
/// @date Last Modified: 2024/02/29 17:20:32
/// @details The classes declared in this file can be used by the Weikai family
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
#pragma once
#include <bitset>
#include <memory>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/weikai/weikai.h"
namespace esphome {
namespace weikai_spi {
////////////////////////////////////////////////////////////////////////////////////
/// @brief WeikaiRegisterSPI objects acts as proxies to access remote register through an SPI Bus
////////////////////////////////////////////////////////////////////////////////////
class WeikaiRegisterSPI : public weikai::WeikaiRegister {
public:
WeikaiRegisterSPI(weikai::WeikaiComponent *const comp, uint8_t reg, uint8_t channel)
: weikai::WeikaiRegister(comp, reg, channel) {}
uint8_t read_reg() const override;
void write_reg(uint8_t value) override;
void read_fifo(uint8_t *data, size_t length) const override;
void write_fifo(uint8_t *data, size_t length) override;
};
////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiComponentSPI class stores the information to the WeiKai component
/// connected through an SPI bus.
////////////////////////////////////////////////////////////////////////////////////
class WeikaiComponentSPI : public weikai::WeikaiComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
weikai::WeikaiRegister &reg(uint8_t reg, uint8_t channel) override {
reg_spi_.register_ = reg;
reg_spi_.channel_ = channel;
return reg_spi_;
}
void setup() override;
void dump_config() override;
protected:
WeikaiRegisterSPI reg_spi_{this, 0, 0}; ///< init to this component
};
} // namespace weikai_spi
} // namespace esphome

View file

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,4 @@
/* compiling with esp-idf framework requires a .cpp file for some reason ? */
namespace esphome {
namespace wk2132_i2c {}
} // namespace esphome

View file

@ -0,0 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentSPI),
}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)

View file

@ -0,0 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
CONF_WK2168_I2C = "wk2168_i2c"
weikai_ns = cg.esphome_ns.namespace("weikai")
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentI2C)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)
WK2168_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2168_I2C): cv.use_id(WeikaiComponentI2C),
}
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_I2C, WK2168_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2168_I2C])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View file

@ -0,0 +1,62 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
CONF_WK2168_SPI = "wk2168_spi"
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
weikai_ns = cg.esphome_ns.namespace("weikai")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentSPI)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(WeikaiComponentSPI)}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)
WK2168_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2168_SPI): cv.use_id(WeikaiComponentSPI),
},
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_SPI, WK2168_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2168_SPI])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View file

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentSPI),
}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)

View file

@ -0,0 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
CONF_WK2212_I2C = "wk2212_i2c"
weikai_ns = cg.esphome_ns.namespace("weikai")
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentI2C)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)
WK2212_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2212_I2C): cv.use_id(WeikaiComponentI2C),
}
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_I2C, WK2212_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2212_I2C])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View file

@ -0,0 +1,62 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
CONF_WK2212_SPI = "wk2212_spi"
weikai_ns = cg.esphome_ns.namespace("weikai")
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentSPI)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(WeikaiComponentSPI)}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)
WK2212_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2212_SPI): cv.use_id(WeikaiComponentSPI),
},
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_SPI, WK2212_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2212_SPI])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View file

@ -0,0 +1,20 @@
i2c:
id: i2c_bus
scl: ${scl_pin}
sda: ${sda_pin}
scan: true
frequency: 600kHz
wk2132_i2c:
- id: wk2132_i2c_id
address: 0x70
i2c_id: i2c_bus
uart:
- id: wk2132_id_0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: wk2132_id_1
channel: 1
baud_rate: 19200

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,21 @@
spi:
id: spi_bus
clk_pin: ${clk_pin}
mosi_pin: ${mosi_pin}
miso_pin: ${miso_pin}
wk2132_spi:
- id: wk2132_spi_id
cs_pin: ${cs_pin}
spi_id: spi_bus
crystal: 11059200
data_rate: 1MHz
uart:
- id: wk2132_spi_id0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: wk2132_spi_id1
channel: 1
baud_rate: 921600

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,63 @@
i2c:
id: i2c_bus
scl: ${scl_pin}
sda: ${sda_pin}
scan: true
frequency: 600kHz
# component declaration
wk2168_i2c:
- id: bridge_i2c
i2c_id: i2c_bus
address: 0x70
uart:
- id: id0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: id1
channel: 1
baud_rate: 115200
- id: id2
channel: 2
baud_rate: 115200
- id: id3
channel: 3
baud_rate: 115200
# individual binary_sensor inputs
binary_sensor:
- platform: gpio
name: "pin_0"
pin:
wk2168_i2c: bridge_i2c
number: 0
mode:
input: true
- platform: gpio
name: "pin_1"
pin:
wk2168_i2c: bridge_i2c
number: 1
mode:
input: true
inverted: true
# Individual binary outputs
switch:
- platform: gpio
name: "pin_2"
pin:
wk2168_i2c: bridge_i2c
number: 2
mode:
output: true
- platform: gpio
name: "pin_3"
pin:
wk2168_i2c: bridge_i2c
number: 3
mode:
output: true
inverted: true

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,63 @@
spi:
id: spi_bus
clk_pin: ${clk_pin}
mosi_pin: ${mosi_pin}
miso_pin: ${miso_pin}
wk2168_spi:
- id: bridge_spi
cs_pin: ${cs_pin}
spi_id: spi_bus
crystal: 11059200
data_rate: 1MHz
uart:
- id: id0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: id1
channel: 1
baud_rate: 115200
- id: id2
channel: 2
baud_rate: 115200
- id: id3
channel: 3
baud_rate: 115200
# individual binary_sensor inputs
binary_sensor:
- platform: gpio
name: "pin_0"
pin:
wk2168_spi: bridge_spi
number: 0
mode:
input: true
- platform: gpio
name: "pin_1"
pin:
wk2168_spi: bridge_spi
number: 1
mode:
input: true
inverted: true
# Individual binary outputs
switch:
- platform: gpio
name: "pin_2"
pin:
wk2168_spi: bridge_spi
number: 2
mode:
output: true
- platform: gpio
name: "pin_3"
pin:
wk2168_spi: bridge_spi
number: 3
mode:
output: true
inverted: true

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,28 @@
i2c:
id: i2c_bus
scl: ${scl_pin}
sda: ${sda_pin}
scan: true
frequency: 600kHz
wk2204_i2c:
- id: wk2204_i2c_id
i2c_id: i2c_bus
address: 0x70
uart:
- id: wk2204_id_0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: wk2204_id_1
channel: 1
baud_rate: 19200
- id: wk2204_id_2
channel: 2
baud_rate: 115200
stop_bits: 1
parity: none
- id: wk2204_id_3
channel: 3
baud_rate: 19200

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,29 @@
spi:
id: spi_bus
clk_pin: ${clk_pin}
mosi_pin: ${mosi_pin}
miso_pin: ${miso_pin}
wk2204_spi:
- id: wk2204_spi_id
cs_pin: ${cs_pin}
spi_id: spi_bus
crystal: 11059200
data_rate: 1MHz
uart:
- id: wk2204_spi_id0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: wk2204_spi_id1
channel: 1
baud_rate: 921600
- id: wk2204_spi_id2
channel: 2
baud_rate: 115200
stop_bits: 1
parity: none
- id: wk2204_spi_id3
channel: 3
baud_rate: 921600

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,59 @@
i2c:
id: i2c_bus
scl: ${scl_pin}
sda: ${sda_pin}
scan: true
frequency: 600kHz
# component declaration
wk2212_i2c:
- id: bridge_i2c
i2c_id: i2c_bus
address: 0x70
uart:
- id: uart_i2c_id0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: uart_i2c_id1
channel: 1
baud_rate: 115200
stop_bits: 1
parity: none
# individual binary_sensor inputs
binary_sensor:
- platform: gpio
name: "pin_0"
pin:
wk2212_i2c: bridge_i2c
number: 0
mode:
input: true
- platform: gpio
name: "pin_1"
pin:
wk2212_i2c: bridge_i2c
number: 1
mode:
input: true
inverted: true
# Individual binary outputs
switch:
- platform: gpio
name: "pin_2"
pin:
wk2212_i2c: bridge_i2c
number: 2
mode:
output: true
- platform: gpio
name: "pin_3"
pin:
wk2212_i2c: bridge_i2c
number: 3
mode:
output: true
inverted: true

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO40
sda_pin: GPIO41
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO22
sda_pin: GPIO21
<<: !include common.yaml

View file

@ -0,0 +1,58 @@
spi:
id: spi_bus
clk_pin: ${clk_pin}
mosi_pin: ${mosi_pin}
miso_pin: ${miso_pin}
wk2212_spi:
- id: bridge_spi
cs_pin: ${cs_pin}
spi_id: spi_bus
crystal: 11059200
data_rate: 1MHz
uart:
- id: id0
channel: 0
baud_rate: 115200
stop_bits: 1
parity: none
- id: id1
channel: 1
baud_rate: 115200
# individual binary_sensor inputs
binary_sensor:
- platform: gpio
name: "pin_0"
pin:
wk2212_spi: bridge_spi
number: 0
mode:
input: true
- platform: gpio
name: "pin_1"
pin:
wk2212_spi: bridge_spi
number: 1
mode:
input: true
inverted: true
# Individual binary outputs
switch:
- platform: gpio
name: "pin_2"
pin:
wk2212_spi: bridge_spi
number: 2
mode:
output: true
- platform: gpio
name: "pin_3"
pin:
wk2212_spi: bridge_spi
number: 3
mode:
output: true
inverted: true

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO40
miso_pin: GPIO41
mosi_pin: GPIO6
cs_pin: GPIO19
<<: !include common.yaml

View file

@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cs_pin: GPIO5
<<: !include common.yaml