mirror of
https://github.com/meshtastic/firmware.git
synced 2026-10-09 06:31:35 -04:00
Radiolib recording hal tests (#11997)
* test: exercise RadioLib's drivers through a recording HAL, pinned to RadioLib 7.8.0 test_radiolib_drivers drives the RadioLib calls Meshtastic's radio interfaces make against RecordingHal, which logs every SPI transaction and answers with a success status, a few scripted replies (the packet type the LoRa setters check) and, for SX127x, a register file. One set per chip family - SX126x, SX127x, SX128x, LR11x0, LR2021 - each ending in a list of the reply-dependent tests still to write. SX126x and SX128x get the SPI framing their begin() would set. RadioLib is pinned to 7.8.0 to show the failure: the LR2021 DC-DC workaround (jgromes/RadioLib#1864) passes sizeof(uint32_t) as a word count, and under [env:coverage] ASan aborts the LR2021 set with a stack-buffer-overflow in readRegMem32. The other four sets pass. * test: pin RadioLib to the DC-DC word-count fix NomDeTom/radiolib@3509dbc8e (branch lr2021-dcdc-regmem32-len, on RadioLib master after 7.8.0) passes 1 as the word count at the three DC-DC workaround call sites. The overrun is gone and the LR2021 set runs to completion. test_lr2021_dcdc_freq_lf_write_sends_one_word still fails, on a second RadioLib bug: LRxxxx::writeCommon() sends 4 + 4*len bytes whatever the address width, so every LR2021 WriteRegMem32 (24-bit address) carries one uninitialised trailing byte - 10 bytes where 9 are meant. * test: keep RecordingHal reachable across a failed assertion A failed TEST_ASSERT longjmps out of the test, skipping destructors, so each test's local RecordingHal leaked its transaction log. LeakSanitizer then reported it at exit, and under [env:coverage] a plain assertion failure came out as an ERRORED suite (run 36412456860: 26 Tests 1 Failures, then a 384-byte leak from RecordingHal::spiTransfer). One static HAL, reset at the start of each test, stays reachable, so a failure reports as FAILED. * test: run the RadioLib driver suite against develop's RadioLib pin Revert the pin to develop's 510e00cfb (RadioLib 7.7.1). That RadioLib predates the LR2021 DC-DC workaround, so its three tests would only fail on their own premise there. They now register only when LR2021 has the public setRegulatorDCDC() that jgromes/RadioLib#1864 added with the workaround: any RadioLib carrying it runs them, and none is left ignored. Against 7.8.0 the suite shows the DC-DC overrun (ASan, run 36412379592). Against the DC-DC fix (NomDeTom/radiolib@3509dbc8e) it shows a second bug: every LR2021 WriteRegMem32 sends one byte too many, since LRxxxx::writeCommon() still sizes the frame for a 4-byte address after jgromes/RadioLib@e3af85867 made it 3 on the LR2021 (run 36412456860). Neither is reachable at this pin. * test: pin the coding-rate byte the radio interfaces send LR11x0Interface, LR20x0Interface and SX128xInterface all call setCodingRate(cr, cr != 7), so 4/5 goes out with the long interleaver. The modulation tests used the driver default (short), and only counted frames. Pass the flag the firmware passes, and add a test per chip that reads the coding-rate field back from the last modulation-params frame: 5 for 4/5 long, 3 for 4/7 short (RadioLib 7.7.1's encoding). Also correct test_main.cpp: at the 7.7.1 pin the LR2021 DC-DC tests are not registered, so the set does not fail; the 7.8.0 overrun is now stated as conditional on a RadioLib that carries it.
This commit is contained in:
7 files changed
+732
No files matched your search
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
// A stand-in for the radio chip: records every SPI transaction and answers with scripted bytes.
|
||||
// Shared by every chip family's tests in this suite; see test_main.cpp for why it is enough.
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
class RecordingHal : public RadioLibHal
|
||||
{
|
||||
public:
|
||||
// A scripted answer, chosen by the leading bytes (the opcode) of what the driver sent.
|
||||
struct Reply {
|
||||
std::vector<uint8_t> prefix;
|
||||
bool nextTransaction; // answer the transaction after the match: LRxxxx reads reply in a second one
|
||||
std::vector<uint8_t> head;
|
||||
uint8_t fill; // every byte after head
|
||||
};
|
||||
|
||||
// 0x04 decodes as success on every status-byte family: LRxxxx CMD_OK, SX126x DATA_AVAILABLE,
|
||||
// SX128x CMD_PROCESSED, and is neither 0x00 nor 0xFF (CHIP_NOT_FOUND).
|
||||
explicit RecordingHal(uint8_t defaultFill = 0x04) : RadioLibHal(0, 1, 0, 1, 2, 3), defaultFill(defaultFill) {}
|
||||
|
||||
// Back to a fresh chip, keeping the allocation: see freshHal().
|
||||
void reset(uint8_t fill = 0x04)
|
||||
{
|
||||
transactions.clear();
|
||||
replies.clear();
|
||||
registerEcho = false;
|
||||
memset(registers, 0, sizeof(registers));
|
||||
defaultFill = fill;
|
||||
pending = nullptr;
|
||||
nowUs = 0;
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint8_t>> transactions;
|
||||
std::vector<Reply> replies;
|
||||
|
||||
// SX127x mode: no status byte, and RADIOLIB_SPI_PARANOID reads every register write back, so
|
||||
// writes are stored and reads answered from the store.
|
||||
bool registerEcho = false;
|
||||
uint8_t registers[128] = {};
|
||||
|
||||
void reply(std::vector<uint8_t> prefix, uint8_t fill, std::vector<uint8_t> head = {}, bool nextTransaction = false)
|
||||
{
|
||||
replies.push_back({std::move(prefix), nextTransaction, std::move(head), fill});
|
||||
}
|
||||
|
||||
// Transactions whose leading bytes are `prefix`.
|
||||
size_t count(const std::vector<uint8_t> &prefix) const
|
||||
{
|
||||
size_t n = 0;
|
||||
for (const auto &t : transactions)
|
||||
n += startsWith(t.data(), t.size(), prefix);
|
||||
return n;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> *first(const std::vector<uint8_t> &prefix) const
|
||||
{
|
||||
for (const auto &t : transactions)
|
||||
if (startsWith(t.data(), t.size(), prefix))
|
||||
return &t;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> *last(const std::vector<uint8_t> &prefix) const
|
||||
{
|
||||
for (auto it = transactions.rbegin(); it != transactions.rend(); ++it)
|
||||
if (startsWith(it->data(), it->size(), prefix))
|
||||
return &*it;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void pinMode(uint32_t, uint32_t) override {}
|
||||
void digitalWrite(uint32_t, uint32_t) override {}
|
||||
uint32_t digitalRead(uint32_t) override { return 0; } // BUSY low
|
||||
void attachInterrupt(uint32_t, void (*)(void), uint32_t) override {}
|
||||
void detachInterrupt(uint32_t) override {}
|
||||
void delay(RadioLibTime_t ms) override { nowUs += ms * 1000; }
|
||||
void delayMicroseconds(RadioLibTime_t us) override { nowUs += us; }
|
||||
// Advances on every read, so a RadioLib wait loop always reaches its timeout instead of spinning.
|
||||
RadioLibTime_t millis() override { return (nowUs += 1000) / 1000; }
|
||||
RadioLibTime_t micros() override { return nowUs += 1000; }
|
||||
long pulseIn(uint32_t, uint32_t, RadioLibTime_t) override { return 0; }
|
||||
void spiBegin() override {}
|
||||
void spiBeginTransaction() override {}
|
||||
void spiEndTransaction() override {}
|
||||
void spiEnd() override {}
|
||||
|
||||
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override
|
||||
{
|
||||
transactions.emplace_back(out, out + len);
|
||||
if (registerEcho) {
|
||||
echoRegisters(out, len, in);
|
||||
return;
|
||||
}
|
||||
const Reply *r = pending;
|
||||
pending = nullptr;
|
||||
for (const auto &c : replies) {
|
||||
if (!startsWith(out, len, c.prefix))
|
||||
continue;
|
||||
if (c.nextTransaction)
|
||||
pending = &c;
|
||||
else if (!r)
|
||||
r = &c;
|
||||
}
|
||||
for (size_t i = 0; i < len; i++)
|
||||
in[i] = !r ? defaultFill : (i < r->head.size() ? r->head[i] : r->fill);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t defaultFill;
|
||||
const Reply *pending = nullptr;
|
||||
RadioLibTime_t nowUs = 0;
|
||||
|
||||
static bool startsWith(const uint8_t *data, size_t len, const std::vector<uint8_t> &prefix)
|
||||
{
|
||||
return len >= prefix.size() && std::equal(prefix.begin(), prefix.end(), data);
|
||||
}
|
||||
|
||||
// SX127x framing: first byte is the address, bit 7 set for a write; the rest is the burst.
|
||||
void echoRegisters(const uint8_t *out, size_t len, uint8_t *in)
|
||||
{
|
||||
const uint8_t addr = out[0] & 0x7F;
|
||||
in[0] = 0;
|
||||
for (size_t i = 1; i < len; i++) {
|
||||
uint8_t ® = registers[(addr + i - 1) & 0x7F];
|
||||
if (out[0] & 0x80)
|
||||
reg = out[i], in[i] = 0;
|
||||
else
|
||||
in[i] = reg;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Big-endian opcode bytes, for the 16-bit LRxxxx command set.
|
||||
inline std::vector<uint8_t> op16(uint16_t opcode)
|
||||
{
|
||||
return {static_cast<uint8_t>(opcode >> 8), static_cast<uint8_t>(opcode & 0xFF)};
|
||||
}
|
||||
|
||||
// A failed TEST_ASSERT longjmps out of the test, skipping destructors, so a test-local HAL would leak
|
||||
// its transaction log and LeakSanitizer would turn every assertion failure into a crash. One static
|
||||
// instance, reset per test, stays reachable.
|
||||
inline RecordingHal &freshHal()
|
||||
{
|
||||
static RecordingHal hal;
|
||||
hal.reset();
|
||||
return hal;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
// LR11x0 (LR1121): the calls LR11x0Interface makes, minus begin() and updateFirmware().
|
||||
|
||||
#include "RecordingHal.h"
|
||||
#include "TestUtil.h"
|
||||
|
||||
// LRxxxx reads answer in a second transaction: status (0x04 = CMD_OK), then data. The LoRa setters
|
||||
// ask for the packet type first; 0x02 is LoRa, which as a status byte would decode as CMD_PERR,
|
||||
// so it goes in the data byte only.
|
||||
#define LR11X0_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
hal.reply(op16(RADIOLIB_LR11X0_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR11X0_PACKET_TYPE_LORA}, true); \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, 2); \
|
||||
LR1121 radio(&mod)
|
||||
|
||||
static void test_lr11x0_setFrequency_sends_hertz()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setFrequency(915.0));
|
||||
const auto *t = hal.first(op16(RADIOLIB_LR11X0_CMD_SET_RF_FREQUENCY));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(6, t->size());
|
||||
const uint8_t hz[] = {0x36, 0x89, 0xCA, 0xC0}; // 915000000
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(hz, t->data() + 2, 4);
|
||||
}
|
||||
|
||||
static void test_lr11x0_lora_modulation_setters_send_modulation_params()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(9));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setBandwidth(250.0));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5, true));
|
||||
TEST_ASSERT_EQUAL_UINT32(3, hal.count(op16(RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS)));
|
||||
}
|
||||
|
||||
// LR11x0Interface passes cr != 7 as the long-interleave flag: 4/7 has no long-interleaver code.
|
||||
static void test_lr11x0_coding_rate_long_interleaves_except_4_7()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5, true));
|
||||
const auto *t = hal.last(op16(RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(6, t->size()); // opcode(2) + sf, bw, cr, ldro
|
||||
TEST_ASSERT_EQUAL_UINT8(5, (*t)[4]); // 4/5, long interleaver
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(7, false));
|
||||
t = hal.last(op16(RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT8(3, (*t)[4]); // 4/7, short interleaver
|
||||
}
|
||||
|
||||
static void test_lr11x0_packet_setters_send_packet_params()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setPreambleLength(16));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCRC(2));
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR11X0_CMD_SET_PACKET_PARAMS)) >= 2);
|
||||
}
|
||||
|
||||
static void test_lr11x0_other_setters_and_modes_succeed()
|
||||
{
|
||||
LR11X0_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSyncWord(0x2B));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setOutputPower(22));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.standby());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startReceive());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.sleep());
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR11X0_CMD_SET_LORA_SYNC_WORD)) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR11X0_CMD_SET_TX_PARAMS)) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR11X0_CMD_SET_RX)) >= 1);
|
||||
}
|
||||
|
||||
// Grows here - each needs replies scripted per opcode:
|
||||
// begin(): getVersion() device-type check
|
||||
// updateFirmware(): enter bootloader, report RADIOLIB_LR11X0_DEVICE_BOOT, then normal. Pins every
|
||||
// image word written exactly once (an image that is an exact multiple of 64 words loses its last
|
||||
// chunk in 7.8.0) and a failed chunk being reported (its result is currently dropped)
|
||||
// readData() / getPacketLength(): a chip-reported length longer than the caller's buffer
|
||||
// getRSSI() / getSNR() / getPacketStatus(): decoding of known reply bytes
|
||||
// scanChannel(): the CAD parameters sent
|
||||
|
||||
static void runLr11x0Tests()
|
||||
{
|
||||
RUN_TEST(test_lr11x0_setFrequency_sends_hertz);
|
||||
RUN_TEST(test_lr11x0_lora_modulation_setters_send_modulation_params);
|
||||
RUN_TEST(test_lr11x0_coding_rate_long_interleaves_except_4_7);
|
||||
RUN_TEST(test_lr11x0_packet_setters_send_packet_params);
|
||||
RUN_TEST(test_lr11x0_other_setters_and_modes_succeed);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
#pragma once
|
||||
|
||||
// LR2021: the calls LR20x0Interface makes, minus begin(). The DC-DC workaround tests come first:
|
||||
// under ASan the first overrun aborts the program, and it should abort on the test that names it.
|
||||
|
||||
#include "RecordingHal.h"
|
||||
#include "TestUtil.h"
|
||||
#include <modules/LR2021/LR2021_registers.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// jgromes/RadioLib#1864 added the DC-DC workaround together with a public setRegulatorDCDC(), so that
|
||||
// method marks a RadioLib the DC-DC tests apply to. Earlier pins (7.7.1 and before) have neither.
|
||||
template <typename T, typename = void> struct HasDcdcWorkaround : std::false_type {
|
||||
};
|
||||
template <typename T>
|
||||
struct HasDcdcWorkaround<T, std::void_t<decltype(std::declval<T &>().setRegulatorDCDC())>> : std::true_type {
|
||||
};
|
||||
|
||||
// begin() sets a frequency before anything else; without one the DC-DC workaround's closing
|
||||
// setFrequency(freqMHz) fails with INVALID_FREQUENCY. The log starts after it.
|
||||
static void lr2021Tune(RecordingHal &hal, LR2021 &radio)
|
||||
{
|
||||
(void)radio.setFrequency(915.0);
|
||||
hal.transactions.clear();
|
||||
}
|
||||
|
||||
// As LR11x0: the packet type comes back in the data byte of the next transaction. 0x00 is LoRa.
|
||||
#define LR2021_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
hal.reply(op16(RADIOLIB_LR2021_CMD_GET_PACKET_TYPE), 0x04, {0x04, RADIOLIB_LR2021_PACKET_TYPE_LORA}, true); \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, 2); \
|
||||
LR2021 radio(&mod); \
|
||||
lr2021Tune(hal, radio)
|
||||
|
||||
// The first transaction carrying `opcode` for register `addr`: 16-bit opcode, then a 24-bit address.
|
||||
static const std::vector<uint8_t> *lr2021RegMemAccess(const RecordingHal &hal, uint16_t opcode, uint32_t addr)
|
||||
{
|
||||
return hal.first({static_cast<uint8_t>(opcode >> 8), static_cast<uint8_t>(opcode & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 16) & 0xFF), static_cast<uint8_t>((addr >> 8) & 0xFF),
|
||||
static_cast<uint8_t>(addr & 0xFF)});
|
||||
}
|
||||
|
||||
// setRxBoostedGainMode() -> setRxPath() -> setDCDCworkaround(), the route LR20x0Interface takes.
|
||||
static void lr2021RunDcdcWorkaround(RecordingHal &hal, LR2021 &radio)
|
||||
{
|
||||
(void)hal;
|
||||
(void)radio.setRxBoostedGainMode(0);
|
||||
}
|
||||
|
||||
static void test_lr2021_setRxPath_runs_the_dcdc_workaround()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021RunDcdcWorkaround(hal, radio);
|
||||
// Guards the premise: without these the two word-count tests below would pass vacuously.
|
||||
TEST_ASSERT_NOT_NULL(lr2021RegMemAccess(hal, RADIOLIB_LR2021_CMD_READ_REG_MEM_32, RADIOLIB_LR2021_REG_DCDC_ADC_CTRL));
|
||||
TEST_ASSERT_NOT_NULL(lr2021RegMemAccess(hal, RADIOLIB_LR2021_CMD_WRITE_REG_MEM_32, RADIOLIB_LR2021_REG_DCDC_FREQ_LF));
|
||||
}
|
||||
|
||||
static void test_lr2021_dcdc_adc_ctrl_read_asks_for_one_word()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021RunDcdcWorkaround(hal, radio);
|
||||
const auto *req = lr2021RegMemAccess(hal, RADIOLIB_LR2021_CMD_READ_REG_MEM_32, RADIOLIB_LR2021_REG_DCDC_ADC_CTRL);
|
||||
TEST_ASSERT_NOT_NULL(req);
|
||||
// READ_REG_MEM_32 request: opcode(2) + address(3) + word count(1)
|
||||
TEST_ASSERT_EQUAL_UINT32(6, req->size());
|
||||
TEST_ASSERT_EQUAL_UINT8(1, (*req)[5]);
|
||||
}
|
||||
|
||||
static void test_lr2021_dcdc_freq_lf_write_sends_one_word()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
lr2021RunDcdcWorkaround(hal, radio);
|
||||
const auto *wr = lr2021RegMemAccess(hal, RADIOLIB_LR2021_CMD_WRITE_REG_MEM_32, RADIOLIB_LR2021_REG_DCDC_FREQ_LF);
|
||||
TEST_ASSERT_NOT_NULL(wr);
|
||||
// WRITE_REG_MEM_32: opcode(2) + address(3) + one data word(4); four words is 21 bytes
|
||||
TEST_ASSERT_EQUAL_UINT32(2 + 3 + 4, wr->size());
|
||||
}
|
||||
|
||||
static void test_lr2021_setFrequency_sends_hertz()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setFrequency(915.0));
|
||||
const auto *t = hal.first(op16(RADIOLIB_LR2021_CMD_SET_RF_FREQUENCY));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(6, t->size());
|
||||
const uint8_t hz[] = {0x36, 0x89, 0xCA, 0xC0}; // 915000000
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(hz, t->data() + 2, 4);
|
||||
}
|
||||
|
||||
static void test_lr2021_lora_modulation_setters_send_modulation_params()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(9));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setBandwidth(250.0));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5, true));
|
||||
TEST_ASSERT_EQUAL_UINT32(3, hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_MODULATION_PARAMS)));
|
||||
}
|
||||
|
||||
// LR20x0Interface passes cr != 7 as the long-interleave flag: 4/7 has no long-interleaver code.
|
||||
static void test_lr2021_coding_rate_long_interleaves_except_4_7()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5, true));
|
||||
const auto *t = hal.last(op16(RADIOLIB_LR2021_CMD_SET_LORA_MODULATION_PARAMS));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(4, t->size()); // opcode(2) + sf|bw, cr|ldro
|
||||
TEST_ASSERT_EQUAL_UINT8(5, (*t)[3] >> 4); // 4/5, long interleaver
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(7, false));
|
||||
t = hal.last(op16(RADIOLIB_LR2021_CMD_SET_LORA_MODULATION_PARAMS));
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT8(3, (*t)[3] >> 4); // 4/7, short interleaver
|
||||
}
|
||||
|
||||
static void test_lr2021_packet_setters_send_packet_params()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setPreambleLength(16));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCRC(2));
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_PACKET_PARAMS)) >= 2);
|
||||
}
|
||||
|
||||
static void test_lr2021_other_setters_and_modes_succeed()
|
||||
{
|
||||
LR2021_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSyncWord(0x2B));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setOutputPower(22));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.standby());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startReceive());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.sleep());
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR2021_CMD_SET_LORA_SYNCWORD)) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR2021_CMD_SET_TX_PARAMS)) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count(op16(RADIOLIB_LR2021_CMD_SET_RX)) >= 1);
|
||||
}
|
||||
|
||||
// Grows here - each needs replies scripted per opcode:
|
||||
// begin(): getVersion() check and the calibration sequence
|
||||
// resetDCDCworkaround() via setPacketType(): only begin() reaches it; same one-word write as above
|
||||
// setPaTable(): custom LF table entry chosen per output power (cf. meshtastic/firmware#11980)
|
||||
// readData() / getPacketLength(): a chip-reported length longer than the caller's buffer
|
||||
// getRSSI() / getSNR() / getPacketStatus(): decoding of known reply bytes
|
||||
// scanChannel(): the CAD parameters sent (LR2021 has two CAD commands)
|
||||
|
||||
static void runLr2021Tests()
|
||||
{
|
||||
if constexpr (HasDcdcWorkaround<LR2021>::value) {
|
||||
RUN_TEST(test_lr2021_setRxPath_runs_the_dcdc_workaround);
|
||||
RUN_TEST(test_lr2021_dcdc_adc_ctrl_read_asks_for_one_word);
|
||||
RUN_TEST(test_lr2021_dcdc_freq_lf_write_sends_one_word);
|
||||
}
|
||||
RUN_TEST(test_lr2021_setFrequency_sends_hertz);
|
||||
RUN_TEST(test_lr2021_lora_modulation_setters_send_modulation_params);
|
||||
RUN_TEST(test_lr2021_coding_rate_long_interleaves_except_4_7);
|
||||
RUN_TEST(test_lr2021_packet_setters_send_packet_params);
|
||||
RUN_TEST(test_lr2021_other_setters_and_modes_succeed);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
// SX126x (SX1262): the calls SX126xInterface makes, minus begin().
|
||||
|
||||
#include "RecordingHal.h"
|
||||
#include "TestUtil.h"
|
||||
|
||||
// The LoRa setters ask the chip for its packet type first. 0x01 is LoRa, and as a status byte it
|
||||
// matches no SX126x error code, so the whole reply can be 0x01.
|
||||
static void sx126xAnswerLora(RecordingHal &hal)
|
||||
{
|
||||
hal.reply({RADIOLIB_SX126X_CMD_GET_PACKET_TYPE}, RADIOLIB_SX126X_PACKET_TYPE_LORA);
|
||||
}
|
||||
|
||||
// Exposes RadioLib's own status parser, which is protected in SX126x.
|
||||
struct TestSX1262 : public SX1262 {
|
||||
using SX1262::SX1262;
|
||||
static int16_t parseStatus(uint8_t in) { return SPIparseStatus(in); }
|
||||
};
|
||||
|
||||
// SX126x sets its SPI framing in begin() (modSetup()), not its constructor, and begin() cannot run
|
||||
// without a chip. This is the same framing, as of RadioLib 7.8.0.
|
||||
static void sx126xFraming(Module &mod)
|
||||
{
|
||||
mod.spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16;
|
||||
mod.spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8;
|
||||
mod.spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_8;
|
||||
mod.spiConfig.statusPos = 1;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX126X_CMD_READ_REGISTER;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX126X_CMD_WRITE_REGISTER;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX126X_CMD_NOP;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX126X_CMD_GET_STATUS;
|
||||
mod.spiConfig.stream = true;
|
||||
mod.spiConfig.parseStatusCb = TestSX1262::parseStatus;
|
||||
}
|
||||
|
||||
#define SX126X_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
sx126xAnswerLora(hal); \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, 2); \
|
||||
TestSX1262 radio(&mod); \
|
||||
sx126xFraming(mod)
|
||||
|
||||
static void test_sx126x_setFrequency_sends_the_frf_word()
|
||||
{
|
||||
SX126X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setFrequency(915.0));
|
||||
// frf = 915 MHz * 2^25 / 32 MHz = 0x39300000
|
||||
const auto *t = hal.first({RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY});
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(5, t->size());
|
||||
const uint8_t frf[] = {0x39, 0x30, 0x00, 0x00};
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(frf, t->data() + 1, 4);
|
||||
}
|
||||
|
||||
static void test_sx126x_lora_modulation_setters_send_modulation_params()
|
||||
{
|
||||
SX126X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(9));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setBandwidth(250.0));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5));
|
||||
TEST_ASSERT_EQUAL_UINT32(3, hal.count({RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS}));
|
||||
}
|
||||
|
||||
static void test_sx126x_packet_setters_send_packet_params()
|
||||
{
|
||||
SX126X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setPreambleLength(16));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCRC(2));
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS}) >= 2);
|
||||
}
|
||||
|
||||
static void test_sx126x_setSyncWord_writes_a_register()
|
||||
{
|
||||
SX126X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSyncWord(0x2B));
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_WRITE_REGISTER}) >= 1);
|
||||
}
|
||||
|
||||
static void test_sx126x_setOutputPower_sends_pa_config_and_tx_params()
|
||||
{
|
||||
SX126X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setOutputPower(22));
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_SET_PA_CONFIG}) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_SET_TX_PARAMS}) >= 1);
|
||||
}
|
||||
|
||||
static void test_sx126x_mode_commands_reach_the_chip()
|
||||
{
|
||||
SX126X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.standby());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startReceive());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.sleep());
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_SET_STANDBY}) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_SET_RX}) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX126X_CMD_SET_SLEEP}) >= 1);
|
||||
}
|
||||
|
||||
// Grows here - each needs replies scripted per opcode, not just a status byte:
|
||||
// begin(): version string check (SX126X_REG_VERSION_STRING)
|
||||
// readData() / getPacketLength(): a chip-reported length longer than the caller's buffer
|
||||
// getRSSI() / getSNR() / getPacketStatus(): decoding of known reply bytes
|
||||
// scanChannel(): the CAD parameters sent, per the part's symNum encoding
|
||||
// startReceiveDutyCycleAuto(): the RX and sleep periods derived from preamble length
|
||||
|
||||
static void runSx126xTests()
|
||||
{
|
||||
RUN_TEST(test_sx126x_setFrequency_sends_the_frf_word);
|
||||
RUN_TEST(test_sx126x_lora_modulation_setters_send_modulation_params);
|
||||
RUN_TEST(test_sx126x_packet_setters_send_packet_params);
|
||||
RUN_TEST(test_sx126x_setSyncWord_writes_a_register);
|
||||
RUN_TEST(test_sx126x_setOutputPower_sends_pa_config_and_tx_params);
|
||||
RUN_TEST(test_sx126x_mode_commands_reach_the_chip);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
// SX127x (SX1276): the calls RF95Interface makes, minus begin(). Register-mapped, so the HAL runs in
|
||||
// echo mode and the assertions read the resulting register values.
|
||||
|
||||
#include "RecordingHal.h"
|
||||
#include "TestUtil.h"
|
||||
|
||||
// The setters check REG_OP_MODE bit 7 for LoRa mode; begin() would have set it.
|
||||
#define SX127X_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
hal.registerEcho = true; \
|
||||
hal.registers[RADIOLIB_SX127X_REG_OP_MODE] = RADIOLIB_SX127X_LORA | RADIOLIB_SX127X_STANDBY; \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC); \
|
||||
SX1276 radio(&mod)
|
||||
|
||||
static void test_sx127x_setFrequency_writes_the_frf_registers()
|
||||
{
|
||||
SX127X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setFrequency(915.0));
|
||||
// frf = 915 MHz * 2^19 / 32 MHz = 0xE4C000
|
||||
TEST_ASSERT_EQUAL_HEX8(0xE4, hal.registers[RADIOLIB_SX127X_REG_FRF_MSB]);
|
||||
TEST_ASSERT_EQUAL_HEX8(0xC0, hal.registers[RADIOLIB_SX127X_REG_FRF_MID]);
|
||||
TEST_ASSERT_EQUAL_HEX8(0x00, hal.registers[RADIOLIB_SX127X_REG_FRF_LSB]);
|
||||
}
|
||||
|
||||
static void test_sx127x_setSpreadingFactor_writes_modem_config_2()
|
||||
{
|
||||
SX127X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(9));
|
||||
TEST_ASSERT_EQUAL_HEX8(9, hal.registers[RADIOLIB_SX127X_REG_MODEM_CONFIG_2] >> 4);
|
||||
}
|
||||
|
||||
static void test_sx127x_setSyncWord_writes_the_sync_register()
|
||||
{
|
||||
SX127X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSyncWord(0x2B));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x2B, hal.registers[RADIOLIB_SX127X_REG_SYNC_WORD]);
|
||||
}
|
||||
|
||||
static void test_sx127x_setPreambleLength_writes_both_bytes()
|
||||
{
|
||||
SX127X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setPreambleLength(16));
|
||||
TEST_ASSERT_EQUAL_HEX8(0x00, hal.registers[RADIOLIB_SX127X_REG_PREAMBLE_MSB]);
|
||||
TEST_ASSERT_EQUAL_HEX8(16, hal.registers[RADIOLIB_SX127X_REG_PREAMBLE_LSB]);
|
||||
}
|
||||
|
||||
static void test_sx127x_other_setters_and_modes_succeed()
|
||||
{
|
||||
SX127X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setBandwidth(250.0));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setOutputPower(17));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.standby());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startReceive());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.sleep());
|
||||
}
|
||||
|
||||
// Grows here - each needs more than an echoing register file:
|
||||
// begin(): REG_VERSION check and the reset sequence
|
||||
// readData() / getPacketLength(): REG_RX_NB_BYTES larger than the caller's buffer
|
||||
// getRSSI() / getSNR(): decoding of REG_PKT_RSSI_VALUE / REG_PKT_SNR_VALUE
|
||||
// scanChannel(): CAD done/detected flags in REG_IRQ_FLAGS
|
||||
|
||||
static void runSx127xTests()
|
||||
{
|
||||
RUN_TEST(test_sx127x_setFrequency_writes_the_frf_registers);
|
||||
RUN_TEST(test_sx127x_setSpreadingFactor_writes_modem_config_2);
|
||||
RUN_TEST(test_sx127x_setSyncWord_writes_the_sync_register);
|
||||
RUN_TEST(test_sx127x_setPreambleLength_writes_both_bytes);
|
||||
RUN_TEST(test_sx127x_other_setters_and_modes_succeed);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
// SX128x (SX1280): the calls SX128xInterface makes, minus begin().
|
||||
|
||||
#include "RecordingHal.h"
|
||||
#include "TestUtil.h"
|
||||
|
||||
// SX128x sets its SPI framing in begin() (modSetup()), not its constructor, and begin() cannot run
|
||||
// without a chip. This is the same framing, as of RadioLib 7.8.0, minus the status parser: it is
|
||||
// private in SX128x, and the HAL never answers with an error status anyway.
|
||||
static void sx128xFraming(Module &mod)
|
||||
{
|
||||
mod.spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16;
|
||||
mod.spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8;
|
||||
mod.spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_8;
|
||||
mod.spiConfig.statusPos = 0;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX128X_CMD_READ_REGISTER;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX128X_CMD_WRITE_REGISTER;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX128X_CMD_NOP;
|
||||
mod.spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX128X_CMD_GET_STATUS;
|
||||
mod.spiConfig.stream = true;
|
||||
mod.spiConfig.parseStatusCb = nullptr;
|
||||
}
|
||||
|
||||
// The LoRa setters ask for the packet type first. 0x01 is LoRa and matches no SX128x error code.
|
||||
#define SX128X_RADIO(hal) \
|
||||
RecordingHal &hal = freshHal(); \
|
||||
hal.reply({RADIOLIB_SX128X_CMD_GET_PACKET_TYPE}, RADIOLIB_SX128X_PACKET_TYPE_LORA); \
|
||||
Module mod(&hal, 1, RADIOLIB_NC, RADIOLIB_NC, 2); \
|
||||
SX1280 radio(&mod); \
|
||||
sx128xFraming(mod)
|
||||
|
||||
static void test_sx128x_setFrequency_sends_rf_frequency()
|
||||
{
|
||||
SX128X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setFrequency(2440.0));
|
||||
const auto *t = hal.first({RADIOLIB_SX128X_CMD_SET_RF_FREQUENCY});
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(4, t->size()); // opcode + 24-bit frf
|
||||
}
|
||||
|
||||
static void test_sx128x_lora_modulation_setters_send_modulation_params()
|
||||
{
|
||||
SX128X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSpreadingFactor(9));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setBandwidth(812.5));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5, true));
|
||||
TEST_ASSERT_EQUAL_UINT32(3, hal.count({RADIOLIB_SX128X_CMD_SET_MODULATION_PARAMS}));
|
||||
}
|
||||
|
||||
// SX128xInterface passes cr != 7 as the long-interleave flag: 4/7 has no long-interleaver code.
|
||||
static void test_sx128x_coding_rate_long_interleaves_except_4_7()
|
||||
{
|
||||
SX128X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(5, true));
|
||||
const auto *t = hal.last({RADIOLIB_SX128X_CMD_SET_MODULATION_PARAMS});
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(4, t->size()); // opcode + sf, bw, cr
|
||||
TEST_ASSERT_EQUAL_UINT8(5, (*t)[3]); // 4/5, long interleaver
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCodingRate(7, false));
|
||||
t = hal.last({RADIOLIB_SX128X_CMD_SET_MODULATION_PARAMS});
|
||||
TEST_ASSERT_NOT_NULL(t);
|
||||
TEST_ASSERT_EQUAL_UINT8(3, (*t)[3]); // 4/7, short interleaver
|
||||
}
|
||||
|
||||
static void test_sx128x_packet_setters_send_packet_params()
|
||||
{
|
||||
SX128X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setPreambleLength(16));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setCRC(2));
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX128X_CMD_SET_PACKET_PARAMS}) >= 2);
|
||||
}
|
||||
|
||||
static void test_sx128x_other_setters_and_modes_succeed()
|
||||
{
|
||||
SX128X_RADIO(hal);
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setSyncWord(0x12));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.setOutputPower(10));
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.standby());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.startReceive());
|
||||
TEST_ASSERT_EQUAL_INT16(RADIOLIB_ERR_NONE, radio.sleep());
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX128X_CMD_SET_TX_PARAMS}) >= 1);
|
||||
TEST_ASSERT_TRUE(hal.count({RADIOLIB_SX128X_CMD_SET_RX}) >= 1);
|
||||
}
|
||||
|
||||
// Grows here - each needs replies scripted per opcode:
|
||||
// begin(): version string check
|
||||
// readData() / getPacketLength(): a chip-reported length longer than the caller's buffer
|
||||
// getRSSI() / getSNR() / getPacketStatus(): decoding of known reply bytes
|
||||
// scanChannel(): CAD parameters and the CAD-done IRQ
|
||||
|
||||
static void runSx128xTests()
|
||||
{
|
||||
RUN_TEST(test_sx128x_setFrequency_sends_rf_frequency);
|
||||
RUN_TEST(test_sx128x_lora_modulation_setters_send_modulation_params);
|
||||
RUN_TEST(test_sx128x_coding_rate_long_interleaves_except_4_7);
|
||||
RUN_TEST(test_sx128x_packet_setters_send_packet_params);
|
||||
RUN_TEST(test_sx128x_other_setters_and_modes_succeed);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Unit tests for the RadioLib driver calls Meshtastic's radio interfaces make, one set per chip
|
||||
// family: SX126x (sx126x_tests.h), SX127x (sx127x_tests.h), SX128x (sx128x_tests.h), LR11x0
|
||||
// (lr11x0_tests.h) and LR2021 (lr2021_tests.h), against the RadioLib pinned in platformio.ini.
|
||||
//
|
||||
// No chip is needed. RecordingHal (RecordingHal.h) logs every SPI transaction and answers with a
|
||||
// status byte every status-byte family reads as success, plus the few scripted replies the setters
|
||||
// check before acting (the packet type); for SX127x it keeps a register file instead. That reaches
|
||||
// every setter and mode command the interfaces use, and the tests pin what reaches the chip: the
|
||||
// opcode, the frame length, and where the value is fixed, the payload.
|
||||
//
|
||||
// The regressions guarded are driver changes that send the wrong bytes, or read or write past a
|
||||
// buffer, on paths no hardware-free test reached before. Under [env:coverage] (-fsanitize=address)
|
||||
// an out-of-bounds access aborts the program at the call. The first such bug is jgromes/RadioLib#1864:
|
||||
// the LR2021 DC-DC workaround passes sizeof(uint32_t) as a word count, overrunning the stack on
|
||||
// every setRxPath() and LoRa modulation change. The pin here, 7.7.1, predates that workaround, so
|
||||
// the LR2021 DC-DC tests register only on a RadioLib that has it (see lr2021_tests.h). On one that
|
||||
// also carries the overrun (7.8.0) they abort, so the LR2021 set runs last and the rest report first.
|
||||
//
|
||||
// Anything that decodes a chip reply (begin(), readData(), getRSSI(), updateFirmware()) needs replies
|
||||
// scripted per opcode; each family's header lists those under "Grows here".
|
||||
#include "TestUtil.h"
|
||||
#include <RadioLib.h>
|
||||
#include <unity.h>
|
||||
|
||||
#include "lr11x0_tests.h"
|
||||
#include "lr2021_tests.h"
|
||||
#include "sx126x_tests.h"
|
||||
#include "sx127x_tests.h"
|
||||
#include "sx128x_tests.h"
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN();
|
||||
runSx126xTests();
|
||||
runSx127xTests();
|
||||
runSx128xTests();
|
||||
runLr11x0Tests();
|
||||
runLr2021Tests(); // last: its DC-DC tests abort under ASan on a RadioLib with the #1864 overrun
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
Reference in new issue
Block a user