init
This commit is contained in:
413
src/hal_spi.cpp
Normal file
413
src/hal_spi.cpp
Normal file
@ -0,0 +1,413 @@
|
||||
//
|
||||
// Created by Kurosu Chan on 2024/1/18.
|
||||
//
|
||||
|
||||
#include <cstdint>
|
||||
#include <driver/spi_master.h>
|
||||
#include <esp_log.h>
|
||||
#include "esp_err.h"
|
||||
#include "hal_error.hpp"
|
||||
#include "hal_gpio.hpp"
|
||||
#include "hal_spi.hpp"
|
||||
#include "utils/app_utils.hpp"
|
||||
#include "utils/app_instant.hpp"
|
||||
// https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32h2/api-reference/peripherals/spi_master.html
|
||||
// https://github.com/espressif/esp-idf/blob/v5.3.2/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c
|
||||
namespace app::driver::hal::spi {
|
||||
constexpr auto SPI_CMD_BIT_SIZE = 8;
|
||||
constexpr auto SPI_BUFFER_OFFSET_BIT_SIZE = 8;
|
||||
constexpr auto SPI_REGISTER_ADDR_BIT_SIZE = 16;
|
||||
constexpr auto MAX_BUFFER_SIZE = 256;
|
||||
|
||||
namespace details {
|
||||
static bool is_initialized = false;
|
||||
static spi_device_handle_t spi_device;
|
||||
|
||||
/// should satisfy most of the control register write/read
|
||||
/// without overlap with the data buffer
|
||||
constexpr auto DATA_BUFFER_OFFSET = 16;
|
||||
static_assert(DATA_BUFFER_OFFSET < MAX_BUFFER_SIZE,
|
||||
"DATA_BUFFER_OFFSET must be less than MAX_BUFFER_SIZE");
|
||||
static uint8_t _shared_buffer[MAX_BUFFER_SIZE];
|
||||
/**
|
||||
* @brief used in generic stream io
|
||||
*/
|
||||
static std::span<uint8_t> shared_buffer = std::span{_shared_buffer};
|
||||
/**
|
||||
* @brief only used in read/write buffer io
|
||||
*/
|
||||
static std::span<uint8_t> data_buffer = std::span{_shared_buffer + DATA_BUFFER_OFFSET, MAX_BUFFER_SIZE - DATA_BUFFER_OFFSET};
|
||||
}
|
||||
|
||||
|
||||
static constexpr auto verify_statuses = [](std::span<const uint8_t> statuses) {
|
||||
uint8_t i = 0;
|
||||
for (const auto st : statuses) {
|
||||
if (const auto err = status_to_err(st); err != error::OK) {
|
||||
ESP_LOGE(TAG, "failed to verify status 0x%02x at byte %u", st, i);
|
||||
return err;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return error::OK;
|
||||
};
|
||||
|
||||
std::span<uint8_t> mut_shared_buffer() {
|
||||
return std::span{details::shared_buffer};
|
||||
}
|
||||
|
||||
void init() {
|
||||
if (details::is_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure SPI bus
|
||||
spi_bus_config_t bus_config = {
|
||||
.mosi_io_num = MOSI_PIN,
|
||||
.miso_io_num = MISO_PIN,
|
||||
.sclk_io_num = SCLK_PIN,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.max_transfer_sz = 0,
|
||||
.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS,
|
||||
.intr_flags = 0,
|
||||
};
|
||||
|
||||
// Configure SPI device
|
||||
// https://github.com/nopnop2002/esp-idf-sx126x/blob/main/components/ra01s/ra01s.c
|
||||
//
|
||||
// SPI_DEVICE_HALFDUPLEX flag will turn the device into half-duplex
|
||||
// See also the phase
|
||||
// https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32/api-reference/peripherals/spi_master.html#spi-transactions
|
||||
spi_device_interface_config_t dev_config = {
|
||||
.command_bits = 8,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
.duty_cycle_pos = 128,
|
||||
.clock_speed_hz = 10'000'000,
|
||||
.spics_io_num = CS_PIN,
|
||||
.flags = SPI_DEVICE_NO_DUMMY,
|
||||
.queue_size = 1,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus_config, SPI_DMA_CH_AUTO));
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev_config, &details::spi_device));
|
||||
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << BUSY_PIN),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
details::is_initialized = true;
|
||||
}
|
||||
|
||||
// https://docs.espressif.com/projects/esp-idf/en/release-v5.3/esp32/api-reference/peripherals/spi_master.html
|
||||
// https://github.com/espressif/arduino-esp32/blob/5ba4c21a990f46b61ae8c7913b81e15064b2a8ef/cores/esp32/esp32-hal-spi.c#L836-L843
|
||||
// https://github.com/espressif/esp-idf/issues/5223
|
||||
|
||||
/*!
|
||||
\brief wait for busy pin to go down
|
||||
\return true if the pin goes down, return false if timeout
|
||||
\note The LLCC68 uses the pin BUSY to indicate the status of the chip and
|
||||
its ability (or not) to receive another command while internal processing
|
||||
occurs. Prior to executing one of the generic functions, it is thus
|
||||
necessary to check the status of BUSY to make sure the chip is in a state
|
||||
where it can process another function.
|
||||
*/
|
||||
inline bool wait_for_not_busy(const size_t timeout_ms) {
|
||||
if constexpr (BUSY_PIN == GPIO_NUM_NC) {
|
||||
app::utils::delay_ms(timeout_ms);
|
||||
return true;
|
||||
} else {
|
||||
const auto io_inst = app::utils::Instant<>{};
|
||||
while (hal::gpio::digital_read(BUSY_PIN) == hal::gpio::HIGH) {
|
||||
if (io_inst.elapsed_ms() > timeout_ms) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The communication with the LLCC68 is organized around generic functions which
|
||||
// allow the user to control the device behavior. Each function is based on an
|
||||
// Operational Command (refer throughout this document as “Opcode”), which is
|
||||
// then followed by a set of parameters. The LLCC68 use the BUSY pin to indicate
|
||||
// the status of the chip. In the following chapters, it is assumed that host
|
||||
// micro-controller has an SPI and access to it via spi.write(data). Data is an
|
||||
// 8-bit word. The SPI chip select is defined by NSS, active low.
|
||||
|
||||
|
||||
// Through the SPI interface, the host can issue commands to the chip or access
|
||||
// the data memory space to directly retrieve or write data. In normal
|
||||
// operation, a reduced number of direct data write operations is required
|
||||
// except when accessing the data buffer. The user interacts with the circuit
|
||||
// through an API (instruction set).
|
||||
|
||||
/*! READ */
|
||||
|
||||
Result<Unit, error_t>
|
||||
read_stream(uint8_t cmd, std::span<uint8_t> data, const size_t timeout_ms) {
|
||||
if (not wait_for_not_busy(timeout_ms)) {
|
||||
return ue_t{error::TIMEOUT};
|
||||
}
|
||||
|
||||
if (data.size() > MAX_BUFFER_SIZE - 1) {
|
||||
return ue_t{error::INVALID_SIZE};
|
||||
}
|
||||
|
||||
auto rx_buffer = details::shared_buffer.subspan(0, data.size() + 1);
|
||||
|
||||
spi_transaction_ext_t transaction;
|
||||
transaction = spi_transaction_ext_t{
|
||||
.base = {
|
||||
.flags = SPI_TRANS_VARIABLE_CMD,
|
||||
.cmd = cmd,
|
||||
.length = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.tx_buffer = nullptr,
|
||||
.rx_buffer = rx_buffer.data(),
|
||||
},
|
||||
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
};
|
||||
esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err);
|
||||
return ue_t{error::FAILED};
|
||||
}
|
||||
|
||||
auto status = rx_buffer[0];
|
||||
if (const auto err = status_to_err(status); err != error::OK) {
|
||||
return ue_t{err};
|
||||
}
|
||||
|
||||
std::copy(rx_buffer.begin() + 1, rx_buffer.end(), data.begin());
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
/*! WRITE */
|
||||
|
||||
Result<Unit, error_t>
|
||||
write_stream(uint8_t cmd, std::span<const uint8_t> data, const size_t timeout_ms) {
|
||||
if (not wait_for_not_busy(timeout_ms)) {
|
||||
return ue_t{error::TIMEOUT};
|
||||
}
|
||||
|
||||
if (data.size() > MAX_BUFFER_SIZE) {
|
||||
return ue_t{error::INVALID_SIZE};
|
||||
}
|
||||
|
||||
const auto rx_buffer = details::shared_buffer.subspan(0, data.size());
|
||||
spi_transaction_ext_t transaction;
|
||||
transaction = spi_transaction_ext_t{
|
||||
.base = {
|
||||
.flags = SPI_TRANS_VARIABLE_CMD,
|
||||
.cmd = cmd,
|
||||
.length = static_cast<size_t>(data.size() * 8),
|
||||
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.tx_buffer = data.data(),
|
||||
.rx_buffer = rx_buffer.data(),
|
||||
},
|
||||
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
};
|
||||
esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err);
|
||||
return ue_t{error::FAILED};
|
||||
}
|
||||
|
||||
if (const auto err = verify_statuses(rx_buffer); err != error::OK) {
|
||||
return ue_t{err};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
/*! READ REGISTER */
|
||||
|
||||
Result<Unit, error_t>
|
||||
read_register(uint16_t reg, std::span<uint8_t> data, const size_t timeout_ms) {
|
||||
if (not wait_for_not_busy(timeout_ms)) {
|
||||
return ue_t{error::TIMEOUT};
|
||||
}
|
||||
|
||||
if (data.size() > MAX_BUFFER_SIZE - 1) {
|
||||
return ue_t{error::INVALID_SIZE};
|
||||
}
|
||||
|
||||
// Note that the host has to send an NOP after sending the 2
|
||||
// bytes of address to start receiving data bytes on the next NOP sent.
|
||||
//
|
||||
// NOTE: I'm not sure if ESP32 would send 0x00 (NOP) after MOSI phase
|
||||
// if the tx_buffer is nullptr.
|
||||
spi_transaction_ext_t transaction;
|
||||
|
||||
auto rx_buffer = details::shared_buffer.subspan(0, data.size() + 1);
|
||||
transaction = spi_transaction_ext_t{
|
||||
.base = {
|
||||
.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR,
|
||||
.cmd = SPI_READ_COMMAND,
|
||||
.addr = reg,
|
||||
.length = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.tx_buffer = nullptr,
|
||||
.rx_buffer = rx_buffer.data(),
|
||||
},
|
||||
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
|
||||
.address_bits = static_cast<uint8_t>(SPI_REGISTER_ADDR_BIT_SIZE),
|
||||
.dummy_bits = 0,
|
||||
};
|
||||
esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err);
|
||||
return ue_t{error::FAILED};
|
||||
}
|
||||
|
||||
auto status = rx_buffer[0];
|
||||
if (const auto err = status_to_err(status); err != error::OK) {
|
||||
return ue_t{err};
|
||||
}
|
||||
std::copy(rx_buffer.begin() + 1, rx_buffer.end(), data.begin());
|
||||
return {};
|
||||
}
|
||||
|
||||
/*! WRITE REGISTER */
|
||||
|
||||
Result<Unit, error_t>
|
||||
write_register(uint16_t offset, std::span<const uint8_t> data, const size_t timeout_ms) {
|
||||
if (not wait_for_not_busy(timeout_ms)) {
|
||||
return ue_t{error::TIMEOUT};
|
||||
}
|
||||
|
||||
if (data.size() > MAX_BUFFER_SIZE) {
|
||||
return ue_t{error::INVALID_SIZE};
|
||||
}
|
||||
|
||||
auto rx_buffer = details::shared_buffer.subspan(0, data.size());
|
||||
|
||||
spi_transaction_ext_t transaction;
|
||||
transaction = spi_transaction_ext_t{
|
||||
.base = {
|
||||
.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR,
|
||||
.cmd = SPI_WRITE_COMMAND,
|
||||
.addr = offset,
|
||||
.length = static_cast<size_t>(data.size() * 8),
|
||||
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.tx_buffer = data.data(),
|
||||
.rx_buffer = rx_buffer.data(),
|
||||
},
|
||||
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
|
||||
.address_bits = static_cast<uint8_t>(SPI_REGISTER_ADDR_BIT_SIZE),
|
||||
.dummy_bits = 0,
|
||||
};
|
||||
|
||||
esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err);
|
||||
return ue_t{error::FAILED};
|
||||
}
|
||||
|
||||
if (const auto err = verify_statuses(rx_buffer); err != error::OK) {
|
||||
return ue_t{err};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
/*! READ BUFFER */
|
||||
Result<std::span<const uint8_t>, error_t>
|
||||
read_buffer(uint8_t offset, uint8_t size, const size_t timeout_ms) {
|
||||
const auto size_required = static_cast<size_t>(size) + 1;
|
||||
if (size_required > details::data_buffer.size()) {
|
||||
return ue_t{error::INVALID_SIZE};
|
||||
}
|
||||
|
||||
auto rx_buffer = details::data_buffer.subspan(0, size_required);
|
||||
|
||||
spi_transaction_ext_t transaction;
|
||||
transaction = spi_transaction_ext_t{
|
||||
.base = {
|
||||
.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR,
|
||||
.cmd = SPI_READ_BUFFER_CMD,
|
||||
.addr = offset,
|
||||
.length = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.tx_buffer = nullptr,
|
||||
.rx_buffer = rx_buffer.data(),
|
||||
},
|
||||
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
|
||||
.address_bits = static_cast<uint8_t>(SPI_BUFFER_OFFSET_BIT_SIZE),
|
||||
.dummy_bits = 0,
|
||||
};
|
||||
esp_err_t err = spi_device_polling_transmit(details::spi_device, &transaction.base);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err);
|
||||
return ue_t{error::FAILED};
|
||||
}
|
||||
|
||||
const auto status = rx_buffer[0];
|
||||
if (const auto err = status_to_err(status); err != error::OK) {
|
||||
return ue_t{err};
|
||||
}
|
||||
|
||||
return std::span{rx_buffer.begin() + 1, rx_buffer.end()};
|
||||
};
|
||||
|
||||
/*! WRITE BUFFER */
|
||||
|
||||
Result<Unit, error_t>
|
||||
write_buffer(uint8_t offset, std::span<const uint8_t> data_from_host, const size_t timeout_ms) {
|
||||
if (not wait_for_not_busy(timeout_ms)) {
|
||||
return ue_t{error::TIMEOUT};
|
||||
}
|
||||
|
||||
if (data_from_host.size() > details::data_buffer.size()) {
|
||||
return ue_t{error::INVALID_SIZE};
|
||||
}
|
||||
auto rx_buffer = details::data_buffer.subspan(0, data_from_host.size());
|
||||
|
||||
spi_transaction_ext_t transaction;
|
||||
transaction = spi_transaction_ext_t{
|
||||
.base = {
|
||||
.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR,
|
||||
.cmd = SPI_WRITE_BUFFER_CMD,
|
||||
.addr = offset,
|
||||
.length = static_cast<size_t>(data_from_host.size() * 8),
|
||||
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
|
||||
.tx_buffer = data_from_host.data(),
|
||||
.rx_buffer = rx_buffer.data(),
|
||||
},
|
||||
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
|
||||
.address_bits = static_cast<uint8_t>(SPI_BUFFER_OFFSET_BIT_SIZE),
|
||||
.dummy_bits = 0,
|
||||
};
|
||||
|
||||
esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err);
|
||||
return ue_t{error::FAILED};
|
||||
}
|
||||
|
||||
if (const auto err = verify_statuses(rx_buffer); err != error::OK) {
|
||||
return ue_t{err};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
36
src/llcc68.cpp
Normal file
36
src/llcc68.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Created by Kurosu Chan on 2024/1/17.
|
||||
//
|
||||
#include "llcc68.hpp"
|
||||
#include "driver/gpio.h"
|
||||
#include "app_const_llcc68.hpp"
|
||||
|
||||
constexpr auto ESP_INTR_FLAG_DEFAULT = 0;
|
||||
|
||||
namespace app::driver::llcc68 {
|
||||
namespace details {
|
||||
uint32_t __tcxo_delay__;
|
||||
transmit_state_t __tx_state__;
|
||||
// TODO: use freeRTOS queue to handle received data
|
||||
bool __dio_flag__ = false;
|
||||
}
|
||||
|
||||
void init_exti() {
|
||||
constexpr auto isr = [](void *param) {
|
||||
details::__dio_flag__ = true;
|
||||
};
|
||||
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << DIO2_PIN),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_POSEDGE,
|
||||
};
|
||||
// https://github.com/espressif/esp-idf/blob/v5.3.2/examples/peripherals/gpio/generic_gpio/main/gpio_example_main.c
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
|
||||
gpio_isr_handler_add(DIO2_PIN, isr, nullptr);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user