Micropyton ako prostriedok pre tvorbu programov embedded systémov je čoraz obľúbenejší a naberá na popularite. Často je limitujúcim faktorom veľkosť operačnej pamäte ,alebo pamäte flash . Preto rôzne obrazy systému mikropython majú často obmedzenú funkcionalitu a to vynechaním niektorých funkcií. Veľmi často sú to obrázkové a písmenné fonty , ktoré zaberajú veľa pamäte. Takýmto prípadom je aj obraz mikropythonu používaný pre obľúbený mikropočítač série esp32 v kombinácii s knižnicou LVGL.
Popisovaná knižnica pomáha tento problém riešiť a ilustračný obrázok ukazuje obrazovku laboratórneho zdroja s použitím tejto knižnice, kôli ktorej tato knižnica vznikla.
Vychádzal som z nasledujúcich kritérií:
- minimálna možná potreba operačnej pamäte
- minimálna možná spotreba pamäte programu (flash, alebo sd karta)
- dobrá schopnosť prekresľovania obrazovky
- minimalizovať blikanie
- možnosť zväčšovania , amenšovania znakov
-možnosť kombinovať ľubovoľné fonty a symboly
Použil som vlastnú knižnicu obsahujúcu nasledovný kód:
# large_font.py - Library for large digit displays with zoom capability import lvgl as lv import os, gc class DisplayManager: def __init__(self, width=800, height=480): self.WIDTH = width self.HEIGHT = height self.current_screen = None def create_screen(self, bg_color=lv.color_hex(0x000000)): """Create a new screen with optional background color""" screen = lv.obj() screen.set_size(self.WIDTH, self.HEIGHT) screen.set_style_bg_color(bg_color, lv.PART.MAIN) return screen def show_screen(self, screen): """Display the specified screen""" screen.set_parent(lv.scr_act()) self.current_screen = screen lv.scr_load(screen) class DigitDisplay: def __init__(self, display_manager, x_pos, y_pos, suffix=None, zoom=128): self.dm = display_manager self.img_pool = {} # Image pool for all digits self.current_screen = None self.x_pos = x_pos self.y_pos = y_pos self.base_digit_spacing = 50 # Základný rozostup pri zoom=255 (100%) self.base_comma_offset = 25 # Základný offset desatinnej čiarky pri zoom=255 self.base_suffix_offset = 5 # Základný offset sufixu pri zoom=255 self.max_digits = 4 # For 4-digit display self.img_usage = {} self.suffix = suffix self.zoom = zoom # Zoom factor (255 = 100%, 128 = 50%) # Vypočítame aktuálne rozostupy podľa zoomu self._update_spacing() def init_digits(self, screen): """Initialize display with parent screen""" self.current_screen = screen self._load_digit_images() def _load_bmp(self, filename): """Load BMP image from file""" try: with open(filename, 'rb') as f: data = f.read() if data[0:2] != b'BM': raise ValueError("Not a valid BMP file") width = int.from_bytes(data[18:22], 'little') height = int.from_bytes(data[22:26], 'little') bpp = int.from_bytes(data[28:30], 'little') data_offset = int.from_bytes(data[10:14], 'little') if bpp != 24: raise ValueError("Only 24-bit BMP supported") row_padding = (4 - (width * 3) % 4) % 4 img_data = bytearray(width * height * 2) for y in range(height): for x in range(width): bmp_pos = data_offset + (height - 1 - y) * (width * 3 + row_padding) + x * 3 b, g, r = data[bmp_pos], data[bmp_pos+1], data[bmp_pos+2] rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) img_data[(y * width + x) * 2] = rgb565 & 0xFF img_data[(y * width + x) * 2 + 1] = (rgb565 >> 8) & 0xFF img_dsc = lv.img_dsc_t({ 'data_size': len(img_data), 'data': img_data, 'header': { 'w': width, 'h': height, 'cf': lv.img.CF.TRUE_COLOR } }) img = lv.img(self.current_screen) img.set_src(img_dsc) img.set_pos(-1000, -1000) # Hide initially return img except Exception as e: print(f"Error loading {filename}: {e}") return None def _load_digit_images(self): """Load all digit images into pool""" digit_files = { '0': '30.bmp', '1': '31.bmp', '2': '32.bmp', '3': '33.bmp', '4': '34.bmp', '5': '35.bmp', '6': '36.bmp', '7': '37.bmp', '8': '38.bmp', '9': '39.bmp', ',': '2e.bmp', 'V': 'V.bmp', 'A': 'A.bmp' } for digit, filename in digit_files.items(): self.img_pool[digit] = [] original_img = self._load_bmp(filename) if not original_img: print(f"Warning: Failed to load '{digit}' from {filename}") continue src = original_img.get_src() original_img.set_pos(-1000, -1000) self.img_pool[digit].append(original_img) # Create extra copies for each digit position for _ in range(self.max_digits - 1): img = lv.img(self.current_screen) img.set_src(src) img.set_pos(-1000, -1000) self.img_pool[digit].append(img) def _get_next_img(self, digit): """Get next available image for digit from pool""" if digit not in self.img_usage: self.img_usage[digit] = 0 index = self.img_usage[digit] pool = self.img_pool.get(digit, []) if index < len(pool): img = pool[index] self.img_usage[digit] += 1 return img else: print(f"Not enough images for digit '{digit}'!") return None def display_value(self, int_part, frac_part): """Display a numeric value with decimal point""" str_int = f"{int_part:02d}" # 2-digit integer part str_frac = f"{frac_part:02d}" # 2-digit fractional part all_digits = list(str_int + str_frac) # Combine all digits self.img_usage = {} # Reset image usage tracking # Pred zobrazením prepočítame rozostupy podľa aktuálneho zoomu self._update_spacing() # Display integer part digits (first 2 digits) for i in range(2): digit = all_digits[i] pos_x = self.x_pos + i * self.digit_spacing # Hide leading zero if i == 0 and digit == '0': for other_digit, pool in self.img_pool.items(): for other_img in pool: if other_img.get_x() == pos_x: other_img.set_pos(-1000, self.y_pos) continue img = self._get_next_img(digit) if img: # Hide any other image at this position for other_digit, pool in self.img_pool.items(): for other_img in pool: if other_img is not img and other_img.get_x() == pos_x: other_img.set_pos(-1000, self.y_pos) img.set_pos(pos_x, self.y_pos) img.set_zoom(self.zoom) # Display decimal comma comma_pos_x = self.x_pos + 2 * self.digit_spacing comma_img = self._get_next_img(',') if comma_img: comma_img.set_pos(comma_pos_x, self.y_pos) comma_img.set_zoom(self.zoom) # Display fractional part digits (last 2 digits) for i in range(2, 4): digit = all_digits[i] pos_x = self.x_pos + (i) * self.digit_spacing + self.comma_offset img = self._get_next_img(digit) if img: for other_digit, pool in self.img_pool.items(): for other_img in pool: if other_img is not img and other_img.get_x() == pos_x: other_img.set_pos(-1000, self.y_pos) img.set_pos(pos_x, self.y_pos) img.set_zoom(self.zoom) # Display suffix if specified if self.suffix in self.img_pool: suffix_img = self._get_next_img(self.suffix) if suffix_img: suffix_x = self.x_pos + 4 * self.digit_spacing + self.comma_offset + self.suffix_offset # Nastavenie priamej pozície pre suffix suffix_img.set_pos(suffix_x, self.y_pos) suffix_img.set_zoom(self.zoom) def _update_spacing(self): """Prepočíta rozostupy medzi číslicami podľa aktuálneho zoomu""" zoom_factor = self.zoom / 255.0 # 255 = 100%, 128 = 50% self.digit_spacing = int(self.base_digit_spacing * zoom_factor) self.comma_offset = int(self.base_comma_offset * zoom_factor) self.suffix_offset = int(self.base_suffix_offset * zoom_factor) def set_zoom(self, zoom_factor): """Set zoom factor for all digits (255 = 100%)""" self.zoom = zoom_factor self._update_spacing() # Prepočítame rozostupy po zmene zoomu def create_4_digit_displays(dm, screen, zoom=255): """Create 4 digit displays with specified zoom level (255 = 100%)""" digit_displays = [] positions = [ (70, 50), # Top left (V) (70, 200), # Top right (V) (70, 300), # Bottom left (A) (70, 370), # Bottom right (A) ] suffixes = ['V', 'V', 'A', 'A'] for (x, y), suffix in zip(positions, suffixes): disp = DigitDisplay(dm, x, y, suffix=suffix) disp.set_zoom(zoom) disp.init_digits(screen) digit_displays.append(disp) return digit_displays def refresh_digit_displays(digit_displays, num1, num2, num3, num4): """Refresh all 4 digit displays with new values""" values = [ (num1 // 100, num1 % 100), # First display (voltage) (num2 // 100, num2 % 100), # Second display (voltage) (num3 // 100, num3 % 100), # Third display (current) (num4 // 100, num4 % 100) # Fourth display (current) ] for disp, (int_p, frac_p) in zip(digit_displays, values): disp.display_value(int_p, frac_p) return values
Riešnie :
Použil som metódu, pri ktorej zobrazované znaky sú obrázkami. Každý znak je BMP obrázok. Obrázok musí byť 24 bitový , inak ho nedokáže LVGL knižnica importovať. Ja com zvolil obrázky 50x96 bodov. ktorého veľkosť sa pohybuje okolo 10kB. Pri veľkých zobrazovaných znakoch je ich počet na obrazovke, ktoré sa dajú zmysluplne umiestniť aj tak obmedzený, takže vo funkcii def _load_digit_images(self) som si zadefinoval znaky a zodpovedajúce bmp súbory, ktoré budú zobraziteľné niekde na obrazovke. Počet načítaných obrázkov sa môže ľubovoľne meniť. Každý obrázok sa načíta do pamäte len raz. Potrebujeme ho ale zobraziť x krát na roznych miestach. Na tento účel som použil volanie funkcie lvgl.scr_load(), ktorá vytvorí objedkt obrázku, ale len pomocou odkazu na už načítaný BMP obrázok, čo výrazne šetrí operačnú pamäť. Pre každú zobrazovanú pozíciu som načítal do poolu obrázky , ktoré sa potencionálne môžu na danej pozícií požadovať na zobrazenie. Teraz stači obrázky na danej pozícií iba zamienať. Je to jednoduché, ale pomalé a refresh displeja "hnusne " bliká. Riešením je, že všetky znaky sa načítajú na danú pozíciu na hromadu a tie ktoré sa práve nezobrazujú sú posunuté na pozíciu mimo zobrazovanú oblasť displeja, čo je rýchle a efektívne a vykonateľné jednoducho volaním funkcie lvgl.img.set_offset_x(). Pre optimalizáciu rýchlosti, sa na obrazovke prekresľujú iba znaky, kde sa vyžaduje zmena. Uvedený prístup umožňuje svižné zmeny (asi 40 veľkých obrázkových znakov na obrazovke za sekundu ). Výhodou je aj možnosť zväčšovať, či zmenšovať zanky a tým dosiahnuť zobrazenie ľubovoľnej veľkosti znakov.
Nevýhoda tohto prístupu je, že manipulujeme s BMP obrázkami, ktoré majú stanovenú farebnosť popredia a pozadia , takže meniť priehľadnosť pozadia, lebo farbu znakov, či pozadia zmenou LVGL atribútov nie je možné.
Príklad použítia v hlavnom programe , ktorý slúži ako hlavná aplikácia na zobrazenie štyroch rôznych hodnôt v rôznych úrovniach zväčšenia (zoomu), pre ilustračné účely:
import lvgl as lv
import display
import large_fonts
display.init()
# Initialize display manager
dm = large_fonts.DisplayManager()
# Create screen
screen = dm.create_screen()
dm.show_screen(screen)
# Create 4 digit displays with different zoom levels
displays = large_fonts.create_4_digit_displays(dm, screen, zoom=128)
displays[0].set_zoom(512) # 200% zoom
displays[1].set_zoom(255) # 100% zoom
displays[2].set_zoom(128) # 50% zoom
displays[3].set_zoom(64) # 25% zoom
# Update displays with values
large_fonts.refresh_digit_displays(displays, 1234, 5678, 9012, 3456)
import snapshot
Výsledkom je takáto obrazovka:
Popis knižničných funkcii a ich atribútov:
Documentation for large_fonts.py Library
Introduction
The large_fonts.py library provides tools for displaying large digits on screens using bitmap images. It's designed for use with the LVGL library and allows smooth resizing of displayed digits using zoom functionality.
DisplayManager Class
Description
Base class for display and screen management.
Attributes
- WIDTH (int): Display width in pixels (default 800)
- HEIGHT (int): Display height in pixels (default 480)
- current_screen (lv.obj): Reference to currently displayed screen
Methods
create_screen(bg_color=lv.color_hex(0x000000))
Creates a new screen with given background color.
Parameters:
- bg_color: Background color (default black)
Returns:
- lv.obj: Created screen object
show_screen(screen)
Displays the specified screen.
Parameters:
- screen: Screen object to display
DigitDisplay Class
Description
Class for displaying large digits with zoom capability and decimal point.
Attributes
- img_pool (dict): Pool of images for digits
- current_screen (lv.obj): Parent screen
- x_pos, y_pos (int): Display position
- base_digit_spacing (int): Base digit spacing at 100% zoom (255)
- base_comma_offset (int): Base decimal point offset at 100% zoom
- base_suffix_offset (int): Base suffix offset at 100% zoom
- max_digits (int): Maximum number of digits (default 4)
- img_usage (dict): Image usage tracking
- suffix (str): Display suffix (e.g. 'V', 'A')
- zoom (int): Current zoom value (255 = 100%)
- digit_spacing (int): Current digit spacing (calculated from zoom)
- comma_offset (int): Current decimal point offset
- suffix_offset (int): Current suffix offset
Methods
init_digits(screen)
Initializes the display with given parent screen.
Parameters:
- screen: Parent screen object
display_value(int_part, frac_part)
Displays a numeric value with decimal point.
Parameters:
- int_part: Integer part (2 digits)
- frac_part: Fractional part (2 digits)
set_zoom(zoom_factor)
Sets zoom scale for all digits.
Parameters:
- zoom_factor: Zoom value (255 = 100%, 128 = 50%)
_update_spacing()
Internal method for recalculating spacing based on current zoom.
_load_bmp(filename)
Loads BMP image from file.
Parameters:
- filename: Filename to load
Returns:
- lv.img or None on error
_load_digit_images()
Loads all required digit images into pool.
_get_next_img(digit)
Gets next available image for given digit from pool.
Parameters:
- digit: Digit character ('0'-'9', ',')
Returns:
- lv.img or None if not available
Functions
create_4_digit_displays(dm, screen, zoom=255)
Creates 4 digit displays (2 for voltage, 2 for current) with given zoom scale.
Parameters:
- dm: DisplayManager instance
- screen: Target screen
- zoom: Initial zoom value (default 255 = 100%)
Returns:
- list: List of 4 DigitDisplay instances
refresh_digit_displays(digit_displays, num1, num2, num3, num4)
Updates all 4 displays with new values.
Parameters:
- digit_displays: List of displays from create_4_digit_displays
- num1, num2, num3, num4: New values for displays (as integers, e.g. 1234 = 12.34)
Returns:
- list: List of values in format [(int_part, frac_part), ...]
Usage Example
import large_fonts
import lvgl as lv
Initialization
dm = large_fonts.DisplayManager()
screen = dm.create_screen()
dm.show_screen(screen)
Create displays with 150% zoom
displays = large_fonts.create_4_digit_displays(dm, screen, zoom=382)
Display values
large_fonts.refresh_digit_displays(displays, 1234, 5678, 9012, 3456)
Change zoom
for disp in displays:
disp.set_zoom(200) # ~80% of original size
Notes
- Library requires BMP files with digits in working directory
- Zoom value 255 corresponds to 100% size (1:1)
- For proper display, call lv.task_handler() in main loop
- Digits are shown/hidden by moving them on/off screen (x=-1000)
...