Radio: Iteration 2 with Sx1262 - TX Update

Not quite as reliable still, but sending works.
This commit is contained in:
Dominic Höglinger 2025-09-17 18:26:57 +02:00
parent 9f05bcf066
commit 04edfa7c99
7 changed files with 243 additions and 49 deletions

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 PwmBacklight driver esp_adc
REQUIRES Tactility esp_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 SX126x PwmBacklight driver esp_adc
)

View File

@ -7,6 +7,7 @@
#include <Bq25896.h>
#include <Drv2605.h>
#include <Sx1262.h>
#include <Tactility/hal/Configuration.h>
#define TPAGER_SPI_TRANSFER_SIZE_LIMIT (480 * 222 * (LV_COLOR_DEPTH / 8))
@ -22,6 +23,17 @@ static DeviceVector createDevices() {
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
auto keyboard = std::make_shared<TpagerKeyboard>(tca8418);
auto sx1262 = std::make_shared<Sx1262>("SX1262", Sx1262::Configuration{
.spiHostDevice = SPI2_HOST,
.spiFrequency = 10'000'000,
.csPin = GPIO_NUM_36,
.resetPin = GPIO_NUM_47,
.busyPin = GPIO_NUM_48,
.irqPin = GPIO_NUM_14,
.tcxoVoltage = 3.0,
.useRegulatorLdo = false
});
return std::vector<std::shared_ptr<Device>> {
tca8418,
std::make_shared<Bq25896>(I2C_NUM_0),
@ -31,7 +43,8 @@ static DeviceVector createDevices() {
createTpagerSdCard(),
createDisplay(),
keyboard,
std::make_shared<TpagerEncoder>()
std::make_shared<TpagerEncoder>(),
sx1262
};
}

View File

@ -114,20 +114,24 @@ long RadiolibTactilityHal::pulseIn(uint32_t pin, uint32_t state, unsigned long t
}
void RadiolibTactilityHal::spiBegin() {
spi_device_interface_config_t devcfg = {};
devcfg.clock_speed_hz = 1 * 1000 * 1000; // 1MHz
devcfg.mode = 0;
devcfg.spics_io_num = csPin;
devcfg.queue_size = 1;
esp_err_t ret = spi_bus_add_device(spiHostDevice, &devcfg, &spiDeviceHandle);
if (ret != ESP_OK) {
TT_LOG_E(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
if (!spiInitialized) {
TT_LOG_I(TAG, "SPI Begin!");
spi_device_interface_config_t devcfg = {};
devcfg.clock_speed_hz = spiFrequency;
devcfg.mode = 0;
devcfg.spics_io_num = csPin;
devcfg.queue_size = 1;
esp_err_t ret = spi_bus_add_device(spiHostDevice, &devcfg, &spiDeviceHandle);
if (ret != ESP_OK) {
TT_LOG_E(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
}
spiInitialized = true;
}
}
void RadiolibTactilityHal::spiBeginTransaction() {
// not needed - in ESP32 Arduino core, this function
// repeats clock div, mode and bit order configuration
// This function is used to set up the transaction (speed, bit order, mode, ...).
// With the ESP-IDF HAL this is automatically done, so no code needed.
}
void RadiolibTactilityHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
@ -151,5 +155,8 @@ void RadiolibTactilityHal::spiEndTransaction() {
}
void RadiolibTactilityHal::spiEnd() {
if (spiInitialized) {
spi_bus_remove_device(spiDeviceHandle);
spiInitialized = false;
}
}

View File

@ -16,12 +16,14 @@
class RadiolibTactilityHal : public RadioLibHal {
private:
spi_host_device_t spiHostDevice;
int spiFrequency;
gpio_num_t csPin;
spi_device_handle_t spiDeviceHandle;
std::shared_ptr<tt::Lock> lock;
bool spiInitialized;
public:
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, gpio_num_t csPin)
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, int spiFrequency, gpio_num_t csPin, std::shared_ptr<tt::Lock> spiLock)
: RadioLibHal(
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
@ -30,8 +32,12 @@ public:
GPIO_INTR_POSEDGE,
GPIO_INTR_NEGEDGE)
, spiHostDevice(spiHostDevice)
, spiFrequency(spiFrequency)
, csPin(csPin)
, lock(tt::hal::spi::getLock(spiHostDevice)) {}
, lock(spiLock)
, spiInitialized(false) {
if (!lock) lock = tt::hal::spi::getLock(spiHostDevice);
}
void init() override;
void term() override;

View File

@ -6,7 +6,7 @@
constexpr const char* TAG = "Sx1262";
void IRAM_ATTR dio1handler(void* context) {
((Sx1262*)context)->setRxEvent();
((Sx1262*)context)->dio1Event();
}
bool Sx1262::configure(const Parameter parameter, const float value) {
@ -15,24 +15,31 @@ bool Sx1262::configure(const Parameter parameter, const float value) {
switch (parameter) {
case Power:
power = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), power);
return true;
case Frequency:
frequency = value;
TT_LOG_I(TAG, "Configure %s=%f", toString(parameter), frequency);
return true;
case Bandwidth:
bandwidth = value;
TT_LOG_I(TAG, "Configure %s=%f", toString(parameter), bandwidth);
return true;
case SpreadFactor:
spreadFactor = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), spreadFactor);
return true;
case CodingRate:
codingRate = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), codingRate);
return true;
case SyncWord:
syncWord = value;
TT_LOG_I(TAG, "Configure %s=%X", toString(parameter), syncWord);
return true;
case PreambleLength:
preambleLength = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), preambleLength);
return true;
case DataRate:
bitRate = value;
@ -48,6 +55,21 @@ bool Sx1262::configure(const Parameter parameter, const float value) {
}
void Sx1262::registerDio1Isr() {
hal.pinMode(GPIO_NUM_9, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_9, 0);
gpio_hal_context_t gpiohal;
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
gpio_config_t conf = {
.pin_bit_mask = (1ULL<<configuration.irqPin),
.mode = (gpio_mode_t)GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[configuration.irqPin].int_type,
};
gpio_config(&conf);
// We cannot use the RadioLib API to register this action,
// as it does not have the capability to pass an instance pointer via context.
// A trampoline has been tried, but is not linkable to be in IRAM_ATTR (dangerous relocation).
@ -61,8 +83,35 @@ void Sx1262::unregisterDio1Isr() {
gpio_wakeup_disable(configuration.irqPin);
gpio_set_intr_type(configuration.irqPin, GPIO_INTR_DISABLE);
}
/*
void IRAM_ATTR Sx1262::dio1Event() {
static const auto DRAM_ATTR rxbit = RADIO_RECEIVED_BIT;
static const auto DRAM_ATTR txbit = RADIO_TRANSMITTED_BIT;
switch (exchangeState) {
case ExchangeState::Receive:
getEventFlag().set(rxbit);
break;
case ExchangeState::TransmitWaiting:
getEventFlag().set(txbit);
break;
default:
break;
}
gpio_set_level(GPIO_NUM_9, 1);
}*/
void IRAM_ATTR Sx1262::dio1Event() {
static const auto DRAM_ATTR bit = SX1262_DIO1_EVENT_BIT;
getEventFlag().set(bit);
gpio_set_level(GPIO_NUM_9, 1);
}
int32_t Sx1262::threadMain(const Modulation modulation) {
using enum ExchangeState;
uint16_t rc = RADIOLIB_ERR_NONE;
@ -78,6 +127,13 @@ int32_t Sx1262::threadMain(const Modulation modulation) {
configuration.tcxoVoltage,
configuration.useRegulatorLdo
);
/*
radio.forceLDRO(false);
radio.setCRC(true);
radio.invertIQ(false);
radio.setWhitening(true, 0x00FF);
radio.explicitHeader();*/
} else if (modulation == Modulation::Fsk) {
rc = radio.beginFSK(
frequency,
@ -101,41 +157,85 @@ int32_t Sx1262::threadMain(const Modulation modulation) {
return -1;
}
registerDio1Isr();
setState(State::On);
TT_LOG_I(TAG, "SX1262 device ready to receive!");
exchangeState = Receive;
while (!isThreadInterrupted()) {
radio.startReceive();
getEventFlag().wait(RADIO_TERMINATE_BIT, RADIO_RECEIVED_BIT);
TT_LOG_I(TAG, "WAIT FLAG");
auto eventFlags = getEventFlag().wait(RADIO_TERMINATE_BIT | SX1262_DIO1_EVENT_BIT | RADIO_TRANSMIT_QUEUED_BIT);
TT_LOG_W(TAG, "Event, flag=%X", eventFlags);
// Thread might've been interrupted in the meanwhile
if (isThreadInterrupted()) {
break;
}
uint16_t rxSize = radio.getPacketLength(true);
uint8_t *dataBuffer = new uint8_t[rxSize];
uint16_t rc = radio.readData(dataBuffer, rxSize);
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Error receiving data, RadioLib returned %hi", rc);
} else {
float rssi = radio.getRSSI();
float snr = radio.getSNR();
TT_LOG_I(TAG, "LoRa RX size=%d RSSI=%f SNR=%f", rxSize, rssi, snr);
std::string message((char*)dataBuffer, rxSize);
TT_LOG_I(TAG, "msg=%s", message.c_str());
auto rxPacket = tt::hal::radio::RxPacket {
.data = dataBuffer,
.size = rxSize,
.rssi = rssi,
.snr = snr
};
if ((eventFlags & RADIO_TRANSMIT_QUEUED_BIT) && (getTxQueueSize() > 0)) {
currentTx = popNextQueuedTx();
radio.standby();
uint16_t rc = radio.startTransmit(currentTx.data.data(), currentTx.data.size());
if (rc == RADIOLIB_ERR_NONE) {
exchangeState = TransmitWaiting;
currentTx.callback(currentTx.id, TransmissionState::PendingTransmit);
publishRx(rxPacket);
TT_LOG_I(TAG, "WAIT TX FLAG");
auto txEventFlags = getEventFlag().wait(RADIO_TERMINATE_BIT | SX1262_DIO1_EVENT_BIT, tt::EventFlag::WaitAny, pdMS_TO_TICKS(2000));
TT_LOG_W(TAG, "Event, flag=%X", txEventFlags);
// Thread might've been interrupted in the meanwhile
if (isThreadInterrupted()) {
break;
}
if (txEventFlags & SX1262_DIO1_EVENT_BIT) {
currentTx.callback(currentTx.id, TransmissionState::Transmitted);
} else {
currentTx.callback(currentTx.id, TransmissionState::Timeout);
}
exchangeState = Receive;
} else {
TT_LOG_E(TAG, "Error transmitting id=%d, rc=%hi", currentTx.id, rc);
currentTx.callback(currentTx.id, TransmissionState::Error);
exchangeState = Receive;
}
} else if (eventFlags & SX1262_DIO1_EVENT_BIT) {
uint16_t rxSize = radio.getPacketLength(true);
uint8_t *dataBuffer = new uint8_t[rxSize];
uint16_t rc = radio.readData(dataBuffer, rxSize);
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Error receiving data, RadioLib returned %hi", rc);
} else if(rxSize == 0) {
//TT_LOG_W(TAG, "Received data length 0");
} else {
float rssi = radio.getRSSI();
float snr = radio.getSNR();
TT_LOG_I(TAG, "LoRa RX size=%d RSSI=%f SNR=%f", rxSize, rssi, snr);
std::string message((char*)dataBuffer, rxSize);
TT_LOG_I(TAG, "msg=%s", message.c_str());
auto rxPacket = tt::hal::radio::RxPacket {
.data = dataBuffer,
.size = rxSize,
.rssi = rssi,
.snr = snr
};
publishRx(rxPacket);
}
delete[] dataBuffer;
// A delay is needed before a new command
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_NUM_9, 0);
} else {
TT_LOG_W(TAG, "Unhandled event, flag=%X", eventFlags);
}
delete[] dataBuffer;
}
unregisterDio1Isr();
return 0;
}

