m5stack / M5Core2

M5Core2 Arduino Library
MIT License
262 stars 113 forks source link

Feature Request: Soft Reset, Reading Power Button Presses, LCD Power Off, Processor Sleep, Touch Interrupt, Dual Core Support. #129

Open JasonPittenger opened 1 year ago

JasonPittenger commented 1 year ago

I wrote a few features that I thought might be useful to incorporate into this library. Of course, modify the names and code as you see fit.

Soft Reset: This allows a reset based on software rather than the physical reset button

esp_sleep_enable_timer_wakeup(1);
esp_deep_sleep(1);

Read Power Button Presses: The chip that reads the power button has built in interrupts for detecting pressing or holding the power button. I have used this to put the m5 to sleep with a short press, or soft power down with a long press. The short press is anything less than 3 seconds. The long press is 3+ seconds. Of course, holding the button for ~7 seconds forces a hard power down.

Wire1.beginTransmission(0x34);
Wire1.write(0x46);
Wire1.endTransmission();
Wire1.requestFrom(0x34, 1);
u8_PowerWasPressed = Wire1.read();
if((u8_PowerWasPressed & 0x01) == 0x01)
{
    //Power button was long pressed!
}
if((u8_PowerWasPressed & 0x02) == 0x02)
{
    //Power button was short pressed!
}
if(u8_PowerWasPressed & 0x03)
{
    //Clear IRQ
    Wire1.beginTransmission(0x34);
    Wire1.write(0x46);
    Wire1.write(0x03);
    Wire1.endTransmission();
}

LCD Power Off: This completely turns off the LCD to reduce power usage. The screen must be redrawn on power up.

M5.Axp.SetDCDC3(false);         //Turn LCD backlight off
M5.Lcd.sleep();                 //Turn off LCD logic

LCD Power On: The screen must be redrawn on power up.

M5.Lcd.wakeup();                    //Wake up LCD logic
M5.Axp.SetDCDC3(true);              //Turn LCD backlight on

Processor Sleep: The sleep mode does not effect what's displayed on the LCD.

void SleepProcessor(uint64_t time_in_us)
{
    if (time_in_us > 0) 
    {
        esp_sleep_enable_timer_wakeup(time_in_us);
    }
    else
    {
        esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER);
    }
    esp_light_sleep_start();
}

Touch Interrupt: By default, the touch controller uses an interrupt whenever there is a touch detected. I found the touch screen to be much more responsive if the interrupt pin is polled. This goes it pin 39. Whenever the pin is low, there is an unread touch.

#define TOUCH_PANEL_INTERRUPT_PIN       39

void setup() 
{
    pinMode(TOUCH_PANEL_INTERRUPT_PIN, INPUT);    // sets the digital pin as input to detect a touch input
}

void loop() 
{
    if(!digitalRead(TOUCH_PANEL_INTERRUPT_PIN))
    {
        M5.update();
    }
}

Dual-Core Support: Here is all that is needed to set up a main task loop on the 2nd core. You need to set "Tools->Arduino Runs On: Core 1" and "Tools->Events Run On: Core 0" for this to work. Also, interfaces like I2C cannot be accessed by both loops or there will be data corruption on the bus.

void setup() 
{
    xTaskCreatePinnedToCore (
                                LoopCore0,      //TaskFunction_t pvTaskCode,
                                "LoopCore0",        //const char *const pcName,
                                4096,           //const uint32_t usStackDepth,
                                NULL,           //void *const pvParameters,
                                1,                  //UBaseType_t uxPriority,
                                NULL,           //TaskHandle_t *const pvCreatedTask
                                0               //Core
                            );
}

void loop() 
{
    //Main Loop Tasks running on Core 1
}

void LoopCore0(void *pvParameters)
{
    uint32_t WatchDogTimer;

    while(1)
    {
        //Second Loop Tasks running on Core 0

        if((millis() - WatchDogTimer) > 500)
        {
            delay(1);   //This must be called every 500ms to trigger the watchdog timer
        }
    }
}
JasonPittenger commented 1 year ago

One change I made just after posting this, was to turn on the pull up on the interrupt pin for the touch controller. I was getting some phantom touches and this seemed to eliminate those.

`#define TOUCH_PANEL_INTERRUPT_PIN 39

void setup() { pinMode(TOUCH_PANEL_INTERRUPT_PIN, INPUT_PULLUP); // sets the digital pin as input to detect a touch input }

void loop() { if(!digitalRead(TOUCH_PANEL_INTERRUPT_PIN)) { M5.update(); } }`

capedra commented 1 year ago

@JasonPittenger Hello Jason! Thank you for your contributions !! This last one didn't work well for me: whenever the second core is operating, the keys of the screen stop responding. I even tried it with interrupts (FALLING and CHANGE modes) but this same issue persists. If anyone would like to test it with ISR:

#define TOUCH_PANEL_INTERRUPT_PIN 39
struct TouchPanelButton {
  const uint8_t PIN;
  bool pressed;
};
TouchPanelButton tpb = {TOUCH_PANEL_INTERRUPT_PIN, false};
void IRAM_ATTR isr() {
    tpb.pressed = true;
}
void setup() {
    pinMode(TOUCH_PANEL_INTERRUPT_PIN, INPUT_PULLUP);    // sets the digital pin as input to detect a touch input
    attachInterrupt(TOUCH_PANEL_INTERRUPT_PIN, isr, FALLING);
}
void loop() {
    if (tpb.pressed) {
        M5.update();
        tpb.pressed = false;
    }
}
capedra commented 1 year ago

It got a bit better after I configured the priority of the second core task to 0 instead of 2.

JasonPittenger commented 1 year ago

I have not tried it using interrupts and I've always used core 0 task at priority 1. How do you have the IDE set up? I have mine set to 'Arduino runs on: "Core 1"' and 'Events run on: "Core 0"'. I have not had any issues with it not responding.
I only ever had problems with phantom touches before I changed to INPUT_PULLUP.

capedra commented 1 year ago

@JasonPittenger the same setup of yours, the default configuration.

@Tinyu-Zhao How may I completely disable the power button of the device? In other words, I want to keep the device turned on even after holding its power button for 6 seconds..

JasonPittenger commented 1 year ago

@capedra when using dual core, you have to make sure not to share an interface across the cores. M5.update(); uses I2C to read from the touch controller. You must not use the same I2C bus on the other core, unless you have some sort of global variable to ensure exclusive access to the I2C bus. Otherwise what can happen is you could be in the middle of an I2C transaction on core 1 when core 0 starts reading from I2C. This will interrupt the core 1 read and corrupt the data. For my program, I make sure all I2C transactions are done on core 1. This is true for other interfaces as well, so all my UART transactions are done on core 0 and all SPI transactions are on core 1 as well.

But if you want to use both cores, use something like this around every I2C transaction. It will wait for the bus to be available before reading. Then it will lock it out during the read so the other core has to wait. The timeout is there in case the variable gets out of sync and both cores are waiting on each other.

u16_Timeout = 100; while(u8_TimeLockout && u16_Timeout) { u16_Timeout--; delay(1); } //Lock out the variables while the bus is active. u8_TimeLockout = 1; //Read I2C u8_TimeLockout = 0;

As for the power button, the datasheet says the time length can be changed, but I didn't see any way to disable it. I was working with a Google Translated version of the datasheet, so I might have missed it. Personally I wouldn't want to disable it, as it provides a hard power down if the software locks up. I do wish I could disable the physical reset button.

Tinyu-Zhao commented 1 month ago

Maybe this is a general feature request? You can file it in M5Unified.