firmware/src/main.ino

503 lines
14 KiB
Arduino
Raw Normal View History

/*
Main module
# Modified by Kyle T. Gabriel to fix issue with incorrect GPS data for TTNMapper
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "configuration.h"
#include "rom/rtc.h"
#include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "BluetoothUtil.h"
2020-02-02 00:14:34 +00:00
#include "MeshBluetoothService.h"
#include "MeshService.h"
2020-02-06 15:39:21 +00:00
#include "GPS.h"
2020-02-07 21:51:17 +00:00
#include "screen.h"
#include "NodeDB.h"
#include "Periodic.h"
#ifdef T_BEAM_V10
#include "axp20x.h"
AXP20X_Class axp;
bool pmu_irq = false;
#endif
2020-02-12 17:13:49 +00:00
bool isCharging = false;
2020-02-14 22:00:08 +00:00
bool isUSBPowered = false;
2020-02-12 17:13:49 +00:00
bool ssd1306_found = false;
bool axp192_found = false;
bool packetSent, packetQueued;
// deep sleep support
RTC_DATA_ATTR int bootCount = 0;
esp_sleep_source_t wakeCause; // the reason we booted this time
// -----------------------------------------------------------------------------
// Application
// -----------------------------------------------------------------------------
void doDeepSleep(uint64_t msecToWake)
{
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
2020-02-02 00:14:34 +00:00
// not using wifi yet, but once we are this is needed to shutoff the radio hw
// esp_wifi_stop();
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
2020-02-02 00:14:34 +00:00
screen_off(); // datasheet says this will draw only 10ua
// Put radio in sleep mode (will still draw power but only 0.2uA)
service.radio.sleep();
nodeDB.saveToDisk();
#ifdef RESET_OLED
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
#endif
#ifdef VEXT_ENABLE
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
#endif
#ifdef LED_PIN
digitalWrite(LED_PIN, 0); // turn off the led
#endif
2020-02-02 00:14:34 +00:00
#ifdef T_BEAM_V10
if (axp192_found)
{
2020-02-02 21:55:44 +00:00
axp.setChgLEDMode(AXP20X_LED_OFF); // turn off the AXP LED
2020-02-02 16:17:45 +00:00
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
// leave floating input for the IRQ line
2020-02-02 16:17:45 +00:00
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
// all the time.
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
2020-02-02 00:14:34 +00:00
axp.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // GPS main power
}
#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.
2020-02-02 16:17:45 +00:00
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
12, 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]);
2020-02-02 02:45:27 +00:00
// 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.
2020-02-02 16:17:45 +00:00
2020-02-02 00:14:34 +00:00
// FIXME - use an external 10k pulldown so we can leave the RTC peripherals powered off
// until then we need the following lines
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
2020-02-01 22:23:21 +00:00
#ifdef BUTTON_PIN
2020-02-02 00:14:34 +00:00
// 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);
2020-02-02 16:17:45 +00:00
// 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);
2020-02-02 00:14:34 +00:00
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
2020-02-01 22:23:21 +00:00
#endif
2020-02-02 00:14:34 +00:00
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
}
2020-02-12 00:04:25 +00:00
#include "esp32/pm.h"
#include "esp_pm.h"
/**
* enable modem sleep mode as needed and available. Should lower our CPU current draw to an average of about 20mA.
*
* per https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/power_management.html
*/
2020-02-12 22:24:57 +00:00
void enableModemSleep()
{
2020-02-12 00:04:25 +00:00
static esp_pm_config_esp32_t config; // filled with zeros because bss
config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
config.min_freq_mhz = 10; // 10Mhz is minimum recommended
2020-02-12 22:24:57 +00:00
config.light_sleep_enable = false;
2020-02-12 00:04:25 +00:00
DEBUG_MSG("Sleep request result %x\n", esp_pm_configure(&config));
}
2020-02-02 00:14:34 +00:00
void sleep()
{
2020-02-02 02:45:27 +00:00
#ifdef SLEEP_MSECS
// If the user has a screen, tell them we are about to sleep
2020-02-02 00:14:34 +00:00
if (ssd1306_found)
{
// Show the going to sleep message on the screen
char buffer[20];
snprintf(buffer, sizeof(buffer), "Sleeping in %3.1fs\n", (MESSAGE_TO_SLEEP_DELAY / 1000.0));
screen_print(buffer);
// Wait for MESSAGE_TO_SLEEP_DELAY millis to sleep
delay(MESSAGE_TO_SLEEP_DELAY);
2020-02-02 00:14:34 +00:00
}
// We sleep for the interval between messages minus the current millis
// this way we distribute the messages evenly every SEND_INTERVAL millis
2020-02-02 02:45:27 +00:00
doDeepSleep(SLEEP_MSECS);
#endif
}
void scanI2Cdevice(void)
{
2020-02-02 00:14:34 +00:00
byte err, addr;
int nDevices = 0;
for (addr = 1; addr < 127; addr++)
{
Wire.beginTransmission(addr);
err = Wire.endTransmission();
if (err == 0)
{
DEBUG_MSG("I2C device found at address 0x%x\n", addr);
2020-02-02 00:14:34 +00:00
nDevices++;
if (addr == SSD1306_ADDRESS)
{
ssd1306_found = true;
DEBUG_MSG("ssd1306 display found\n");
2020-02-02 00:14:34 +00:00
}
#ifdef T_BEAM_V10
if (addr == AXP192_SLAVE_ADDRESS)
{
axp192_found = true;
DEBUG_MSG("axp192 PMU found\n");
2020-02-02 00:14:34 +00:00
}
#endif
}
2020-02-02 00:14:34 +00:00
else if (err == 4)
{
DEBUG_MSG("Unknow error at address 0x%x\n", addr);
2020-02-02 00:14:34 +00:00
}
}
if (nDevices == 0)
DEBUG_MSG("No I2C devices found\n");
2020-02-02 00:14:34 +00:00
else
DEBUG_MSG("done\n");
}
/**
* Init the power manager chip
*
* axp192 power
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 share the same i2c bus, instead use ssd1306 sleep mode
DCDC2 -> unused
DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!)
LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can not be turned off
LDO2 200mA -> LORA
LDO3 200mA -> GPS
*/
2020-02-02 00:14:34 +00:00
void axp192Init()
{
#ifdef T_BEAM_V10
if (axp192_found)
{
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
{
DEBUG_MSG("AXP192 Begin PASS\n");
// axp.setChgLEDMode(LED_BLINK_4HZ);
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("----------------------------------------\n");
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
axp.setDCDC1Voltage(3300); // for the OLED power
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
axp.debugCharging();
2020-02-12 17:13:49 +00:00
#ifdef PMU_IRQ
pinMode(PMU_IRQ, INPUT_PULLUP);
attachInterrupt(PMU_IRQ, [] {
pmu_irq = true;
},
FALLING);
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
axp.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ, 1);
axp.clearIRQ();
2020-02-12 17:13:49 +00:00
#endif
2020-02-12 17:13:49 +00:00
isCharging = axp.isChargeing();
2020-02-14 22:00:08 +00:00
isUSBPowered = axp.isVBUSPlug();
2020-02-02 00:14:34 +00:00
}
else
{
DEBUG_MSG("AXP192 Begin FAIL\n");
}
2020-02-02 00:14:34 +00:00
}
else
{
DEBUG_MSG("AXP192 not found\n");
2020-02-02 00:14:34 +00:00
}
#endif
}
// Perform power on init that we do on each wake from deep sleep
2020-02-02 00:14:34 +00:00
void initDeepSleep()
{
bootCount++;
wakeCause = esp_sleep_get_wakeup_cause();
/*
Not using yet because we are using wake on all buttons being low
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed
wakeButtons = ((uint64_t)1) << buttons.gpios[0];
*/
DEBUG_MSG("booted, wake cause %d (boot count %d)\n", wakeCause, bootCount);
}
2020-02-02 20:55:26 +00:00
const char *getDeviceName()
{
uint8_t dmac[6];
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
// Meshtastic_ab3c
static char name[20];
sprintf(name, "Meshtastic_%02x%02x", dmac[4], dmac[5]);
2020-02-02 20:55:26 +00:00
return name;
}
2020-02-02 00:14:34 +00:00
void setup()
{
// Debug
#ifdef DEBUG_PORT
DEBUG_PORT.begin(SERIAL_BAUD);
2020-02-02 00:14:34 +00:00
#endif
initDeepSleep();
// delay(1000); FIXME - remove
2020-02-01 22:23:21 +00:00
#ifdef VEXT_ENABLE
pinMode(VEXT_ENABLE, OUTPUT);
digitalWrite(VEXT_ENABLE, 0); // turn on the display power
2020-02-02 00:14:34 +00:00
#endif
2020-02-01 22:23:21 +00:00
#ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
2020-02-02 00:14:34 +00:00
#endif
2020-02-01 22:23:21 +00:00
#ifdef I2C_SDA
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
#endif
axp192Init();
// Buttons & LED
2020-02-01 22:23:21 +00:00
#ifdef BUTTON_PIN
pinMode(BUTTON_PIN, INPUT_PULLUP);
2020-02-01 22:23:21 +00:00
digitalWrite(BUTTON_PIN, 1);
#endif
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
2020-02-02 00:05:12 +00:00
digitalWrite(LED_PIN, 1); // turn on for now
#endif
// Hello
DEBUG_MSG(APP_NAME " " APP_VERSION "\n");
// Don't init display if we don't have one or we are waking headless due to a timer event
2020-02-02 00:14:34 +00:00
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
ssd1306_found = false; // forget we even have the hardware
2020-02-02 00:14:34 +00:00
if (ssd1306_found)
screen_setup();
// Init GPS
2020-02-06 15:39:21 +00:00
gps.setup();
2020-02-08 01:48:12 +00:00
screen_print("Started...\n");
service.init();
2020-02-05 05:24:11 +00:00
bool useBluetooth = true;
if (useBluetooth)
{
DEBUG_MSG("Starting bluetooth\n");
2020-02-14 22:00:08 +00:00
BLEServer *serve = initBLE(getDeviceName(), HW_VENDOR, APP_VERSION, HW_VERSION); // FIXME, use a real name based on the macaddr
2020-02-05 05:24:11 +00:00
createMeshBluetoothService(serve);
// Start advertising - this must be done _after_ creating all services
serve->getAdvertising()->start();
2020-02-05 05:24:11 +00:00
}
2020-02-12 00:04:25 +00:00
enableModemSleep();
}
uint32_t ledBlinker()
{
static bool ledOn;
ledOn ^= 1;
#ifdef LED_PIN
// toggle the led so we can get some rough sense of how often loop is pausing
digitalWrite(LED_PIN, ledOn);
#endif
if (axp192_found)
{
// blink the axp led
axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
}
// have a very sparse duty cycle of LED being on
return ledOn ? 2 : 1000;
}
Periodic ledPeriodic(ledBlinker);
2020-02-02 00:14:34 +00:00
void loop()
{
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
2020-02-06 15:39:21 +00:00
gps.loop();
msecstosleep = min(screen_loop(), msecstosleep);
service.loop();
ledPeriodic.loop();
loopBLE();
2020-02-08 17:39:26 +00:00
2020-02-03 02:33:46 +00:00
#ifdef T_BEAM_V10
if (axp192_found)
{
2020-02-12 17:13:49 +00:00
#ifdef PMU_IRQ
2020-02-08 15:55:12 +00:00
if (pmu_irq)
{
pmu_irq = false;
axp.readIRQ();
2020-02-12 17:13:49 +00:00
2020-02-14 22:00:08 +00:00
isCharging = axp.isChargeing();
isUSBPowered = axp.isVBUSPlug();
2020-02-12 22:24:57 +00:00
2020-02-14 22:00:08 +00:00
axp.clearIRQ();
2020-02-08 15:55:12 +00:00
}
2020-02-12 17:13:49 +00:00
#endif
2020-02-02 21:55:44 +00:00
}
2020-02-03 02:33:46 +00:00
#endif
2020-02-02 21:55:44 +00:00
2020-02-01 22:23:21 +00:00
#ifdef BUTTON_PIN
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of this boilerplate)
static bool wasPressed = false;
static uint32_t minPressMs; // what tick should we call this press long enough
static uint32_t lastPingMs, lastPressMs;
2020-02-02 00:14:34 +00:00
if (!digitalRead(BUTTON_PIN))
{
if (!wasPressed)
{ // just started a new press
DEBUG_MSG("pressing\n");
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
wasPressed = true;
2020-02-12 22:24:57 +00:00
uint32_t now = millis();
lastPressMs = now;
2020-02-12 22:24:57 +00:00
minPressMs = now + 3000;
if (now - lastPingMs > 60 * 1000)
{ // if more than a minute since our last press, ask other nodes to update their state
service.sendNetworkPing();
lastPingMs = now;
}
2020-02-08 01:48:12 +00:00
screen_press();
2020-02-02 00:14:34 +00:00
}
}
else if (wasPressed)
{
// we just did a release
wasPressed = false;
2020-02-02 00:14:34 +00:00
if (millis() > minPressMs)
{
// held long enough
screen_print("Erasing prefs");
delay(5000); // Give some time to read the screen
// ESP.restart();
}
}
2020-02-01 22:23:21 +00:00
#endif
2020-02-02 02:45:27 +00:00
#ifdef MINWAKE_MSECS
// Don't deepsleep if we have USB power or if the user as pressed a button recently
2020-02-14 22:00:08 +00:00
if (millis() - lastPressMs > MINWAKE_MSECS && !isUSBPowered)
{
sleep();
}
#endif
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
// i.e. don't just keep spinning in loop as fast as we can.
//DEBUG_MSG("msecs %d\n", msecstosleep);
2020-02-08 01:48:12 +00:00
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons feel slow
msecstosleep = 10;
delay(msecstosleep);
}