Compare commits

..

26 Commits

Author SHA1 Message Date
bcbc7c6776 fix: change snr_pkt type from uint8_t to int8_t for correct SNR representation 2025-09-18 14:44:02 +08:00
536214241c feat: update set_packet_type_lora function to remove parameter for LORA packet type configuration 2025-08-25 18:07:41 +08:00
3bcc5bf273 feat: update default power setting in tx_params_t structure to TX_POWER_AUTO 2025-08-18 16:11:22 +08:00
70a51cdb5c feat: introduce pa_setting_t structure for power amplifier configuration 2025-08-15 16:30:23 +08:00
42e6c4d213 feat: add tx_params_t structure and related functions for transmission parameter management 2025-08-14 18:20:19 +08:00
d80eeb7c3f feat: enhance error handling and logging in calibration functions 2025-08-12 12:10:04 +08:00
5d629415f6 feat: add Kconfig options for SPI status check and radio calibration 2025-08-12 11:59:49 +08:00
cecca5b711 feat: update clear_irq_status to accept IRQ mask for improved interrupt handling 2025-08-08 15:31:21 +08:00
59ba0f81d8 feat: add frequency conversion functions for RF frequency management 2025-08-08 12:26:30 +08:00
fc9b06aec0 x 2025-08-08 12:16:21 +08:00
333d2f5874 feat: update LLCC68 documentation and replace outdated PDF with the latest version 2025-08-08 12:04:32 +08:00
92130d4a7a feat: add TX/RX pin state management and initialize pins correctly 2025-08-08 11:28:12 +08:00
0bdd03aedc feat: improve error handling for radio operations and streamline logging 2025-08-08 10:43:50 +08:00
1237e5ea48 refactor: update TAG constants and improve logging format in SPI implementation 2025-08-07 19:25:16 +08:00
edcdc779d9 refactor: remove unnecessary reset call in begin function 2025-08-07 16:11:25 +08:00
059070e146 refactor: update Instant usage and adjust regulator configuration 2025-08-07 16:00:31 +08:00
7637906efe feat: enhance error handling and add device error reporting for radio operations 2025-08-07 15:36:20 +08:00
72299b62ce feat: add option to disable radio calibration and update status error handling 2025-08-07 14:51:57 +08:00
c4a61d2708 feat: add TX continuous wave mode and update regulator mode handling 2025-08-07 12:01:12 +08:00
249c125447 refactor: adjust SPI transaction queue size and improve read_stream validation 2025-08-07 11:06:03 +08:00
ffcaba10fd refactor: remove mut_shared_buffer function and streamline buffer handling in SPI transactions 2025-08-07 00:26:53 +08:00
6269bb99c4 feat: add spi_config_t structure and update init function to accept configuration 2025-08-06 23:54:13 +08:00
1d31faef01 refactor: unify string conversion functions for LoRaBandwidth and LoRaCodingRate 2025-08-04 15:37:04 +08:00
04efb1e40b feat: add irq_status_t structure and update get_irq_status to use it
feat: implement string conversion functions for CommandStatus and ChipMode enums
2025-08-04 15:02:18 +08:00
0653fc2e3d fix: increase delay duration in reset function and adjust data reading logic 2025-08-04 11:56:47 +08:00
fca2e8cc47 refactor: improve function signatures and type safety in llcc68 definitions
- Removed unnecessary include of <cstddef>.
- Updated function signatures to use trailing return types and added [[nodiscard]] attribute for better safety and clarity.
- Standardized float literals to use uppercase 'F' for consistency.
- Simplified the valid_ldr_optimize function for improved readability.
2025-06-05 16:31:44 +08:00
10 changed files with 889 additions and 405 deletions

View File

@ -9,8 +9,12 @@ idf_component_register(
app_constant
)
# This option would make LLCC68 ignore `SPI_CMD_INVALID` error.
option(APP_SPI_DISABLE_INVALID_STATUS_CHECK "Disable invalid status check" ON)
if (APP_SPI_DISABLE_INVALID_STATUS_CHECK)
# Map ESP-IDF Kconfig options to existing compile-time macros used in the codebase.
if (CONFIG_LLCC68_SPI_IGNORE_INVALID_STATUS_CHECK)
target_compile_definitions(${COMPONENT_LIB} PUBLIC APP_SPI_DISABLE_INVALID_STATUS_CHECK)
endif()
if (CONFIG_LLCC68_RADIO_DISABLE_CALIBRATION)
target_compile_definitions(${COMPONENT_LIB} PUBLIC APP_RADIO_DISABLE_CALIBRATION)
endif()

19
Kconfig Normal file
View File

