Merge pull request #521 from meshtastic/master

Update dev-https from master
This commit is contained in:
Jm Casler 2020-11-13 21:35:20 -08:00 committed by GitHub
commit 71d1d4d8fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 412 additions and 192 deletions

View File

@ -1,3 +1,3 @@
export VERSION=1.1.7
export VERSION=1.1.8

View File

@ -1,7 +1,7 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
"ldscript": "nrf52840_s113_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
@ -16,9 +16,9 @@
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_flags": "-DS113",
"sd_name": "s113",
"sd_version": "7.2.0",
"sd_fwid": "0x00B6"
},
"bootloader": {

Binary file not shown.

View File

@ -2,15 +2,11 @@
You probably don't care about this section - skip to the next one.
Threading tasks:
For high speed/lots of devices/short range tasks:
- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines
- clean up main loop()
- check that we are mostly asleep, show which thread is causing us to wake
-
- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle
- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?)
- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed.
- When guessing numhops for sending: if I've heard from many local (0 hop neighbors) decrease hopcount by 2 rather than 1.
This should nicely help 'router' nodes do the right thing when long range, or if there are many local nodes for short range.
- fix timeouts/delays to be based on packet length at current radio settings
Nimble tasks:

View File

@ -14,6 +14,13 @@ Notes here on using that driver: https://www.linuxquestions.org/questions/linux-
Or if **absolutely** necessary could bitbang: https://www.cnx-software.com/2018/02/16/wch-ch341-usb-to-serial-chip-gets-linux-driver-to-control-gpios-over-usb/
## Portduino tasks
* How to access spi devices via ioctl (spidev): https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md#:~:text=Troubleshooting-,Overview,bus)%2C%20UARTs%2C%20etc.
* access gpio via libgpiod?
* Use dkms to distribute driver?
* echo 100 > /sys/module/spi_ch341_usb/parameters/poll_period
## Task list
* Port meshtastic to build (under platformio) for a poxix target. spec: no screen, no gpios, sim network interface, posix threads, posix semaphores & queues, IO to the console only

View File

@ -251,13 +251,11 @@ void PowerFSM_setup()
#ifndef NRF52_SERIES
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
lowPowerState = &stateDARK;
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
#else
lowPowerState = &stateDARK;
#endif
auto meshSds = getPref_mesh_sds_timeout_secs();

View File

@ -1,4 +1,5 @@
#include "RedirectablePrint.h"
#include "configuration.h"
#include <assert.h>
/**
@ -11,3 +12,13 @@ void RedirectablePrint::setDestination(Print *_dest)
assert(_dest);
dest = _dest;
}
size_t RedirectablePrint::write(uint8_t c)
{
#ifdef SEGGER_STDOUT_CH
SEGGER_RTT_PutCharSkip(SEGGER_STDOUT_CH, c);
#endif
dest->write(c);
return 1; // We always claim one was written, rather than trusting what the serial port said (which could be zero)
}

View File

@ -19,7 +19,7 @@ class RedirectablePrint : public Print
*/
void setDestination(Print *dest);
virtual size_t write(uint8_t c) { return dest->write(c); }
virtual size_t write(uint8_t c);
};
class NoopPrint : public Print

View File

@ -404,8 +404,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Always include the SEGGER code on NRF52 - because useful for debugging
#include "SEGGER_RTT.h"
// The channel we send stdout data to
#define SEGGER_STDOUT_CH 0
// Debug printing to segger console
#define SEGGER_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define SEGGER_MSG(...) SEGGER_RTT_printf(SEGGER_STDOUT_CH, __VA_ARGS__)
// If we are not on a NRF52840 (which has built in USB-ACM serial support) and we don't have serial pins hooked up, then we MUST
// use SEGGER for debug output

View File

@ -9,6 +9,7 @@
#include "utils.h"
#include <nvs.h>
#include <nvs_flash.h>
#include <driver/rtc_io.h>
void getMacAddr(uint8_t *dmac)
{
@ -87,3 +88,57 @@ void esp32Loop()
// for debug printing
// radio.radioIf.canSleep();
}
void cpuDeepSleep(uint64_t msecToWake)
{
/*
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
If an external circuit drives this pin in deep sleep mode, current consumption may
increase due to current flowing through these pullups and pulldowns.
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
some current will flow through these external and internal resistors, increasing deep
sleep current above the minimal possible value.
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
*/
static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */
#ifndef USE_JTAG
13,
/* 14, */ /* 15, */
#endif
/* 25, */ 26, /* 27, */
32, 33, 34, 35,
36, 37
/* 38, 39 */};
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#ifdef BUTTON_PIN
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = (1ULL << BUTTON_PIN);
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
}

