1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-15 09:42:19 +01:00
Files
esphome/esphome/components/weikai/weikai.cpp

566 lines
19 KiB
C++

/// @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 {
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->is_in_loop_state())
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, "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\n"
" Baud rate: %" PRIu32 " Bd\n"
" Data bits: %u\n"
" Stop bits: %u\n"
" Parity: %s",
this->get_channel_name(), this->baud_rate_, this->data_bits_, this->stop_bits_, 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=%" PRId32 " baudrate=%" PRId32 " => 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