@ -0,0 +1,19 @@
menu "LLCC68 options"
config LLCC68_SPI_IGNORE_INVALID_STATUS_CHECK
bool "Ignore SPI_CMD_INVALID status in SPI driver"
default n
help
When enabled, the LLCC68 SPI HAL will ignore the SPI_CMD_INVALID
status returned from the device. This maps to the compile definition
APP_SPI_DISABLE_INVALID_STATUS_CHECK used in the component sources.
config LLCC68_RADIO_DISABLE_CALIBRATION
bool "Disable radio calibration"
default y
help
When enabled, radio calibration routines are disabled to save time
or avoid issues on certain hardware. This maps to the compile
definition APP_RADIO_DISABLE_CALIBRATION used in the component.
endmenu

View File

@ -7,6 +7,15 @@ Internal buffer, C++ 20 features is used.
See [app_const_llcc68_template.hpp](inc/template/app_const_llcc68_template.hpp)
Configuration via menuconfig:
- LLCC68 options → Ignore SPI_CMD_INVALID status in SPI driver
- Maps to APP_SPI_DISABLE_INVALID_STATUS_CHECK
- Default: disabled (N)
- LLCC68 options → Disable radio calibration
- Maps to APP_RADIO_DISABLE_CALIBRATION
- Default: enabled (Y)
## TODO
- [ ] Long-Range Frequency Hopping Spread Spectrum (LR-FHSS)