View File

@ -25,9 +25,15 @@ uint8_t GPS::i2cAddress = 0;
GPS *gps;
/// Multiple GPS instances might use the same serial port (in sequence), but we can
/// only init that port once.
static bool didSerialInit;
bool GPS::setupGPS()
{
if (_serial_gps) {
if (_serial_gps && !didSerialInit) {
didSerialInit = true;
#ifdef GPS_RX_PIN
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
#else
@ -59,8 +65,10 @@ bool GPS::setup()
setAwake(true); // Wake GPS power before doing any init
bool ok = setupGPS();
if (ok)
if (ok) {
notifySleepObserver.observe(&notifySleep);
notifyDeepSleepObserver.observe(&notifyDeepSleep);
}
return ok;
}
@ -275,7 +283,19 @@ void GPS::forceWake(bool on)
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
int GPS::prepareSleep(void *unused)
{
DEBUG_MSG("GPS prepare sleep!\n");
forceWake(false);
return 0;
}
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
int GPS::prepareDeepSleep(void *unused)
{
DEBUG_MSG("GPS deep sleep!\n");
// For deep sleep we also want abandon any lock attempts (because we want minimum power)
setAwake(false);
return 0;
}

View File

@ -30,6 +30,7 @@ class GPS : private concurrency::OSThread
uint8_t numSatellites = 0;
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
public:
/** If !NULL we will use this serial port to construct our GPS */
@ -115,6 +116,10 @@ class GPS : private concurrency::OSThread
/// always returns 0 to indicate okay to sleep
int prepareSleep(void *unused);
/// Prepare the GPS for the cpu entering deep sleep, expect to be gone for at least 100s of msecs
/// always returns 0 to indicate okay to sleep
int prepareDeepSleep(void *unused);
/**
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
*

View File

@ -85,13 +85,16 @@ static uint16_t displayWidth, displayHeight;
#define SCREEN_TRANSITION_MSECS 300
#endif
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
/**
* Draw the icon with extra info printed around the corners
*/
static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// draw an xbm image.
// Please note that everything that should be transitioned
// needs to be drawn relative to x and y
// draw centered left to right and centered above the one line of app text
// draw centered icon left to right and centered above the one line of app text
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
icon_width, icon_height, (const uint8_t *)icon_bits);
@ -101,15 +104,31 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
const char *region = myRegion ? myRegion->name : NULL;
if (region)
display->drawString(x + 0, y + 0, region);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
// Draw version in upper right
char buf[16];
snprintf(buf, sizeof(buf), "%s",
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
screen->forceDisplay();
// FIXME - draw serial # somewhere?
}
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawIconScreen(region, display, state, x, y);
}
/// Used on eink displays while in deep sleep
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
drawIconScreen("Sleeping...", display, state, x, y);
}
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@ -600,6 +619,21 @@ Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue
cmdQueue.setReader(this);
}
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
*/
void Screen::doDeepSleep()
{
#ifdef HAS_EINK
static FrameCallback sleepFrames[] = {drawSleepScreen};
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
ui.setFrames(sleepFrames, sleepFrameCount);
ui.update();
#endif
setOn(false);
}
void Screen::handleSetOn(bool on)
{
if (!useDisplay)

View File

@ -99,6 +99,12 @@ class Screen : public concurrency::OSThread
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
}
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
*/
void doDeepSleep();
/// Handles a button press.
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }

View File

@ -172,6 +172,10 @@ class ButtonThread : public OSThread
{
#ifdef BUTTON_PIN
userButton = OneButton(BUTTON_PIN, true, true);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN, INPUT_PULLUP_SENSE);
#endif
userButton.attachClick(userButtonPressed);
userButton.attachDuringLongPress(userButtonPressedLong);
userButton.attachDoubleClick(userButtonDoublePressed);
@ -179,6 +183,10 @@ class ButtonThread : public OSThread
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressed);
userButtonAlt.attachDuringLongPress(userButtonPressedLong);
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
@ -233,8 +241,8 @@ RadioInterface *rIf = NULL;
void setup()
{
#ifdef USE_SEGGER
SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
#ifdef SEGGER_STDOUT_CH
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, 1024, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
#endif
// Debug

View File

@ -143,11 +143,15 @@ bool NodeDB::resetRadioConfig()
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
radioConfig.preferences.screen_on_secs = 30;
radioConfig.preferences.wait_bluetooth_secs = 30;
radioConfig.preferences.screen_on_secs = 10;
radioConfig.preferences.wait_bluetooth_secs = 10;
radioConfig.preferences.position_broadcast_secs = 6 * 60;
radioConfig.preferences.ls_secs = 60;
radioConfig.preferences.region = RegionCode_TW;
// Enter super deep sleep soon and stay there not very long
//radioConfig.preferences.mesh_sds_timeout_secs = 10;
//radioConfig.preferences.sds_secs = 60;
}
// Update the global myRegion

