InfiniTimeOrg / InfiniTime

Firmware for Pinetime smartwatch written in C++ and based on FreeRTOS
GNU General Public License v3.0
2.76k stars 944 forks source link

Touch, button & power detection doesn't work when button is hold during boot #693

Closed hubmartin closed 3 years ago

hubmartin commented 3 years ago

I've noticed similar behavior also on P8 and now on PineTime, both with 1.4.0 Infinitime FW. It is hard to reproduce. Sometimes watch is frozen (cutting-edge PRs or something else maybe?), screen black and button does not react. I try to reboot by holding button. After reboot, the screen is on but the watch did not vibrate and button, touchscreen (touch or doubletap to wake up) or accelerometer (wake on wrist) does not work. After timeout the screen goes dark. Sometimes I need to make like 10 reboots. Other way to fix it is to short-circuit 3.3V rail for a while, or disconnect and reconnect the battery.

This happend few times to me.

There was #566 which tried to fix it but the problem is that even after 8/16 SCL pulses the sensor still holds SDA low, which blocks the Start condition on the I2C bus. The sensor should give up after 1 byte transfer since it does not get ACK, but it seems is does not release the bus and needs more clocks.

You can read deeper info and scope signals when this happened on my P8 in the #566 . I thought that happened because of special accelerometer on P8, but now it happened also on PineTime.

Here I have video of like 13 resets which fixed that. https://photos.app.goo.gl/j5HsZEDtwyau68A37

For now I have all my watches sealed, it would be great if someone could try this code devkit and test how it behaves if you ground SDA. I did some tests on my sealed watches but it seems like something more could be wrong with the code, maybe GPIO configuration. I wasn't able to get into while (nrf_gpio_pin_read(Pinetime::PinMap::TwiSda) == 0) loop. Any ideas are welcome.

void declock_i2c()
{
    // I2C declocking issue
    nrf_gpio_cfg_input(Pinetime::PinMap::TwiSda, NRF_GPIO_PIN_PULLUP);

    volatile int i = 0;

    while (nrf_gpio_pin_read(Pinetime::PinMap::TwiSda) == 0)
    {
        nrf_gpio_cfg_output(Pinetime::PinMap::TwiScl);
        nrf_gpio_pin_clear(Pinetime::PinMap::TwiScl);
        nrf_delay_ms(2);
        nrf_gpio_cfg_input(Pinetime::PinMap::TwiScl, NRF_GPIO_PIN_PULLUP);
        nrf_delay_ms(2);

        if (++i > 100)
        {
            NVIC_SystemReset();
        }
    }

}

Not entirely sure how to figure out which sensor does it. I do not use heartrate sensor. Also on P8 is completely different accelerometer (SC7A20) vs BMA42x on Pine. But I would say it might be accelerometer beacuse it is the most frequently read sensor. Sometimes I was able to get to this stuck state by rebooting Pine with button hold. I guess you just have to hit the middle of the I2C transaction to get sensor stuck.

It would be also nice, to have this for some time in release firmware with some notification or counter which would say to the user that this I2C stuck condition happened and how many clocks it needed to get it right. Otherwise we will never know if this code helped in any way.

I did not wanted to write this to already closed and merged PR, but since I cannottest it I also did not created new PR. But if you think this should be PR even with not tested code, I might create it.

Thanks

Riksu9000 commented 3 years ago

There's something weird about this behaviour. Normally the watchdog automatically reboots it, hence the bootloop issue, and it shows the pine logo. Is it that you don't have a bootloader or a recovery? I'm not very familiar with how they work. I haven't heard that something like this has happened to anyone else.

hubmartin commented 3 years ago

Oh, I forget to mention that I did not use bootloader. Anyway when the screen goes off after display timeout I don't see any reboot loop, so the wathcdog check is somehow happy. Next time I'll check whether watch is advertising on BLE.

hubmartin commented 3 years ago

So when the watch is in this semi-freezed state, the bluetooth is advertising but button or touch panel do not work and display goes off after proper Display timeout interval.

Also the watch does not vibrate after reboot as they usually do. Seems like some task might get stuck? I was not able to find the vibrate command RunForDuration which might run these vibration after boot, is this run somehow differently after boot?

I was able to get to this stuck state three times in a row with this procedure - while holding the button to reboot I was moving my finger on the touchscreen.

My guess: By having finger on the touch the touch panel IRQ is triggering a lot of interrupts and that generates lot of I2C traffic making it perfect to break the transaction in the middle.

I'll try to open my wathces again and investigate it further with scope/analyzer soon. Any ideas are welcome.

hubmartin commented 3 years ago

I've opened the wach and found another indicies. Hope that these pieces of info could be analyzed by someone else.

Using current develop branch 6cf5797. I'm not using the bootloader, just the InfiniTime firmware.

Vibration after start/reboot is a side effect of power detection, I see a red power icon for a fraction of second on the digital watchface.

When the watch is stuck, then the motor does not vibrate and the red power icon does not disappear.

