mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-17 23:55:04 +00:00
Adds sub-GHz radio support to Tactility as a first-class kernel device type, plus a driver for the Semtech SX1262. Continues the radio work discussed in #342, brought up to the current kernel driver model (rather than the deprecated tt::hal layer the earlier prototype targeted).
176 lines
6.3 KiB
C++
176 lines
6.3 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#pragma once
|
|
|
|
#include <tactility/concurrent/recursive_mutex.h>
|
|
#include <tactility/concurrent/thread.h>
|
|
#include <tactility/drivers/lora.h>
|
|
#include <tactility/error.h>
|
|
#include <tactility/freertos/event_groups.h>
|
|
|
|
#include <driver/gpio.h>
|
|
#include <driver/spi_master.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <vector>
|
|
|
|
struct Device;
|
|
struct GpioDescriptor;
|
|
|
|
/**
|
|
* SX1262 radio engine: owns the radio thread, the TX queue and the callback lists.
|
|
* The public methods are thread-safe. Callbacks are invoked on a snapshot of the list with
|
|
* the internal mutex released, either from the radio thread (RX, TX progress, state) or from
|
|
* the caller of transmit() (QUEUED).
|
|
*
|
|
* The RadioLib types live behind the RadioParts indirection: RadioLib declares a global
|
|
* `class Module` that collides with the kernel's `struct Module` when both are visible
|
|
* in the same translation unit, so RadioLib headers must not leak out of the implementation.
|
|
*/
|
|
class Sx1262Radio final {
|
|
public:
|
|
struct Settings {
|
|
/** The kernel device, passed to callbacks */
|
|
Device* device;
|
|
/** The parent SPI controller device, for the SPI controller bus lock */
|
|
Device* spi_controller;
|
|
spi_host_device_t spi_host;
|
|
int spi_frequency_hz;
|
|
// CS/RESET/BUSY are native SoC GPIO numbers: the RadioLib HAL drives them directly
|
|
// through ESP-IDF, so they can't sit behind an IO expander (unlike enable/antenna-select,
|
|
// which the driver-registration layer resolves through the GPIO descriptor API).
|
|
gpio_num_t pin_cs;
|
|
gpio_num_t pin_reset;
|
|
gpio_num_t pin_busy;
|
|
/** DIO1 IRQ line, owned by the driver. The radio thread arms it as a
|
|
* HIGH_LEVEL one-shot via the GPIO descriptor callback API. */
|
|
struct GpioDescriptor* dio1;
|
|
float tcxo_voltage;
|
|
bool use_regulator_ldo;
|
|
bool dio2_rf_switch;
|
|
};
|
|
|
|
private:
|
|
struct RadioParts;
|
|
|
|
struct TxItem {
|
|
LoraTxId id = 0;
|
|
std::vector<uint8_t> data;
|
|
};
|
|
|
|
template<typename Callback>
|
|
struct CallbackEntry {
|
|
void* context;
|
|
Callback callback;
|
|
};
|
|
|
|
const Settings settings;
|
|
RadioParts* parts;
|
|
mutable RecursiveMutex mutex = {};
|
|
EventGroupHandle_t events = nullptr;
|
|
|
|
Thread* thread = nullptr;
|
|
bool threadInterrupted = false;
|
|
|
|
enum LoraRadioState state = LORA_RADIO_STATE_OFF;
|
|
enum LoraModulation modulation = LORA_MODULATION_NONE;
|
|
|
|
std::deque<TxItem> txQueue;
|
|
TxItem currentTx;
|
|
LoraTxId lastTxId = 0;
|
|
|
|
std::vector<CallbackEntry<LoraStateCallback>> stateCallbacks;
|
|
std::vector<CallbackEntry<LoraRxCallback>> rxCallbacks;
|
|
std::vector<CallbackEntry<LoraTxCallback>> txCallbacks;
|
|
|
|
// Parameter store, applied on the next doBegin(). Frequencies/rates are held in base SI
|
|
// units (Hz, bit/s) and converted to RadioLib's MHz/kHz/kbps floats in doBegin().
|
|
int8_t power = -9;
|
|
int32_t frequency = 150000000; // Hz
|
|
int32_t bandwidth = 0; // Hz
|
|
uint8_t spreadingFactor = 0;
|
|
uint8_t codingRate = 0;
|
|
uint8_t syncWord = 0;
|
|
uint16_t preambleLength = 0;
|
|
int32_t bitRate = 0; // bit/s
|
|
int32_t frequencyDeviation = 0; // Hz
|
|
bool narrowGrid = false;
|
|
bool boostedGain = false;
|
|
// PA over-current protection limit in mA. Default matches RadioLib's fail-safe 60 mA,
|
|
// which caps output below +22 dBm; a board-aware consumer can raise it (up to 140 mA).
|
|
int32_t currentLimit = 60; // mA
|
|
|
|
static void dio1Isr(void* context);
|
|
static int32_t threadMainStatic(void* context);
|
|
|
|
void lock() const { recursive_mutex_lock(&mutex); }
|
|
void unlock() const { recursive_mutex_unlock(&mutex); }
|
|
|
|
bool isThreadInterrupted() const;
|
|
int32_t threadMain();
|
|
|
|
void setState(enum LoraRadioState newState);
|
|
void publishRx(const struct LoraRxPacket& packet);
|
|
void publishTx(LoraTxId id, enum LoraTransmissionState txState);
|
|
|
|
size_t getTxQueueSize() const;
|
|
TxItem popNextQueuedTx();
|
|
|
|
void registerDio1Isr();
|
|
void unregisterDio1Isr();
|
|
|
|
error_t setBaseParameter(enum LoraParameter parameter, int32_t value);
|
|
error_t setLoraParameter(enum LoraParameter parameter, int32_t value);
|
|
error_t setFskParameter(enum LoraParameter parameter, int32_t value);
|
|
error_t setLrFhssParameter(enum LoraParameter parameter, int32_t value);
|
|
error_t getBaseParameter(enum LoraParameter parameter, int32_t* value) const;
|
|
error_t getLoraParameter(enum LoraParameter parameter, int32_t* value) const;
|
|
error_t getFskParameter(enum LoraParameter parameter, int32_t* value) const;
|
|
error_t getLrFhssParameter(enum LoraParameter parameter, int32_t* value) const;
|
|
|
|
int doBegin(enum LoraModulation beginModulation);
|
|
void doEnd();
|
|
void doTransmit();
|
|
bool doListen();
|
|
void doReceive();
|
|
|
|
public:
|
|
explicit Sx1262Radio(const Settings& settings);
|
|
~Sx1262Radio();
|
|
|
|
/**
|
|
* Verify a live SX1262 responds on the wired pins, using only GPIO (no SPI traffic):
|
|
* pulse NRESET and expect the chip to drive BUSY low once it reaches standby.
|
|
* @return ERROR_NONE when the chip responded
|
|
*/
|
|
error_t probe() const;
|
|
|
|
enum LoraRadioState getState() const;
|
|
error_t setEnabled(bool enabled);
|
|
error_t setModulation(enum LoraModulation newModulation);
|
|
enum LoraModulation getModulation() const;
|
|
|
|
bool canTransmit(enum LoraModulation withModulation) const {
|
|
return (withModulation == LORA_MODULATION_FSK) ||
|
|
(withModulation == LORA_MODULATION_LORA) ||
|
|
(withModulation == LORA_MODULATION_LR_FHSS);
|
|
}
|
|
|
|
bool canReceive(enum LoraModulation withModulation) const {
|
|
return (withModulation == LORA_MODULATION_FSK) || (withModulation == LORA_MODULATION_LORA);
|
|
}
|
|
|
|
error_t setParameter(enum LoraParameter parameter, int32_t value);
|
|
error_t getParameter(enum LoraParameter parameter, int32_t* value) const;
|
|
|
|
error_t transmit(const uint8_t* data, size_t length, LoraTxId* id);
|
|
|
|
error_t addRxCallback(void* context, LoraRxCallback callback);
|
|
error_t removeRxCallback(LoraRxCallback callback);
|
|
error_t addStateCallback(void* context, LoraStateCallback callback);
|
|
error_t removeStateCallback(LoraStateCallback callback);
|
|
error_t addTxCallback(void* context, LoraTxCallback callback);
|
|
error_t removeTxCallback(LoraTxCallback callback);
|
|
};
|