still would like to investigate this early return on display sleep and wake functions use new sleep and wake functions in the hardware class to replace timing math.
383 lines
No EOL
11 KiB
C++
383 lines
No EOL
11 KiB
C++
#include "HardwareRevX.hpp"
|
|
#include "display.hpp"
|
|
#include "wifihandler.hpp"
|
|
#include "driver/ledc.h"
|
|
|
|
std::shared_ptr<HardwareRevX> HardwareRevX::mInstance = nullptr;
|
|
|
|
void HardwareRevX::initIO() {
|
|
// Button Pin Definition
|
|
pinMode(SW_1, OUTPUT);
|
|
pinMode(SW_2, OUTPUT);
|
|
pinMode(SW_3, OUTPUT);
|
|
pinMode(SW_4, OUTPUT);
|
|
pinMode(SW_5, OUTPUT);
|
|
pinMode(SW_A, INPUT);
|
|
pinMode(SW_B, INPUT);
|
|
pinMode(SW_C, INPUT);
|
|
pinMode(SW_D, INPUT);
|
|
pinMode(SW_E, INPUT);
|
|
|
|
// Power Pin Definition
|
|
pinMode(CRG_STAT, INPUT_PULLUP);
|
|
pinMode(ADC_BAT, INPUT);
|
|
|
|
// IR Pin Definition
|
|
pinMode(IR_RX, INPUT);
|
|
pinMode(IR_LED, OUTPUT);
|
|
pinMode(IR_VCC, OUTPUT);
|
|
digitalWrite(IR_LED, HIGH); // HIGH off - LOW on
|
|
digitalWrite(IR_VCC, LOW); // HIGH on - LOW off
|
|
|
|
// LCD Pin Definition
|
|
pinMode(LCD_EN, OUTPUT);
|
|
digitalWrite(LCD_EN, HIGH);
|
|
pinMode(LCD_BL, OUTPUT);
|
|
digitalWrite(LCD_BL, HIGH);
|
|
|
|
// Other Pin Definition
|
|
pinMode(ACC_INT, INPUT);
|
|
pinMode(USER_LED, OUTPUT);
|
|
digitalWrite(USER_LED, LOW);
|
|
|
|
// Release GPIO hold in case we are coming out of standby
|
|
gpio_hold_dis((gpio_num_t)SW_1);
|
|
gpio_hold_dis((gpio_num_t)SW_2);
|
|
gpio_hold_dis((gpio_num_t)SW_3);
|
|
gpio_hold_dis((gpio_num_t)SW_4);
|
|
gpio_hold_dis((gpio_num_t)SW_5);
|
|
gpio_hold_dis((gpio_num_t)LCD_EN);
|
|
gpio_hold_dis((gpio_num_t)LCD_BL);
|
|
gpio_deep_sleep_hold_dis();
|
|
}
|
|
|
|
HardwareRevX::HardwareRevX():
|
|
HardwareAbstract(){
|
|
}
|
|
|
|
HardwareRevX::WakeReason getWakeReason() {
|
|
// Find out wakeup cause
|
|
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1) {
|
|
if (log(esp_sleep_get_ext1_wakeup_status()) / log(2) == 13)
|
|
return HardwareRevX::WakeReason::IMU;
|
|
else
|
|
return HardwareRevX::WakeReason::KEYPAD;
|
|
} else {
|
|
return HardwareRevX::WakeReason::RESET;
|
|
}
|
|
}
|
|
|
|
void HardwareRevX::init() {
|
|
// Make sure ESP32 is running at full speed
|
|
setCpuFrequencyMhz(240);
|
|
|
|
mDisplay = Display::getInstance();
|
|
mBattery = std::make_shared<Battery>(ADC_BAT,CRG_STAT);
|
|
mWifiHandler = wifiHandler::getInstance();
|
|
|
|
mDisplay->onTouch([this]([[maybe_unused]] auto touchPoint){ standbyTimer = SLEEP_TIMEOUT;});
|
|
|
|
wakeup_reason = getWakeReason();
|
|
initIO();
|
|
setupBacklight();
|
|
Serial.begin(115200);
|
|
restorePreferences();
|
|
slowDisplayWakeup();
|
|
setupIMU();
|
|
setupIR();
|
|
|
|
debugPrint("Finished Hardware Setup in %d", millis());
|
|
}
|
|
|
|
void HardwareRevX::debugPrint(const char* fmt, ...)
|
|
{
|
|
char result[100];
|
|
va_list arguments;
|
|
|
|
va_start(arguments, fmt);
|
|
vsnprintf(result, 100, fmt, arguments);
|
|
va_end (arguments);
|
|
|
|
Serial.print(result);
|
|
}
|
|
|
|
std::shared_ptr<HardwareRevX> HardwareRevX::getInstance(){
|
|
if (!mInstance) {
|
|
mInstance = std::shared_ptr<HardwareRevX>(new HardwareRevX());
|
|
}
|
|
return mInstance;
|
|
}
|
|
|
|
std::shared_ptr<wifiHandlerInterface> HardwareRevX::wifi()
|
|
{
|
|
return mWifiHandler;
|
|
}
|
|
|
|
std::shared_ptr<BatteryInterface> HardwareRevX::battery(){
|
|
return mBattery;
|
|
}
|
|
|
|
std::shared_ptr<DisplayAbstract> HardwareRevX::display(){
|
|
return mDisplay;
|
|
}
|
|
|
|
void HardwareRevX::activityDetection() {
|
|
static int accXold;
|
|
static int accYold;
|
|
static int accZold;
|
|
int accX = IMU.readFloatAccelX() * 1000;
|
|
int accY = IMU.readFloatAccelY() * 1000;
|
|
int accZ = IMU.readFloatAccelZ() * 1000;
|
|
|
|
// determine motion value as da/dt
|
|
motion = (abs(accXold - accX) + abs(accYold - accY) + abs(accZold - accZ));
|
|
// Calculate time to standby
|
|
standbyTimer -= 100;
|
|
if (standbyTimer < 0)
|
|
standbyTimer = 0;
|
|
// If the motion exceeds the threshold, the standbyTimer is reset
|
|
if (motion > MOTION_THRESHOLD)
|
|
standbyTimer = SLEEP_TIMEOUT;
|
|
|
|
// Store the current acceleration and time
|
|
accXold = accX;
|
|
accYold = accY;
|
|
accZold = accZ;
|
|
}
|
|
|
|
void HardwareRevX::enterSleep() {
|
|
// Save settings to internal flash memory
|
|
preferences.putBool("wkpByIMU", wakeupByIMUEnabled);
|
|
preferences.putUChar("blBrightness", backlight_brightness);
|
|
preferences.putUChar("currentDevice", currentDevice);
|
|
if (!preferences.getBool("alreadySetUp"))
|
|
preferences.putBool("alreadySetUp", true);
|
|
preferences.end();
|
|
|
|
// Configure IMU
|
|
uint8_t intDataRead;
|
|
IMU.readRegister(&intDataRead, LIS3DH_INT1_SRC); // clear interrupt
|
|
configIMUInterrupts();
|
|
IMU.readRegister(&intDataRead,
|
|
LIS3DH_INT1_SRC); // really clear interrupt
|
|
|
|
// Prepare IO states
|
|
digitalWrite(LCD_DC, LOW); // LCD control signals off
|
|
digitalWrite(LCD_CS, LOW);
|
|
digitalWrite(LCD_MOSI, LOW);
|
|
digitalWrite(LCD_SCK, LOW);
|
|
digitalWrite(LCD_EN, HIGH); // LCD logic off
|
|
digitalWrite(LCD_BL, HIGH); // LCD backlight off
|
|
pinMode(CRG_STAT, INPUT); // Disable Pull-Up
|
|
digitalWrite(IR_VCC, LOW); // IR Receiver off
|
|
|
|
// Configure button matrix for ext1 interrupt
|
|
pinMode(SW_1, OUTPUT);
|
|
pinMode(SW_2, OUTPUT);
|
|
pinMode(SW_3, OUTPUT);
|
|
pinMode(SW_4, OUTPUT);
|
|
pinMode(SW_5, OUTPUT);
|
|
digitalWrite(SW_1, HIGH);
|
|
digitalWrite(SW_2, HIGH);
|
|
digitalWrite(SW_3, HIGH);
|
|
digitalWrite(SW_4, HIGH);
|
|
digitalWrite(SW_5, HIGH);
|
|
gpio_hold_en((gpio_num_t)SW_1);
|
|
gpio_hold_en((gpio_num_t)SW_2);
|
|
gpio_hold_en((gpio_num_t)SW_3);
|
|
gpio_hold_en((gpio_num_t)SW_4);
|
|
gpio_hold_en((gpio_num_t)SW_5);
|
|
// Force display pins to high impedance
|
|
// Without this the display might not wake up from sleep
|
|
pinMode(LCD_BL, INPUT);
|
|
pinMode(LCD_EN, INPUT);
|
|
gpio_hold_en((gpio_num_t)LCD_BL);
|
|
gpio_hold_en((gpio_num_t)LCD_EN);
|
|
gpio_deep_sleep_hold_en();
|
|
|
|
esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK, ESP_EXT1_WAKEUP_ANY_HIGH);
|
|
|
|
delay(100);
|
|
// Sleep
|
|
esp_deep_sleep_start();
|
|
}
|
|
|
|
void HardwareRevX::configIMUInterrupts() {
|
|
uint8_t dataToWrite = 0;
|
|
|
|
// LIS3DH_INT1_CFG
|
|
// dataToWrite |= 0x80;//AOI, 0 = OR 1 = AND
|
|
// dataToWrite |= 0x40;//6D, 0 = interrupt source, 1 = 6 direction source
|
|
// Set these to enable individual axes of generation source (or direction)
|
|
// -- high and low are used generically
|
|
dataToWrite |= 0x20; // Z high
|
|
// dataToWrite |= 0x10;//Z low
|
|
dataToWrite |= 0x08; // Y high
|
|
// dataToWrite |= 0x04;//Y low
|
|
dataToWrite |= 0x02; // X high
|
|
// dataToWrite |= 0x01;//X low
|
|
if (wakeupByIMUEnabled)
|
|
IMU.writeRegister(LIS3DH_INT1_CFG, 0b00101010);
|
|
else
|
|
IMU.writeRegister(LIS3DH_INT1_CFG, 0b00000000);
|
|
|
|
// LIS3DH_INT1_THS
|
|
dataToWrite = 0;
|
|
// Provide 7 bit value, 0x7F always equals max range by accelRange setting
|
|
dataToWrite |= 0x45;
|
|
IMU.writeRegister(LIS3DH_INT1_THS, dataToWrite);
|
|
|
|
// LIS3DH_INT1_DURATION
|
|
dataToWrite = 0;
|
|
// minimum duration of the interrupt
|
|
// LSB equals 1/(sample rate)
|
|
dataToWrite |= 0x00; // 1 * 1/50 s = 20ms
|
|
IMU.writeRegister(LIS3DH_INT1_DURATION, dataToWrite);
|
|
|
|
// LIS3DH_CTRL_REG5
|
|
// Int1 latch interrupt and 4D on int1 (preserve fifo en)
|
|
IMU.readRegister(&dataToWrite, LIS3DH_CTRL_REG5);
|
|
dataToWrite &= 0xF3; // Clear bits of interest
|
|
dataToWrite |= 0x08; // Latch interrupt (Cleared by reading int1_src)
|
|
// dataToWrite |= 0x04; //Pipe 4D detection from 6D recognition to int1?
|
|
IMU.writeRegister(LIS3DH_CTRL_REG5, dataToWrite);
|
|
|
|
// LIS3DH_CTRL_REG3
|
|
// Choose source for pin 1
|
|
dataToWrite = 0;
|
|
// dataToWrite |= 0x80; //Click detect on pin 1
|
|
dataToWrite |= 0x40; // AOI1 event (Generator 1 interrupt on pin 1)
|
|
dataToWrite |= 0x20; // AOI2 event ()
|
|
// dataToWrite |= 0x10; //Data ready
|
|
// dataToWrite |= 0x04; //FIFO watermark
|
|
// dataToWrite |= 0x02; //FIFO overrun
|
|
IMU.writeRegister(LIS3DH_CTRL_REG3, dataToWrite);
|
|
}
|
|
|
|
// TODO move to display
|
|
void HardwareRevX::setupBacklight() {
|
|
// Configure the backlight PWM
|
|
// Manual setup because ledcSetup() briefly turns on the backlight
|
|
ledc_channel_config_t ledc_channel_left;
|
|
ledc_channel_left.gpio_num = (gpio_num_t)LCD_BL;
|
|
ledc_channel_left.speed_mode = LEDC_HIGH_SPEED_MODE;
|
|
ledc_channel_left.channel = LEDC_CHANNEL_5;
|
|
ledc_channel_left.intr_type = LEDC_INTR_DISABLE;
|
|
ledc_channel_left.timer_sel = LEDC_TIMER_1;
|
|
ledc_channel_left.flags.output_invert = 1; // Can't do this with ledcSetup()
|
|
ledc_channel_left.duty = 0;
|
|
|
|
ledc_timer_config_t ledc_timer;
|
|
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;
|
|
ledc_timer.duty_resolution = LEDC_TIMER_8_BIT;
|
|
ledc_timer.timer_num = LEDC_TIMER_1;
|
|
ledc_timer.freq_hz = 640;
|
|
|
|
ledc_channel_config(&ledc_channel_left);
|
|
ledc_timer_config(&ledc_timer);
|
|
}
|
|
|
|
void HardwareRevX::restorePreferences() {
|
|
// Restore settings from internal flash memory
|
|
preferences.begin("settings", false);
|
|
if (preferences.getBool("alreadySetUp")) {
|
|
wakeupByIMUEnabled = preferences.getBool("wkpByIMU");
|
|
backlight_brightness = preferences.getUChar("blBrightness");
|
|
currentDevice = preferences.getUChar("currentDevice");
|
|
}
|
|
}
|
|
|
|
void HardwareRevX::setupIMU() {
|
|
// Setup hal
|
|
IMU.settings.accelSampleRate =
|
|
50; // Hz. Can be: 0,1,10,25,50,100,200,400,1600,5000 Hz
|
|
IMU.settings.accelRange = 2; // Max G force readable. Can be: 2, 4, 8, 16
|
|
IMU.settings.adcEnabled = 0;
|
|
IMU.settings.tempEnabled = 0;
|
|
IMU.settings.xAccelEnabled = 1;
|
|
IMU.settings.yAccelEnabled = 1;
|
|
IMU.settings.zAccelEnabled = 1;
|
|
IMU.begin();
|
|
uint8_t intDataRead;
|
|
IMU.readRegister(&intDataRead, LIS3DH_INT1_SRC); // clear interrupt
|
|
}
|
|
|
|
// TODO move to display
|
|
void HardwareRevX::slowDisplayWakeup() {
|
|
// Slowly charge the VSW voltage to prevent a brownout
|
|
// Workaround for hardware rev 1!
|
|
for (int i = 0; i < 100; i++) {
|
|
digitalWrite(LCD_EN, HIGH); // LCD Logic off
|
|
delayMicroseconds(1);
|
|
digitalWrite(LCD_EN, LOW); // LCD Logic on
|
|
}
|
|
|
|
delay(100); // Wait for the LCD driver to power on
|
|
}
|
|
|
|
void HardwareRevX::setupIR() {
|
|
// Setup IR
|
|
IrSender.begin();
|
|
digitalWrite(IR_VCC, HIGH); // Turn on IR receiver
|
|
IrReceiver.enableIRIn(); // Start the receiver
|
|
}
|
|
|
|
void HardwareRevX::startTasks() {
|
|
if (xTaskCreate(&HardwareRevX::updateBatteryTask, "Battery Percent Update",
|
|
1024, nullptr, 5, &batteryUpdateTskHndl) != pdPASS) {
|
|
debugPrint("ERROR Could not Create Battery Update Task!");
|
|
}
|
|
}
|
|
|
|
void HardwareRevX::updateBatteryTask(void*){
|
|
while(true){
|
|
mInstance->battery()->NotifyCurrentStatus();
|
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
}
|
|
}
|
|
|
|
void HardwareRevX::loopHandler() {
|
|
standbyTimer < 2000 ? mDisplay->sleep() : mDisplay->wake();
|
|
|
|
// TODO move to debug task
|
|
// Blink debug LED at 1 Hz
|
|
digitalWrite(USER_LED, millis() % 1000 > 500);
|
|
|
|
// Refresh IMU data at 10Hz
|
|
static unsigned long IMUTaskTimer = millis();
|
|
if (millis() - IMUTaskTimer >= 100) {
|
|
activityDetection();
|
|
if (standbyTimer == 0) {
|
|
Serial.println("Entering Sleep Mode. Goodbye.");
|
|
enterSleep();
|
|
}
|
|
IMUTaskTimer = millis();
|
|
}
|
|
|
|
// Keypad Handling
|
|
customKeypad.getKey(); // Populate key list
|
|
for (int i = 0; i < LIST_MAX;
|
|
i++) { // Handle multiple keys (Not really necessary in this case)
|
|
if (customKeypad.key[i].kstate == PRESSED ||
|
|
customKeypad.key[i].kstate == HOLD) {
|
|
standbyTimer =
|
|
SLEEP_TIMEOUT; // Reset the sleep timer when a button is pressed
|
|
int keyCode = customKeypad.key[i].kcode;
|
|
Serial.println(customKeypad.key[i].kchar);
|
|
// Send IR codes depending on the current device (tabview page)
|
|
if (currentDevice == 1) {
|
|
IrSender.sendRC5(IrSender.encodeRC5X(
|
|
0x00, keyMapTechnisat[keyCode / ROWS][keyCode % ROWS]));
|
|
} else if (currentDevice == 2) {
|
|
IrSender.sendSony((keyCode / ROWS) * (keyCode % ROWS), 15);
|
|
}
|
|
}
|
|
}
|
|
// IR Test
|
|
// tft.drawString("IR Command: ", 10, 90, 1);
|
|
// decode_results results;
|
|
// if (IrReceiver.decode(&results)) {
|
|
// IrReceiver.resume(); // Enable receiving of the next value
|
|
//} //tft.drawString(String(results.command) + " ", 80, 90, 1);
|
|
//
|
|
} |