Add RadioDevice and support for SX1262
This commit is contained in:
parent
179e44ec60
commit
1ab7c4ce9a
@ -20,4 +20,9 @@ dependencies:
|
||||
version: "1.7.6~1"
|
||||
rules:
|
||||
- if: "target == esp32s3"
|
||||
jgromes/radiolib:
|
||||
version: "7.2.1"
|
||||
rules:
|
||||
- if: "target in [esp32s3, esp32p4]"
|
||||
|
||||
idf: '5.5'
|
||||
|
||||
5
Drivers/RadioLibCompat/CMakeLists.txt
Normal file
5
Drivers/RadioLibCompat/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility radiolib
|
||||
)
|
||||
3
Drivers/RadioLibCompat/README.md
Normal file
3
Drivers/RadioLibCompat/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# RadioLibCompat
|
||||
|
||||
A set of helper classes to implement `RadioLib` drivers in Tactility.
|
||||
38
Drivers/RadioLibCompat/Source/IsrTrampoline.cpp
Normal file
38
Drivers/RadioLibCompat/Source/IsrTrampoline.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "IsrTrampoline.h"
|
||||
|
||||
std::array<IsrTrampolines::VoidFunction, IsrTrampolines::MAX_SLOTS> IsrTrampolines::functions{};
|
||||
void (*const IsrTrampolines::trampolines[IsrTrampolines::MAX_SLOTS])() = {
|
||||
&IsrTrampolines::trampoline0,
|
||||
&IsrTrampolines::trampoline1,
|
||||
&IsrTrampolines::trampoline2,
|
||||
&IsrTrampolines::trampoline3,
|
||||
&IsrTrampolines::trampoline4,
|
||||
&IsrTrampolines::trampoline5,
|
||||
&IsrTrampolines::trampoline6,
|
||||
&IsrTrampolines::trampoline7,
|
||||
&IsrTrampolines::trampoline8,
|
||||
&IsrTrampolines::trampoline9,
|
||||
&IsrTrampolines::trampoline10,
|
||||
&IsrTrampolines::trampoline11,
|
||||
&IsrTrampolines::trampoline12,
|
||||
&IsrTrampolines::trampoline13,
|
||||
&IsrTrampolines::trampoline14,
|
||||
&IsrTrampolines::trampoline15,
|
||||
&IsrTrampolines::trampoline16,
|
||||
&IsrTrampolines::trampoline17,
|
||||
&IsrTrampolines::trampoline18,
|
||||
&IsrTrampolines::trampoline19,
|
||||
&IsrTrampolines::trampoline20,
|
||||
&IsrTrampolines::trampoline21,
|
||||
&IsrTrampolines::trampoline22,
|
||||
&IsrTrampolines::trampoline23,
|
||||
&IsrTrampolines::trampoline24,
|
||||
&IsrTrampolines::trampoline25,
|
||||
&IsrTrampolines::trampoline26,
|
||||
&IsrTrampolines::trampoline27,
|
||||
&IsrTrampolines::trampoline28,
|
||||
&IsrTrampolines::trampoline29,
|
||||
&IsrTrampolines::trampoline30,
|
||||
&IsrTrampolines::trampoline31
|
||||
|
||||
};
|
||||
118
Drivers/RadioLibCompat/Source/IsrTrampoline.h
Normal file
118
Drivers/RadioLibCompat/Source/IsrTrampoline.h
Normal file
@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
|
||||
#include "esp_attr.h"
|
||||
|
||||
#define ISR_TRAMPOLINE_FUNC(slot) \
|
||||
static void IRAM_ATTR trampoline##slot() { static const int DRAM_ATTR i = slot; auto &f = IsrTrampolines::functions[i]; if (f) f(); }
|
||||
|
||||
class IsrTrampolines {
|
||||
|
||||
public:
|
||||
|
||||
typedef std::size_t SlotNumber;
|
||||
static constexpr SlotNumber MAX_SLOTS = 32;
|
||||
using TrampolinePointer = void (*)();
|
||||
using VoidFunction = std::function<void()>;
|
||||
static std::array<VoidFunction, MAX_SLOTS> functions;
|
||||
static void (*const trampolines[MAX_SLOTS])();
|
||||
|
||||
static bool registerSlot(VoidFunction f, SlotNumber& slotRegistered) {
|
||||
for (SlotNumber slot = 0; slot < MAX_SLOTS; ++slot) {
|
||||
if (!functions[slot]) {
|
||||
slotRegistered = slot;
|
||||
functions[slot] = f;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool unregisterSlot(const SlotNumber slotRegistered) {
|
||||
if (!functions[slotRegistered]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
functions[slotRegistered] = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
static TrampolinePointer getForSlot(const SlotNumber slotRegistered) {
|
||||
if (slotRegistered < MAX_SLOTS) {
|
||||
return trampolines[slotRegistered];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Unfortunately, these cannot be templated because the linker cannot correctly handle
|
||||
// its integer constant indexing the function table (dangerous relocation)
|
||||
ISR_TRAMPOLINE_FUNC(0);
|
||||
ISR_TRAMPOLINE_FUNC(1);
|
||||
ISR_TRAMPOLINE_FUNC(2);
|
||||
ISR_TRAMPOLINE_FUNC(3);
|
||||
ISR_TRAMPOLINE_FUNC(4);
|
||||
ISR_TRAMPOLINE_FUNC(5);
|
||||
ISR_TRAMPOLINE_FUNC(6);
|
||||
ISR_TRAMPOLINE_FUNC(7);
|
||||
ISR_TRAMPOLINE_FUNC(8);
|
||||
ISR_TRAMPOLINE_FUNC(9);
|
||||
ISR_TRAMPOLINE_FUNC(10);
|
||||
ISR_TRAMPOLINE_FUNC(11);
|
||||
ISR_TRAMPOLINE_FUNC(12);
|
||||
ISR_TRAMPOLINE_FUNC(13);
|
||||
ISR_TRAMPOLINE_FUNC(14);
|
||||
ISR_TRAMPOLINE_FUNC(15);
|
||||
ISR_TRAMPOLINE_FUNC(16);
|
||||
ISR_TRAMPOLINE_FUNC(17);
|
||||
ISR_TRAMPOLINE_FUNC(18);
|
||||
ISR_TRAMPOLINE_FUNC(19);
|
||||
ISR_TRAMPOLINE_FUNC(20);
|
||||
ISR_TRAMPOLINE_FUNC(21);
|
||||
ISR_TRAMPOLINE_FUNC(22);
|
||||
ISR_TRAMPOLINE_FUNC(23);
|
||||
ISR_TRAMPOLINE_FUNC(24);
|
||||
ISR_TRAMPOLINE_FUNC(25);
|
||||
ISR_TRAMPOLINE_FUNC(26);
|
||||
ISR_TRAMPOLINE_FUNC(27);
|
||||
ISR_TRAMPOLINE_FUNC(28);
|
||||
ISR_TRAMPOLINE_FUNC(29);
|
||||
ISR_TRAMPOLINE_FUNC(30);
|
||||
ISR_TRAMPOLINE_FUNC(31);
|
||||
|
||||
/*static void IRAM_ATTR trampoline0() ISR_TRAMPOLINE_FUNC(0)
|
||||
static void IRAM_ATTR trampoline1() { auto &f = IsrTrampolines::functions[1]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline2() { auto &f = IsrTrampolines::functions[2]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline3() { auto &f = IsrTrampolines::functions[3]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline4() { auto &f = IsrTrampolines::functions[4]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline5() { auto &f = IsrTrampolines::functions[5]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline6() { auto &f = IsrTrampolines::functions[6]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline7() { auto &f = IsrTrampolines::functions[7]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline8() { auto &f = IsrTrampolines::functions[8]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline9() { auto &f = IsrTrampolines::functions[9]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline10() { auto &f = IsrTrampolines::functions[10]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline11() { auto &f = IsrTrampolines::functions[11]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline12() { auto &f = IsrTrampolines::functions[12]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline13() { auto &f = IsrTrampolines::functions[13]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline14() { auto &f = IsrTrampolines::functions[14]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline15() { auto &f = IsrTrampolines::functions[15]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline16() { auto &f = IsrTrampolines::functions[16]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline17() { auto &f = IsrTrampolines::functions[17]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline18() { auto &f = IsrTrampolines::functions[18]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline19() { auto &f = IsrTrampolines::functions[19]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline20() { auto &f = IsrTrampolines::functions[20]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline21() { auto &f = IsrTrampolines::functions[21]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline22() { auto &f = IsrTrampolines::functions[22]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline23() { auto &f = IsrTrampolines::functions[23]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline24() { auto &f = IsrTrampolines::functions[24]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline25() { auto &f = IsrTrampolines::functions[25]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline26() { auto &f = IsrTrampolines::functions[26]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline27() { auto &f = IsrTrampolines::functions[27]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline28() { auto &f = IsrTrampolines::functions[28]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline29() { auto &f = IsrTrampolines::functions[29]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline30() { auto &f = IsrTrampolines::functions[30]; if (f) f(); }
|
||||
static void IRAM_ATTR trampoline31() { auto &f = IsrTrampolines::functions[31]; if (f) f(); }
|
||||
*/
|
||||
};
|
||||
155
Drivers/RadioLibCompat/Source/RadiolibTactilityHal.cpp
Normal file
155
Drivers/RadioLibCompat/Source/RadiolibTactilityHal.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
#include "RadiolibTactilityHal.h"
|
||||
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
constexpr const char* TAG = "RadiolibTactilityHal";
|
||||
|
||||
void RadiolibTactilityHal::init() {
|
||||
// we only need to init the SPI here
|
||||
spiBegin();
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::term() {
|
||||
// we only need to stop the SPI here
|
||||
spiEnd();
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::pinMode(uint32_t pin, uint32_t mode) {
|
||||
if(pin == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_hal_context_t gpiohal;
|
||||
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||
|
||||
gpio_config_t conf = {
|
||||
.pin_bit_mask = (1ULL<<pin),
|
||||
.mode = (gpio_mode_t)mode,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[pin].int_type,
|
||||
};
|
||||
gpio_config(&conf);
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::digitalWrite(uint32_t pin, uint32_t value) {
|
||||
if(pin == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_set_level((gpio_num_t)pin, value);
|
||||
}
|
||||
|
||||
uint32_t RadiolibTactilityHal::digitalRead(uint32_t pin) {
|
||||
if(pin == RADIOLIB_NC) {
|
||||
return(0);
|
||||
}
|
||||
|
||||
return(gpio_get_level((gpio_num_t)pin));
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) {
|
||||
if(interruptNum == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
|
||||
gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7));
|
||||
|
||||
// this uses function typecasting, which is not defined when the functions have different signatures
|
||||
// untested and might not work
|
||||
gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL);
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::detachInterrupt(uint32_t interruptNum) {
|
||||
if(interruptNum == RADIOLIB_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_isr_handler_remove((gpio_num_t)interruptNum);
|
||||
gpio_wakeup_disable((gpio_num_t)interruptNum);
|
||||
gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE);
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::delay(unsigned long ms) {
|
||||
vTaskDelay(ms / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::delayMicroseconds(unsigned long us) {
|
||||
uint64_t m = (uint64_t)esp_timer_get_time();
|
||||
if(us) {
|
||||
uint64_t e = (m + us);
|
||||
if(m > e) { // overflow
|
||||
while((uint64_t)esp_timer_get_time() > e);
|
||||
}
|
||||
while((uint64_t)esp_timer_get_time() < e);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long RadiolibTactilityHal::millis() {
|
||||
return((unsigned long)(esp_timer_get_time() / 1000ULL));
|
||||
}
|
||||
|
||||
unsigned long RadiolibTactilityHal::micros() {
|
||||
return((unsigned long)(esp_timer_get_time()));
|
||||
}
|
||||
|
||||
long RadiolibTactilityHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) {
|
||||
if(pin == RADIOLIB_NC) {
|
||||
return(0);
|
||||
}
|
||||
|
||||
this->pinMode(pin, GPIO_MODE_INPUT);
|
||||
uint32_t start = this->micros();
|
||||
uint32_t curtick = this->micros();
|
||||
|
||||
while(this->digitalRead(pin) == state) {
|
||||
if((this->micros() - curtick) > timeout) {
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
return(this->micros() - start);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiBeginTransaction() {
|
||||
// not needed - in ESP32 Arduino core, this function
|
||||
// repeats clock div, mode and bit order configuration
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
|
||||
spi_transaction_t t;
|
||||
|
||||
auto lock = getLock()->asScopedLock();
|
||||
bool locked = lock.lock(portMAX_DELAY);
|
||||
if (!locked) {
|
||||
TT_LOG_E(TAG, "Failed to aquire SPI lock");
|
||||
}
|
||||
|
||||
memset(&t, 0, sizeof(t)); // Zero out the transaction
|
||||
t.length = len * 8; // Length is in bits
|
||||
t.tx_buffer = out; // The data to send
|
||||
t.rx_buffer = in; // The data to receive
|
||||
spi_device_polling_transmit(spiDeviceHandle, &t);
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiEndTransaction() {
|
||||
// nothing needs to be done here
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiEnd() {
|
||||
|
||||
}
|
||||
58
Drivers/RadioLibCompat/Source/RadiolibTactilityHal.h
Normal file
58
Drivers/RadioLibCompat/Source/RadiolibTactilityHal.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/Lock.h>
|
||||
#include <Tactility/hal/spi/Spi.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class RadiolibTactilityHal : public RadioLibHal {
|
||||
private:
|
||||
spi_host_device_t spiHostDevice;
|
||||
gpio_num_t csPin;
|
||||
spi_device_handle_t spiDeviceHandle;
|
||||
std::shared_ptr<tt::Lock> lock;
|
||||
|
||||
public:
|
||||
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, gpio_num_t csPin)
|
||||
: RadioLibHal(
|
||||
GPIO_MODE_INPUT,
|
||||
GPIO_MODE_OUTPUT,
|
||||
0, // LOW
|
||||
1, // HIGH
|
||||
GPIO_INTR_POSEDGE,
|
||||
GPIO_INTR_NEGEDGE)
|
||||
, spiHostDevice(spiHostDevice)
|
||||
, csPin(csPin)
|
||||
, lock(tt::hal::spi::getLock(spiHostDevice)) {}
|
||||
|
||||
void init() override;
|
||||
void term() override;
|
||||
|
||||
void pinMode(uint32_t pin, uint32_t mode) override;
|
||||
void digitalWrite(uint32_t pin, uint32_t value) override;
|
||||
uint32_t digitalRead(uint32_t pin) override;
|
||||
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override;
|
||||
void detachInterrupt(uint32_t interruptNum) override;
|
||||
|
||||
void delay(unsigned long ms) override;
|
||||
void delayMicroseconds(unsigned long us) override;
|
||||
unsigned long millis() override;
|
||||
unsigned long micros() override;
|
||||
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override;
|
||||
|
||||
void spiBegin() override;
|
||||
void spiBeginTransaction() override;
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override;
|
||||
void spiEndTransaction() override;
|
||||
void spiEnd();
|
||||
|
||||
std::shared_ptr<tt::Lock> getLock() const { return lock; }
|
||||
};
|
||||
5
Drivers/SX126x/CMakeLists.txt
Normal file
5
Drivers/SX126x/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility driver RadioLibCompat radiolib
|
||||
)
|
||||
7
Drivers/SX126x/README.md
Normal file
7
Drivers/SX126x/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# SX126x
|
||||
|
||||
Radio with LoRa/(G)FSK capabilities.
|
||||
|
||||
## SX1262
|
||||
|
||||
- [Product Information](https://www.semtech.com/products/wireless-rf/lora-connect/sx1262)
|
||||
125
Drivers/SX126x/Source/Sx1262.cpp
Normal file
125
Drivers/SX126x/Source/Sx1262.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "Sx1262.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
constexpr const char* TAG = "LoraDevice";
|
||||
|
||||
bool Sx1262::configure(const Parameter parameter, const float value) {
|
||||
using enum Parameter;
|
||||
|
||||
switch (parameter) {
|
||||
case Power:
|
||||
power = value;
|
||||
return true;
|
||||
case Frequency:
|
||||
frequency = value;
|
||||
return true;
|
||||
case Bandwidth:
|
||||
bandwidth = value;
|
||||
return true;
|
||||
case SpreadFactor:
|
||||
spreadFactor = value;
|
||||
return true;
|
||||
case CodingRate:
|
||||
codingRate = value;
|
||||
return true;
|
||||
case SyncWord:
|
||||
syncWord = value;
|
||||
return true;
|
||||
case PreambleLength:
|
||||
preambleLength = value;
|
||||
return true;
|
||||
case DataRate:
|
||||
bitRate = value;
|
||||
return true;
|
||||
case FrequencyDeviation:
|
||||
frequencyDeviation = value;
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sx1262::onReceive() {
|
||||
getEventFlag().set(RADIO_RECEIVED_BIT);
|
||||
}
|
||||
|
||||
int32_t Sx1262::threadMain(const Modulation modulation) {
|
||||
|
||||
uint16_t rc = RADIOLIB_ERR_NONE;
|
||||
|
||||
if (modulation == Modulation::LoRa) {
|
||||
rc = radio.begin(
|
||||
frequency,
|
||||
bandwidth,
|
||||
spreadFactor,
|
||||
codingRate,
|
||||
syncWord,
|
||||
power,
|
||||
preambleLength,
|
||||
configuration.tcxoVoltage,
|
||||
configuration.useRegulatorLdo
|
||||
);
|
||||
} else if (modulation == Modulation::Fsk) {
|
||||
rc = radio.beginFSK(
|
||||
frequency,
|
||||
bitRate,
|
||||
frequencyDeviation,
|
||||
bandwidth,
|
||||
power,
|
||||
preambleLength,
|
||||
configuration.tcxoVoltage,
|
||||
configuration.useRegulatorLdo
|
||||
);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "SX1262 not capable of modulation \"%s\"", toString(modulation));
|
||||
setState(State::Error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc != RADIOLIB_ERR_NONE) {
|
||||
TT_LOG_E(TAG, "Radiolib init failed with code %hi", rc);
|
||||
setState(State::Error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
setState(State::On);
|
||||
|
||||
TT_LOG_I(TAG, "SX1262 device ready to receive!");
|
||||
|
||||
while (!isThreadInterrupted()) {
|
||||
radio.startReceive();
|
||||
getEventFlag().wait(RADIO_TERMINATE_BIT, RADIO_RECEIVED_BIT);
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
publishRx(rxPacket);
|
||||
}
|
||||
delete[] dataBuffer;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
80
Drivers/SX126x/Source/Sx1262.h
Normal file
80
Drivers/SX126x/Source/Sx1262.h
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/hal/radio/RadioDevice.h>
|
||||
#include <Tactility/hal/spi/Spi.h>
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "RadiolibTactilityHal.h"
|
||||
#include "IsrTrampoline.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
class Sx1262 final : public tt::hal::radio::RadioDevice {
|
||||
|
||||
public:
|
||||
struct Configuration {
|
||||
spi_host_device_t spiHostDevice;
|
||||
gpio_num_t csPin;
|
||||
gpio_num_t resetPin;
|
||||
gpio_num_t busyPin;
|
||||
gpio_num_t irqPin;
|
||||
float tcxoVoltage;
|
||||
bool useRegulatorLdo;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
std::string name;
|
||||
const Configuration configuration;
|
||||
std::shared_ptr<tt::Lock> lock;
|
||||
RadiolibTactilityHal hal;
|
||||
Module radioModule;
|
||||
SX1262 radio;
|
||||
IsrTrampolines::SlotNumber isrTrampolineSlot;
|
||||
int8_t power = 0;
|
||||
float frequency = 0.0;
|
||||
float bandwidth = 0.0;
|
||||
uint8_t spreadFactor = 0.0;
|
||||
uint8_t codingRate = 0;
|
||||
uint8_t syncWord = 0;
|
||||
uint16_t preambleLength = 0;
|
||||
float bitRate = 0.0;
|
||||
float frequencyDeviation = 0.0;
|
||||
|
||||
int32_t threadMain();
|
||||
|
||||
void onReceive();
|
||||
|
||||
public:
|
||||
|
||||
explicit Sx1262(const std::string& name, const Configuration& configuration)
|
||||
: RadioDevice(name, 1024)
|
||||
, name(name)
|
||||
, configuration(configuration)
|
||||
, hal(configuration.spiHostDevice, configuration.csPin)
|
||||
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
|
||||
, radio(&radioModule) {
|
||||
IsrTrampolines::registerSlot([this]() {
|
||||
this->onReceive();
|
||||
}, isrTrampolineSlot);
|
||||
radio.setDio1Action(IsrTrampolines::getForSlot(isrTrampolineSlot));
|
||||
}
|
||||
|
||||
~Sx1262() override {
|
||||
IsrTrampolines::unregisterSlot(isrTrampolineSlot);
|
||||
}
|
||||
|
||||
std::string getName() const override { return name; }
|
||||
|
||||
std::string getDescription() const override { return "SX1262 LoRa and FSK capable radio"; }
|
||||
|
||||
bool configure(const Parameter parameter, const float value) override;
|
||||
|
||||
bool isCapableOf(const Modulation modulation) {
|
||||
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
int32_t threadMain(const Modulation modulation) override;
|
||||
};
|
||||
@ -21,7 +21,8 @@ public:
|
||||
Keyboard,
|
||||
Encoder,
|
||||
Power,
|
||||
Gps
|
||||
Gps,
|
||||
Radio
|
||||
};
|
||||
|
||||
typedef uint32_t Id;
|
||||
|
||||
118
Tactility/Include/Tactility/hal/radio/RadioDevice.h
Normal file
118
Tactility/Include/Tactility/hal/radio/RadioDevice.h
Normal file
@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
#include <Tactility/EventFlag.h>
|
||||
#include <Tactility/Lock.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Thread.h>
|
||||
#include <utility>
|
||||
|
||||
namespace tt::hal::radio {
|
||||
|
||||
struct RxPacket {
|
||||
uint8_t const *data;
|
||||
size_t const size;
|
||||
float rssi;
|
||||
float snr;
|
||||
};
|
||||
|
||||
class RadioDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
enum class Modulation {
|
||||
Fsk,
|
||||
LoRa
|
||||
};
|
||||
|
||||
enum class Parameter {
|
||||
Power,
|
||||
Frequency,
|
||||
Bandwidth,
|
||||
SpreadFactor,
|
||||
CodingRate,
|
||||
SyncWord,
|
||||
PreambleLength,
|
||||
FrequencyDeviation,
|
||||
DataRate,
|
||||
AddressWidth
|
||||
};
|
||||
|
||||
typedef int RxSubscriptionId;
|
||||
|
||||
enum class State {
|
||||
PendingOn,
|
||||
On,
|
||||
Error,
|
||||
PendingOff,
|
||||
Off
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
struct RxSubscription {
|
||||
RxSubscriptionId id;
|
||||
std::shared_ptr<std::function<void(Device::Id id, const RxPacket&)>> onData;
|
||||
};
|
||||
|
||||
std::string threadName;
|
||||
size_t threadSize;
|
||||
State state;
|
||||
EventFlag events;
|
||||
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
||||
std::unique_ptr<Thread> _Nullable thread;
|
||||
bool threadInterrupted = false;
|
||||
std::vector<RxSubscription> rxSubscriptions;
|
||||
RxSubscriptionId lastRxSubscriptionId = 0;
|
||||
|
||||
protected:
|
||||
|
||||
static constexpr auto RADIO_TERMINATE_BIT = BIT0;
|
||||
static constexpr auto RADIO_RECEIVED_BIT = BIT1;
|
||||
|
||||
virtual int32_t threadMain(const Modulation modulation) = 0;
|
||||
Mutex &getMutex() { return mutex; }
|
||||
EventFlag &getEventFlag() { return events; }
|
||||
bool isThreadInterrupted() const;
|
||||
void setState(State newState);
|
||||
|
||||
void publishRx(const RxPacket& packet);
|
||||
|
||||
public:
|
||||
explicit RadioDevice(const std::string& threadName, const size_t threadSize)
|
||||
: threadName(threadName)
|
||||
, threadSize(threadSize)
|
||||
, state(State::Off) {}
|
||||
|
||||
~RadioDevice() override = default;
|
||||
|
||||
Type getType() const override { return Type::Radio; }
|
||||
|
||||
virtual bool configure(const Parameter parameter, const float value) = 0;
|
||||
virtual bool isCapableOf(const Modulation modulation) = 0;
|
||||
|
||||
bool start(const Modulation modulation);
|
||||
bool stop();
|
||||
|
||||
RxSubscriptionId subscribeRx(const std::function<void(Device::Id id, const RxPacket&)>& onData) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
rxSubscriptions.push_back({
|
||||
.id = ++lastRxSubscriptionId,
|
||||
.onData = std::make_shared<std::function<void(Device::Id, const RxPacket&)>>(onData)
|
||||
});
|
||||
return lastRxSubscriptionId;
|
||||
}
|
||||
|
||||
void unsubscribeRx(RxSubscriptionId subscriptionId) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
std::erase_if(rxSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
|
||||
}
|
||||
|
||||
State getState() const;
|
||||
};
|
||||
|
||||
const char* toString(RadioDevice::Modulation modulation);
|
||||
const char* toString(RadioDevice::Parameter parameter);
|
||||
}
|
||||
136
Tactility/Source/hal/radio/RadioDevice.cpp
Normal file
136
Tactility/Source/hal/radio/RadioDevice.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include "Tactility/hal/radio/RadioDevice.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace tt::hal::radio {
|
||||
|
||||
constexpr const char* TAG = "RadioDevice";
|
||||
|
||||
bool RadioDevice::start(const Modulation modulation) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
|
||||
if (!isCapableOf(modulation)) {
|
||||
TT_LOG_E(TAG, "Can't start device \"%s\", not capable of modulation \"%s\"", getName().c_str(), toString(modulation));
|
||||
return false;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
|
||||
if (thread != nullptr && thread->getState() != Thread::State::Stopped) {
|
||||
TT_LOG_W(TAG, "Already started");
|
||||
return true;
|
||||
}
|
||||
|
||||
threadInterrupted = false;
|
||||
|
||||
TT_LOG_I(TAG, "Starting thread");
|
||||
setState(State::PendingOn);
|
||||
|
||||
thread = std::make_unique<Thread>(
|
||||
threadName,
|
||||
threadSize,
|
||||
[this, modulation]() {
|
||||
return this->threadMain(modulation);
|
||||
}
|
||||
);
|
||||
thread->setPriority(tt::Thread::Priority::High);
|
||||
thread->start();
|
||||
|
||||
TT_LOG_I(TAG, "Starting finished");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioDevice::stop() {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
setState(State::PendingOff);
|
||||
|
||||
if (thread != nullptr) {
|
||||
threadInterrupted = true;
|
||||
getEventFlag().set(RADIO_TERMINATE_BIT);
|
||||
|
||||
// Detach thread, it will auto-delete when leaving the current scope
|
||||
auto old_thread = std::move(thread);
|
||||
|
||||
if (old_thread->getState() != Thread::State::Stopped) {
|
||||
// Unlock so thread can lock
|
||||
lock.unlock();
|
||||
// Wait for thread to finish
|
||||
old_thread->join();
|
||||
// Re-lock to continue logic below
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
setState(State::Off);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioDevice::isThreadInterrupted() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return threadInterrupted;
|
||||
}
|
||||
|
||||
RadioDevice::State RadioDevice::getState() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return state; // Make copy because of thread safety
|
||||
}
|
||||
|
||||
void RadioDevice::setState(State newState) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
state = newState;
|
||||
}
|
||||
|
||||
void RadioDevice::publishRx(const RxPacket& packet) {
|
||||
mutex.lock();
|
||||
for (auto& subscription : rxSubscriptions) {
|
||||
(*subscription.onData)(getId(), packet);
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
const char* toString(RadioDevice::Modulation modulation) {
|
||||
using enum RadioDevice::Modulation;
|
||||
switch (modulation) {
|
||||
case Fsk:
|
||||
return "FSK";
|
||||
case LoRa:
|
||||
return "LoRa";
|
||||
default:
|
||||
return "Unkown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* toString(RadioDevice::Parameter parameter) {
|
||||
using enum RadioDevice::Parameter;
|
||||
switch (parameter) {
|
||||
case Power:
|
||||
return TT_STRINGIFY(Power);
|
||||
case Frequency:
|
||||
return TT_STRINGIFY(Frequency);
|
||||
case Bandwidth:
|
||||
return TT_STRINGIFY(Bandwidth);
|
||||
case SpreadFactor:
|
||||
return TT_STRINGIFY(SpreadFactor);
|
||||
case CodingRate:
|
||||
return TT_STRINGIFY(CodingRate);
|
||||
case SyncWord:
|
||||
return TT_STRINGIFY(SyncWord);
|
||||
case PreambleLength:
|
||||
return TT_STRINGIFY(PreambleLength);
|
||||
case FrequencyDeviation:
|
||||
return TT_STRINGIFY(FrequencyDeviation);
|
||||
case DataRate:
|
||||
return TT_STRINGIFY(DataRate);
|
||||
case AddressWidth:
|
||||
return TT_STRINGIFY(AddressWidth);
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tt::hal::radio
|
||||
Loading…
x
Reference in New Issue
Block a user