mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +01:00
Refactor SPI code; Add ESP-IDF hardware support (#5311)
* Checkpoint * Checkpoint * Checkpoint * Revert hal change * Checkpoint * Checkpoint * Checkpoint * Checkpoint * ESP-IDF working * clang-format * use bus_list * Add spi_device; fix 16 bit transfer. * Enable multi_conf; Fix LSB 16 bit transactions * Formatting fixes * Clang-format, codeowners * Add test * Formatting * clang tidy * clang-format * clang-tidy * clang-format * Checkpoint * Checkpoint * Checkpoint * Revert hal change * Checkpoint * Checkpoint * Checkpoint * Checkpoint * ESP-IDF working * clang-format * use bus_list * Add spi_device; fix 16 bit transfer. * Enable multi_conf; Fix LSB 16 bit transactions * Formatting fixes * Clang-format, codeowners * Add test * Formatting * clang tidy * clang-format * clang-tidy * clang-format * Clang-tidy * Clang-format * clang-tidy * clang-tidy * Fix ESP8266 * RP2040 * RP2040 * Avoid use of spi1 as id * Refactor SPI code. Add support for ESP-IDF hardware SPI * Force SW only for RP2040 * Break up large transfers * Add interface: option for spi. validate pins in python. * Can't use match/case with Python 3.9. Check for inverted pins. * Work around target_platform issue with * Remove debug code * Optimize write_array16 * Show errors in hex * Only one spi on ESP32Cx variants * Ensure bus is claimed before asserting /CS. * Check on init/deinit * Allow maximum rate write only SPI on GPIO MUXed pins. * Clang-format * Clang-tidy * Fix issue with reads. * Finger trouble... * Make comment about missing SPI on Cx variants * Pacify CI clang-format. Did not complain locally?? * Restore 8266 to its former SPI glory * Fix per clang-format * Move validation and choice of SPI into Python code. * Add test for interface: config * Fix issues found on self-review. --------- Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
parent
ce171f5c00
commit
5c26f95a4b
11 changed files with 954 additions and 473 deletions
|
@ -270,6 +270,7 @@ esphome/components/socket/* @esphome/core
|
|||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/speaker/* @jesserockz
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/spi_device/* @clydebarrow
|
||||
esphome/components/sprinkler/* @kbx81
|
||||
esphome/components/sps30/* @martgras
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
import re
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome.components.esp32.const import (
|
||||
KEY_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
)
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CLK_PIN,
|
||||
|
@ -9,6 +20,11 @@ from esphome.const import (
|
|||
CONF_MOSI_PIN,
|
||||
CONF_SPI_ID,
|
||||
CONF_CS_PIN,
|
||||
CONF_NUMBER,
|
||||
CONF_INVERTED,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
KEY_VARIANT,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
|
@ -34,10 +50,147 @@ SPI_DATA_RATE_OPTIONS = {
|
|||
}
|
||||
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
|
||||
|
||||
MULTI_CONF = True
|
||||
CONF_FORCE_SW = "force_sw"
|
||||
CONF_INTERFACE = "interface"
|
||||
CONF_INTERFACE_INDEX = "interface_index"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
|
||||
def get_target_platform():
|
||||
return (
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM]
|
||||
if KEY_TARGET_PLATFORM in CORE.data[KEY_CORE]
|
||||
else ""
|
||||
)
|
||||
|
||||
|
||||
def get_target_variant():
|
||||
return (
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] if KEY_VARIANT in CORE.data[KEY_ESP32] else ""
|
||||
)
|
||||
|
||||
|
||||
# Get a list of available hardware interfaces based on target and variant.
|
||||
# The returned value is a list of lists of names
|
||||
def get_hw_interface_list():
|
||||
target_platform = get_target_platform()
|
||||
if target_platform == "esp8266":
|
||||
return [["spi", "hspi"]]
|
||||
if target_platform == "esp32":
|
||||
if get_target_variant() in [
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
]:
|
||||
return [["spi", "spi2"]]
|
||||
return [["spi", "spi2"], ["spi3"]]
|
||||
if target_platform == "rp2040":
|
||||
return [["spi"]]
|
||||
return []
|
||||
|
||||
|
||||
# Given an SPI name, return the index of it in the available list
|
||||
def get_spi_index(name):
|
||||
for i, ilist in enumerate(get_hw_interface_list()):
|
||||
if name in ilist:
|
||||
return i
|
||||
# Should never get to here.
|
||||
raise cv.Invalid(f"{name} is not an available SPI")
|
||||
|
||||
|
||||
# Check that pins are suitable for HW spi
|
||||
# TODO verify that the pins are internal
|
||||
def validate_hw_pins(spi):
|
||||
clk_pin = spi[CONF_CLK_PIN]
|
||||
if clk_pin[CONF_INVERTED]:
|
||||
return False
|
||||
clk_pin_no = clk_pin[CONF_NUMBER]
|
||||
sdo_pin_no = -1
|
||||
sdi_pin_no = -1
|
||||
if CONF_MOSI_PIN in spi:
|
||||
sdo_pin = spi[CONF_MOSI_PIN]
|
||||
if sdo_pin[CONF_INVERTED]:
|
||||
return False
|
||||
sdo_pin_no = sdo_pin[CONF_NUMBER]
|
||||
if CONF_MISO_PIN in spi:
|
||||
sdi_pin = spi[CONF_MISO_PIN]
|
||||
if sdi_pin[CONF_INVERTED]:
|
||||
return False
|
||||
sdi_pin_no = sdi_pin[CONF_NUMBER]
|
||||
|
||||
target_platform = get_target_platform()
|
||||
if target_platform == "esp8266":
|
||||
if clk_pin_no == 6:
|
||||
return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7)
|
||||
if clk_pin_no == 14:
|
||||
return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12)
|
||||
return False
|
||||
|
||||
if target_platform == "esp32":
|
||||
return clk_pin_no >= 0
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def validate_spi_config(config):
|
||||
available = list(range(len(get_hw_interface_list())))
|
||||
for spi in config:
|
||||
interface = spi[CONF_INTERFACE]
|
||||
if spi[CONF_FORCE_SW]:
|
||||
if interface == "any":
|
||||
spi[CONF_INTERFACE] = interface = "software"
|
||||
elif interface != "software":
|
||||
raise cv.Invalid("force_sw is deprecated - use interface: software")
|
||||
if interface == "software":
|
||||
pass
|
||||
elif interface == "any":
|
||||
if not validate_hw_pins(spi):
|
||||
spi[CONF_INTERFACE] = "software"
|
||||
elif interface == "hardware":
|
||||
if len(available) == 0:
|
||||
raise cv.Invalid("No hardware interface available")
|
||||
index = spi[CONF_INTERFACE_INDEX] = available[0]
|
||||
available.remove(index)
|
||||
else:
|
||||
# Must be a specific name
|
||||
index = spi[CONF_INTERFACE_INDEX] = get_spi_index(interface)
|
||||
if index not in available:
|
||||
raise cv.Invalid(
|
||||
f"interface '{interface}' not available here (may be already assigned)"
|
||||
)
|
||||
available.remove(index)
|
||||
|
||||
# Second time around:
|
||||
# Any specific names and any 'hardware' requests will have already been filled,
|
||||
# so just need to assign remaining hardware to 'any' requests.
|
||||
for spi in config:
|
||||
if spi[CONF_INTERFACE] == "any" and len(available) != 0:
|
||||
index = available[0]
|
||||
spi[CONF_INTERFACE_INDEX] = index
|
||||
available.remove(index)
|
||||
if CONF_INTERFACE_INDEX in spi and not validate_hw_pins(spi):
|
||||
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Given an SPI index, convert to a string that represents the C++ object for it.
|
||||
def get_spi_interface(index):
|
||||
if CORE.using_esp_idf:
|
||||
return ["SPI2_HOST", "SPI3_HOST"][index]
|
||||
# Arduino code follows
|
||||
platform = get_target_platform()
|
||||
if platform == "rp2040":
|
||||
return "&spi1"
|
||||
if index == 0:
|
||||
return "&SPI"
|
||||
# Following code can't apply to C2, H2 or 8266 since they have only one SPI
|
||||
if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2):
|
||||
return "new SPIClass(FSPI)"
|
||||
return "return new SPIClass(HSPI)"
|
||||
|
||||
|
||||
SPI_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SPIComponent),
|
||||
|
@ -45,28 +198,47 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FORCE_SW, default=False): cv.boolean,
|
||||
cv.Optional(CONF_INTERFACE, default="any"): cv.one_of(
|
||||
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
|
||||
lower=True,
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
|
||||
cv.only_on(["esp32", "esp8266", "rp2040"]),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.ensure_list(SPI_SCHEMA),
|
||||
validate_spi_config,
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(1.0)
|
||||
async def to_code(config):
|
||||
async def to_code(configs):
|
||||
cg.add_global(spi_ns.using)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
for spi in configs:
|
||||
var = cg.new_Pvariable(spi[CONF_ID])
|
||||
await cg.register_component(var, spi)
|
||||
|
||||
clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN])
|
||||
cg.add(var.set_clk(clk))
|
||||
cg.add(var.set_force_sw(config[CONF_FORCE_SW]))
|
||||
if CONF_MISO_PIN in config:
|
||||
miso = await cg.gpio_pin_expression(config[CONF_MISO_PIN])
|
||||
cg.add(var.set_miso(miso))
|
||||
if CONF_MOSI_PIN in config:
|
||||
mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
|
||||
cg.add(var.set_mosi(mosi))
|
||||
clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
|
||||
cg.add(var.set_clk(clk))
|
||||
if CONF_MISO_PIN in spi:
|
||||
miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
|
||||
cg.add(var.set_miso(miso))
|
||||
if CONF_MOSI_PIN in spi:
|
||||
mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
|
||||
cg.add(var.set_mosi(mosi))
|
||||
if CONF_INTERFACE_INDEX in spi:
|
||||
index = spi[CONF_INTERFACE_INDEX]
|
||||
cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index))))
|
||||
cg.add(
|
||||
var.set_interface_name(
|
||||
re.sub(
|
||||
r"\W", "", get_spi_interface(index).replace("new SPIClass", "")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("SPI", None)
|
||||
|
|
|
@ -1,268 +1,116 @@
|
|||
#include "spi.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace spi {
|
||||
|
||||
static const char *const TAG = "spi";
|
||||
const char *const TAG = "spi";
|
||||
|
||||
void IRAM_ATTR HOT SPIComponent::disable() {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
this->hw_spi_->endTransaction();
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
if (this->active_cs_) {
|
||||
this->active_cs_->digital_write(true);
|
||||
this->active_cs_ = nullptr;
|
||||
SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
new SPIDelegateDummy();
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=48040
|
||||
|
||||
bool SPIDelegate::is_ready() { return true; }
|
||||
|
||||
GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
|
||||
GPIOPin *cs_pin) {
|
||||
if (this->devices_.count(device) != 0) {
|
||||
ESP_LOGE(TAG, "SPI device already registered");
|
||||
return this->devices_[device];
|
||||
}
|
||||
SPIDelegate *delegate = this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin); // NOLINT
|
||||
this->devices_[device] = delegate;
|
||||
return delegate;
|
||||
}
|
||||
|
||||
void SPIComponent::unregister_device(SPIClient *device) {
|
||||
if (this->devices_.count(device) == 0) {
|
||||
esph_log_e(TAG, "SPI device not registered");
|
||||
return;
|
||||
}
|
||||
delete this->devices_[device]; // NOLINT
|
||||
this->devices_.erase(device);
|
||||
}
|
||||
|
||||
void SPIComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SPI bus...");
|
||||
this->clk_->setup();
|
||||
this->clk_->digital_write(true);
|
||||
ESP_LOGD(TAG, "Setting up SPI bus...");
|
||||
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
bool use_hw_spi = !this->force_sw_;
|
||||
const bool has_miso = this->miso_ != nullptr;
|
||||
const bool has_mosi = this->mosi_ != nullptr;
|
||||
int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1;
|
||||
|
||||
if (!this->clk_->is_internal())
|
||||
use_hw_spi = false;
|
||||
if (has_miso && !miso_->is_internal())
|
||||
use_hw_spi = false;
|
||||
if (has_mosi && !mosi_->is_internal())
|
||||
use_hw_spi = false;
|
||||
if (use_hw_spi) {
|
||||
auto *clk_internal = (InternalGPIOPin *) clk_;
|
||||
auto *miso_internal = (InternalGPIOPin *) miso_;
|
||||
auto *mosi_internal = (InternalGPIOPin *) mosi_;
|
||||
|
||||
if (clk_internal->is_inverted())
|
||||
use_hw_spi = false;
|
||||
if (has_miso && miso_internal->is_inverted())
|
||||
use_hw_spi = false;
|
||||
if (has_mosi && mosi_internal->is_inverted())
|
||||
use_hw_spi = false;
|
||||
|
||||
if (use_hw_spi) {
|
||||
clk_pin = clk_internal->get_pin();
|
||||
miso_pin = has_miso ? miso_internal->get_pin() : -1;
|
||||
mosi_pin = has_mosi ? mosi_internal->get_pin() : -1;
|
||||
}
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) &&
|
||||
!(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)))
|
||||
use_hw_spi = false;
|
||||
|
||||
if (use_hw_spi) {
|
||||
this->hw_spi_ = &SPI;
|
||||
this->hw_spi_->pins(clk_pin, miso_pin, mosi_pin, 0);
|
||||
this->hw_spi_->begin();
|
||||
if (this->sdo_pin_ == nullptr)
|
||||
this->sdo_pin_ = NullPin::NULL_PIN;
|
||||
if (this->sdi_pin_ == nullptr)
|
||||
this->sdi_pin_ = NullPin::NULL_PIN;
|
||||
if (this->clk_pin_ == nullptr) {
|
||||
ESP_LOGE(TAG, "No clock pin for SPI");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_ESP32
|
||||
static uint8_t spi_bus_num = 0;
|
||||
if (spi_bus_num >= 2) {
|
||||
use_hw_spi = false;
|
||||
}
|
||||
|
||||
if (use_hw_spi) {
|
||||
if (spi_bus_num == 0) {
|
||||
this->hw_spi_ = &SPI;
|
||||
} else {
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
this->hw_spi_ = new SPIClass(FSPI); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
#else
|
||||
this->hw_spi_ = new SPIClass(HSPI); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
#endif // USE_ESP32_VARIANT
|
||||
if (this->using_hw_) {
|
||||
this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
|
||||
if (this->spi_bus_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Unable to allocate SPI interface");
|
||||
this->mark_failed();
|
||||
}
|
||||
spi_bus_num++;
|
||||
this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin);
|
||||
return;
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_RP2040
|
||||
static uint8_t spi_bus_num = 0;
|
||||
if (spi_bus_num >= 2) {
|
||||
use_hw_spi = false;
|
||||
}
|
||||
if (use_hw_spi) {
|
||||
SPIClassRP2040 *spi;
|
||||
if (spi_bus_num == 0) {
|
||||
spi = &SPI;
|
||||
} else {
|
||||
spi = &SPI1;
|
||||
}
|
||||
spi_bus_num++;
|
||||
|
||||
if (miso_pin != -1)
|
||||
spi->setRX(miso_pin);
|
||||
if (mosi_pin != -1)
|
||||
spi->setTX(mosi_pin);
|
||||
spi->setSCK(clk_pin);
|
||||
this->hw_spi_ = spi;
|
||||
this->hw_spi_->begin();
|
||||
return;
|
||||
}
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
|
||||
if (this->miso_ != nullptr) {
|
||||
this->miso_->setup();
|
||||
}
|
||||
if (this->mosi_ != nullptr) {
|
||||
this->mosi_->setup();
|
||||
this->mosi_->digital_write(false);
|
||||
} else {
|
||||
this->spi_bus_ = new SPIBus(this->clk_pin_, this->sdo_pin_, this->sdi_pin_); // NOLINT
|
||||
this->clk_pin_->setup();
|
||||
this->clk_pin_->digital_write(true);
|
||||
this->sdo_pin_->setup();
|
||||
this->sdi_pin_->setup();
|
||||
}
|
||||
}
|
||||
|
||||
void SPIComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SPI bus:");
|
||||
LOG_PIN(" CLK Pin: ", this->clk_);
|
||||
LOG_PIN(" MISO Pin: ", this->miso_);
|
||||
LOG_PIN(" MOSI Pin: ", this->mosi_);
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr));
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
}
|
||||
float SPIComponent::get_setup_priority() const { return setup_priority::BUS; }
|
||||
|
||||
void SPIComponent::cycle_clock_(bool value) {
|
||||
uint32_t start = arch_get_cpu_cycle_count();
|
||||
while (start - arch_get_cpu_cycle_count() < this->wait_cycle_)
|
||||
;
|
||||
this->clk_->digital_write(value);
|
||||
start += this->wait_cycle_;
|
||||
while (start - arch_get_cpu_cycle_count() < this->wait_cycle_)
|
||||
;
|
||||
LOG_PIN(" CLK Pin: ", this->clk_pin_)
|
||||
LOG_PIN(" SDI Pin: ", this->sdi_pin_)
|
||||
LOG_PIN(" SDO Pin: ", this->sdo_pin_)
|
||||
if (this->spi_bus_->is_hw()) {
|
||||
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Using software SPI");
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#ifndef CLANG_TIDY
|
||||
#pragma GCC optimize("unroll-loops")
|
||||
// NOLINTNEXTLINE
|
||||
#pragma GCC optimize("O2")
|
||||
#endif // CLANG_TIDY
|
||||
void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); }
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE>
|
||||
uint8_t HOT SPIComponent::transfer_(uint8_t data) {
|
||||
uint8_t SPIDelegateBitBash::transfer(uint8_t data) {
|
||||
// Clock starts out at idle level
|
||||
this->clk_->digital_write(CLOCK_POLARITY);
|
||||
this->clk_pin_->digital_write(clock_polarity_);
|
||||
uint8_t out_data = 0;
|
||||
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint8_t shift;
|
||||
if (BIT_ORDER == BIT_ORDER_MSB_FIRST) {
|
||||
if (bit_order_ == BIT_ORDER_MSB_FIRST) {
|
||||
shift = 7 - i;
|
||||
} else {
|
||||
shift = i;
|
||||
}
|
||||
|
||||
if (CLOCK_PHASE == CLOCK_PHASE_LEADING) {
|
||||
if (clock_phase_ == CLOCK_PHASE_LEADING) {
|
||||
// sampling on leading edge
|
||||
if (WRITE) {
|
||||
this->mosi_->digital_write(data & (1 << shift));
|
||||
}
|
||||
|
||||
// SAMPLE!
|
||||
this->cycle_clock_(!CLOCK_POLARITY);
|
||||
|
||||
if (READ) {
|
||||
out_data |= uint8_t(this->miso_->digital_read()) << shift;
|
||||
}
|
||||
|
||||
this->cycle_clock_(CLOCK_POLARITY);
|
||||
this->sdo_pin_->digital_write(data & (1 << shift));
|
||||
this->cycle_clock_();
|
||||
out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift;
|
||||
this->clk_pin_->digital_write(!this->clock_polarity_);
|
||||
this->cycle_clock_();
|
||||
this->clk_pin_->digital_write(this->clock_polarity_);
|
||||
} else {
|
||||
// sampling on trailing edge
|
||||
this->cycle_clock_(!CLOCK_POLARITY);
|
||||
|
||||
if (WRITE) {
|
||||
this->mosi_->digital_write(data & (1 << shift));
|
||||
}
|
||||
|
||||
// SAMPLE!
|
||||
this->cycle_clock_(CLOCK_POLARITY);
|
||||
|
||||
if (READ) {
|
||||
out_data |= uint8_t(this->miso_->digital_read()) << shift;
|
||||
}
|
||||
this->cycle_clock_();
|
||||
this->clk_pin_->digital_write(!this->clock_polarity_);
|
||||
this->sdo_pin_->digital_write(data & (1 << shift));
|
||||
this->cycle_clock_();
|
||||
out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift;
|
||||
this->clk_pin_->digital_write(this->clock_polarity_);
|
||||
}
|
||||
}
|
||||
|
||||
App.feed_wdt();
|
||||
|
||||
return out_data;
|
||||
}
|
||||
|
||||
// Generate with (py3):
|
||||
//
|
||||
// from itertools import product
|
||||
// bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST']
|
||||
// clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH']
|
||||
// clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING']
|
||||
// reads = [False, True]
|
||||
// writes = [False, True]
|
||||
// cpp_bool = {False: 'false', True: 'true'}
|
||||
// for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes):
|
||||
// if not r and not w:
|
||||
// continue
|
||||
// print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t
|
||||
// data);")
|
||||
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>(
|
||||
uint8_t data);
|
||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>(
|
||||
uint8_t data);
|
||||
|
||||
} // namespace spi
|
||||
} // namespace esphome
|
||||
|
|
|
@ -2,16 +2,34 @@
|
|||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#define USE_SPI_ARDUINO_BACKEND
|
||||
#endif
|
||||
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
#include <SPI.h>
|
||||
|
||||
#ifdef USE_RP2040
|
||||
using SPIInterface = SPIClassRP2040 *;
|
||||
#else
|
||||
using SPIInterface = SPIClass *;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
using SPIInterface = spi_host_device_t;
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
/**
|
||||
* Implementation of SPI Controller mode.
|
||||
*/
|
||||
namespace esphome {
|
||||
namespace spi {
|
||||
|
||||
|
@ -48,10 +66,19 @@ enum SPIClockPhase {
|
|||
/// The data is sampled on a trailing clock edge. (CPHA=1)
|
||||
CLOCK_PHASE_TRAILING,
|
||||
};
|
||||
/** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW.
|
||||
* So effectively the rate of bytes can be calculated using
|
||||
|
||||
/**
|
||||
* Modes mapping to clock phase and polarity.
|
||||
*
|
||||
* effective_byte_rate = spi_data_rate / 16
|
||||
*/
|
||||
|
||||
enum SPIMode {
|
||||
MODE0 = 0,
|
||||
MODE1 = 1,
|
||||
MODE2 = 2,
|
||||
MODE3 = 3,
|
||||
};
|
||||
/** The SPI clock signal frequency, which determines the transfer bit rate/second.
|
||||
*
|
||||
* Implementations can use the pre-defined constants here, or use an integer in the template definition
|
||||
* to manually use a specific data rate.
|
||||
|
@ -71,270 +98,340 @@ enum SPIDataRate : uint32_t {
|
|||
DATA_RATE_80MHZ = 80000000,
|
||||
};
|
||||
|
||||
class SPIComponent : public Component {
|
||||
/**
|
||||
* A pin to replace those that don't exist.
|
||||
*/
|
||||
class NullPin : public GPIOPin {
|
||||
friend class SPIComponent;
|
||||
|
||||
friend class SPIDelegate;
|
||||
|
||||
friend class Utility;
|
||||
|
||||
public:
|
||||
void set_clk(GPIOPin *clk) { clk_ = clk; }
|
||||
void set_miso(GPIOPin *miso) { miso_ = miso; }
|
||||
void set_mosi(GPIOPin *mosi) { mosi_ = mosi; }
|
||||
void set_force_sw(bool force_sw) { force_sw_ = force_sw; }
|
||||
void setup() override {}
|
||||
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override {}
|
||||
|
||||
void dump_config() override;
|
||||
bool digital_read() override { return false; }
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> uint8_t read_byte() {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
return this->hw_spi_->transfer(0x00);
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, false>(0x00);
|
||||
}
|
||||
void digital_write(bool value) override {}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
void read_array(uint8_t *data, size_t length) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
this->hw_spi_->transfer(data, length);
|
||||
return;
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
data[i] = this->read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>();
|
||||
}
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
void write_byte(uint8_t data) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
#ifdef USE_RP2040
|
||||
this->hw_spi_->transfer(data);
|
||||
#else
|
||||
this->hw_spi_->write(data);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, false, true>(data);
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
void write_byte16(const uint16_t data) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
#ifdef USE_RP2040
|
||||
this->hw_spi_->transfer16(data);
|
||||
#else
|
||||
this->hw_spi_->write16(data);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
|
||||
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data >> 8);
|
||||
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
void write_array16(const uint16_t *data, size_t length) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
#ifdef USE_RP2040
|
||||
this->hw_spi_->transfer16(data[i]);
|
||||
#else
|
||||
this->hw_spi_->write16(data[i]);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
this->write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
void write_array(const uint8_t *data, size_t length) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
auto *data_c = const_cast<uint8_t *>(data);
|
||||
#ifdef USE_RP2040
|
||||
this->hw_spi_->transfer(data_c, length);
|
||||
#else
|
||||
this->hw_spi_->writeBytes(data_c, length);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
uint8_t transfer_byte(uint8_t data) {
|
||||
if (this->miso_ != nullptr) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
return this->hw_spi_->transfer(data);
|
||||
} else {
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, true>(data);
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
}
|
||||
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
|
||||
void transfer_array(uint8_t *data, size_t length) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
if (this->miso_ != nullptr) {
|
||||
this->hw_spi_->transfer(data, length);
|
||||
} else {
|
||||
#ifdef USE_RP2040
|
||||
this->hw_spi_->transfer(data, length);
|
||||
#else
|
||||
this->hw_spi_->writeBytes(data, length);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
|
||||
if (this->miso_ != nullptr) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
data[i] = this->transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
|
||||
}
|
||||
} else {
|
||||
this->write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE>
|
||||
void enable(GPIOPin *cs) {
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
if (this->hw_spi_ != nullptr) {
|
||||
uint8_t data_mode = SPI_MODE0;
|
||||
if (!CLOCK_POLARITY && CLOCK_PHASE) {
|
||||
data_mode = SPI_MODE1;
|
||||
} else if (CLOCK_POLARITY && !CLOCK_PHASE) {
|
||||
data_mode = SPI_MODE2;
|
||||
} else if (CLOCK_POLARITY && CLOCK_PHASE) {
|
||||
data_mode = SPI_MODE3;
|
||||
}
|
||||
#ifdef USE_RP2040
|
||||
SPISettings settings(DATA_RATE, static_cast<BitOrder>(BIT_ORDER), data_mode);
|
||||
#else
|
||||
SPISettings settings(DATA_RATE, BIT_ORDER, data_mode);
|
||||
#endif
|
||||
this->hw_spi_->beginTransaction(settings);
|
||||
} else {
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
this->clk_->digital_write(CLOCK_POLARITY);
|
||||
uint32_t cpu_freq_hz = arch_get_cpu_freq_hz();
|
||||
this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL;
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
}
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
|
||||
if (cs != nullptr) {
|
||||
this->active_cs_ = cs;
|
||||
this->active_cs_->digital_write(false);
|
||||
}
|
||||
}
|
||||
|
||||
void disable();
|
||||
|
||||
float get_setup_priority() const override;
|
||||
std::string dump_summary() const override { return std::string(); }
|
||||
|
||||
protected:
|
||||
inline void cycle_clock_(bool value);
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE>
|
||||
uint8_t transfer_(uint8_t data);
|
||||
|
||||
GPIOPin *clk_;
|
||||
GPIOPin *miso_{nullptr};
|
||||
GPIOPin *mosi_{nullptr};
|
||||
GPIOPin *active_cs_{nullptr};
|
||||
bool force_sw_{false};
|
||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||
SPIClass *hw_spi_{nullptr};
|
||||
#endif // USE_SPI_ARDUINO_BACKEND
|
||||
uint32_t wait_cycle_;
|
||||
static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=48040
|
||||
};
|
||||
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE>
|
||||
class SPIDevice {
|
||||
class Utility {
|
||||
public:
|
||||
SPIDevice() = default;
|
||||
SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {}
|
||||
static int get_pin_no(GPIOPin *pin) {
|
||||
if (pin == nullptr || !pin->is_internal())
|
||||
return -1;
|
||||
if (((InternalGPIOPin *) pin)->is_inverted())
|
||||
return -1;
|
||||
return ((InternalGPIOPin *) pin)->get_pin();
|
||||
}
|
||||
|
||||
void set_spi_parent(SPIComponent *parent) { parent_ = parent; }
|
||||
void set_cs_pin(GPIOPin *cs) { cs_ = cs; }
|
||||
static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) {
|
||||
if (polarity == CLOCK_POLARITY_HIGH) {
|
||||
return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3;
|
||||
}
|
||||
return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1;
|
||||
}
|
||||
|
||||
void spi_setup() {
|
||||
if (this->cs_) {
|
||||
this->cs_->setup();
|
||||
this->cs_->digital_write(true);
|
||||
static SPIClockPhase get_phase(SPIMode mode) {
|
||||
switch (mode) {
|
||||
case MODE0:
|
||||
case MODE2:
|
||||
return CLOCK_PHASE_LEADING;
|
||||
default:
|
||||
return CLOCK_PHASE_TRAILING;
|
||||
}
|
||||
}
|
||||
|
||||
void enable() { this->parent_->template enable<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, DATA_RATE>(this->cs_); }
|
||||
static SPIClockPolarity get_polarity(SPIMode mode) {
|
||||
switch (mode) {
|
||||
case MODE0:
|
||||
case MODE1:
|
||||
return CLOCK_POLARITY_LOW;
|
||||
default:
|
||||
return CLOCK_POLARITY_HIGH;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void disable() { this->parent_->disable(); }
|
||||
class SPIDelegateDummy;
|
||||
|
||||
uint8_t read_byte() { return this->parent_->template read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(); }
|
||||
// represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is
|
||||
// a thin wrapper over SPIClass.
|
||||
class SPIDelegate {
|
||||
friend class SPIClient;
|
||||
|
||||
void read_array(uint8_t *data, size_t length) {
|
||||
return this->parent_->template read_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
|
||||
public:
|
||||
SPIDelegate() = default;
|
||||
|
||||
SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin)
|
||||
: bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) {
|
||||
if (this->cs_pin_ == nullptr)
|
||||
this->cs_pin_ = NullPin::NULL_PIN;
|
||||
this->cs_pin_->setup();
|
||||
this->cs_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
template<size_t N> std::array<uint8_t, N> read_array() {
|
||||
std::array<uint8_t, N> data;
|
||||
this->read_array(data.data(), N);
|
||||
return data;
|
||||
virtual ~SPIDelegate(){};
|
||||
|
||||
// enable CS if configured.
|
||||
virtual void begin_transaction() { this->cs_pin_->digital_write(false); }
|
||||
|
||||
// end the transaction
|
||||
virtual void end_transaction() { this->cs_pin_->digital_write(true); }
|
||||
|
||||
// transfer one byte, return the byte that was read.
|
||||
virtual uint8_t transfer(uint8_t data) = 0;
|
||||
|
||||
// transfer a buffer, replace the contents with read data
|
||||
virtual void transfer(uint8_t *ptr, size_t length) { this->transfer(ptr, ptr, length); }
|
||||
|
||||
virtual void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) {
|
||||
for (size_t i = 0; i != length; i++)
|
||||
rxbuf[i] = this->transfer(txbuf[i]);
|
||||
}
|
||||
|
||||
void write_byte(uint8_t data) {
|
||||
return this->parent_->template write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
|
||||
// write 16 bits
|
||||
virtual void write16(uint16_t data) {
|
||||
if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
|
||||
uint16_t buffer;
|
||||
buffer = (data >> 8) | (data << 8);
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&buffer), 2);
|
||||
} else {
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&data), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void write_byte16(uint16_t data) {
|
||||
return this->parent_->template write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
|
||||
virtual void write_array16(const uint16_t *data, size_t length) {
|
||||
for (size_t i = 0; i != length; i++) {
|
||||
this->write16(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void write_array16(const uint16_t *data, size_t length) {
|
||||
this->parent_->template write_array16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
|
||||
// write the contents of a buffer, ignore read data (buffer is unchanged.)
|
||||
virtual void write_array(const uint8_t *ptr, size_t length) {
|
||||
for (size_t i = 0; i != length; i++)
|
||||
this->transfer(ptr[i]);
|
||||
}
|
||||
|
||||
void write_array(const uint8_t *data, size_t length) {
|
||||
this->parent_->template write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
|
||||
// read into a buffer, write nulls
|
||||
virtual void read_array(uint8_t *ptr, size_t length) {
|
||||
for (size_t i = 0; i != length; i++)
|
||||
ptr[i] = this->transfer(0);
|
||||
}
|
||||
|
||||
// check if device is ready
|
||||
virtual bool is_ready();
|
||||
|
||||
protected:
|
||||
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
|
||||
uint32_t data_rate_{1000000};
|
||||
SPIMode mode_{MODE0};
|
||||
GPIOPin *cs_pin_{NullPin::NULL_PIN};
|
||||
static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
};
|
||||
|
||||
/**
|
||||
* A dummy SPIDelegate that complains if it's used.
|
||||
*/
|
||||
|
||||
class SPIDelegateDummy : public SPIDelegate {
|
||||
public:
|
||||
SPIDelegateDummy() = default;
|
||||
|
||||
uint8_t transfer(uint8_t data) override { return 0; }
|
||||
|
||||
void begin_transaction() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* An implementation of SPI that relies only on software toggling of pins.
|
||||
*
|
||||
*/
|
||||
class SPIDelegateBitBash : public SPIDelegate {
|
||||
public:
|
||||
SPIDelegateBitBash(uint32_t clock, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, GPIOPin *clk_pin,
|
||||
GPIOPin *sdo_pin, GPIOPin *sdi_pin)
|
||||
: SPIDelegate(clock, bit_order, mode, cs_pin), clk_pin_(clk_pin), sdo_pin_(sdo_pin), sdi_pin_(sdi_pin) {
|
||||
// this calculation is pretty meaningless except at very low bit rates.
|
||||
this->wait_cycle_ = uint32_t(arch_get_cpu_freq_hz()) / this->data_rate_ / 2ULL;
|
||||
this->clock_polarity_ = Utility::get_polarity(this->mode_);
|
||||
this->clock_phase_ = Utility::get_phase(this->mode_);
|
||||
}
|
||||
|
||||
uint8_t transfer(uint8_t data) override;
|
||||
|
||||
protected:
|
||||
GPIOPin *clk_pin_;
|
||||
GPIOPin *sdo_pin_;
|
||||
GPIOPin *sdi_pin_;
|
||||
uint32_t last_transition_{0};
|
||||
uint32_t wait_cycle_;
|
||||
SPIClockPolarity clock_polarity_;
|
||||
SPIClockPhase clock_phase_;
|
||||
|
||||
void HOT cycle_clock_() {
|
||||
while (this->last_transition_ - arch_get_cpu_cycle_count() < this->wait_cycle_)
|
||||
continue;
|
||||
this->last_transition_ += this->wait_cycle_;
|
||||
}
|
||||
};
|
||||
|
||||
class SPIBus {
|
||||
public:
|
||||
SPIBus() = default;
|
||||
|
||||
SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {}
|
||||
|
||||
virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) {
|
||||
return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
|
||||
}
|
||||
|
||||
virtual bool is_hw() { return false; }
|
||||
|
||||
protected:
|
||||
GPIOPin *clk_pin_{};
|
||||
GPIOPin *sdo_pin_{};
|
||||
GPIOPin *sdi_pin_{};
|
||||
};
|
||||
|
||||
class SPIClient;
|
||||
|
||||
class SPIComponent : public Component {
|
||||
public:
|
||||
SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
|
||||
GPIOPin *cs_pin);
|
||||
void unregister_device(SPIClient *device);
|
||||
|
||||
void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; }
|
||||
|
||||
void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; }
|
||||
|
||||
void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; }
|
||||
|
||||
void set_interface(SPIInterface interface) {
|
||||
this->interface_ = interface;
|
||||
this->using_hw_ = true;
|
||||
}
|
||||
|
||||
void set_interface_name(const char *name) { this->interface_name_ = name; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
GPIOPin *clk_pin_{nullptr};
|
||||
GPIOPin *sdi_pin_{nullptr};
|
||||
GPIOPin *sdo_pin_{nullptr};
|
||||
SPIInterface interface_{};
|
||||
bool using_hw_{false};
|
||||
const char *interface_name_{nullptr};
|
||||
SPIBus *spi_bus_{};
|
||||
std::map<SPIClient *, SPIDelegate *> devices_;
|
||||
|
||||
static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi);
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for SPIDevice, un-templated.
|
||||
*/
|
||||
class SPIClient {
|
||||
public:
|
||||
SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate)
|
||||
: bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {}
|
||||
|
||||
virtual void spi_setup() {
|
||||
this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_);
|
||||
}
|
||||
|
||||
virtual void spi_teardown() {
|
||||
this->parent_->unregister_device(this);
|
||||
this->delegate_ = SPIDelegate::NULL_DELEGATE;
|
||||
}
|
||||
|
||||
bool spi_is_ready() { return this->delegate_->is_ready(); }
|
||||
|
||||
protected:
|
||||
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
|
||||
SPIMode mode_{MODE0};
|
||||
uint32_t data_rate_{1000000};
|
||||
SPIComponent *parent_{nullptr};
|
||||
GPIOPin *cs_{nullptr};
|
||||
SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
|
||||
};
|
||||
|
||||
/**
|
||||
* The SPIDevice is what components using the SPI will create.
|
||||
*
|
||||
* @tparam BIT_ORDER
|
||||
* @tparam CLOCK_POLARITY
|
||||
* @tparam CLOCK_PHASE
|
||||
* @tparam DATA_RATE
|
||||
*/
|
||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE>
|
||||
class SPIDevice : public SPIClient {
|
||||
public:
|
||||
SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {}
|
||||
|
||||
SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) {
|
||||
this->set_spi_parent(parent);
|
||||
this->set_cs_pin(cs_pin);
|
||||
}
|
||||
|
||||
void spi_setup() override { SPIClient::spi_setup(); }
|
||||
|
||||
void spi_teardown() override { SPIClient::spi_teardown(); }
|
||||
|
||||
void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; }
|
||||
|
||||
void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; }
|
||||
|
||||
void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; }
|
||||
|
||||
void set_bit_order(SPIBitOrder order) {
|
||||
this->bit_order_ = order;
|
||||
esph_log_d("spi.h", "bit order set to %d", order);
|
||||
}
|
||||
|
||||
void set_mode(SPIMode mode) { this->mode_ = mode; }
|
||||
|
||||
uint8_t read_byte() { return this->delegate_->transfer(0); }
|
||||
|
||||
void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); }
|
||||
|
||||
void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); }
|
||||
|
||||
void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); }
|
||||
|
||||
uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); }
|
||||
|
||||
// the driver will byte-swap if required.
|
||||
void write_byte16(uint16_t data) { this->delegate_->write16(data); }
|
||||
|
||||
// avoid use of this if possible. It's inefficient and ugly.
|
||||
void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); }
|
||||
|
||||
void enable() { this->delegate_->begin_transaction(); }
|
||||
|
||||
void disable() { this->delegate_->end_transaction(); }
|
||||
|
||||
void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); }
|
||||
|
||||
template<size_t N> void write_array(const std::array<uint8_t, N> &data) { this->write_array(data.data(), N); }
|
||||
|
||||
void write_array(const std::vector<uint8_t> &data) { this->write_array(data.data(), data.size()); }
|
||||
|
||||
uint8_t transfer_byte(uint8_t data) {
|
||||
return this->parent_->template transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
|
||||
}
|
||||
|
||||
void transfer_array(uint8_t *data, size_t length) {
|
||||
this->parent_->template transfer_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
|
||||
}
|
||||
|
||||
template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); }
|
||||
|
||||
protected:
|
||||
SPIComponent *parent_{nullptr};
|
||||
GPIOPin *cs_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace spi
|
||||
|
|
89
esphome/components/spi/spi_arduino.cpp
Normal file
89
esphome/components/spi/spi_arduino.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "spi.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace spi {
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
static const char *const TAG = "spi-esp-arduino";
|
||||
class SPIDelegateHw : public SPIDelegate {
|
||||
public:
|
||||
SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin)
|
||||
: SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel) {}
|
||||
|
||||
void begin_transaction() override {
|
||||
#ifdef USE_RP2040
|
||||
SPISettings const settings(this->data_rate_, static_cast<BitOrder>(this->bit_order_), this->mode_);
|
||||
#else
|
||||
SPISettings const settings(this->data_rate_, this->bit_order_, this->mode_);
|
||||
#endif
|
||||
this->channel_->beginTransaction(settings);
|
||||
SPIDelegate::begin_transaction();
|
||||
}
|
||||
|
||||
void transfer(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); }
|
||||
|
||||
void end_transaction() override {
|
||||
this->channel_->endTransaction();
|
||||
SPIDelegate::end_transaction();
|
||||
}
|
||||
|
||||
uint8_t transfer(uint8_t data) override { return this->channel_->transfer(data); }
|
||||
|
||||
void write16(uint16_t data) override { this->channel_->transfer16(data); }
|
||||
|
||||
#ifdef USE_RP2040
|
||||
void write_array(const uint8_t *ptr, size_t length) override {
|
||||
// avoid overwriting the supplied buffer
|
||||
uint8_t *rxbuf = new uint8_t[length]; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
memcpy(rxbuf, ptr, length);
|
||||
this->channel_->transfer((void *) rxbuf, length);
|
||||
delete[] rxbuf; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
#else
|
||||
void write_array(const uint8_t *ptr, size_t length) override { this->channel_->writeBytes(ptr, length); }
|
||||
#endif
|
||||
|
||||
void read_array(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); }
|
||||
|
||||
protected:
|
||||
SPIInterface channel_{};
|
||||
};
|
||||
|
||||
class SPIBusHw : public SPIBus {
|
||||
public:
|
||||
SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) {
|
||||
#ifdef USE_ESP8266
|
||||
channel->pins(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1);
|
||||
channel->begin();
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_ESP32
|
||||
channel->begin(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1);
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
if (Utility::get_pin_no(sdi) != -1)
|
||||
channel->setRX(Utility::get_pin_no(sdi));
|
||||
if (Utility::get_pin_no(sdo) != -1)
|
||||
channel->setTX(Utility::get_pin_no(sdo));
|
||||
channel->setSCK(Utility::get_pin_no(clk));
|
||||
channel->begin();
|
||||
#endif
|
||||
}
|
||||
|
||||
SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override {
|
||||
return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin);
|
||||
}
|
||||
|
||||
protected:
|
||||
SPIInterface channel_{};
|
||||
bool is_hw() override { return true; }
|
||||
};
|
||||
|
||||
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
|
||||
return new SPIBusHw(clk, sdo, sdi, interface);
|
||||
}
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace spi
|
||||
} // namespace esphome
|
163
esphome/components/spi/spi_esp_idf.cpp
Normal file
163
esphome/components/spi/spi_esp_idf.cpp
Normal file
|
@ -0,0 +1,163 @@
|
|||
#include "spi.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace spi {
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
static const char *const TAG = "spi-esp-idf";
|
||||
static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API.
|
||||
|
||||
class SPIDelegateHw : public SPIDelegate {
|
||||
public:
|
||||
SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
|
||||
bool write_only)
|
||||
: SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel), write_only_(write_only) {
|
||||
spi_device_interface_config_t config = {};
|
||||
config.mode = static_cast<uint8_t>(mode);
|
||||
config.clock_speed_hz = static_cast<int>(data_rate);
|
||||
config.spics_io_num = -1;
|
||||
config.flags = 0;
|
||||
config.queue_size = 1;
|
||||
config.pre_cb = nullptr;
|
||||
config.post_cb = nullptr;
|
||||
if (bit_order == BIT_ORDER_LSB_FIRST)
|
||||
config.flags |= SPI_DEVICE_BIT_LSBFIRST;
|
||||
if (write_only)
|
||||
config.flags |= SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY;
|
||||
esp_err_t const err = spi_bus_add_device(channel, &config, &this->handle_);
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "Add device failed - err %X", err);
|
||||
}
|
||||
|
||||
bool is_ready() override { return this->handle_ != nullptr; }
|
||||
|
||||
void begin_transaction() override {
|
||||
if (this->is_ready()) {
|
||||
if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK)
|
||||
ESP_LOGE(TAG, "Failed to acquire SPI bus");
|
||||
SPIDelegate::begin_transaction();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "spi_setup called before initialisation");
|
||||
}
|
||||
}
|
||||
|
||||
void end_transaction() override {
|
||||
if (this->is_ready()) {
|
||||
SPIDelegate::end_transaction();
|
||||
spi_device_release_bus(this->handle_);
|
||||
}
|
||||
}
|
||||
|
||||
~SPIDelegateHw() override {
|
||||
esp_err_t const err = spi_bus_remove_device(this->handle_);
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "Remove device failed - err %X", err);
|
||||
}
|
||||
|
||||
// do a transfer. either txbuf or rxbuf (but not both) may be null.
|
||||
// transfers above the maximum size will be split.
|
||||
// TODO - make use of the queue for interrupt transfers to provide a (short) pipeline of blocks
|
||||
// when splitting is required.
|
||||
void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) override {
|
||||
if (rxbuf != nullptr && this->write_only_) {
|
||||
ESP_LOGE(TAG, "Attempted read from write-only channel");
|
||||
return;
|
||||
}
|
||||
spi_transaction_t desc = {};
|
||||
desc.flags = 0;
|
||||
while (length != 0) {
|
||||
size_t const partial = std::min(length, MAX_TRANSFER_SIZE);
|
||||
desc.length = partial * 8;
|
||||
desc.rxlength = this->write_only_ ? 0 : partial * 8;
|
||||
desc.tx_buffer = txbuf;
|
||||
desc.rx_buffer = rxbuf;
|
||||
esp_err_t const err = spi_device_transmit(this->handle_, &desc);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Transmit failed - err %X", err);
|
||||
break;
|
||||
}
|
||||
length -= partial;
|
||||
if (txbuf != nullptr)
|
||||
txbuf += partial;
|
||||
if (rxbuf != nullptr)
|
||||
rxbuf += partial;
|
||||
}
|
||||
}
|
||||
|
||||
void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); }
|
||||
|
||||
uint8_t transfer(uint8_t data) override {
|
||||
uint8_t rxbuf;
|
||||
this->transfer(&data, &rxbuf, 1);
|
||||
return rxbuf;
|
||||
}
|
||||
|
||||
void write16(uint16_t data) override {
|
||||
if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
|
||||
uint16_t txbuf = SPI_SWAP_DATA_TX(data, 16);
|
||||
this->transfer((uint8_t *) &txbuf, nullptr, 2);
|
||||
} else {
|
||||
this->transfer((uint8_t *) &data, nullptr, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void write_array(const uint8_t *ptr, size_t length) override { this->transfer(ptr, nullptr, length); }
|
||||
|
||||
void write_array16(const uint16_t *data, size_t length) override {
|
||||
if (this->bit_order_ == BIT_ORDER_LSB_FIRST) {
|
||||
this->write_array((uint8_t *) data, length * 2);
|
||||
} else {
|
||||
uint16_t buffer[MAX_TRANSFER_SIZE / 2];
|
||||
while (length != 0) {
|
||||
size_t const partial = std::min(length, MAX_TRANSFER_SIZE / 2);
|
||||
for (size_t i = 0; i != partial; i++) {
|
||||
buffer[i] = SPI_SWAP_DATA_TX(*data++, 16);
|
||||
}
|
||||
this->write_array((const uint8_t *) buffer, partial * 2);
|
||||
length -= partial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void read_array(uint8_t *ptr, size_t length) override { this->transfer(nullptr, ptr, length); }
|
||||
|
||||
protected:
|
||||
SPIInterface channel_{};
|
||||
spi_device_handle_t handle_{};
|
||||
bool write_only_{false};
|
||||
};
|
||||
|
||||
class SPIBusHw : public SPIBus {
|
||||
public:
|
||||
SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = Utility::get_pin_no(sdo);
|
||||
buscfg.miso_io_num = Utility::get_pin_no(sdi);
|
||||
buscfg.sclk_io_num = Utility::get_pin_no(clk);
|
||||
buscfg.quadwp_io_num = -1;
|
||||
buscfg.quadhd_io_num = -1;
|
||||
buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
|
||||
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "Bus init failed - err %X", err);
|
||||
}
|
||||
|
||||
SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override {
|
||||
return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin,
|
||||
Utility::get_pin_no(this->sdi_pin_) == -1);
|
||||
}
|
||||
|
||||
protected:
|
||||
SPIInterface channel_{};
|
||||
|
||||
bool is_hw() override { return true; }
|
||||
};
|
||||
|
||||
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
|
||||
return new SPIBusHw(clk, sdo, sdi, interface);
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace spi
|
||||
} // namespace esphome
|
49
esphome/components/spi_device/__init__.py
Normal file
49
esphome/components/spi_device/__init__.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID, CONF_DATA_RATE, CONF_MODE
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
MULTI_CONF = True
|
||||
spi_device_ns = cg.esphome_ns.namespace("spi_device")
|
||||
|
||||
spi_device = spi_device_ns.class_("SPIDeviceComponent", cg.Component, spi.SPIDevice)
|
||||
|
||||
Mode = spi.spi_ns.enum("SPIMode")
|
||||
MODES = {
|
||||
"0": Mode.MODE0,
|
||||
"1": Mode.MODE1,
|
||||
"2": Mode.MODE2,
|
||||
"3": Mode.MODE3,
|
||||
"MODE0": Mode.MODE0,
|
||||
"MODE1": Mode.MODE1,
|
||||
"MODE2": Mode.MODE2,
|
||||
"MODE3": Mode.MODE3,
|
||||
}
|
||||
|
||||
BitOrder = spi.spi_ns.enum("SPIBitOrder")
|
||||
ORDERS = {
|
||||
"msb_first": BitOrder.BIT_ORDER_MSB_FIRST,
|
||||
"lsb_first": BitOrder.BIT_ORDER_LSB_FIRST,
|
||||
}
|
||||
CONF_BIT_ORDER = "bit_order"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(spi_device),
|
||||
cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
|
||||
cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True),
|
||||
cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True),
|
||||
}
|
||||
).extend(spi.spi_device_schema(False))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
|
||||
cg.add(var.set_mode(config[CONF_MODE]))
|
||||
cg.add(var.set_bit_order(config[CONF_BIT_ORDER]))
|
||||
await spi.register_spi_device(var, config)
|
30
esphome/components/spi_device/spi_device.cpp
Normal file
30
esphome/components/spi_device/spi_device.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include "spi_device.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace spi_device {
|
||||
|
||||
static const char *const TAG = "spi_device";
|
||||
|
||||
void SPIDeviceComponent::setup() {
|
||||
ESP_LOGD(TAG, "Setting up SPIDevice...");
|
||||
this->spi_setup();
|
||||
ESP_LOGCONFIG(TAG, "SPIDevice started!");
|
||||
}
|
||||
|
||||
void SPIDeviceComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SPIDevice");
|
||||
LOG_PIN(" CS pin: ", this->cs_);
|
||||
ESP_LOGCONFIG(TAG, " Mode: %d", this->mode_);
|
||||
if (this->data_rate_ < 1000000) {
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %dkHz", this->data_rate_ / 1000);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %dMHz", this->data_rate_ / 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
float SPIDeviceComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace spi_device
|
||||
} // namespace esphome
|
22
esphome/components/spi_device/spi_device.h
Normal file
22
esphome/components/spi_device/spi_device.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace spi_device {
|
||||
|
||||
class SPIDeviceComponent : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
} // namespace spi_device
|
||||
} // namespace esphome
|
|
@ -32,6 +32,7 @@ spi:
|
|||
clk_pin: GPIO21
|
||||
mosi_pin: GPIO22
|
||||
miso_pin: GPIO23
|
||||
interface: hardware
|
||||
|
||||
uart:
|
||||
- id: uart115200
|
||||
|
|
|
@ -31,8 +31,17 @@ light:
|
|||
restore_mode: ALWAYS_OFF
|
||||
|
||||
spi:
|
||||
id: spi_id_1
|
||||
clk_pin: GPIO7
|
||||
mosi_pin: GPIO6
|
||||
interface: any
|
||||
|
||||
spi_device:
|
||||
id: spidev
|
||||
data_rate: 2MHz
|
||||
spi_id: spi_id_1
|
||||
mode: 3
|
||||
bit_order: lsb_first
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
|
|
Loading…
Reference in a new issue