mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-11 17:19:29 +00:00
Praline rssi (#3127)
This commit is contained in:
@@ -182,20 +182,24 @@ void MAX2831::set_mode(const Mode mode) {
|
|||||||
case Mode::Shutdown:
|
case Mode::Shutdown:
|
||||||
gpio_max2831_rx_enable.write(0); /* RXTX=0 */
|
gpio_max2831_rx_enable.write(0); /* RXTX=0 */
|
||||||
gpio_max283x_enable.write(0); /* ENABLE=0 */
|
gpio_max283x_enable.write(0); /* ENABLE=0 */
|
||||||
|
set_rssi_mux(0);
|
||||||
break;
|
break;
|
||||||
case Mode::Standby:
|
case Mode::Standby:
|
||||||
gpio_max2831_rx_enable.write(1); /* RXTX=1 */
|
gpio_max2831_rx_enable.write(1); /* RXTX=1 */
|
||||||
gpio_max283x_enable.write(0); /* ENABLE=0 */
|
gpio_max283x_enable.write(0); /* ENABLE=0 */
|
||||||
|
set_rssi_mux(0);
|
||||||
break;
|
break;
|
||||||
case Mode::Transmit:
|
case Mode::Transmit:
|
||||||
case Mode::Tx_Calibration:
|
case Mode::Tx_Calibration:
|
||||||
gpio_max2831_rx_enable.write(1); /* RXTX=1 for TX */
|
gpio_max2831_rx_enable.write(1); /* RXTX=1 for TX */
|
||||||
gpio_max283x_enable.write(1); /* ENABLE=1 */
|
gpio_max283x_enable.write(1); /* ENABLE=1 */
|
||||||
|
set_rssi_mux(2); // transmit power
|
||||||
break;
|
break;
|
||||||
case Mode::Receive:
|
case Mode::Receive:
|
||||||
case Mode::Rx_Calibration:
|
case Mode::Rx_Calibration:
|
||||||
gpio_max2831_rx_enable.write(0); /* RXTX=0 for RX */
|
gpio_max2831_rx_enable.write(0); /* RXTX=0 for RX */
|
||||||
gpio_max283x_enable.write(1); /* ENABLE=1 */
|
gpio_max283x_enable.write(1); /* ENABLE=1 */
|
||||||
|
set_rssi_mux(1); // RSSI
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,5 +479,39 @@ void MAX2831::write(const address_t reg_num, const reg_t value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MAX2831::set_rssi_mux(const uint8_t mode) {
|
||||||
|
/* RSSI MUX allows switching the RSSI output between different internal signals.
|
||||||
|
* 0 = disable mux
|
||||||
|
* 1 = RSSI
|
||||||
|
* 2 = TX_POWER
|
||||||
|
* 3 = TEMP
|
||||||
|
*/
|
||||||
|
uint16_t mux_val = 0;
|
||||||
|
|
||||||
|
// Select the appropriate constant based on the input mode.
|
||||||
|
if (mode == 0) {
|
||||||
|
mux_val = 0;
|
||||||
|
} else {
|
||||||
|
// Select the appropriate constant based on the input mode.
|
||||||
|
switch (mode) {
|
||||||
|
case 3:
|
||||||
|
mux_val = REG8_RSSI_MUX_TEMP;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
mux_val = REG8_RSSI_MUX_TX_POWER;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
mux_val = REG8_RSSI_MUX_RSSI;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mux_val |= REG8_RSSI_EN;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_reg_field(8, REG8_RSSI_MUX_MASK | REG8_RSSI_EN, mux_val);
|
||||||
|
flush_reg(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace max2831
|
} // namespace max2831
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ constexpr uint16_t REG8_RSSI_MUX_MASK = 0x0300; /* D9:D8 */
|
|||||||
constexpr uint16_t REG8_RSSI_MUX_RSSI = (0 << REG8_RSSI_MUX_SHIFT);
|
constexpr uint16_t REG8_RSSI_MUX_RSSI = (0 << REG8_RSSI_MUX_SHIFT);
|
||||||
constexpr uint16_t REG8_RSSI_MUX_TEMP = (1 << REG8_RSSI_MUX_SHIFT);
|
constexpr uint16_t REG8_RSSI_MUX_TEMP = (1 << REG8_RSSI_MUX_SHIFT);
|
||||||
constexpr uint16_t REG8_RSSI_MUX_TX_POWER = (2 << REG8_RSSI_MUX_SHIFT);
|
constexpr uint16_t REG8_RSSI_MUX_TX_POWER = (2 << REG8_RSSI_MUX_SHIFT);
|
||||||
|
constexpr uint16_t REG8_RSSI_EN = (1 << 10);
|
||||||
|
|
||||||
constexpr uint16_t REG8_RXVGA_GAIN_SPI_EN_SHIFT = 12;
|
constexpr uint16_t REG8_RXVGA_GAIN_SPI_EN_SHIFT = 12;
|
||||||
constexpr uint16_t REG8_RXVGA_GAIN_SPI_EN = (1 << REG8_RXVGA_GAIN_SPI_EN_SHIFT);
|
constexpr uint16_t REG8_RXVGA_GAIN_SPI_EN = (1 << REG8_RXVGA_GAIN_SPI_EN_SHIFT);
|
||||||
@@ -213,6 +214,8 @@ class MAX2831 : public MAX283x {
|
|||||||
reg_t read(const address_t reg_num) override;
|
reg_t read(const address_t reg_num) override;
|
||||||
void write(const address_t reg_num, const reg_t value) override;
|
void write(const address_t reg_num, const reg_t value) override;
|
||||||
|
|
||||||
|
void set_rssi_mux(const uint8_t mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
spi::arbiter::Target& _target;
|
spi::arbiter::Target& _target;
|
||||||
Mode _mode{Mode::Standby};
|
Mode _mode{Mode::Standby};
|
||||||
|
|||||||
@@ -46,17 +46,17 @@ RSSI::RSSI(
|
|||||||
void RSSI::paint(Painter& painter) {
|
void RSSI::paint(Painter& painter) {
|
||||||
const auto r = screen_rect();
|
const auto r = screen_rect();
|
||||||
|
|
||||||
/* RSSI scaling based on transceiver output voltage range.
|
|
||||||
* MAX2837 (HackRF One): 0.4V to 2.2V
|
|
||||||
* MAX2831 (HackRF Pro): 0.5V to 2.0V (similar enough to use same scaling)
|
|
||||||
*/
|
|
||||||
constexpr int rssi_sample_range = 256;
|
constexpr int rssi_sample_range = 256;
|
||||||
// constexpr float rssi_voltage_min = 0.4;
|
constexpr float rssi_voltage_min = 0.4;
|
||||||
|
#ifdef PRALINE
|
||||||
|
constexpr float rssi_voltage_max = 2.4;
|
||||||
|
#else
|
||||||
constexpr float rssi_voltage_max = 2.2;
|
constexpr float rssi_voltage_max = 2.2;
|
||||||
// constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
#endif
|
||||||
constexpr int raw_min = 0;
|
|
||||||
constexpr float adc_voltage_max = 3.3;
|
constexpr float adc_voltage_max = 3.3;
|
||||||
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
constexpr int raw_min = (rssi_sample_range * rssi_voltage_min) / adc_voltage_max;
|
||||||
|
constexpr int raw_max = (int)(((rssi_sample_range * rssi_voltage_max) / adc_voltage_max) + 0.5f);
|
||||||
constexpr int raw_delta = raw_max - raw_min;
|
constexpr int raw_delta = raw_max - raw_min;
|
||||||
|
|
||||||
if (!vertical_rssi_enabled) {
|
if (!vertical_rssi_enabled) {
|
||||||
@@ -117,7 +117,6 @@ void RSSI::paint(Painter& painter) {
|
|||||||
const Rect r_db{r.left() + x_db, r.top(), 1, r.height()};
|
const Rect r_db{r.left() + x_db, r.top(), 1, r.height()};
|
||||||
|
|
||||||
if (db_) painter.fill_rectangle(r_db, Color::green());
|
if (db_) painter.fill_rectangle(r_db, Color::green());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// vertical bottom to top level meters
|
// vertical bottom to top level meters
|
||||||
const range_t<int> y_avg_range{0, r.height() - 1};
|
const range_t<int> y_avg_range{0, r.height() - 1};
|
||||||
@@ -226,7 +225,6 @@ void RSSI::on_statistics_update(const RSSIStatistics& statistics) {
|
|||||||
min_ = statistics.min;
|
min_ = statistics.min;
|
||||||
avg_ = statistics.accumulator / statistics.count;
|
avg_ = statistics.accumulator / statistics.count;
|
||||||
max_ = statistics.max;
|
max_ = statistics.max;
|
||||||
|
|
||||||
if (peak_enabled) {
|
if (peak_enabled) {
|
||||||
peak_duration_ = peak_duration_ + 100;
|
peak_duration_ = peak_duration_ + 100;
|
||||||
if (max_ > peak_) {
|
if (max_ > peak_) {
|
||||||
@@ -434,25 +432,38 @@ void RSSIGraph::paint(Painter& painter) {
|
|||||||
void RSSIGraph::add_values(int16_t rssi_min, int16_t rssi_avg, int16_t rssi_max, int16_t db) {
|
void RSSIGraph::add_values(int16_t rssi_min, int16_t rssi_avg, int16_t rssi_max, int16_t db) {
|
||||||
const auto r = screen_rect();
|
const auto r = screen_rect();
|
||||||
|
|
||||||
|
/* RSSI scaling based on transceiver output voltage range.
|
||||||
|
* MAX2837 (HackRF One): 0.4V to 2.2V
|
||||||
|
* MAX2831 (HackRF Pro): 0.4V to 2.4V
|
||||||
|
*/
|
||||||
|
|
||||||
constexpr int rssi_sample_range = 256;
|
constexpr int rssi_sample_range = 256;
|
||||||
// constexpr float rssi_voltage_min = 0.4;
|
constexpr float rssi_voltage_min = 0.4;
|
||||||
|
#ifdef PRALINE
|
||||||
|
constexpr float rssi_voltage_max = 2.4;
|
||||||
|
#else
|
||||||
constexpr float rssi_voltage_max = 2.2;
|
constexpr float rssi_voltage_max = 2.2;
|
||||||
// constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
#endif
|
||||||
constexpr int raw_min = 0;
|
|
||||||
constexpr float adc_voltage_max = 3.3;
|
constexpr float adc_voltage_max = 3.3;
|
||||||
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
constexpr int raw_min = (rssi_sample_range * rssi_voltage_min) / adc_voltage_max;
|
||||||
|
constexpr int raw_max = (int)(((rssi_sample_range * rssi_voltage_max) / adc_voltage_max) + 0.5f);
|
||||||
constexpr int raw_delta = raw_max - raw_min;
|
constexpr int raw_delta = raw_max - raw_min;
|
||||||
|
|
||||||
// vertical bottom to top level meters
|
// vertical bottom to top level meters
|
||||||
|
// y_avg
|
||||||
const range_t<int> y_avg_range{0, r.height() - 1};
|
const range_t<int> y_avg_range{0, r.height() - 1};
|
||||||
const int16_t y_avg = y_avg_range.clip((rssi_avg - raw_min) * r.height() / raw_delta);
|
const int16_t y_avg = y_avg_range.clip((rssi_avg - raw_min) * r.height() / raw_delta);
|
||||||
|
// y_min
|
||||||
const range_t<int> y_min_range{0, y_avg};
|
const range_t<int> y_min_range{0, y_avg};
|
||||||
const int16_t y_min = y_min_range.clip((rssi_min - raw_min) * r.height() / raw_delta);
|
const int16_t y_min = y_min_range.clip((rssi_min - raw_min) * r.height() / raw_delta);
|
||||||
const range_t<int> y_max_range{y_avg + 1, r.height() - 1};
|
// y_max
|
||||||
|
const range_t<int> y_max_range{y_avg, r.height() - 1};
|
||||||
const int16_t y_max = y_max_range.clip((rssi_max - raw_min) * r.height() / raw_delta);
|
const int16_t y_max = y_max_range.clip((rssi_max - raw_min) * r.height() / raw_delta);
|
||||||
|
// range
|
||||||
const range_t<int> db_range{-80, 10};
|
const range_t<int> db_range{-80, 10};
|
||||||
int16_t db_ = db_range.clip(db);
|
int16_t db_ = db_range.clip(db);
|
||||||
db_ = db_ - 10;
|
db_ -= 10;
|
||||||
db_ = db_ * r.height() / 90;
|
db_ = db_ * r.height() / 90;
|
||||||
db_ = r.height() + db_;
|
db_ = r.height() + db_;
|
||||||
|
|
||||||
@@ -529,12 +540,6 @@ bool RSSI::on_touch(const TouchEvent event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RSSI::set_db(int16_t db) {
|
void RSSI::set_db(int16_t db) {
|
||||||
#ifdef PRALINE
|
|
||||||
/* Add a +30dB global boost to align 40MHz/1.2V VCM data
|
|
||||||
with the UI's existing display scale. */
|
|
||||||
db_ = db + 30;
|
|
||||||
#else
|
|
||||||
db_ = db;
|
db_ = db;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -64,18 +64,11 @@ class RSSI : public Widget {
|
|||||||
void set_db(int16_t db);
|
void set_db(int16_t db);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef PRALINE
|
|
||||||
// Changed from int8_t to uint8_t:
|
|
||||||
uint8_t min_ = 0;
|
uint8_t min_ = 0;
|
||||||
uint8_t avg_ = 0;
|
uint8_t avg_ = 0;
|
||||||
uint8_t max_ = 0;
|
uint8_t max_ = 0;
|
||||||
uint8_t peak_ = 0;
|
uint8_t peak_ = 0;
|
||||||
#else
|
|
||||||
int8_t min_ = 0;
|
|
||||||
int8_t avg_ = 0;
|
|
||||||
int8_t max_ = 0;
|
|
||||||
int8_t peak_ = 0;
|
|
||||||
#endif
|
|
||||||
size_t peak_duration_ = 0;
|
size_t peak_duration_ = 0;
|
||||||
int16_t db_ = 0;
|
int16_t db_ = 0;
|
||||||
bool instant_exec_{false};
|
bool instant_exec_{false};
|
||||||
|
|||||||
@@ -102,31 +102,6 @@ void BasebandThread::run() {
|
|||||||
buffer_c8_t buffer{
|
buffer_c8_t buffer{
|
||||||
buffer_tmp.p, buffer_tmp.count, sampling_rate_};
|
buffer_tmp.p, buffer_tmp.count, sampling_rate_};
|
||||||
|
|
||||||
#ifdef PRALINE
|
|
||||||
/*
|
|
||||||
* Software RSSI: Copy 8 I/Q samples spread across buffer.
|
|
||||||
*
|
|
||||||
* Just pack and copy - no computation here.
|
|
||||||
* rssi_thread does __SMUAD power and rssi calculation.
|
|
||||||
* 8 samples avoids zero-crossing artifacts.
|
|
||||||
*/
|
|
||||||
if (direction_ == baseband::Direction::Receive && buffer_tmp.count >= 32) {
|
|
||||||
const size_t step = buffer_tmp.count / 8;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 8; i++) {
|
|
||||||
const size_t idx = i * step + (step / 2);
|
|
||||||
const auto sample = buffer_tmp.p[idx];
|
|
||||||
|
|
||||||
// Pack into 32 bits: Q in high 16 bits, I in low 16 bits, the safe approach:
|
|
||||||
// 1. Cast to uint16_t to capture the raw 16-bit pattern (e.g., -1 becomes 0xFFFF)
|
|
||||||
// 2. OR them together. The uint16_t will be promoted to uint32_t cleanly.
|
|
||||||
// This is accomplished with the specific hardware instruction designed
|
|
||||||
// for this __PKHBT (Pack Halfword Bottom Top).
|
|
||||||
shared_memory.software_rssi_iq[i] = __PKHBT(sample.real(), sample.imag(), 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (shared_memory.request_m4_performance_counter == 0x02) {
|
if (shared_memory.request_m4_performance_counter == 0x02) {
|
||||||
uint8_t max = shared_memory.m4_performance_counter;
|
uint8_t max = shared_memory.m4_performance_counter;
|
||||||
for (size_t i = 0; i < buffer_tmp.count; i++) {
|
for (size_t i = 0; i < buffer_tmp.count; i++) {
|
||||||
|
|||||||
@@ -28,110 +28,6 @@
|
|||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "portapack_shared_memory.hpp"
|
||||||
|
|
||||||
#ifdef PRALINE
|
|
||||||
/*
|
|
||||||
* =============================================================================
|
|
||||||
* PRALINE Software RSSI
|
|
||||||
* =============================================================================
|
|
||||||
*
|
|
||||||
* Architecture:
|
|
||||||
* - baseband_thread: Copies 8 packed I/Q samples (no computation)
|
|
||||||
* - rssi_thread: __SMUAD power calc, avg power, LUT, trackers
|
|
||||||
*
|
|
||||||
* All DSP happens here to keep baseband_thread minimal.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Power-to-RSSI Lookup Table (32 entries)
|
|
||||||
*
|
|
||||||
* Maps I²+Q² power to RSSI (0-255) using logarithmic scaling.
|
|
||||||
* For 8-bit I/Q: max |I|=|Q|=127, so max I²+Q² = 32258
|
|
||||||
*
|
|
||||||
* For example, the formula: rssi = 32 * log2((index * 8) + 1), clamped to 255
|
|
||||||
* where using >> 8 scaling provide 4x more sensitive than >> 10:
|
|
||||||
*
|
|
||||||
* The trade-off: more sensitivity means the meter saturates (hits max) at lower signal levels.
|
|
||||||
* We'll want the bar to be mid-range at typical signal levels, not pegged at max.
|
|
||||||
* - Index 0: power 0-255 (I/Q magnitude ~11)
|
|
||||||
* - Index 31: power 7936+ (I/Q magnitude ~63+)
|
|
||||||
*/
|
|
||||||
static constexpr uint8_t power_to_rssi_lut[32] = {
|
|
||||||
0, 101, 130, 148, 161, 171, 179, 186,
|
|
||||||
192, 197, 202, 206, 210, 213, 217, 220,
|
|
||||||
222, 225, 227, 230, 232, 234, 236, 238,
|
|
||||||
240, 241, 243, 244, 246, 247, 249, 255};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert power to RSSI using 32-entry LUT.
|
|
||||||
* If more sensitivity thank >> 10 is needed:
|
|
||||||
* use power >= 8192 & >> 8, yields 4x more sensitivity than >> 10.
|
|
||||||
* use power >= 4096 & >> 7, yields 8x more sensitivity than >> 10
|
|
||||||
* use power >= 2048 & >> 6, yields 16x more sensitivity than >> 10
|
|
||||||
* use power >= 1024 & >> 5, yields 32x more sensitivity than >> 10
|
|
||||||
*/
|
|
||||||
static inline uint8_t power_to_rssi(uint32_t power) {
|
|
||||||
// uint8_t index = (power >= 32768) ? 31 : static_cast<uint8_t>(power >> 10);
|
|
||||||
uint8_t index = (power >= 2048) ? 31 : static_cast<uint8_t>(power >> 6);
|
|
||||||
return power_to_rssi_lut[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IIR Smoothing Filter (Exponential Moving Average)
|
|
||||||
* Formula: smooth = (current + 7*smooth) / 8 (α = 1/8)
|
|
||||||
*/
|
|
||||||
class IIRFilter {
|
|
||||||
public:
|
|
||||||
uint8_t update(uint8_t current) {
|
|
||||||
uint16_t current_q8 = static_cast<uint16_t>(current) << 8;
|
|
||||||
smooth_q8_ = (current_q8 + 7 * smooth_q8_) >> 3;
|
|
||||||
return static_cast<uint8_t>(smooth_q8_ >> 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint16_t smooth_q8_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Running min tracker with decay.
|
|
||||||
* Instantly captures new minimums, slowly decays upward.
|
|
||||||
*/
|
|
||||||
class MinTracker {
|
|
||||||
public:
|
|
||||||
uint8_t update(uint8_t current) {
|
|
||||||
if (current < min_) {
|
|
||||||
min_ = current;
|
|
||||||
} else {
|
|
||||||
// Slow decay upward (α = 1/16)
|
|
||||||
min_ = min_ + ((current - min_) >> 4);
|
|
||||||
}
|
|
||||||
return min_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t min_ = 255;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Running max tracker with decay.
|
|
||||||
* Instantly captures new maximums, slowly decays downward.
|
|
||||||
*/
|
|
||||||
class MaxTracker {
|
|
||||||
public:
|
|
||||||
uint8_t update(uint8_t current) {
|
|
||||||
if (current > max_) {
|
|
||||||
max_ = current;
|
|
||||||
} else {
|
|
||||||
// Slow decay downward (α = 1/16)
|
|
||||||
max_ = max_ - ((max_ - current) >> 4);
|
|
||||||
}
|
|
||||||
return max_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t max_ = 0;
|
|
||||||
};
|
|
||||||
#endif // PRALINE
|
|
||||||
|
|
||||||
WORKING_AREA(rssi_thread_wa, 128);
|
WORKING_AREA(rssi_thread_wa, 128);
|
||||||
|
|
||||||
Thread* RSSIThread::thread = nullptr;
|
Thread* RSSIThread::thread = nullptr;
|
||||||
@@ -158,85 +54,6 @@ void RSSIThread::start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RSSIThread::run() {
|
void RSSIThread::run() {
|
||||||
#ifdef PRALINE
|
|
||||||
|
|
||||||
/*
|
|
||||||
* PRALINE (HackRF Pro): Software RSSI from I/Q samples
|
|
||||||
*
|
|
||||||
* Hardware ADC-based RSSI doesn't work on HackRF Pro.
|
|
||||||
*
|
|
||||||
* baseband_thread copies 8 packed I/Q samples.
|
|
||||||
* We use __SMUAD to compute power, then apply LUT and
|
|
||||||
* maintain running stats.
|
|
||||||
*/
|
|
||||||
|
|
||||||
IIRFilter avg_filter;
|
|
||||||
MinTracker min_tracker;
|
|
||||||
MaxTracker max_tracker;
|
|
||||||
|
|
||||||
RSSIStatistics stats{};
|
|
||||||
uint32_t accumulator = 0;
|
|
||||||
uint32_t sample_count = 0;
|
|
||||||
|
|
||||||
constexpr uint32_t samples_per_report = 50; // ~10Hz reporting at 2ms poll
|
|
||||||
|
|
||||||
while (!chThdShouldTerminate()) {
|
|
||||||
chThdSleepMilliseconds(2); // Poll at ~500Hz
|
|
||||||
|
|
||||||
/*
|
|
||||||
* SIMD-accelerated I/Q power calculation for Cortex-M4.
|
|
||||||
*
|
|
||||||
* Uses __SMUAD (Signed Multiply Accumulate Dual) instruction to compute
|
|
||||||
* sum of products of packed halfwords: (a0*a0) + (a1*a1)
|
|
||||||
*
|
|
||||||
* Processes complex samples per iteration, computing I²+Q² with SIMD.
|
|
||||||
* SIMD-accelerated I/Q power calculation for Cortex-M4.
|
|
||||||
* ~4x faster than scalar loop.
|
|
||||||
* Sum power from all 8 samples (more stable than peak).
|
|
||||||
*
|
|
||||||
* __SMUAD(val, val) computes:
|
|
||||||
* (low16 * low16) + (high16 * high16) = I² + Q²
|
|
||||||
*/
|
|
||||||
|
|
||||||
uint32_t total_power = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 8; i++) {
|
|
||||||
const uint32_t packed = shared_memory.software_rssi_iq[i];
|
|
||||||
total_power += __SMUAD(packed, packed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Average the 8 samples for min, and avg power
|
|
||||||
const uint32_t avg_power = total_power >> 3;
|
|
||||||
|
|
||||||
// Convert to RSSI scale (0-255) via LUT
|
|
||||||
const uint8_t avg_rssi = power_to_rssi(avg_power);
|
|
||||||
|
|
||||||
// Update running trackers
|
|
||||||
const uint8_t smooth_min = min_tracker.update(avg_rssi);
|
|
||||||
const uint8_t smooth_avg = avg_filter.update(avg_rssi);
|
|
||||||
const uint8_t smooth_max = max_tracker.update(avg_rssi);
|
|
||||||
|
|
||||||
// Accumulate for periodic report
|
|
||||||
accumulator += smooth_avg;
|
|
||||||
sample_count++;
|
|
||||||
|
|
||||||
// Report periodically
|
|
||||||
if (sample_count >= samples_per_report) {
|
|
||||||
stats.min = smooth_min;
|
|
||||||
stats.max = smooth_max;
|
|
||||||
stats.accumulator = accumulator;
|
|
||||||
stats.count = sample_count;
|
|
||||||
|
|
||||||
const RSSIStatisticsMessage message{stats};
|
|
||||||
shared_memory.application_queue.push(message);
|
|
||||||
|
|
||||||
// Reset accumulator for next period
|
|
||||||
accumulator = 0;
|
|
||||||
sample_count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/* HackRF One: Use hardware ADC-based RSSI */
|
|
||||||
rf::rssi::init();
|
rf::rssi::init();
|
||||||
rf::rssi::dma::allocate(4, 400);
|
rf::rssi::dma::allocate(4, 400);
|
||||||
|
|
||||||
@@ -260,5 +77,4 @@ void RSSIThread::run() {
|
|||||||
|
|
||||||
rf::rssi::stop();
|
rf::rssi::stop();
|
||||||
rf::rssi::dma::free();
|
rf::rssi::dma::free();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,19 +55,6 @@ struct ToneData {
|
|||||||
|
|
||||||
/* NOTE: These structures must be located in the same location in both M4 and M0 binaries */
|
/* NOTE: These structures must be located in the same location in both M4 and M0 binaries */
|
||||||
struct SharedMemory {
|
struct SharedMemory {
|
||||||
#ifdef PRALINE
|
|
||||||
/*
|
|
||||||
* Software RSSI: 8 packed I/Q samples from baseband_thread.
|
|
||||||
*
|
|
||||||
* baseband_thread copies 8 samples spread across buffer (no computation).
|
|
||||||
* Each sample is packed: Q in high 16 bits, I in low 16 bits.
|
|
||||||
* rssi_thread uses __SMUAD on each to compute I²+Q², finds peak.
|
|
||||||
*
|
|
||||||
* 8 samples avoids zero-crossing artifacts from single-sample approach.
|
|
||||||
*/
|
|
||||||
volatile uint32_t software_rssi_iq[8]{0}; // 8 packed I/Q samples
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static constexpr size_t application_queue_k = 11;
|
static constexpr size_t application_queue_k = 11;
|
||||||
static constexpr size_t app_local_queue_k = 11;
|
static constexpr size_t app_local_queue_k = 11;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user