View File

@ -53,6 +53,68 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts
// 1kb was too small
#define RADIO_STACK_SIZE 4096
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t RadioInterface::getPacketTime(uint32_t pl)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
DEBUG_MSG("(bw=%d, sf=%d, cr=4/%d) packet symLen=%d ms, payloadSize=%u, time %d ms\n", (int)bw, sf, cr, (int)(tSym * 1000),
pl, msecs);
return msecs;
}
uint32_t RadioInterface::getPacketTime(MeshPacket *p)
{
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
uint32_t pl = p->encrypted.size + sizeof(PacketHeader);
return getPacketTime(pl);
}
/** The delay to use for retransmitting dropped packets */
uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p)
{
// was 20 and 22 secs respectively, but now with shortPacketMsec as 2269, this should give the same range
return random(9 * shortPacketMsec, 10 * shortPacketMsec);
}
/** The delay to use when we want to send something but the ether is busy */
uint32_t RadioInterface::getTxDelayMsec()
{
/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node)
* has had enough time to switch their radio back into receive mode.
*/
const uint32_t MIN_TX_WAIT_MSEC = 100;
/**
* At the high end, this value is used to spread node attempts across time so when they are replying to a packet
* they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount
* one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough
* to guarantee this.
*/
// const uint32_t MAX_TX_WAIT_MSEC = 2000; // stress test would still fail occasionally with 1000
return random(MIN_TX_WAIT_MSEC, shortPacketMsec);
}
void printPacket(const char *prefix, const MeshPacket *p)
{
DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff, p->want_ack,
@ -125,6 +187,12 @@ bool RadioInterface::init()
return true;
}
int RadioInterface::notifyDeepSleepCb(void *unused)
{
sleep();
return 0;
}
/** hash a string into an integer
*
* djb2 by Dan Bernstein.
@ -149,8 +217,47 @@ void RadioInterface::applyModemConfig()
// Set up default configuration
// No Sync Words in LORA mode
if (channelSettings.spread_factor == 0) {
switch (channelSettings.modem_config) {
case ChannelSettings_ModemConfig_Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium
///< range
bw = 125;
cr = 5;
sf = 7;
break;
case ChannelSettings_ModemConfig_Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short
///< range
bw = 500;
cr = 5;
sf = 7;
break;
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long
///< range
bw = 31.25;
cr = 8;
sf = 9;
break;
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
bw = 125;
cr = 8;
sf = 12;
break;
default:
assert(0); // Unknown enum
}
} else {
sf = channelSettings.spread_factor;
cr = channelSettings.coding_rate;
bw = channelSettings.bandwidth;
if (bw == 31) // This parameter is not an integer
bw = 31.25;
}
power = channelSettings.tx_power;
shortPacketMsec = getPacketTime(sizeof(PacketHeader));
assert(myRegion); // Should have been found in init
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
@ -165,6 +272,7 @@ void RadioInterface::applyModemConfig()
DEBUG_MSG("Radio myRegion->numChannels: %d\n", myRegion->numChannels);
DEBUG_MSG("Radio channel_num: %d\n", channel_num);
DEBUG_MSG("Radio frequency: %f\n", freq);
DEBUG_MSG("Short packet time: %u msec\n", shortPacketMsec);
}
/**

View File

@ -48,9 +48,18 @@ class RadioInterface
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::preflightSleepCb);
CallbackObserver<RadioInterface, void *> notifyDeepSleepObserver =
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::notifyDeepSleepDb);
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::notifyDeepSleepCb);
/// Number of msecs we expect our shortest actual packet to be over the wire (used in retry timeout calcs)
uint32_t shortPacketMsec;
protected:
float bw = 125;
uint8_t sf = 9;
uint8_t cr = 7;
uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
uint32_t lastTxStart = 0L;
@ -108,6 +117,22 @@ class RadioInterface
/// \return true if initialisation succeeded.
virtual bool reconfigure() = 0;
/** The delay to use for retransmitting dropped packets */
uint32_t getRetransmissionMsec(const MeshPacket *p);
/** The delay to use when we want to send something but the ether is busy */
uint32_t getTxDelayMsec();
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t getPacketTime(MeshPacket *p);
uint32_t getPacketTime(uint32_t totalPacketLen);
protected:
int8_t power = 17; // Set by applyModemConfig()
@ -136,11 +161,7 @@ class RadioInterface
/// Return 0 if sleep is okay
int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; }
int notifyDeepSleepDb(void *unused = NULL)
{
sleep();
return 0;
}
int notifyDeepSleepCb(void *unused = NULL);
int reloadConfig(void *unused)
{

View File

@ -58,50 +58,6 @@ void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
*/
RadioLibInterface *RadioLibInterface::instance;
/**
* Convert our modemConfig enum into wf, sf, etc...
*/
void RadioLibInterface::applyModemConfig()
{
RadioInterface::applyModemConfig();
if (channelSettings.spread_factor == 0) {
switch (channelSettings.modem_config) {
case ChannelSettings_ModemConfig_Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium
///< range
bw = 125;
cr = 5;
sf = 7;
break;
case ChannelSettings_ModemConfig_Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short
///< range
bw = 500;
cr = 5;
sf = 7;
break;
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long
///< range
bw = 31.25;
cr = 8;
sf = 9;
break;
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
bw = 125;
cr = 8;
sf = 12;
break;
default:
assert(0); // Unknown enum
}
} else {
sf = channelSettings.spread_factor;
cr = channelSettings.coding_rate;
bw = channelSettings.bandwidth;
if (bw == 31) // This parameter is not an integer
bw = 31.25;
}
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool RadioLibInterface::canSendImmediately()
@ -130,6 +86,8 @@ ErrorCode RadioLibInterface::send(MeshPacket *p)
// Sometimes when testing it is useful to be able to never turn on the xmitter
#ifndef LORA_DISABLE_SENDING
printPacket("enqueuing for send", p);
uint32_t xmitMsec = getPacketTime(p);
DEBUG_MSG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
@ -158,19 +116,6 @@ bool RadioLibInterface::canSleep()
return res;
}
/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node)
* has had enough time to switch their radio back into receive mode.
*/
#define MIN_TX_WAIT_MSEC 100
/**
* At the high end, this value is used to spread node attempts across time so when they are replying to a packet
* they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount
* one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough
* to guarantee this.
*/
#define MAX_TX_WAIT_MSEC 2000 // stress test would still fail occasionally with 1000
/** radio helper thread callback.
We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and
@ -226,8 +171,7 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.isEmpty()) {
uint32_t delay =
!withDelay ? 1 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
uint32_t delay = !withDelay ? 1 : getTxDelayMsec();
// DEBUG_MSG("xmit timer %d\n", delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
@ -236,20 +180,25 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
void RadioLibInterface::handleTransmitInterrupt()
{
// DEBUG_MSG("handling lora TX interrupt\n");
assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR!
completeSending();
// This can be null if we forced the device to enter standby mode. In that case
// ignore the transmit interrupt
if (sendingPacket)
completeSending();
}
void RadioLibInterface::completeSending()
{
if (sendingPacket) {
// We are careful to clear sending packet before calling printPacket because
// that can take a long time
auto p = sendingPacket;
sendingPacket = NULL;
if (p) {
txGood++;
printPacket("Completed sending", sendingPacket);
printPacket("Completed sending", p);
// We are done sending that packet, release it
packetPool.release(sendingPacket);
sendingPacket = NULL;
packetPool.release(p);
// DEBUG_MSG("Done with send\n");
}
}
@ -295,7 +244,7 @@ void RadioLibInterface::handleReceiveInterrupt()
addReceiveMetadata(mp);
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= sizeof(mp->encrypted.bytes));
assert(((uint32_t) payloadLen) <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;

View File

@ -77,9 +77,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
PointerQueue<MeshPacket> txQueue = PointerQueue<MeshPacket>(MAX_TX_QUEUE);
protected:
float bw = 125;
uint8_t sf = 9;
uint8_t cr = 7;
/**
* FIXME, use a meshtastic sync word, but hashed with the Channel name. Currently picking the same default
@ -88,7 +85,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE;
float currentLimit = 100; // FIXME
uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving
LockingModule module; // The HW interface to the radio
@ -165,13 +161,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Do any hardware setup needed on entry into send configuration for the radio. Subclasses can customize */
virtual void configHardwareForSend() {}
/**
* Convert our modemConfig enum into wf, sf, etc...
*
* These paramaters will be pull from the channelSettings global
*/
virtual void applyModemConfig();
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();

