firmware/src/main.ino

361 lines
10 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 <TinyGPS++.h>
#include <Wire.h>
#include "BluetoothUtil.h"
2020-02-02 00:14:34 +00:00
#include "MeshBluetoothService.h"
2020-02-01 16:59:16 +00:00
#include "MeshRadio.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();
screen_off(); // datasheet says this will draw only 10ua
2020-02-02 02:45:27 +00:00
// FIXME, shutdown radiohead interrupts before powering off device
2020-02-02 00:14:34 +00:00
// Put radio in sleep mode (will still draw power but only 0.2uA)
radio.sleep();
2020-02-02 00:14:34 +00:00
#ifdef T_BEAM_V10
if (axp192_found)
{
// turn on after initial testing with real hardware
axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
axp.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // GPS main power
}
#endif
2020-02-02 02:45:27 +00:00
#ifdef VEXT_ENABLE
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
#endif
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 00:14:34 +00:00
// FIXME change polarity 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");
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 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
Wire.begin(I2C_SDA, I2C_SCL);
scanI2Cdevice();
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
//}
2020-02-01 16:59:16 +00:00
mesh_init();
2020-02-02 00:14:34 +00:00
BLEServer *serve = initBLE("KHBT Test"); // FIXME, use a real name based on the macaddr
BLEService *bts = createMeshBluetoothService(serve);
bts->start();
serve->getAdvertising()->addServiceUUID(bts->getUUID());
}
2020-02-02 00:14:34 +00:00
void loop()
{
gps_loop();
screen_loop();
2020-02-01 16:59:16 +00:00
mesh_loop();
loopBLE();
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
digitalWrite(LED_PIN, digitalRead(LED_PIN) ? 0 : 1);
#endif
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);
}