firmware/src/main.ino

431 lines
13 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"
#ifdef T_BEAM_V10
#include "axp20x.h"
AXP20X_Class axp;
bool pmu_irq = false;
String baChStatus = "No charging";
#endif
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)
{
2020-02-02 00:14:34 +00:00
Serial.printf("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
// 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();
#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-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)
{
Serial.print("I2C device found at address 0x");
if (addr < 16)
Serial.print("0");
Serial.print(addr, HEX);
Serial.println(" !");
nDevices++;
if (addr == SSD1306_ADDRESS)
{
ssd1306_found = true;
Serial.println("ssd1306 display found");
}
#ifdef T_BEAM_V10
if (addr == AXP192_SLAVE_ADDRESS)
{
axp192_found = true;
Serial.println("axp192 PMU found");
}
#endif
}
2020-02-02 00:14:34 +00:00
else if (err == 4)
{
Serial.print("Unknow error at address 0x");
if (addr < 16)
Serial.print("0");
Serial.println(addr, HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found\n");
else
Serial.println("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))
{
Serial.println("AXP192 Begin PASS");
}
else
{
Serial.println("AXP192 Begin FAIL");
}
// axp.setChgLEDMode(LED_BLINK_4HZ);
Serial.printf("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
Serial.printf("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
Serial.printf("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
Serial.printf("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
Serial.printf("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
Serial.printf("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
Serial.println("----------------------------------------");
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
Serial.printf("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
Serial.printf("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
Serial.printf("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
Serial.printf("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
Serial.printf("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
Serial.printf("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
2020-02-03 02:33:46 +00:00
axp.debugCharging();
2020-02-02 00:14:34 +00:00
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();
if (axp.isChargeing())
{
baChStatus = "Charging";
}
2020-02-02 00:14:34 +00:00
}
else
{
Serial.println("AXP192 not found");
}
#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];
*/
2020-02-02 00:14:34 +00:00
Serial.printf("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
gps_setup();
// Show logo on first boot after removing battery
2020-02-02 00:05:12 +00:00
//if (bootCount == 0) {
2020-02-02 00:14:34 +00:00
screen_print(APP_NAME " " APP_VERSION, 0, 0);
screen_show_logo();
screen_update();
delay(LOGO_DELAY);
2020-02-02 00:05:12 +00:00
//}
service.init();
BLEServer *serve = initBLE(getDeviceName(), HW_VENDOR, APP_VERSION); // FIXME, use a real name based on the macaddr
2020-02-02 21:55:44 +00:00
createMeshBluetoothService(serve);
}
2020-02-02 00:14:34 +00:00
void loop()
{
gps_loop();
screen_loop();
service.loop();
loopBLE();
2020-02-02 21:55:44 +00:00
static bool ledon;
ledon ^= 1;
2020-02-02 02:45:27 +00:00
#ifdef LED_PIN
// toggle the led so we can get some rough sense of how often loop is pausing
2020-02-02 21:55:44 +00:00
digitalWrite(LED_PIN, ledon);
#endif
2020-02-03 02:33:46 +00:00
#ifdef T_BEAM_V10
if (axp192_found)
{
2020-02-02 21:55:44 +00:00
// blink the axp led
axp.setChgLEDMode(ledon ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
}
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
2020-02-02 00:14:34 +00:00
if (!digitalRead(BUTTON_PIN))
{
if (!wasPressed)
{ // just started a new press
Serial.println("pressing");
wasPressed = true;
minPressMs = millis() + 3000;
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
if (millis() > MINWAKE_MSECS)
{
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.
delay(100);
}