BIN
docs/DS_LLCC68 V1-1.pdf LFS Normal file

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,8 @@
#include <array>
#include <tuple>
#define APP_ERR_TBL_IT(err) {err, #err}
#define APP_ERR_TBL_IT(err) \
{ err, #err }
namespace app::driver::hal::error {
using t = int;
@ -31,16 +32,18 @@ constexpr t GENERIC_ERR_BASE = 0x120;
constexpr t AGAIN = GENERIC_ERR_BASE + 1; /*!< Operation failed, retry */
constexpr t BUSY = GENERIC_ERR_BASE + 2; /*!< Busy */
constexpr t SPI_ERR_BASE = 0x1'2000;
constexpr t RADIO_TRANS_ERR_BASE = 0x1'2000;
// A transaction from host took too long to complete and triggered an internal watchdog.
// The watchdog mechanism can be disabled by host; it is meant to ensure all outcomes are flagged to the host MCU.
constexpr t SPI_TIMEOUT = SPI_ERR_BASE + 2;
constexpr t RADIO_TRANS_TIMEOUT = RADIO_TRANS_ERR_BASE + 2;
// Processor was unable to process command either because of an invalid opcode or
// because an incorrect number of parameters has been provided.
constexpr t SPI_CMD_INVALID = SPI_ERR_BASE + 3;
constexpr t RADIO_TRANS_CMD_PROC_ERR = RADIO_TRANS_ERR_BASE + 3;
// The command was successfully processed, however the chip could not execute the command;
// for instance it was unable to enter the specified device mode or send the requested data
constexpr t SPI_CMD_FAILED = SPI_ERR_BASE + 4;
constexpr t RADIO_TRANS_FAIL_TO_EXE = RADIO_TRANS_ERR_BASE + 4;
/*!< Radio is in an expected state (not necessary an error) */
constexpr t RADIO_TRANS_INVALID_RADIO_STATE = RADIO_TRANS_ERR_BASE + 5;
constexpr t RADIO_ERR_BASE = 0x1'3000;
constexpr t RADIO_CHIP_NOT_FOUND = RADIO_ERR_BASE + 1;
@ -73,9 +76,10 @@ constexpr auto error_table = std::to_array<std::tuple<t, const char *>>(
APP_ERR_TBL_IT(AGAIN),
APP_ERR_TBL_IT(BUSY),
APP_ERR_TBL_IT(SPI_TIMEOUT),
APP_ERR_TBL_IT(SPI_CMD_INVALID),
APP_ERR_TBL_IT(SPI_CMD_FAILED),
APP_ERR_TBL_IT(RADIO_TRANS_TIMEOUT),
APP_ERR_TBL_IT(RADIO_TRANS_CMD_PROC_ERR),
APP_ERR_TBL_IT(RADIO_TRANS_FAIL_TO_EXE),
APP_ERR_TBL_IT(RADIO_TRANS_INVALID_RADIO_STATE),
APP_ERR_TBL_IT(RADIO_CHIP_NOT_FOUND),
APP_ERR_TBL_IT(RADIO_INVALID_TCXO_VOLTAGE),

View File

@ -5,6 +5,7 @@
#define B7431E31_4075_4B09_B4B3_EAD0234EFB40
#include <cstdint>
#include <span>
#include "hal/spi_types.h"
#include "radiolib_definitions.hpp"
#include "llcc68_definitions.hpp"
#include "utils/app_result.hpp"
@ -16,7 +17,7 @@ namespace app::driver::hal::spi {
template <typename T, typename E>
using Result = app::utils::Result<T, E>;
using Unit = app::utils::Unit;
constexpr auto TAG = "spi";
constexpr auto TAG = "app_spi";
constexpr auto MOSI_PIN = llcc68::MOSI_PIN;
constexpr auto MISO_PIN = llcc68::MISO_PIN;
@ -34,30 +35,49 @@ constexpr uint8_t SPI_READ_BUFFER_CMD = RADIOLIB_SX126X_CMD_READ_BUFFER;
constexpr size_t DEFAULT_TIMEOUT_MS = 1000;
std::span<uint8_t> mut_shared_buffer();
struct spi_config_t {
spi_host_device_t host_id;
int clock_speed_hz;
};
/*!
\brief Initialize the SPI interface.
*/
void init();
void init(spi_config_t config = {SPI2_HOST, 4'000'000});
inline error_t status_to_err(const uint8_t status) {
const auto st = *reinterpret_cast<const llcc68::status_t *>(&status);
switch (st.command_status) {
case llcc68::CommandStatus::COMMAND_TIMEOUT:
return error::SPI_TIMEOUT;
case llcc68::CommandStatus::FAILURE_TO_EXECUTE_COMMAND:
return error::SPI_CMD_FAILED;
case llcc68::CommandStatus::COMMAND_PROCESSING_ERROR:
const auto st = *reinterpret_cast<const llcc68::status_t *>(&status);
error_t status_ok = [](llcc68::CommandStatus st) {
switch (st) {
case llcc68::CommandStatus::COMMAND_TIMEOUT:
return error::RADIO_TRANS_TIMEOUT;
case llcc68::CommandStatus::FAILURE_TO_EXECUTE_COMMAND:
return error::RADIO_TRANS_FAIL_TO_EXE;
case llcc68::CommandStatus::COMMAND_PROCESSING_ERROR:
#ifdef APP_SPI_DISABLE_INVALID_STATUS_CHECK
return error::OK;
return error::OK;
#else
return error::SPI_CMD_INVALID;
return error::RADIO_TRANS_CMD_PROC_ERR;
#endif
default:
return error::OK;
default:
return error::OK;
}
}(st.command_status);
if (status_ok != error::OK) {
return status_ok;
}
error_t chip_mode_usual = [](llcc68::ChipMode mode) {
switch (mode) {
case llcc68::ChipMode::TX:
case llcc68::ChipMode::RX:
case llcc68::ChipMode::STBY_RC:
return error::OK;
default:
return error::RADIO_TRANS_INVALID_RADIO_STATE;
}
}(st.chip_mode);
return chip_mode_usual;
}
/*!

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,9 @@
#ifndef LLCC68_DEFINITIONS_H
#define LLCC68_DEFINITIONS_H
#include <cstddef>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string>
#include <format>
#include <tuple>
@ -28,7 +28,32 @@ enum LoRaBandwidth : uint8_t {
BW_500_0 = 0x06,
};
inline const char *bw_to_string(const LoRaBandwidth &bw) {
// - RampTime Value RampTime (µs)
// - SET_RAMP_10U 0x00 10
// - SET_RAMP_20U 0x01 20
// - SET_RAMP_ 40U 0x02 40
// - SET_RAMP_80U 0x03 80
// - SET_RAMP_200U 0x04 200
// - SET_RAMP_800U 0x05 800
// - SET_RAMP_1700U 0x06 1700
// - SET_RAMP_3400U 0x07 3400
enum TxRampTime : uint8_t {
SET_RAMP_10U = 0x00,
SET_RAMP_20U = 0x01,
SET_RAMP_40U = 0x02,
SET_RAMP_80U = 0x03,
SET_RAMP_200U = 0x04,
SET_RAMP_800U = 0x05,
SET_RAMP_1700U = 0x06,
SET_RAMP_3400U = 0x07,
};
enum RegulatorMode : uint8_t {
REGULATOR_LDO = RADIOLIB_SX126X_REGULATOR_LDO,
REGULATOR_DC_DC = RADIOLIB_SX126X_REGULATOR_DC_DC,
};
inline const char *to_str(LoRaBandwidth bw) {
switch (bw) {
case LoRaBandwidth::BW_7_8:
return "7.8kHz";
@ -62,7 +87,7 @@ enum LoRaCodingRate : uint8_t {
CR_4_8 = 0x04,
};
inline const char *cr_to_string(const LoRaCodingRate &cr) {
inline const char *to_str(LoRaCodingRate cr) {
switch (cr) {
case LoRaCodingRate::CR_4_5:
return "4/5";
@ -112,11 +137,12 @@ struct modulation_params_t {
};
}
std::string to_string() const {
[[nodiscard]]
auto to_string() const -> std::string {
return std::format("mod_params: bw={}, sf={}, cr={}, ldr_optimize={}",
bw_to_string(static_cast<LoRaBandwidth>(bw)),
to_str(static_cast<LoRaBandwidth>(bw)),
sf,
cr_to_string(static_cast<LoRaCodingRate>(cr)),
to_str(static_cast<LoRaCodingRate>(cr)),
ldr_optimize ? "ON" : "OFF");
}
};
@ -137,9 +163,94 @@ struct packet_params_t {
}
};
struct pa_setting_t {
uint8_t pa_duty_cycle;
uint8_t hp_max;
uint8_t device_sel;
static constexpr pa_setting_t Default22dBm() {
return {0x04, 0x07, 0x00};
}
/**
* @note SetTxParams should be set to +22dBm
*/
static constexpr pa_setting_t Default20dBm() {
return {0x03, 0x05, 0x00};
}
/**
* @note SetTxParams should be set to +22dBm
*/
static constexpr pa_setting_t Default17dBm() {
return {0x02, 0x03, 0x00};
}
/**
* @note SetTxParams should be set to +14dBm
*/
static constexpr pa_setting_t Default14dBm() {
return {0x02, 0x02, 0x00};
}
static constexpr pa_setting_t Default() {
return Default22dBm();
}
};
inline const char *to_str(llcc68::TxRampTime ramp_time) {
switch (ramp_time) {
case llcc68::SET_RAMP_10U:
return "10us";
case llcc68::SET_RAMP_20U:
return "20us";
case llcc68::SET_RAMP_40U:
return "40us";
case llcc68::SET_RAMP_80U:
return "80us";
case llcc68::SET_RAMP_200U:
return "200us";
case llcc68::SET_RAMP_800U:
return "800us";
case llcc68::SET_RAMP_1700U:
return "1700us";
case llcc68::SET_RAMP_3400U:
return "3400us";
default:
return "Unknown";
}
}
struct tx_params_t {
/**
* @brief magic number for automatic max TX power depending on
* chip type
*/
static constexpr auto TX_POWER_AUTO = 0x7f;
static constexpr auto MAX_POWER_HIGH_PA = 22;
static constexpr auto MAX_POWER_LOW_PA = 14;
int8_t power;
TxRampTime ramp_time;
static tx_params_t Default() {
return tx_params_t{
.power = TX_POWER_AUTO,
.ramp_time = SET_RAMP_200U,
};
}
[[nodiscard]]
std::string to_string() const {
if (power == TX_POWER_AUTO) {
return "tx_params: power=auto, ramp_time=" + std::string(to_str(ramp_time));
}
return std::format("tx_params: power={}, ramp_time={}",
power,
to_str(ramp_time));
}
};
struct lora_parameters_t {
modulation_params_t mod_params;
packet_params_t packet_params;
tx_params_t tx_params;
freq_t frequency;
uint8_t sync_word;
@ -147,6 +258,7 @@ struct lora_parameters_t {
return lora_parameters_t{
.mod_params = modulation_params_t::Default(),
.packet_params = packet_params_t::Default(),
.tx_params = tx_params_t::Default(),
.frequency = DEFAULT_FREQUENCY,
.sync_word = DEFAULT_SYNC_WORD,
};
@ -225,6 +337,27 @@ enum class CommandStatus : uint8_t {
COMMAND_TX_DONE = 0x06, // 0b110
};
inline const char *to_str(const CommandStatus &status) {
switch (status) {
case CommandStatus::RESERVED:
return "RESERVED";
case CommandStatus::RFU:
return "RFU";
case CommandStatus::DATA_AVAILABLE:
return "DATA_AVAILABLE";
case CommandStatus::COMMAND_TIMEOUT:
return "COMMAND_TIMEOUT";
case CommandStatus::COMMAND_PROCESSING_ERROR:
return "COMMAND_PROCESSING_ERROR";
case CommandStatus::FAILURE_TO_EXECUTE_COMMAND:
return "FAILURE_TO_EXECUTE_COMMAND";
case CommandStatus::COMMAND_TX_DONE:
return "COMMAND_TX_DONE";
default:
return "Unknown";
}
}
enum class ChipMode : uint8_t {
UNUSED = 0x00, // 0b000
RFU = 0x01, // 0b001
@ -235,12 +368,35 @@ enum class ChipMode : uint8_t {
TX = 0x06, // 0b110
};
inline const char *to_str(const ChipMode &mode) {
switch (mode) {
case ChipMode::UNUSED:
return "UNUSED";
case ChipMode::RFU:
return "RFU";
case ChipMode::STBY_RC:
return "STBY_RC";
case ChipMode::STBY_XOSC:
return "STBY_XOSC";
case ChipMode::FS:
return "FS";
case ChipMode::RX:
return "RX";
case ChipMode::TX:
return "TX";
default:
return "Unknown";
}
}
struct __attribute__((packed)) status_t {
// LSB
uint8_t reserved_1 : 1;
CommandStatus command_status : 3;
ChipMode chip_mode : 3;
uint8_t reserved_2 : 1;
// MSB
};
static_assert(sizeof(status_t) == 1);
@ -251,21 +407,24 @@ struct __attribute__((packed)) lora_packet_status_t {
uint8_t rssi_pkt;
// Estimation of SNR on last packet received in twos compliment format multiplied by 4.
// Actual SNR in dB =SnrPkt/4
uint8_t snr_pkt;
int8_t snr_pkt;
// Estimation of RSSI of the LoRa® signal (after despreading) on last packet received.
// Actual Rssi in dB = -SignalRssiPkt/2
uint8_t signal_rssi_pkt;
float rssi_pkt_dbm() const {
return -rssi_pkt / 2.0f;
[[nodiscard]]
auto rssi_pkt_dbm() const -> float {
return -rssi_pkt / 2.0F;
}
float snr_pkt_db() const {
return snr_pkt / 4.0f;
[[nodiscard]]
auto snr_pkt_db() const -> float {
return snr_pkt / 4.0F;
}
float signal_rssi_pkt_dbm() const {
return -signal_rssi_pkt / 2.0f;
[[nodiscard]]
auto signal_rssi_pkt_dbm() const -> float {
return -signal_rssi_pkt / 2.0F;
}
};
@ -296,20 +455,61 @@ union packet_status_t {
};
struct __attribute__((packed)) op_error_t {
bool RC64K_CALIB_ERR : 1; // RC64k calibration failed
bool RC13M_CALIB_ERR : 1; // RC13M calibration failed
bool PLL_CALIB_ERR : 1; // PLL calibration failed
bool ADC_CALIB_ERR : 1; // ADC calibration failed
bool IMG_CALIB_ERR : 1; // IMG calibration failed
bool XOSC_START_ERR : 1; // XOSC failed to start
bool PLL_LOCK_ERR : 1; // PLL failed to lock
uint8_t rfu_1 : 1; // RFU
bool PA_RAMP_ERR : 1; // PA ramping failed
uint8_t rfu_2 : 7; // RFU
};
struct __attribute__((packed)) DeviceErrors {
// LSB
static_assert(sizeof(op_error_t) == 2);
bool RC64K_CALIB_ERR : 1; // [0] RC64k calibration failed
bool RC13M_CALIB_ERR : 1; // [1] RkC13M calibration failed
bool PLL_CALIB_ERR : 1; // [2] PLL calibration failed
bool ADC_CALIB_ERR : 1; // [3] ADC calibration failed
bool IMG_CALIB_ERR : 1; // [4] IMG calibration failed
bool XOSC_START_ERR : 1; // [5] XOSC failed to start
bool PLL_LOCK_ERR : 1; // [6] PLL failed to lock
uint8_t rfu_1 : 1; // [7] RFU
bool PA_RAMP_ERR : 1; // [8] PA ramping failed
uint8_t rfu_2 : 7; // [15:9] RFU
// MSB
[[nodiscard]]
std::string to_string() const {
auto ss = std::stringstream();
ss << "DeviceErrors{";
if (RC64K_CALIB_ERR) {
ss << "RC64K_CALIB_ERR(0) ";
}
if (RC13M_CALIB_ERR) {
ss << "RC13M_CALIB_ERR(1) ";
}
if (PLL_CALIB_ERR) {
ss << "PLL_CALIB_ERR(2) ";
}
if (ADC_CALIB_ERR) {
ss << "ADC_CALIB_ERR(3) ";
}
if (IMG_CALIB_ERR) {
ss << "IMG_CALIB_ERR(4) ";
}
if (XOSC_START_ERR) {
ss << "XOSC_START_ERR(5) ";
}
if (PLL_LOCK_ERR) {
ss << "PLL_LOCK_ERR(6) ";
}
if (PA_RAMP_ERR) {
ss << "PA_RAMP_ERR(8) ";
}
ss << "}";
return ss.str();
}
[[nodiscard]]
bool has_any_error() const {
return RC64K_CALIB_ERR || RC13M_CALIB_ERR || PLL_CALIB_ERR || ADC_CALIB_ERR ||
IMG_CALIB_ERR || XOSC_START_ERR || PLL_LOCK_ERR || PA_RAMP_ERR;
}
};
static_assert(sizeof(DeviceErrors) == 2);
constexpr bool in_range(const auto v, const auto min, const auto max) {
return v >= min && v <= max;
@ -344,10 +544,7 @@ cr_to_ratio(const uint8_t cr) {
}
constexpr bool valid_ldr_optimize(const uint8_t ldr_optimize) {
if (ldr_optimize > RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON) {
return false;
}
return true;
return static_cast<bool>(ldr_optimize <= RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON);
}
constexpr bool valid_bw(const uint8_t bw) {
@ -372,25 +569,25 @@ using bw_t = float;
constexpr bw_t bw_khz(const uint8_t bw) {
switch (bw) {
case RADIOLIB_SX126X_LORA_BW_7_8:
return bw_t{7.8f};
return bw_t{7.8F};
case RADIOLIB_SX126X_LORA_BW_10_4:
return bw_t{10.4f};
return bw_t{10.4F};
case RADIOLIB_SX126X_LORA_BW_15_6:
return bw_t{15.6f};
return bw_t{15.6F};
case RADIOLIB_SX126X_LORA_BW_20_8:
return bw_t{20.8f};
return bw_t{20.8F};
case RADIOLIB_SX126X_LORA_BW_31_25:
return bw_t{31.25f};
return bw_t{31.25F};
case RADIOLIB_SX126X_LORA_BW_41_7:
return bw_t{41.7f};
return bw_t{41.7F};
case RADIOLIB_SX126X_LORA_BW_62_5:
return bw_t{62.5f};
return bw_t{62.5F};
case RADIOLIB_SX126X_LORA_BW_125_0:
return bw_t{125.0f};
return bw_t{125.0F};
case RADIOLIB_SX126X_LORA_BW_250_0:
return bw_t{250.0f};
return bw_t{250.0F};
case RADIOLIB_SX126X_LORA_BW_500_0:
return bw_t{500.0f};
return bw_t{500.0F};
default:
return bw_t{0};
}
@ -412,6 +609,38 @@ constexpr bool valid_sf(const uint8_t bw, const uint8_t sf) {
}
}
constexpr bool valid_tx_power(int8_t power) {
if (power == tx_params_t::TX_POWER_AUTO) {
return true;
}
if (power >= -9 && power <= 22) {
return true;
}
if (power >= -17 && power <= 14) {
return true;
}
return false;
}
constexpr bool valid_ramp_time(const llcc68::TxRampTime ramp_time) {
switch (ramp_time) {
case llcc68::SET_RAMP_10U:
case llcc68::SET_RAMP_20U:
case llcc68::SET_RAMP_40U:
case llcc68::SET_RAMP_80U:
case llcc68::SET_RAMP_200U:
case llcc68::SET_RAMP_800U:
case llcc68::SET_RAMP_1700U:
case llcc68::SET_RAMP_3400U:
return true;
default:
return false;
}
}
constexpr bool valid_freq(const freq_t freq) {
return in_range(freq, freq_t{150.0}, freq_t{960.0});
}

View File

@ -10,41 +10,40 @@
#include "hal_gpio.hpp"
#include "hal_spi.hpp"
#include "utils/app_utils.hpp"
#include "utils/app_instant.hpp"
#include "utils/app_clock_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;
static constexpr uint8_t CMD_BIT_SIZE = 8;
static constexpr uint8_t OFFSET_BIT_SIZE = 8;
static constexpr uint8_t REG_ADDR_BIT_SIZE = 16;
static 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};
static uint8_t _data_buffer[MAX_BUFFER_SIZE];
/**
* @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 std::span<uint8_t> data_buffer = std::span{_data_buffer};
}
/**
* a traditional way to complete an synchronous SPI transaction
*
* @sa https://github.com/nopnop2002/esp-idf-sx126x/blob/bd26fd20b118c6d57d35b0554d8de80378d614a8/components/ra01s/ra01s.c#L123-L161
* @sa https://github.com/IanBurwell/DynamicLRS/blob/c26f7f8dcca0c1b70af0aa6aee3aba3a1652aba6/components/DLRS_LoRadio/radiolib_esp32s3_hal.hpp#L193-L201
*
*/
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);
ESP_LOGE(TAG, "s[%u]=0x%02x", i, st);
return err;
}
i += 1;
@ -52,11 +51,7 @@ static constexpr auto verify_statuses = [](std::span<const uint8_t> statuses) {
return error::OK;
};
std::span<uint8_t> mut_shared_buffer() {
return std::span{details::shared_buffer};
}
void init() {
void init(spi_config_t config) {
if (details::is_initialized) {
return;
}
@ -84,19 +79,19 @@ void init() {
// 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,
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
.duty_cycle_pos = 128,
.clock_speed_hz = 10'000'000,
.clock_speed_hz = config.clock_speed_hz,
.spics_io_num = CS_PIN,
.flags = SPI_DEVICE_NO_DUMMY,
.queue_size = 1,
.queue_size = 4,
};
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));
ESP_ERROR_CHECK(spi_bus_initialize(config.host_id, &bus_config, SPI_DMA_CH_AUTO));
ESP_ERROR_CHECK(spi_bus_add_device(config.host_id, &dev_config, &details::spi_device));
gpio_config_t io_conf = {
@ -128,7 +123,7 @@ inline bool wait_for_not_busy(const size_t timeout_ms) {
app::utils::delay_ms(timeout_ms);
return true;
} else {
const auto io_inst = app::utils::Instant<>{};
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;
@ -155,44 +150,58 @@ inline bool wait_for_not_busy(const size_t timeout_ms) {
/*! READ */
constexpr auto STACK_BUFFER_SIZE = 32;
constexpr auto PADDING = 0xFE;
Result<Unit, error_t>
read_stream(uint8_t cmd, std::span<uint8_t> data, const size_t timeout_ms) {
std::array<uint8_t, STACK_BUFFER_SIZE> _rx_buf;
std::array<uint8_t, STACK_BUFFER_SIZE> _tx_buf;
if (data.empty() or data.size() > STACK_BUFFER_SIZE - 1) {
return ue_t{error::INVALID_SIZE};
}
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 = std::span{_rx_buf}.subspan(0, data.size() + 1);
auto tx_buffer = std::span{_tx_buf}.subspan(0, rx_buffer.size());
std::ranges::fill(tx_buffer, PADDING);
auto rx_buffer = details::shared_buffer.subspan(0, data.size() + 1);
spi_transaction_ext_t transaction;
spi_transaction_ext_t transaction{};
// during the Command and Address phases, the members spi_transaction_t::cmd
// and spi_transaction_t::addr are sent to the bus, nothing is read at this
// time.
transaction = spi_transaction_ext_t{
.base = {
.flags = SPI_TRANS_VARIABLE_CMD,
.cmd = cmd,
.length = static_cast<size_t>(rx_buffer.size() * 8),
.flags = SPI_TRANS_VARIABLE_CMD,
.cmd = cmd,
// total data length, in bits
.length = static_cast<size_t>(rx_buffer.size() * 8),
// total data length received, should be not greater than length in
// full-duplex mode (0 defaults this to the value of length).
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
.tx_buffer = nullptr,
.tx_buffer = tx_buffer.data(),
.rx_buffer = rx_buffer.data(),
},
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
.command_bits = 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);
ESP_LOGE(TAG, "spi transmit: %s (%d)", esp_err_to_name(err), err);
return ue_t{error::FAILED};
}
std::copy(rx_buffer.begin() + 1, rx_buffer.end(), data.begin());
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 {};
}
@ -201,16 +210,18 @@ read_stream(uint8_t cmd, std::span<uint8_t> data, const size_t timeout_ms) {
Result<Unit, error_t>
write_stream(uint8_t cmd, std::span<const uint8_t> data, const size_t timeout_ms) {
std::array<uint8_t, STACK_BUFFER_SIZE> _rx_buf;
if (data.size() > STACK_BUFFER_SIZE - 1) {
return ue_t{error::INVALID_SIZE};
}
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;
const auto rx_buffer = std::span{_rx_buf}.subspan(0, data.size());
spi_transaction_ext_t transaction{};
transaction = spi_transaction_ext_t{
.base = {
.flags = SPI_TRANS_VARIABLE_CMD,
@ -220,13 +231,13 @@ write_stream(uint8_t cmd, std::span<const uint8_t> data, const size_t timeout_ms
.tx_buffer = data.data(),
.rx_buffer = rx_buffer.data(),
},
.command_bits = static_cast<uint8_t>(SPI_CMD_BIT_SIZE),
.command_bits = 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);
ESP_LOGE(TAG, "%s (%d)", esp_err_to_name(err), err);
return ue_t{error::FAILED};
}
@ -242,12 +253,15 @@ write_stream(uint8_t cmd, std::span<const uint8_t> data, const size_t timeout_ms
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};
std::array<uint8_t, STACK_BUFFER_SIZE> _rx_buf;
std::array<uint8_t, STACK_BUFFER_SIZE> _tx_buf;
if (data.size() > STACK_BUFFER_SIZE - 1) {
return ue_t{error::INVALID_SIZE};
}
if (data.size() > MAX_BUFFER_SIZE - 1) {
return ue_t{error::INVALID_SIZE};
if (not wait_for_not_busy(timeout_ms)) {
return ue_t{error::TIMEOUT};
}
// Note that the host has to send an NOP after sending the 2
@ -257,24 +271,27 @@ read_register(uint16_t reg, std::span<uint8_t> data, const size_t timeout_ms) {
// 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,
};
auto rx_buffer = std::span{_rx_buf}.subspan(0, data.size() + 1);
auto tx_buffer = std::span{_tx_buf}.subspan(0, rx_buffer.size());
std::ranges::fill(tx_buffer, PADDING);
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 = tx_buffer.data(),
.rx_buffer = rx_buffer.data(),
},
.command_bits = static_cast<uint8_t>(CMD_BIT_SIZE),
.address_bits = static_cast<uint8_t>(REG_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);
ESP_LOGE(TAG, "%s (%d)", esp_err_to_name(err), err);
return ue_t{error::FAILED};
}
@ -290,15 +307,16 @@ read_register(uint16_t reg, std::span<uint8_t> data, const size_t timeout_ms) {
Result<Unit, error_t>
write_register(uint16_t offset, std::span<const uint8_t> data, const size_t timeout_ms) {
std::array<uint8_t, STACK_BUFFER_SIZE> _rx_buf;
if (data.size() > STACK_BUFFER_SIZE - 1) {
return ue_t{error::INVALID_SIZE};
}
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());
auto rx_buffer = std::span{_rx_buf}.subspan(0, data.size());
spi_transaction_ext_t transaction;
transaction = spi_transaction_ext_t{
@ -311,14 +329,14 @@ write_register(uint16_t offset, std::span<const uint8_t> data, const size_t time
.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),
.command_bits = static_cast<uint8_t>(CMD_BIT_SIZE),
.address_bits = static_cast<uint8_t>(REG_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);
ESP_LOGE(TAG, "spi transmit: %s (%d)", esp_err_to_name(err), err);
return ue_t{error::FAILED};
}
@ -338,7 +356,10 @@ read_buffer(uint8_t offset, uint8_t size, const size_t timeout_ms) {
return ue_t{error::INVALID_SIZE};
}
auto rx_buffer = details::data_buffer.subspan(0, size_required);
auto rx_buffer = details::data_buffer.subspan(0, size_required);
auto _tx_buffer = std::make_unique<uint8_t[]>(rx_buffer.size());
auto tx_buffer = std::span{_tx_buffer.get(), rx_buffer.size()};
std::ranges::fill(tx_buffer, PADDING);
spi_transaction_ext_t transaction;
transaction = spi_transaction_ext_t{
@ -348,21 +369,22 @@ read_buffer(uint8_t offset, uint8_t size, const size_t timeout_ms) {
.addr = offset,
.length = static_cast<size_t>(rx_buffer.size() * 8),
.rxlength = static_cast<size_t>(rx_buffer.size() * 8),
.tx_buffer = nullptr,
.tx_buffer = tx_buffer.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),
.command_bits = static_cast<uint8_t>(CMD_BIT_SIZE),
.address_bits = static_cast<uint8_t>(OFFSET_BIT_SIZE),
.dummy_bits = 0,
};
esp_err_t err = spi_device_polling_transmit(details::spi_device, &transaction.base);
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);
ESP_LOGE(TAG, "%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) {
// FIXME: current hack ignore processing error
if (const auto err = status_to_err(status); err != error::OK && err != error::RADIO_TRANS_CMD_PROC_ERR) {
return ue_t{err};
}
@ -373,13 +395,14 @@ read_buffer(uint8_t offset, uint8_t size, const size_t timeout_ms) {
Result<Unit, error_t>
write_buffer(uint8_t offset, std::span<const uint8_t> data_from_host, const size_t timeout_ms) {
if (data_from_host.size() > details::data_buffer.size()) {
return ue_t{error::INVALID_SIZE};
}
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;
@ -393,14 +416,14 @@ write_buffer(uint8_t offset, std::span<const uint8_t> data_from_host, const size
.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),
.command_bits = static_cast<uint8_t>(CMD_BIT_SIZE),
.address_bits = static_cast<uint8_t>(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);
ESP_LOGE(TAG, "spi transmit: %s (%d)", esp_err_to_name(err), err);
return ue_t{error::FAILED};
}