Public Access
M3 step 1: RadioLib and lora probe
The probe finds the SX1262 (TCXO 1.8 V works) and measures the antenna path: the Cap's PI4IOE5V6408 at 0x43 must drive P0 high, or the receiver is deaf (-111.9 dBm flat vs -87 to -94 dBm with P0 high). DIO2 makes no difference to reception. Receive only; the radio is left asleep. RadioLib 7.8.1 + probe: +23.6 KB flash, +656 B static RAM (release). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
#include "lora_probe.h"
|
||||
|
||||
#include <M5Cardputer.h>
|
||||
#include <RadioLib.h>
|
||||
|
||||
#include "pins.h"
|
||||
#include "shared_spi.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
namespace {
|
||||
|
||||
// The antenna switch enable M5Stack's page describes: P0 of a PI4IOE5V6408 on the internal I2C bus.
|
||||
constexpr uint8_t kExpanderAddresses[] = {0x43, 0x44};
|
||||
constexpr uint32_t kI2cFreq = 400000;
|
||||
enum ExpanderReg : uint8_t { kId = 0x01, kDirection = 0x03, kOutput = 0x05, kHighZ = 0x07, kInput = 0x0F };
|
||||
|
||||
// Meshtastic EU868 LongFast (Q95).
|
||||
constexpr float kFreq = 869.525f, kBw = 250.0f;
|
||||
constexpr uint8_t kSf = 11, kCr = 5, kSyncWord = 0x2B;
|
||||
constexpr uint16_t kPreamble = 16;
|
||||
|
||||
Module& module() {
|
||||
// SPI transactions take the bus lock the SD card uses (Q93); the HAL doesn't re-begin the bus.
|
||||
static ArduinoHal hal(sharedSpi(), SPISettings(8000000, MSBFIRST, SPI_MODE0));
|
||||
static Module m(&hal, pins::kLoraCs, pins::kLoraIrq, pins::kLoraReset, pins::kLoraBusy);
|
||||
return m;
|
||||
}
|
||||
|
||||
SX1262& radio() {
|
||||
static SX1262 sx(&module());
|
||||
return sx;
|
||||
}
|
||||
|
||||
// Mean and spread of the instantaneous RSSI while receiving: the noise floor where nothing transmits.
|
||||
void noiseFloor(SX1262& sx, float& mean, float& low, float& high) {
|
||||
sx.startReceive();
|
||||
delay(20);
|
||||
float sum = 0;
|
||||
low = 0, high = -200;
|
||||
constexpr int kSamples = 64;
|
||||
for (int i = 0; i < kSamples; ++i) {
|
||||
float r = sx.getRSSI(false);
|
||||
sum += r;
|
||||
low = std::min(low, r), high = std::max(high, r);
|
||||
delay(3);
|
||||
}
|
||||
mean = sum / kSamples;
|
||||
sx.standby();
|
||||
}
|
||||
|
||||
int findExpander() {
|
||||
for (uint8_t a : kExpanderAddresses)
|
||||
if (M5.In_I2C.scanID(a, kI2cFreq)) return a;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void setP0High(uint8_t addr) {
|
||||
auto& i2c = M5.In_I2C;
|
||||
i2c.writeRegister8(addr, kOutput, i2c.readRegister8(addr, kOutput, kI2cFreq) | 1, kI2cFreq);
|
||||
i2c.writeRegister8(addr, kHighZ, i2c.readRegister8(addr, kHighZ, kI2cFreq) & ~1, kI2cFreq);
|
||||
i2c.writeRegister8(addr, kDirection, i2c.readRegister8(addr, kDirection, kI2cFreq) | 1, kI2cFreq);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void loraProbe(Print& out) {
|
||||
// The internal I2C bus: what answers, and the expander's registers if there is one.
|
||||
bool found[120] = {};
|
||||
M5.In_I2C.scanID(found, kI2cFreq);
|
||||
out.print("lora probe: i2c (internal bus, 8/9):");
|
||||
for (int a = 8; a < 120; ++a)
|
||||
if (found[a]) out.printf(" 0x%02X", a);
|
||||
out.println();
|
||||
int expander = findExpander();
|
||||
if (expander < 0) {
|
||||
out.println("lora probe: expander none at 0x43/0x44");
|
||||
} else {
|
||||
auto r = [&](uint8_t reg) { return M5.In_I2C.readRegister8(expander, reg, kI2cFreq); };
|
||||
out.printf("lora probe: expander 0x%02X id %02X dir %02X out %02X highz %02X in %02X\n", expander, r(kId),
|
||||
r(kDirection), r(kOutput), r(kHighZ), r(kInput));
|
||||
}
|
||||
|
||||
// The chip, with the TCXO first and the crystal as the fallback (Meshtastic's TCXO_OPTIONAL).
|
||||
SX1262& sx = radio();
|
||||
float tcxo = 1.8f;
|
||||
uint32_t t0 = millis();
|
||||
int16_t state = sx.begin(kFreq, kBw, kSf, kCr, kSyncWord, 0, kPreamble, tcxo, false);
|
||||
if (state != RADIOLIB_ERR_NONE) {
|
||||
out.printf("lora probe: begin with TCXO 1.8 V: error %d, trying the crystal\n", state);
|
||||
tcxo = 0;
|
||||
state = sx.begin(kFreq, kBw, kSf, kCr, kSyncWord, 0, kPreamble, tcxo, false);
|
||||
}
|
||||
if (state != RADIOLIB_ERR_NONE) return (void)out.printf("lora probe: error %d, no SX1262 found\n", state);
|
||||
char version[17] = {};
|
||||
module().SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast<uint8_t*>(version));
|
||||
out.printf("lora probe: %s, %s, ready in %u ms\n", version, tcxo > 0 ? "TCXO 1.8 V" : "crystal (no TCXO)",
|
||||
(unsigned)(millis() - t0));
|
||||
out.printf("lora probe: %.3f MHz, BW %.0f kHz, SF %u, CR 4/%u, sync 0x%02X (LongFast)\n", kFreq, kBw, kSf, kCr,
|
||||
kSyncWord);
|
||||
|
||||
// The antenna switch, by measuring: a receive path that isn't connected to the antenna is
|
||||
// quieter, so the arrangement with the higher noise floor is the one that hears the antenna.
|
||||
auto measure = [&](const char* what) {
|
||||
float mean, low, high;
|
||||
noiseFloor(sx, mean, low, high);
|
||||
out.printf("lora probe: noise %-24s %7.1f dBm (%.1f to %.1f)\n", what, mean, low, high);
|
||||
};
|
||||
sx.setDio2AsRfSwitch(false);
|
||||
measure("DIO2 off");
|
||||
sx.setDio2AsRfSwitch(true);
|
||||
measure("DIO2 on");
|
||||
if (expander >= 0) {
|
||||
setP0High(expander);
|
||||
measure("DIO2 on, expander P0 high");
|
||||
sx.setDio2AsRfSwitch(false);
|
||||
measure("DIO2 off, expander P0 high");
|
||||
sx.setDio2AsRfSwitch(true);
|
||||
}
|
||||
sx.sleep();
|
||||
out.println("lora probe: done, radio asleep");
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <Print.h>
|
||||
|
||||
namespace roro {
|
||||
|
||||
// `lora probe` (M3 step 1, Q91): finds the SX1262 on the Cap and how its oscillator and antenna
|
||||
// switch are wired, by measuring. Receive only; leaves the radio asleep.
|
||||
void loraProbe(Print& out);
|
||||
|
||||
} // namespace roro
|
||||
@@ -14,4 +14,9 @@ constexpr int kSpiMosi = 14;
|
||||
constexpr int kSdCs = 12;
|
||||
constexpr int kLoraCs = 5;
|
||||
|
||||
// The SX1262 on the Cap (as in Meshtastic's board file for the Cardputer ADV).
|
||||
constexpr int kLoraReset = 3;
|
||||
constexpr int kLoraIrq = 4; // DIO1
|
||||
constexpr int kLoraBusy = 6;
|
||||
|
||||
} // namespace roro::pins
|
||||
|
||||
Reference in New Issue
Block a user