Osvitová jednotka
pre výrobu plošných spojov fotocestou.
BananaPi jednodoskový minipočítač
Banana Pi is an open source hardwarový projekt spoločnosti GuangDong BiPai technology co., LTD.i technology co., LTD.
Monitor radiácie s BPI-M2 Zero.
Screenshot z interpretovaných dát monitora radiacie
Záťažovy regulátor otáčok mini vŕtačky
K základnej výbave každej dielne elektrotechnika, či modelára patrí mini vŕtačka
Záťažovy regulátor otáčok mini vŕtačky
pohľad na osadenú dosku plošných spojov
Spájkovacia pec
pre spájkovanie SMD dosiek plošných spojov
RC433 pre Home Assistant
ovládanie garážvej brány z HomeAssistenta

Veľké fonty v micropythone na ESP32

               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:

large_ font

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)

...
 

 

Related Articles

Copyright © Free Joomla! 4 templates / Design by Galusso Themes