View File

@ -13,6 +13,7 @@ class Sx1262 final : public tt::hal::radio::RadioDevice {
public:
struct Configuration {
spi_host_device_t spiHostDevice;
int spiFrequency;
gpio_num_t csPin;
gpio_num_t resetPin;
gpio_num_t busyPin;
@ -22,6 +23,14 @@ public:
};
private:
static constexpr auto SX1262_DIO1_EVENT_BIT = BIT8;
enum class ExchangeState {
Idle,
Receive,
//TransmitInitiated,
TransmitWaiting
};
std::string name;
const Configuration configuration;
@ -29,7 +38,9 @@ private:
RadiolibTactilityHal hal;
Module radioModule;
SX1262 radio;
IsrTrampolines<Sx1262>::SlotNumber isrTrampolineSlot;
TxPacket currentTx;
ExchangeState exchangeState;
int8_t power = 0;
float frequency = 0.0;
float bandwidth = 0.0;
@ -46,19 +57,16 @@ private:
public:
explicit Sx1262(const std::string& name, const Configuration& configuration)
: RadioDevice(name, 1024)
explicit Sx1262(const std::string& name, const Configuration& configuration, std::shared_ptr<tt::Lock> lock = nullptr)
: RadioDevice(name, 4096)
, name(name)
, configuration(configuration)
, hal(configuration.spiHostDevice, configuration.csPin)
, hal(configuration.spiHostDevice, configuration.spiFrequency, configuration.csPin, lock)
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
, radio(&radioModule) {
registerDio1Isr();
}
, radio(&radioModule)
, exchangeState(ExchangeState::Idle) {}
~Sx1262() override {
unregisterDio1Isr();
}
~Sx1262() override = default;
std::string getName() const override { return name; }
@ -70,7 +78,10 @@ public:
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
}
void IRAM_ATTR setRxEvent() { getEventFlag().set(RADIO_RECEIVED_BIT); }
bool hasReceived();
void dio1Event();
//void IRAM_ATTR setRxEvent() { rxFlag = true; }
protected:

