InfiniTime icon indicating copy to clipboard operation
InfiniTime copied to clipboard

Always on display

Open KaffeinatedKat opened this issue 1 year ago • 115 comments

I have added an option in the "Display timeout" for an always on display. Not sure how useful this is, or how good the battery life is (will be testing this and will post a comment with how long the battery lasted).

Any suggestions on how I could possibly increase the battery with the display always on would be great.

image

KaffeinatedKat avatar Sep 30 '23 03:09 KaffeinatedKat

Build size and comparison to main:

Section Size Difference
text 378724B 1516B
data 948B 8B
bss 63504B -52B

github-actions[bot] avatar Sep 30 '23 03:09 github-actions[bot]

This will probably reduce the battery life to just a few hours. The LCD uses between 10 and 30mA when it's ON, which is quite high compared to the current power usage of InfiniTime (less than 1mA).

I can't think of any way to reduce power usage of the display when it's on... Lowering the brightness will effectively reduce the power usage, but probably not enough to maintain a reasonable battery life. This is caused by the LCD technology of the display.

JF002 avatar Sep 30 '23 18:09 JF002

is there anyway to reduce the brightness lower than the lowest setting right now? Currently on the lowest brightness setting it's gone ~15 hours and it's at ~35%, which was better than I was expecting

KaffeinatedKat avatar Sep 30 '23 18:09 KaffeinatedKat

You could drive the brightness using PWM. There might already be a PR for that.

JF002 avatar Sep 30 '23 18:09 JF002

You could drive the brightness using PWM. There might already be a PR for that.

Indeed there is, see #575

mark9064 avatar Oct 01 '23 11:10 mark9064

I've changed it so that the "always on" setting prevents the display from turning off, but the watch still goes into the sleep mode, and just sets it to a low brightness with pwm. Now the display will light up when any of the wakeup actions happen, then go dim again after the display timeout.

My only issue is the watch is in sleep mode and the watchface does not update while sleeping. Because the display is still on the watchface is just frozen till woken up. I've been poking around the code and I cannot find where I can make the display update while sleeping

A video of the watchface not updating while asleep: https://drive.google.com/file/d/1pNt-cbVS1s_8tmAK6B8ZmDR_WU164OI8/view

KaffeinatedKat avatar Oct 01 '23 16:10 KaffeinatedKat

A common pattern you'll spot all across InfiniTime is using queue read timeout to implement periodic tasks. Many tasks have an event queue that they read from forever, and when fetching an event from a queue you can specify a timeout, which is how long to wait if nothing arrives. After this timeout, the queue fetch function returns that no items were fetched from the queue as nothing was available in the time specified. So if you have a loop which does:

  1. get event with timeout 1 minute
  2. process event if one exists
  3. execute x task

then x task runs at least every minute.

This is exactly how it's implemented for the display, with x task in this case being refreshing display contents. So change

    case States::Idle:
      queueTimeout = portMAX_DELAY;
      break;

to

    case States::Idle:
      if (settingsController.GetAlwaysOnDisplay()) {
        queueTimeout = lv_task_handler(); // returns time until LVGL tasks need running
      } else {
        queueTimeout = portMAX_DELAY;
      }
      break;

and you're good :)

mark9064 avatar Oct 01 '23 23:10 mark9064

Seems to work well with the above change. I haven't been running it long enough to test battery yet though. One suggestion would be to disable always on if InfiniTime is set to sleep mode

mark9064 avatar Oct 01 '23 23:10 mark9064

Seems to work well with the above change. I haven't been running it long enough to test battery yet though. One suggestion would be to disable always on if InfiniTime is set to sleep mode

This is something I plan to implement, this feature would pair really nicely with #1461

KaffeinatedKat avatar Oct 02 '23 00:10 KaffeinatedKat

A common pattern you'll spot all across InfiniTime is using queue read timeout to implement periodic tasks. Many tasks have an event queue that they read from forever, and when fetching an event from a queue you can specify a timeout, which is how long to wait if nothing arrives. After this timeout, the queue fetch function returns that no items were fetched from the queue as nothing was available in the time specified. So if you have a loop which does:

1. get event with timeout 1 minute

2. process event if one exists

3. execute x task

then x task runs at least every minute.

This is exactly how it's implemented for the display, with x task in this case being refreshing display contents. So change

    case States::Idle:
      queueTimeout = portMAX_DELAY;
      break;

