This commit is contained in:
2025-05-14 12:17:06 +08:00
commit d24d9d5dda
16 changed files with 5955 additions and 0 deletions

413
src/hal_spi.cpp Normal file
View 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
View 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);
}
}