Watchdog reset might fix this watch state if I reboot it many many times (like 10-30 times). But now I noticed that on the debugger when I press Reset device then watch immediatelly starts working. So this might not be I2C issue. Could this be some uninitialized variable/register? Aparently this RESET SWD command fixes this every time.

When stuck then the SystemTask loop is working properly. Also in the watchdog if condition button GPIO is read and wachdog is fed.

Connected scope and I2C is properly working.

I'm also able to wake up watch when Wake on wrist is activated. However button or screen doubletap (which is also activated) doesn't wake the watch. https://photos.app.goo.gl/NbX59GxG1DzHbApdA

So message loop in SystemTask seems to work fine because WakeOnWrist event triggers turning on of the display.

Not sure why both - the button and the touch screen is not working. The Messages::OnButtonEvent or Messages::OnTouchEvent is not triggered.

The nrfx_gpiote_evt_handler is not called when touching display (or pressing button). It should be triggered by touch IRQ Cst816sIrq.

Now I see that touch controller, button and power detection is using GPIO IRQs. So that is the common part.

I'm not able to measure IRQ signal from accelerometer - maybe it is stuck low and thats why IRQ is not detected? Because it is configured to high to low transition, but the signal from touch is already low after the boot? There might be someting on it because as I wrote in previous post that I'm able to get watch to this state when rebooting and moving finger on the touch.

On the other side these are 3 separate GPIOs so it seems like the whole GPIOTE is not somehow working properly. Button should be working at least.

Let me please know what I should check next, thanks.

Can anyone test that it is possible to get watches to this state by holding the button to force wathcdog reboot and moving finger on the touch at the same time? Preferably on the unsealed watch.

Riksu9000 commented 3 years ago

I can't reproduce this issue by moving my finger across the screen while holding the button to reset. I tried many times. I wonder if the bootloader is required to initialize something correctly as that seems to be the only difference.

hubmartin commented 3 years ago

@Riksu9000 Are you moving your finger on the screen during the InfiniTime firmware boot? Maybe the issue is not in touch IRQs durng reset, but during the boot. I'll upload a video of that process and try bootloader soon.

The issue is that watch are in this state few times a week on my wrist and I'm not touching or using them, they just are stuck with no touchscreen or button reaction. So this moving finger during boot creates a similar state, but I cannot say it is the same issue, just trying to analyze and reproduce it in case it is the same issue.

Riksu9000 commented 3 years ago

I've tried touching the screen at many different timings all the way from pressing the button to firmware booting and for the whole duration and it still always boots correctly for me.

hubmartin commented 3 years ago

@Riksu9000 I think I found it. The button is the issue.

If the button is hold during the InfiniTime firmware boot, then it gets stuck in that state that button, power detection and touch does not work. Seems like GPIO interrupt is not called/working.

It might be similar issue I had in my project with NRF52832 year ago. I had few GPIOTE inputs set as GPIOTE_CONFIG_POLARITY_HiToLo and when one of them was low, then others weren't detected. I had to change them to toggle GPIOTE_CONFIG_POLARITY_Toggle and check gpio state in the IRQ handler. Otherwise when one signal was low, then others hitolo signals weren't triggered until the first was released again. But this might be different since it is happening only while the button is pressed during FW boot.

So when I reboot the watch by holding the button for 6 seconds and the screen is on while I'm holding the button. Then when reboot happens and screen goes dark I can release the button and watch boots fine. But when the display is dark while I'm holind button for 6 seconds, I have no idea when to release it and the button, touch, power detection is not working.

Let me know if you will be able to replicate that. Thanks.

Riksu9000 commented 3 years ago

Pressing the button down before the pine logo is filled indeed produces this behaviour, so this is the issue. This must be the reason for this code as well. 9ab298c09e273479822d10aad9f7bfe1d287ce75. Is there a better solution to this issue?

  if (nrf_gpio_pin_read(PinMap::PowerPresent)) {
    nrf_gpio_cfg_sense_input(PinMap::PowerPresent, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW);
  } else {
    nrf_gpio_cfg_sense_input(PinMap::PowerPresent, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);
  }
hubmartin commented 3 years ago

Tried this change and button is working and even when it is pressed down during boot. That's thanks to TOGGLE mode I guess. I'm not checking pin state anywhere in the IRQ handler. image

Also removed nrf_gpio_cfg_sense_input which was unnecessary because nrfx_gpiote_in_init can do port config itself when skip_gpio_setup is false. And added just nrfx_gpiote_in_event_enable

hubmartin commented 3 years ago

Just for fun tried to change power detection config and it detects changes properly (icon changes) but after start it keeps displaying the red power icon. Maybe just needs to be handled differently in the gui after boot.

image

Pushed this branch for now. Contains working "button pressed while boot" fix and tries to use toggle for power detection. https://github.com/hubmartin/InfiniTime/commit/2aebbe3f474bfaa5058879bde045146f5561fb66

UPDATE: When moved batteryController.Update(); lower after the GPIO pull-up config, then it works fine also for power detection.