View File

@ -111,7 +111,6 @@ PendingPacket::PendingPacket(MeshPacket *p)
{
packet = p;
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
setNextTx();
}
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
@ -151,6 +150,7 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p);
setNextTx(&rec);
stopRetransmission(p->from, p->id);
pending[id] = rec;
@ -190,10 +190,9 @@ int32_t ReliableRouter::doRetransmissions()
// Queue again
--p.numRetransmissions;
p.setNextTx();
setNextTx(&p);
}
}
else {
} else {
// Not yet time
int32_t t = p.nextTxMsec - now;

View File

@ -46,8 +46,6 @@ struct PendingPacket {
PendingPacket() {}
PendingPacket(MeshPacket *p);
void setNextTx() { nextTxMsec = millis() + random(20 * 1000L, 22 * 1000L); }
};
class GlobalPacketIdHashFunction
@ -130,4 +128,8 @@ class ReliableRouter : public FloodingRouter
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
int32_t doRetransmissions();
void setNextTx(PendingPacket *pending) {
assert(iface);
pending->nextTxMsec = millis() + iface->getRetransmissionMsec(pending->packet); }
};

View File

@ -14,12 +14,13 @@
class Router : protected concurrency::OSThread
{
private:
RadioInterface *iface;
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
/// forwarded to the phone.
PointerQueue<MeshPacket> fromRadioQueue;
protected:
RadioInterface *iface = NULL;
public:
/// Local services that want to see _every_ packet this node receives can observe this.
/// Observers should always return 0 and _copy_ any packets they want to keep for use later (this packet will be getting

View File

@ -201,9 +201,21 @@ bool SX1262Interface::isActivelyReceiving()
bool SX1262Interface::sleep()
{
// put chipset into sleep mode
disableInterrupt();
lora.sleep();
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
DEBUG_MSG("sx1262 entering sleep mode (FIXME, don't keep config)\n");
setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
// FIXME - this isn't correct
// lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = true;
lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
#ifdef SX1262_POWER_EN
digitalWrite(SX1262_POWER_EN, LOW);
#endif
return true;
}

View File

@ -202,6 +202,13 @@ void setupMeshService(void)
// FIXME, turn off soft device access for debugging
static bool isSoftDeviceAllowed = true;
void NRF52Bluetooth::shutdown()
{
// Shutdown bluetooth for minimum power draw
DEBUG_MSG("Disable NRF52 bluetooth\n");
Bluefruit.Advertising.stop();
}
void NRF52Bluetooth::setup()
{
// Initialise the Bluefruit module

View File

@ -4,5 +4,6 @@ class NRF52Bluetooth
{
public:
void setup();
void shutdown();
};

View File

@ -1,6 +1,8 @@
#include "NRF52Bluetooth.h"
#include "configuration.h"
#include "graphics/TFTDisplay.h"
#include <SPI.h>
#include <Wire.h>
#include <assert.h>
#include <ble_gap.h>
#include <memory.h>
@ -49,7 +51,7 @@ void getMacAddr(uint8_t *dmac)
NRF52Bluetooth *nrf52Bluetooth;
static bool bleOn = false;
static const bool enableBle = true; // Set to false for easier debugging
static const bool enableBle = false; // Set to false for easier debugging
void setBluetoothEnable(bool on)
{
@ -64,7 +66,8 @@ void setBluetoothEnable(bool on)
}
}
} else {
DEBUG_MSG("FIXME: implement BLE disable\n");
if (nrf52Bluetooth)
nrf52Bluetooth->shutdown();
}
bleOn = on;
}
@ -97,7 +100,7 @@ void nrf52Setup()
#ifdef BQ25703A_ADDR
auto *bq = new BQ25713();
if(!bq->setup())
if (!bq->setup())
DEBUG_MSG("ERROR! Charge controller init failed\n");
#endif
@ -110,3 +113,29 @@ void nrf52Setup()
DEBUG_MSG("FIXME, call randomSeed\n");
// ::printf("TESTING PRINTF\n");
}
void cpuDeepSleep(uint64_t msecToWake)
{
// FIXME, configure RTC or button press to wake us
// FIXME, power down SPI, I2C, RAMs
Wire.end();
SPI.end();
Serial.end();
Serial1.end();
// FIXME, use system off mode with ram retention for key state?
// FIXME, use non-init RAM per
// https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled
auto ok = sd_power_system_off();
if(ok != NRF_SUCCESS) {
DEBUG_MSG("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!\n");
NRF_POWER->SYSTEMOFF = 1;
}
// The following code should not be run, because we are off
while (1) {
delay(5000);
DEBUG_MSG(".");
}
}

