mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-03-30 13:45:38 +00:00
Display and LoRa were creating separate SPIClass(HSPI) instances which claimed GPIO pins via the matrix, preventing SD card (on FSPI) from accessing MISO after Display init. Now all three peripherals use the global SPI (FSPI) instance, eliminating GPIO routing conflicts. - Display: use &SPI instead of new SPIClass(HSPI) - SX1262Interface: use &SPI instead of new SPIClass(HSPI) - SDAccess: enable format_if_empty for unformatted cards Verified on device: SD (128GB SDHC), display, and LoRa all coexist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
303 lines
9.2 KiB
C++
303 lines
9.2 KiB
C++
// Copyright (c) 2024 microReticulum contributors
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "SX1262Interface.h"
|
|
#include "Log.h"
|
|
#include "Utilities/OS.h"
|
|
|
|
#ifdef ARDUINO
|
|
#include <SPI.h>
|
|
#endif
|
|
|
|
using namespace RNS;
|
|
|
|
#ifdef ARDUINO
|
|
// Static members for SPI mutex (shared with display and SD card)
|
|
SemaphoreHandle_t SX1262Interface::_spi_mutex = nullptr;
|
|
bool SX1262Interface::_mutex_initialized = false;
|
|
|
|
void SX1262Interface::set_spi_mutex(SemaphoreHandle_t mutex) {
|
|
_spi_mutex = mutex;
|
|
_mutex_initialized = (mutex != nullptr);
|
|
if (_mutex_initialized) {
|
|
DEBUG("SX1262Interface: Using external SPI mutex");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SX1262Interface::SX1262Interface(const char* name) : InterfaceImpl(name) {
|
|
_IN = true;
|
|
_OUT = true;
|
|
_HW_MTU = HW_MTU;
|
|
_AUTOCONFIGURE_MTU = true;
|
|
|
|
// Calculate bitrate from modulation parameters (matching Python RNS formula)
|
|
// bitrate = sf * ((4.0/cr) / (2^sf / (bw/1000))) * 1000
|
|
_bitrate = (double)_config.spreading_factor *
|
|
((4.0 / _config.coding_rate) /
|
|
(pow(2, _config.spreading_factor) / (_config.bandwidth / 1000.0))) * 1000.0;
|
|
}
|
|
|
|
SX1262Interface::~SX1262Interface() {
|
|
stop();
|
|
}
|
|
|
|
void SX1262Interface::set_config(const SX1262Config& config) {
|
|
_config = config;
|
|
|
|
// Recalculate bitrate
|
|
_bitrate = (double)_config.spreading_factor *
|
|
((4.0 / _config.coding_rate) /
|
|
(pow(2, _config.spreading_factor) / (_config.bandwidth / 1000.0))) * 1000.0;
|
|
}
|
|
|
|
std::string SX1262Interface::toString() const {
|
|
return "SX1262Interface[" + _name + "]";
|
|
}
|
|
|
|
bool SX1262Interface::start() {
|
|
_online = false;
|
|
|
|
#ifdef ARDUINO
|
|
INFO("SX1262Interface: Initializing...");
|
|
INFO(" Frequency: " + std::to_string(_config.frequency) + " MHz");
|
|
INFO(" Bandwidth: " + std::to_string(_config.bandwidth) + " kHz");
|
|
INFO(" SF: " + std::to_string(_config.spreading_factor));
|
|
INFO(" CR: 4/" + std::to_string(_config.coding_rate));
|
|
INFO(" TX Power: " + std::to_string(_config.tx_power) + " dBm");
|
|
|
|
// Use external mutex if provided, otherwise create our own (fallback)
|
|
if (!_mutex_initialized) {
|
|
WARNING("SX1262Interface: No external SPI mutex set, creating own");
|
|
_spi_mutex = xSemaphoreCreateMutex();
|
|
if (_spi_mutex == nullptr) {
|
|
ERROR("SX1262Interface: Failed to create SPI mutex");
|
|
return false;
|
|
}
|
|
_mutex_initialized = true;
|
|
}
|
|
|
|
// Acquire SPI mutex
|
|
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
|
ERROR("SX1262Interface: Failed to acquire SPI mutex for init");
|
|
return false;
|
|
}
|
|
|
|
// Set radio CS high to avoid conflicts
|
|
pinMode(SX1262Pins::CS, OUTPUT);
|
|
digitalWrite(SX1262Pins::CS, HIGH);
|
|
|
|
// Use global SPI (FSPI) — all peripherals share same SPI peripheral
|
|
// to avoid GPIO matrix conflicts. SPI.begin() already called by SDAccess or Display.
|
|
_lora_spi = &SPI;
|
|
DEBUG("SX1262Interface: Using global SPI (FSPI) for LoRa");
|
|
|
|
// Create RadioLib module and radio with the shared SPI instance
|
|
_module = new Module(SX1262Pins::CS, SX1262Pins::DIO1, SX1262Pins::RST, SX1262Pins::BUSY, *_lora_spi);
|
|
_radio = new SX1262(_module);
|
|
|
|
// Initialize radio with configuration
|
|
int16_t state = _radio->begin(
|
|
_config.frequency,
|
|
_config.bandwidth,
|
|
_config.spreading_factor,
|
|
_config.coding_rate,
|
|
_config.sync_word,
|
|
_config.tx_power,
|
|
_config.preamble_length
|
|
);
|
|
|
|
if (state != RADIOLIB_ERR_NONE) {
|
|
ERROR("SX1262Interface: Radio init failed, code " + std::to_string(state));
|
|
xSemaphoreGive(_spi_mutex);
|
|
delete _radio;
|
|
delete _module;
|
|
_radio = nullptr;
|
|
_module = nullptr;
|
|
return false;
|
|
}
|
|
|
|
// Enable CRC for error detection
|
|
state = _radio->setCRC(true);
|
|
if (state != RADIOLIB_ERR_NONE) {
|
|
WARNING("SX1262Interface: Failed to enable CRC, code " + std::to_string(state));
|
|
}
|
|
|
|
// Use explicit header mode (includes length in LoRa header)
|
|
state = _radio->explicitHeader();
|
|
if (state != RADIOLIB_ERR_NONE) {
|
|
WARNING("SX1262Interface: Failed to set explicit header, code " + std::to_string(state));
|
|
}
|
|
|
|
xSemaphoreGive(_spi_mutex);
|
|
|
|
// Start listening for packets
|
|
start_receive();
|
|
|
|
_online = true;
|
|
INFO("SX1262Interface: Initialized successfully");
|
|
INFO(" Bitrate: " + std::to_string(Utilities::OS::round(_bitrate / 1000.0, 2)) + " kbps");
|
|
|
|
return true;
|
|
#else
|
|
ERROR("SX1262Interface: Not supported on this platform");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void SX1262Interface::stop() {
|
|
#ifdef ARDUINO
|
|
if (_radio != nullptr) {
|
|
if (_spi_mutex != nullptr && xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
_radio->standby();
|
|
xSemaphoreGive(_spi_mutex);
|
|
}
|
|
|
|
delete _radio;
|
|
delete _module;
|
|
_radio = nullptr;
|
|
_module = nullptr;
|
|
}
|
|
#endif
|
|
|
|
_online = false;
|
|
INFO("SX1262Interface: Stopped");
|
|
}
|
|
|
|
void SX1262Interface::start_receive() {
|
|
#ifdef ARDUINO
|
|
if (_radio == nullptr) return;
|
|
|
|
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
int16_t state = _radio->startReceive();
|
|
xSemaphoreGive(_spi_mutex);
|
|
|
|
if (state != RADIOLIB_ERR_NONE) {
|
|
ERROR("SX1262Interface: Failed to start receive, code " + std::to_string(state));
|
|
}
|
|
} else {
|
|
ERROR("SX1262Interface: Failed to acquire SPI mutex for start_receive!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SX1262Interface::loop() {
|
|
if (!_online) return;
|
|
|
|
#ifdef ARDUINO
|
|
if (_radio == nullptr) return;
|
|
|
|
// Try to acquire SPI mutex (non-blocking to avoid stalling display)
|
|
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5)) != pdTRUE) {
|
|
return; // Display is using SPI, try again later
|
|
}
|
|
|
|
// Check IRQ status to see if a packet was actually received
|
|
uint16_t irqStatus = _radio->getIrqStatus();
|
|
|
|
// Only process if RX_DONE flag is set (0x0002 for SX126x)
|
|
if (!(irqStatus & 0x0002)) {
|
|
xSemaphoreGive(_spi_mutex);
|
|
return; // No new packet
|
|
}
|
|
|
|
// Read the received packet (this also clears IRQ internally)
|
|
int16_t state = _radio->readData(_rx_buffer.writable(HW_MTU), HW_MTU);
|
|
|
|
// Immediately restart receive to clear IRQ flags and prepare for next packet
|
|
_radio->startReceive();
|
|
|
|
if (state == RADIOLIB_ERR_NONE) {
|
|
// Got a packet
|
|
size_t len = _radio->getPacketLength();
|
|
if (len > 1) { // Must have at least header + data
|
|
_rx_buffer.resize(len);
|
|
|
|
// Get signal quality
|
|
_last_rssi = _radio->getRSSI();
|
|
_last_snr = _radio->getSNR();
|
|
|
|
xSemaphoreGive(_spi_mutex);
|
|
|
|
// RNode packet format: [1-byte random header][payload]
|
|
// Skip header byte, pass payload to transport
|
|
Bytes payload = _rx_buffer.mid(1);
|
|
|
|
DEBUG("SX1262Interface: Received " + std::to_string(len) + " bytes, " +
|
|
"RSSI=" + std::to_string((int)_last_rssi) + " dBm, " +
|
|
"SNR=" + std::to_string((int)_last_snr) + " dB");
|
|
|
|
on_incoming(payload);
|
|
return;
|
|
}
|
|
} else if (state != RADIOLIB_ERR_RX_TIMEOUT) {
|
|
// An error occurred (not just timeout)
|
|
ERROR("SX1262Interface: Receive error, code " + std::to_string(state));
|
|
}
|
|
|
|
xSemaphoreGive(_spi_mutex);
|
|
#endif
|
|
}
|
|
|
|
void SX1262Interface::send_outgoing(const Bytes& data) {
|
|
if (!_online) return;
|
|
|
|
#ifdef ARDUINO
|
|
if (_radio == nullptr) return;
|
|
|
|
DEBUG(toString() + ": Sending " + std::to_string(data.size()) + " bytes");
|
|
|
|
// Build packet with random header (RNode-compatible format)
|
|
// Header: upper 4 bits random, lower 4 bits reserved
|
|
uint8_t header = Cryptography::randomnum(256) & 0xF0;
|
|
|
|
size_t len = 1 + data.size();
|
|
if (len > HW_MTU) {
|
|
ERROR("SX1262Interface: Packet too large (" + std::to_string(len) + " > " + std::to_string(HW_MTU) + ")");
|
|
return;
|
|
}
|
|
|
|
uint8_t* buf = new uint8_t[len];
|
|
buf[0] = header;
|
|
memcpy(buf + 1, data.data(), data.size());
|
|
|
|
// Acquire SPI mutex
|
|
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
|
ERROR("SX1262Interface: Failed to acquire SPI mutex for TX");
|
|
delete[] buf;
|
|
return;
|
|
}
|
|
|
|
_transmitting = true;
|
|
|
|
// Transmit (blocking)
|
|
int16_t state = _radio->transmit(buf, len);
|
|
|
|
_transmitting = false;
|
|
|
|
// Return to receive mode immediately while still holding SPI mutex
|
|
// (no gap for display task to steal the bus and leave radio in STANDBY)
|
|
int16_t rxState = _radio->startReceive();
|
|
xSemaphoreGive(_spi_mutex);
|
|
delete[] buf;
|
|
|
|
if (rxState != RADIOLIB_ERR_NONE) {
|
|
ERROR("SX1262Interface: Failed to restart receive after TX, code " + std::to_string(rxState));
|
|
}
|
|
|
|
if (state == RADIOLIB_ERR_NONE) {
|
|
DEBUG("SX1262Interface: Sent " + std::to_string(len) + " bytes");
|
|
// Perform post-send housekeeping
|
|
InterfaceImpl::handle_outgoing(data);
|
|
} else {
|
|
ERROR("SX1262Interface: Transmit failed, code " + std::to_string(state));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SX1262Interface::on_incoming(const Bytes& data) {
|
|
DEBUG(toString() + ": Incoming " + std::to_string(data.size()) + " bytes");
|
|
// Pass received data to transport
|
|
InterfaceImpl::handle_incoming(data);
|
|
}
|