In Micropython if the ili9XXX display driver base class is used it will automatically start the event loop using the default parameters. One of those parameters isfreq
which is a factory that gets used to determine the timer callback duration. The default value is 25 and the expression that gets used is 1000 / freq
which comes out to 40. so the event loop only gets run every 40 milliseconds.
The event loop can be shut down and a new one started by doing the following. This only applies if asynchronous is set to False for the driver.
import lv_utils
# assume display driver is already loaded
driver.event_loop.deinit()
driver.event_loop = lv_utils.event_loop(freq=100)
This will update every 10 milliseconds. I would not have it go any faster than that because of how long the display update takes and also because of the sheer number of ISR’s taking place. The event loop is not the most efficient because of the timer and not being able to allocate memory in an ISR. So the event loop schedules a call to another function when it is convenient for MicroPython to do so. That could be right away or it could be a while before it happens.
If you want to take control of when the display gets updated you are able to pretty easily. I wrote this for use with an ESP32
import lvgl as lv
import sys
import _thread
import time
##############################################################################
def default_exception_sink(e):
sys.print_exception(e)
class event_loop(object):
_current_instance = None
def __init__(self, *args, **kwargs):
if not lv.is_initialized():
lv.init()
if event_loop._current_instance is not None:
self.__dict__.update(event_loop._current_instance.__dict__)
if not self.is_running():
self._start()
return
self.delay = 0
self._exit_event = False
self.timer_lock = _thread.allocate_lock()
event_loop._current_instance = self
self._start()
def _start(self):
_thread.start_new_thread(self.loop, ())
def deinit(self):
self._exit_event = True
if self.timer_lock.locked():
self.timer_lock.release()
def update(self):
if self.timer_lock.locked():
self.timer_lock.release()
@staticmethod
def is_running():
self = event_loop._current_instance
if self is None:
return False
return self._exit_event is False
def loop(self):
self.timer_lock.acquire()
self._exit_event = False
self.delay = time.ticks_ms()
while not self._exit_event:
self.timer_lock.acquire()
curr_time = time.ticks_ms()
try:
lv.tick_inc(time.ticks_diff(curr_time, self.delay))
lv.task_handler()
except Exception as e:
default_exception_sink(e)
self.deinit()
self.delay = curr_time
sys.modules['lv_utils'] = sys.modules[__name__]
Place that code into a python source file of it’s own. Import that module before you import the driver. This will override the event_loop in lv_utils.
Again this is for the ESP32. This uses threading to handle the loop. what I did was I made a function that could be imported from __main__
that I could call from anywhere in the UI when I wanted to update the display.
def update_display():
driver.event_loop.update()
Now since this is run in its own thread you could call update in the main program loop.
By changing the event loop to the one above I am able to achieve smooth UI updates for progressive changes. Like the ramping up of the speed of a motor and using LVGL to display that speed. The widget no longer makes large jumps as seen on the display.