View File

@ -1,6 +1,7 @@
#include "CryptoEngine.h"
#include "target_specific.h"
#include <Utility.h>
#include "sleep.h"
// FIXME - move getMacAddr/setBluetoothEnable into a HALPlatform class
@ -26,6 +27,10 @@ void setBluetoothEnable(bool on)
notImplemented("setBluetoothEnable");
}
void cpuDeepSleep(uint64_t msecs) {
notImplemented("cpuDeepSleep");
}
// FIXME - implement real crypto for linux
CryptoEngine *crypto = new CryptoEngine();

View File

@ -142,19 +142,22 @@ static void waitEnterSleep()
void doDeepSleep(uint64_t msecToWake)
{
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
DEBUG_MSG("Entering deep sleep for %lu seconds\n", msecToWake / 1000);
#ifndef NO_ESP32
// not using wifi yet, but once we are this is needed to shutoff the radio hw
// esp_wifi_stop();
waitEnterSleep();
notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers
notifyDeepSleep.notifyObservers(NULL);
screen->setOn(false); // datasheet says this will draw only 10ua
screen->doDeepSleep(); // datasheet says this will draw only 10ua
nodeDB.saveToDisk();
// Kill GPS power completely (even if previously we just had it in sleep mode)
setGPSPower(false);
setLed(false);
#ifdef RESET_OLED
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
#endif
@ -163,11 +166,6 @@ void doDeepSleep(uint64_t msecToWake)
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
#endif
// Kill GPS power completely (even if previously we just had it in sleep mode)
setGPSPower(false);
setLed(false);
#ifdef TBEAM_V10
if (axp192_found) {
// Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep.
@ -185,57 +183,7 @@ void doDeepSleep(uint64_t msecToWake)
}
#endif
/*
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
If an external circuit drives this pin in deep sleep mode, current consumption may
increase due to current flowing through these pullups and pulldowns.
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
some current will flow through these external and internal resistors, increasing deep
sleep current above the minimal possible value.
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
*/
static const uint8_t rtcGpios[] = {/* 0, */ 2,
/* 4, */
#ifndef USE_JTAG
13,
/* 14, */ /* 15, */
#endif
/* 25, */ 26, /* 27, */
32, 33, 34, 35,
36, 37
/* 38, 39 */};
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
// to detect wake and in normal operation the external part drives them hard.
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#ifdef BUTTON_PIN
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
uint64_t gpioMask = (1ULL << BUTTON_PIN);
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
// Not needed because both of the current boards have external pullups
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
#endif
cpuDeepSleep(msecToWake);
}
#ifndef NO_ESP32

View File

@ -4,7 +4,8 @@
#include "Observer.h"
#include "configuration.h"
void doDeepSleep(uint64_t msecToWake);
void doDeepSleep(uint64_t msecToWake), cpuDeepSleep(uint64_t msecToWake);
#ifndef NO_ESP32
#include "esp_sleep.h"
esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake);

View File

@ -205,6 +205,7 @@ External serial flash WP25R1635FZUIL0
#define PIN_EINK_MOSI (0 + 29) // also called SDI
// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON
// FIXME - I think this is actually just the board power enable - it enables power to the CPU also
#define PIN_EINK_PWR_ON (0 + 12)
#define HAS_EINK

View File

@ -147,7 +147,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
#define SX1262_ANT_SW (32 + 10) // P1.10
// To debug via the segger JLINK console rather than the CDC-ACM serial device
#define USE_SEGGER
// #define USE_SEGGER
#ifdef __cplusplus
}

View File

@ -168,7 +168,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
#define LORA_DISABLE_SENDING // Define this to disable transmission for testing (power testing etc...)
// To debug via the segger JLINK console rather than the CDC-ACM serial device
#define USE_SEGGER
// #define USE_SEGGER
#ifdef __cplusplus
}