to

    case States::Idle:
      if (settingsController.GetAlwaysOnDisplay()) {
        queueTimeout = lv_task_handler(); // returns time until LVGL tasks need running
      } else {
        queueTimeout = portMAX_DELAY;
      }
      break;

and you're good :)

I have applied this, and it doesn't work. Screen still doesn't update while sleeping and when I wake it up the colors invert, which is extremely weird. Any idea why?

KaffeinatedKat avatar Oct 02 '23 00:10 KaffeinatedKat

I have applied this, and it doesn't work. Screen still doesn't update while sleeping and when I wake it up the colors invert, which is extremely weird. Any idea why?

I have solved this issue, this change worked fine in the simulator but did not work on the watch. Figured out that by not putting the SPI to sleep, these changes work as intended

KaffeinatedKat avatar Oct 03 '23 16:10 KaffeinatedKat

Nice job, I'm running a patchset that disables SPI sleep and I suspected that might be difference - unfortunately I didn't have time to test and report back so sorry about that

One change I'd suggest: running the LVGL tasks full speed (ie max display refresh) is pretty power hungry, and I've found throttling it to 250ms saves power while keeping watchfaces with seconds up to date (as the display effectively refreshes at 4Hz). I'm getting ~30h battery life running the screen on constantly right now (This is a calculated number as I turn it off at night) I also thought for correctness it might be good to pull up the screen running check. It also might be good not to reinitialise the SPI inside GoToRunning if it wasn't actually suspended

So I've been running this - feel free to integrate changes if you fancy

      if (settingsController.GetAlwaysOnDisplay()) {
        if (!currentScreen->IsRunning()) {
          LoadPreviousScreen();
        }
        int lvglWaitTime = lv_task_handler();
        // while in always on mode, throttle LVGL events to 4Hz
        queueTimeout = std::max(lvglWaitTime, 250);
      } else {
        queueTimeout = portMAX_DELAY;
      }
      break;

I've also noticed an issue where sometimes the brightness is on Low rather than Lowest while the watch is asleep - I haven't had time to debug that either but see if you spot it. For reference I have my normal brightness set to Low

mark9064 avatar Oct 03 '23 22:10 mark9064

Wouldn't be an update every minute enough for an always on display? It should increase the battery runtime further.

Off course with hiding the seconds hand...

escoand avatar Oct 04 '23 06:10 escoand

Wouldn't be an update every minute enough for an always on display? It should increase the battery runtime further.

Off course with hiding the seconds hand...

Yeah it's definitely something worth thinking about. On one hand it saves power, but on the other, it allows incorrect information to be onscreen for more than a minute like seconds on watchfaces with them, seconds on the timer app, bluetooth status, music status, steps, heart rate (if continuous/background measuring implemented) etc Whether these are important enough for the power saving, I'm not sure. Maybe it's worth testing to see how big the power difference is?

mark9064 avatar Oct 04 '23 08:10 mark9064

Wouldn't be an update every minute enough for an always on display? It should increase the battery runtime further. Off course with hiding the seconds hand...

Yeah it's definitely something worth thinking about. On one hand it saves power, but on the other, it allows incorrect information to be onscreen for more than a minute like seconds on watchfaces with them, seconds on the timer app, bluetooth status, music status, steps, heart rate (if continuous/background measuring implemented) etc Whether these are important enough for the power saving, I'm not sure. Maybe it's worth testing to see how big the power difference is?

This might be worth it, we can set the update timer to 4Hz when apps like the timer and music are running, but set it back when not running these apps; perhaps an enum with apps that need a higher refresh timer. We can also disable the seconds on every watchface that has them when always on mode. We could also add a setting to disable this if someone wants seconds on. I think with the display the pinetime has, any power savings we can get is worth it

KaffeinatedKat avatar Oct 04 '23 17:10 KaffeinatedKat

This will probably reduce the battery life to just a few hours. The LCD uses between 10 and 30mA when it's ON, which is quite high compared to the current power usage of InfiniTime (less than 1mA).

I can't think of any way to reduce power usage of the display when it's on... Lowering the brightness will effectively reduce the power usage, but probably not enough to maintain a reasonable battery life. This is caused by the LCD technology of the display.

I honestly think this would be pretty useful. You could just have a low quality version of the digital clock display on the screen with like medium brightness and just have everything else suspended in the background.

hexisXz avatar Oct 12 '23 19:10 hexisXz

This pr, for the most part, is complete. Only remaining issue is sometimes the display does not go into the lowest setting when going to sleep, and stays at the brightness setting. I have not been able to figure this out because it happens so infrequently.