View File

@ -1,10 +1,13 @@
#pragma once
#include "../Device.h"
#include <Tactility/EventFlag.h>
#include <Tactility/Lock.h>
#include <Tactility/Mutex.h>
#include <Tactility/Thread.h>
#include <deque>
#include <utility>
namespace tt::hal::radio {
@ -39,6 +42,8 @@ public:
};
typedef int RxSubscriptionId;
typedef int TxId;
enum class State {
PendingOn,
@ -48,8 +53,24 @@ public:
Off
};
private:
enum class TransmissionState {
Queued,
PendingTransmit,
Transmitted,
Timeout,
Error
};
using TxStateCallback = std::function<void(TxId id, TransmissionState state)>;
protected:
struct TxPacket {
TxId id;
std::vector<uint8_t> data;
TxStateCallback callback;
};
private:
struct RxSubscription {
RxSubscriptionId id;
std::shared_ptr<std::function<void(Device::Id id, const RxPacket&)>> onData;
@ -63,12 +84,16 @@ private:
std::unique_ptr<Thread> _Nullable thread;
bool threadInterrupted = false;
std::vector<RxSubscription> rxSubscriptions;
std::deque<TxPacket> txQueue;
TxId lastTxId = 0;
RxSubscriptionId lastRxSubscriptionId = 0;
protected:
static constexpr auto RADIO_TERMINATE_BIT = BIT0;
static constexpr auto RADIO_RECEIVED_BIT = BIT1;
static constexpr auto RADIO_TRANSMIT_QUEUED_BIT = BIT1;
static constexpr auto RADIO_RECEIVED_BIT = BIT2;
static constexpr auto RADIO_TRANSMITTED_BIT = BIT3;
virtual int32_t threadMain(const Modulation modulation) = 0;
Mutex &getMutex() { return mutex; }
@ -76,6 +101,23 @@ protected:
bool isThreadInterrupted() const;
void setState(State newState);
size_t getTxQueueSize() const {
auto lock = mutex.asScopedLock();
lock.lock();
const auto size = txQueue.size();
return size;
}
TxPacket popNextQueuedTx() {
auto lock = mutex.asScopedLock();
lock.lock();
auto tx = std::move(txQueue.front());
txQueue.pop_front();
return tx;
}
void publishRx(const RxPacket& packet);
public:
@ -94,6 +136,21 @@ public:
bool start(const Modulation modulation);
bool stop();
TxId transmit(uint8_t *data, const size_t size, TxStateCallback callback) {
return transmit(std::vector<uint8_t>(data, data + size), callback);
}
TxId transmit(const std::vector<uint8_t>& data, TxStateCallback callback) {
auto lock = mutex.asScopedLock();
lock.lock();
const auto txId = lastTxId;
txQueue.push_back(TxPacket{.id = txId, .data = data, .callback = callback});
callback(txId, TransmissionState::Queued);
lastTxId++;
getEventFlag().set(RADIO_TRANSMIT_QUEUED_BIT);
return txId;
}
RxSubscriptionId subscribeRx(const std::function<void(Device::Id id, const RxPacket&)>& onData) {
auto lock = mutex.asScopedLock();
lock.lock();