@JF002, is there any way this feature would be considered for merging, or is the battery life reduction while enabled to high? Maybe there could be some kind of popup warning when enabled, to notify the user of the significant battery drain while enabled.

It lasts about a full day when enabled, and most smartwatches only last that long anyways. I feel like the people who genuinely find this useful would be willing to charge it everyday

KaffeinatedKat avatar Oct 14 '23 04:10 KaffeinatedKat

The issues with the screen getting stuck in Low are due to the restorebrightness messages coming from the system task. (Edit: I can post some actual patches soon)

I think this PR (with the above issue fixed) is usable. But we should also test what the majority of the power consumption is when always on (I suspect it's the LCD) and if we can reduce it further (the LCD offers a power mode between sleep (display blank) and fully on with reduced colours etc.). Also might be possible to redrive the LCD at a lower refresh rate when in always on, but the datasheet for it on the Pine wiki is an absolute monster and I haven't had the time to fully explore it. As an aside it looks like the solution to the display invert issue might be in there too

mark9064 avatar Oct 14 '23 13:10 mark9064

I took a peek at the datasheet, and implementing switching to reduced colors while always on. The datasheet says this uses less power, but by how much I am unsure. I don't have a devkit, and can't measure power consumption.

This is also essentially free power reduction. Turns out, InfiniTime doesn't utility many colors at all, and while in reduced colors, you can hardly tell. The reduced brightness also helps hide it. The one app where you can really tell is 2048, and I doubt anybody will be letting it fall asleep on 2048 anyways.

I'm gunna keep poking around the datasheet to see if I can figure out the refresh rate thing, if that is possible it would probably help the power consumption by a sizeable margin

KaffeinatedKat avatar Oct 14 '23 16:10 KaffeinatedKat

Oh yeah also forgot to mention, the settings version number should be bumped as a new member has been added

mark9064 avatar Oct 14 '23 21:10 mark9064

https://github.com/InfiniTimeOrg/InfiniTime/pull/1869#issuecomment-1762923811

The patch in question: https://github.com/mark9064/InfiniTime/commit/d43759fcc817be8b1daaadc7bd02f5353be06e47 Feel free to pull the commit into your branch if you're happy with it :)

Locally working well - to reproduce the issue on the original try disconnecting and reconnecting BT (the display should get stuck on after). With the patch it should stay in always on as expected

Reduced colours mode seems to be working, only just flashed it so haven't had time to see if there are power changes (I only have sealed). Some of the watchfaces suffer though

mark9064 avatar Oct 14 '23 21:10 mark9064

Applied your patch, and it has fixed the issue on my device as well. Bumped the settings version, while I was at it

Also figured out how to modify the refresh rate, it now goes to ~4.8hz while always on, which is the lowest setting available. Hopefully this helps increase the battery life

If anyone with a devkit wants to test if the reduced colors actually saves a realistic amount of power that would be great. I feel like it would be worth reverting if it doesn't save enough power due to making some watchfaces look worse

KaffeinatedKat avatar Oct 14 '23 22:10 KaffeinatedKat

+1 on the reduced colours thought

Can confirm refresh rate changes have applied properly, well done on figuring that out! I think it should actually be possible to inform LVGL of the exact refresh rate too and that way we shouldn't have to do the timer throttling hack? I'm not totally sure on this, I'd need to dig through the LVGL internals but it might be worth considering

mark9064 avatar Oct 14 '23 22:10 mark9064

I had a quick look: it seems that LV_DISP_DEF_REFR_PERIOD is used as a define to set the refresh period on all the LVGL tasks to 20ms. So I think what we'd have to do is go through all of these tasks (there are many inside InfiniTime and a couple in LVGL itself), and then use lv_task_set_period to set them to a longer execution period. Quite a bit of work, but looks possible

Edit: Damn this is looking pretty complicated. Some of the tasks should be suspended altogether (like the input reading task) while some should just be slowed. Not sure how feasible doing this is

Edit 2: The more I think about it, the less this seems like a good idea. Trying to bring all of the LGVL tasks together to change the intervals sounds like a bit of a nightmare organisation wise. Perhaps it would be better to properly understand exactly what lv_task_handler does and see if we should implement our own task handler that runs tasks with efficiency in mind rather than latency (ie batching task execution). What are your thoughts?

mark9064 avatar Oct 14 '23 22:10 mark9064

Perhaps it would be better to properly understand exactly what lv_task_handler does and see if we should implement our own task handler that runs tasks with efficiency in mind rather than latency (ie batching task execution). What are your thoughts?

I think that this is a good idea, would this new task handler replace lv_task_handler all together, or only be used for the always on display?

KaffeinatedKat avatar Oct 15 '23 00:10 KaffeinatedKat

Probably just for always on as I suppose we still want responsive inputs etc while on. But I guess it depends on how it's implemented? It might even not be needed at all - I'm not sure how the LVGL scheduler works

A quick list of things I've noticed so far:

  • Screen tearing is quite visible. Try with the analogue face (or timer app), notice how the second hand gets chopped in half occasionally. Also, the lcd pulses with the refresh rate (pretty tricky to spot but try looking at the top right). Maybe the LCD is being driven too slowly? I'm honestly no expert on what refresh rates the panel supports. I think I might try keeping 60Hz but using /8 divider on that and seeing if it does better - will report back. There also might be some VSYNC stuff we can do with the panel to fix this
  • Frame pacing is noticeably poor (visible with seconds) but honestly I think that's fine as a sacrifice if it gets battery life back
  • There are still some wake/sleep state bugs I need to investigate (display gets stuck on in some scenarios)
  • A background transition to the Sleep mode (eg with the auto sleep PR) causes everything to break

Battery life is looking promising though! Not sure if it's the reduced refresh or reduced colours but I'm seeing an improvement for sure

mark9064 avatar Oct 15 '23 23:10 mark9064

Screen tearing is quite visible

I've noticed the screen tearing, it's really bad if you run it at that refresh rate while awake. I don't personally run a watchface with seconds and so I haven't noticed it much, but it's for sure worth investigating for those who do. I'll do some tinkering with the refresh rate and see about VSYNC

A background transition to the Sleep mode (eg with the auto sleep PR) causes everything to break

I have encountered this, stopping the spi from sleeping altogether fixes the crashing, and then I just call "GoToRunning" to fix the screen state being wrong afterwards (see e6d60f1). We should find a way to fix this bug without the autosleep pr however, i'll keep you updated on that

There are still some wake/sleep state bugs I need to investigate (display gets stuck on in some scenarios)

I have not encountered any of these, if you could provide some scenarios that cause this, I can help debug it

KaffeinatedKat avatar Oct 16 '23 00:10 KaffeinatedKat

Coming in on 48h runtime now with 20% still in the tank, very impressive! I'd say it's a lot more usable with those changes, awesome job

Another thought I had for power usage is driving the LCD at a slightly lower voltage. From what I can see the LCD driver allows choosing what drive voltage is used for the LCD, so maybe a slightly lower voltage could save some power? It also might cause the LCD to not turn pixels on/off properly so not sure if this is a viable route (and without a power profiler I'm not even sure if the panel draws much power or if it's mostly the driver chip). Also there appears to be about 10 driver voltages :grimacing: so yeah sounds fun

One other thing: some of the comments from the original PWM PR should probably be addressed. I think the points about not running the PWM engine when the screen is off or at one of the fixed brightness modes has merit, with PWM only being used between brightness settings. I think it also makes sense to PWM just one pin at a time, though I can see the formulas for calculating brightness being annoying as the brightness increase per duty increase is different for each pin Ideally I think this should take the form of a unified brightness interface where it takes some target brightness value between 0-10000 (or whatever) and the brightness code calculates what pin configuration is needed along with whether PWM is required? I could have a crack at implementing this, what do you reckon?

mark9064 avatar Oct 16 '23 22:10 mark9064

I can go ahead and mess with the voltage stuff, still messing around with the refresh stuff to reduce screen tearing, might as well mess with the voltage while i'm at it. Really glad to see how much potential there is for greater battery life with the display being on all the time, considering it's just a regular LCD screen

About the PWM stuff, I honestly have no idea how it works, but if you wanna take a crack at implementing the unified brightness interface, I would happy merge that into the branch. Sounds like it would be a really nice addition to improving the PWM implementation.

KaffeinatedKat avatar Oct 16 '23 22:10 KaffeinatedKat

I have changed all the voltage settings to their lowest and have been running it for a while and everything seems fine, the display still updates normally and I can't notice any pixels failing to turn on/off. Best case scenario, we save a bit of power, worst case, it does nothing. Really wish I had a devkit and power analysis tools to see if this makes any difference

KaffeinatedKat avatar Oct 17 '23 14:10 KaffeinatedKat