Рубрика пятничное.

Когда нужно что-то навскидку отладить, требуется довольно много времени на поиск необходимых инструментов поддержки в прямом смысле. Будь то 3D принтер, или то что необходимо склеить. Не всегда получается совместить материалы, обеспечить отсутствие механических воздействий, последующую ремонтопригодность или доступ осциллографом, например, залив печатные платы строительной пеной. Различные комбинации скотча и синей изоленты также имеют место быть но не всегда обеспечивают приятный результат.
Предлагаемый метод кружка кройки и шитья лишён подобного рода недостатков и позволяет произвести быстрое прототипирование с последующей упаковкой в подарочную коробочку для транспортировки, поработать с VsCode + CortexM3 MD + JTAG + шаговый двигатель и дисплей в автобусе, поезде или самолёте.
Как то необходимо было закрепить печатную плату с контроллером Cortex M3 Medium Density и дисплеем lcd 12864, драйвер шагового двигателя tmc2209, энкодер mt6701, JTAG - эмулятор.
Инструмент для печатных плат, и, как оказывается, для их монтажа.

Исходные материалы: печатные платы и капроновая нитка для товарной упаковки, где-то 0.5 мм диаметр, толстый полиэтиленовый или полипропиленовый/полистироловый (?) белый поролон, иногда выглядит как многослойный толщиной 3-4 см.

Порядок монтажа. Нет ничего проще: продеваем нить через паз инструмента и насквозь протыкаем поролон.

Далее во время обратного хода инструмента можно держать образовавшуюся петлю или продеть через неё держатель, всё что попалось под руку, например, резистор.

Далее показаны пример для крепления драйвера шагового двигателя

В результате получаем следующую конфигурацию

Аналогично производим монтаж главной печатной платы, она не содержит каких-либо отверстий, поэтому её крепим как хомутом

Ну и как же без контроля пакетов по UART. Для этого используем классическую платку с микросхемой FTDI. Но нам необходима экономия места, поэтому, делаем 3D-конструкцию. Прячем тестер последовательного порта в карман, вырезанный канцелярским ножом.

Для фиксации сбоку прошиваем дополнительно нитью, включаем USB, проверяем.

Для монтажа шагового двигателя используем отверстия, чуть ближе расположенные чем его, это позволяет сделать монтаж с натяжением

Конечный результат

Далее переходим к тому, что AI практически полностью с 0-я написал вполне приличную Embedded-систему, как будто бы сам вручную на С или Ассемблере вымучивал каждый байт или такт.
Чтобы не загромождать, перейдём сразу к наборам вайбов в виде md-саммари и результатов под спойлером, которые могут быть использованы для кодогенерации. MD-саммари это фактически скилл позволяющий модели реализовать +- стабильный код без лишних действий с напильником.
Поиск красивых моноширинных шрифтов
# Саммари: скрипт генерации превью шрифтов ## Краткое описание Скрипт автоматически находит в системе все моноширинные шрифты и создаёт единое изображение (PNG) со списком их имён и тестовым текстом. ## Как работает скрипт 1. **Поиск шрифтов**: * Используется утилита `fc-list`. * Фильтруются только моноширинные шрифты (`spacing=100`). 2. **Очистка данных**: * Пути к файлам сортируются. * Удаляются дубликаты. * С помощью `basename` отсекаются лишние директории — остаются только имена файлов. 3. **Рендеринг**: * Для каждого шрифта с помощью ImageMagick создаётся строка текста в формате `ИмяФайла: abcdefghij`. 4. **Сборка итогового изображения**: * Все сгенерированные строки объединяются по вертикали. * Результат сохраняется в один PNG‑файл. ## Параметры и зависимости ### Зависимости * `fontconfig` (для работы `fc-list`). * `ImageMagick` (для рендеринга текста и сборки изображения). * Скрипт адаптируется под разные версии ImageMagick: использует `magick` либо старую команду `convert`. ### Конфигурационные переменные * `OUTPUT_FILE` — имя итогового изображения (по умолчанию: `monospace_filenames.png`). * `FONT_SIZE` — размер шрифта (по умолчанию: 32). * `PREVIEW_TEXT` — шаблон текста для проверки отображения (по умолчанию: `abcdefghij`).
#!/bin/bash # Configuration OUTPUT_FILE="monospace_filenames.png" FONT_SIZE=32 PREVIEW_TEXT="abcdefghij" # Compatibility check for ImageMagick COMMAND=$(command -v magick || command -v convert) if [ -z "$COMMAND" ]; then echo "Error: ImageMagick is not installed." exit 1 fi echo "Generating preview using filenames..." # 1. Fetch file paths for monospace fonts # 2. Sort to remove duplicates # 3. Use basename to extract just the filename (e.g., 'UbuntuMono-R.ttf') fc-list :spacing=100 -f "%{file}\n" | sort -u | while read -r path; do if [ -f "$path" ]; then # Get just the filename from the full path fname=$(basename "$path") $COMMAND -background white \ -fill black \ -font "$path" \ -pointsize "$FONT_SIZE" \ label:"$fname: $PREVIEW_TEXT" miff:- fi done | $COMMAND - -append "$OUTPUT_FILE" echo "Done! Preview saved to: $OUTPUT_FILE"
Результат

Пишем генератор шрифтов, который формирует C-файл с битмапом. Ремарка. Для генерации необходима была обратная связь от того что было на экране, так как рендер и генератор взаимно связаны. Поэтому, для дальнейших изысканий необходимо дополнить h-файл некоторыми константами которые используются в генераторе, так как присутствует паддинг для букв, например, g, у, ц итд, имеющие относительно нижней линии заданное смещение. Оно также задаётся.
Промпты для вайбкода делал на английском, так как на нём бо́льшая кодовая база и по ощущениям генерация идёт чуточку получше, поэтому код с англ. комментариями.
Генератор шрифта с формированием .c и .h
Генератор шрифтов:
# Генератор шрифтов для встраиваемых систем Скрипт на Python генерирует оптимизированные шрифтовые ресурсы для встраиваемых дисплеев. Создаёт три файла: * `display_fonts.c` — данные шрифта в формате C; * `fontdef.h` — заголовочный файл с конфигурацией; * `font_preview.png` — PNG‑превью сетки символов. ## Ключевые константы конфигурации ### Базовые параметры шрифта и сетки * **`FONT_PATH`** — путь к файлу шрифта (по умолчанию `cour.ttf`). * **`FONT_SIZE`** = $12$ — размер шрифта в пунктах. * **`WIDTH`** = $6$, **`HEIGHT`** = $12$ — размеры ячейки для символа в пикселях (ширина × высота). * **`FIRST_CHAR`** = `ord(' ')` ($32$) — начальный ASCII‑код символа. * **`LAST_CHAR`** = `ord('}')` ($125$) — конечный ASCII‑код символа. * **`COLS`** = $16$ — количество столбцов в сетке превью PNG. ### Параметры выравнивания и типографики * **`V_ALIGN`** = `"BOTTOM"` — стратегия вертикального выравнивания: `"TOP"`, `"CENTER"` или `"BOTTOM"`. * **`USE_DUAL_BBOX`** = `True` — флаг использования точных пиксельных масок (`True`) или метрик движка шрифта (`False`). * **`NON_DESCENDER_OFFSET`** = $-2$ — сдвиг обычных символов (положительное значение — вниз, отрицательное — вверх). * **`DESCENDER_RELATIVE_OFFSET`** = $-1$ — относительный сдвиг символов с нижними выносными элементами (например, «g», «p»). * **`DESCENDER_CHARS`** — множество символов с выносными элементами ниже базовой линии: `set("gjpqy,;Q")`. ### Параметры инверсии (для коррекции ориентации дисплея) * **`FLIP_HORIZONTAL`** = `False` — зеркальное отображение по горизонтали (если дисплей инвертирован слева направо). * **`FLIP_VERTICAL`** = `False` — зеркальное отображение по вертикали (если дисплей инвертирован сверху вниз). ### Настройки превью * **`APPLY_FLIPS_TO_PREVIEW`** = `False` — флаг применения инверсий к превью: * `True` — превью повторяет зеркальность дисплея; * `False` — обычное отображение текста. ## Алгоритм работы скрипта 1. **Загрузка шрифта.** Пытается загрузить шрифт из `FONT_PATH`. Если не удаётся — использует шрифт по умолчанию. 2. **Генерация списка символов.** Формирует список символов в диапазоне от `FIRST_CHAR` до `LAST_CHAR`. 3. **Создание атласа PNG.** Инициализирует монохромное изображение (`'1'` mode) с размерами, рассчитанными по `COLS`, `WIDTH` и `HEIGHT`. 4. **Обработка каждого символа:** * создаёт отдельное изображение символа; * рассчитывает bounding box (с учётом `USE_DUAL_BBOX`); * применяет вертикальное выравнивание (`V_ALIGN`) и индивидуальные сдвиги (`NON_DESCENDER_OFFSET`, `DESCENDER_RELATIVE_OFFSET`); * рисует символ с точным позиционированием; * при необходимости применяет инверсию (`FLIP_HORIZONTAL`, `FLIP_VERTICAL`) к данным и превью. 5. **Упаковка пикселей.** Преобразует пиксели символа в последовательность байтов, упакованных вертикально (по столбцам). 6. **Формирование C‑файла.** Записывает данные шрифта в `display_fonts.c` как массив `uint8_t`. 7. **Генерация заголовочного файла.** Создаёт `fontdef.h` с макросами, описывающими конфигурацию (размеры, флаги инверсии и т. д.). 8. **Сохранение превью.** Масштабирует атлас и сохраняет его как `font_preview.png` для визуального контроля. ## Выходные файлы * **`display_fonts.c`** * содержит массив `fontCRC` с упакованными данными символов; * каждый символ представлен последовательностью байтов в формате `0xHH`. * **`fontdef.h`** * заголовочный файл с макросами конфигурации; * включает размеры (`CRCFONT_WIDTH`, `CRCFONT_HEIGHT`), коды символов (`CRCFONT_FIRSTCHAR`, `CRCFONT_LASTCHAR`), флаги выравнивания и инверсии (`CRCFONT_VALIGN_BOTTOM`, `CRCFONT_VFLIP`, `CRCFONT_HFLIP`). * **`font_preview.png`** * визуальное превью сетки символов; * масштабировано в $4$ раза для удобства просмотра; * позволяет проверить корректность выравнивания, инверсии и общего вида шрифта.
Генератор python-вывод
import math from PIL import Image, ImageFont, ImageDraw # ==================== CONFIGURATION ==================== FONT_PATH = "cour.ttf" # Ensure this file is in your script directory FONT_SIZE = 12 # Font size to render WIDTH, HEIGHT = 6, 12 # Works perfectly with odd, even, large, or small grids FIRST_CHAR = ord(' ') # ASCII range start (32) LAST_CHAR = ord('}') # ASCII range end (125) COLS = 16 # Number of columns in the PNG preview grid # --- Alignment & Typographical Tuning --- V_ALIGN = "BOTTOM" # Base alignment strategy: "TOP", "CENTER", or "BOTTOM" USE_DUAL_BBOX = True # True: Use pixel-perfect masks | False: Use font-engine metrics # --- Fine-Grained Fine Tuning Controls --- NON_DESCENDER_OFFSET = -2 # Shift normal chars (+ down, - up). e.g., -2 lifts them up from baseline. DESCENDER_RELATIVE_OFFSET = -1 # Relative shift for descenders (+ down, - up) to position tails perfectly. # Characters that naturally drop below the baseline horizon DESCENDER_CHARS = set("gjpqy,;Q") # --- Hardware Orientation Inversion Toggles --- FLIP_HORIZONTAL = False # Mirror Left-to-Right (True if hardware is H-mirrored) FLIP_VERTICAL = False # Mirror Top-to-Bottom (True if hardware is V-mirrored) # --- Preview Customization --- APPLY_FLIPS_TO_PREVIEW = False # True: preview mimics H/W mirror | False: normal text preview # ======================================================= def generate_font_assets(): try: font = ImageFont.truetype(FONT_PATH, FONT_SIZE) except Exception: print(f"Warning: Could not load '{FONT_PATH}'. Using default font.") font = ImageFont.load_default() chars_to_gen = [chr(code) for code in range(FIRST_CHAR, LAST_CHAR + 1)] rows = math.ceil(len(chars_to_gen) / COLS) # Initialize PNG Atlas (1-bit monochrome mode) atlas = Image.new('1', (COLS * WIDTH, rows * HEIGHT), 0) bytes_per_col = math.ceil(HEIGHT / 8) c_lines = [ '#include "fontdef.h"', '#include <stdint.h>', '', '/* ', ' * Ultimate Optimized Font Data', f' * Configuration - H-Flip: {FLIP_HORIZONTAL}, V-Flip: {FLIP_VERTICAL}, V-Align: {V_ALIGN}', ' * Packed vertically column-by-column to match display page layout.', f' * Each column takes {bytes_per_col} bytes based on an exact height of {HEIGHT}px.', ' */', 'const uint8_t fontCRC [] = {' ] for i, char in enumerate(chars_to_gen): char_img = Image.new('1', (WIDTH, HEIGHT), 0) draw = ImageDraw.Draw(char_img) # 1. Fetch layout metrics bounding box using Left-Top anchor format m_left, m_top, m_right, m_bottom = draw.textbbox((0, 0), char, font=font, anchor="lt") # 2. Fetch True Pixel Mask Rendering Bounding Box mask_tuple = font.getmask(char).getbbox() if USE_DUAL_BBOX else None if USE_DUAL_BBOX and mask_tuple: b_left, b_top, b_right, b_bottom = mask_tuple else: b_left, b_top, b_right, b_bottom = m_left, m_top, m_right, m_bottom tw = b_right - b_left th = b_bottom - b_top if tw <= 0 or th <= 0: x_off, y_off = 0, 0 else: # Horizontal centering calculation relative to absolute left point x_off = math.floor((WIDTH - tw) / 2.0) - b_left # --- VERTICAL ALIGNMENT MATH --- align = V_ALIGN.upper() if align == "TOP": base_y = 0 - b_top elif align == "BOTTOM": base_y = (HEIGHT - th) - b_top else: # CENTER base_y = math.floor((HEIGHT - th) / 2.0) - b_top # --- SEPARATED OFFSET TRIMMING ENGINE --- if char in DESCENDER_CHARS: # Independent shift for descenders relative to the base calculation y_off = base_y + DESCENDER_RELATIVE_OFFSET else: # Independent shift for flat baseline characters y_off = base_y + NON_DESCENDER_OFFSET # Draw character safely with explicit positioning anchors draw.text((x_off, y_off), char, font=font, fill=1, anchor="lt") # Create matching preview glyph preview_img = char_img.copy() # --- Apply Hardware Inversion Adjustments to Data Image --- if FLIP_HORIZONTAL: char_img = char_img.transpose(Image.FLIP_LEFT_RIGHT) if FLIP_VERTICAL: char_img = char_img.transpose(Image.FLIP_TOP_BOTTOM) # --- Apply Inversion Options to Preview Image --- if APPLY_FLIPS_TO_PREVIEW: if FLIP_HORIZONTAL: preview_img = preview_img.transpose(Image.FLIP_LEFT_RIGHT) if FLIP_VERTICAL: preview_img = preview_img.transpose(Image.FLIP_TOP_BOTTOM) # Paste the transformed glyph into the PNG atlas grid atlas.paste(preview_img, ((i % COLS) * WIDTH, (i // COLS) * HEIGHT)) pixels = char_img.load() hex_values = [] # Pack raw pixel bits vertically based on active canvas dimensions for x in range(WIDTH): for b_idx in range(bytes_per_col): byte_val = 0 start_y = b_idx * 8 for bit in range(8): target_y = start_y + bit if target_y < HEIGHT: if pixels[x, target_y]: byte_val |= (1 << bit) hex_values.append(f"0x{byte_val:02X}") comment_char = '\\\\' if char == '\\' else char c_lines.append(f" {', '.join(hex_values)}, /* \"{comment_char}\" */") c_lines.append("};") # Write optimized array source data with open("display_fonts.c", "w", encoding="utf-8") as f: f.write("\n".join(c_lines) + "\n") # --- Map high-level configuration properties to hardware flags --- valign_flag = 1 if V_ALIGN.upper() == "BOTTOM" else 0 vflip_flag = 1 if FLIP_VERTICAL else 0 hflip_flag = 1 if FLIP_HORIZONTAL else 0 # Write tracking architecture configuration header definitions h_lines = [ "#ifndef FONTDEF_H", "#define FONTDEF_H", "", "#include <stdint.h>", "", f"#define CRCFONT_WIDTH {WIDTH}", f"#define CRCFONT_HEIGHT {HEIGHT}", f"#define CRCFONT_BYTES_PER_COL {bytes_per_col}", f"#define CRCFONT_BYTES_PER_CHAR {WIDTH * bytes_per_col}", f"#define CRCFONT_FIRSTCHAR ((uint8_t)'{chr(FIRST_CHAR)}')", f"#define CRCFONT_LASTCHAR ((uint8_t)'{chr(LAST_CHAR)}')", "", "// --- NEW CRITICAL GENERATOR FLAGS ---", f"#define CRCFONT_VALIGN_BOTTOM {valign_flag} // 1 if aligned to container bottom, 0 if top", f"#define CRCFONT_VFLIP {vflip_flag} // 1 if bits flow upside down, 0 if normal", f"#define CRCFONT_HFLIP {hflip_flag} // 1 if columns flow right-to-left, 0 if normal", "", "extern const uint8_t fontCRC [];", "", "#endif /* FONTDEF_H */" ] with open("fontdef.h", "w", encoding="utf-8") as f: f.write("\n".join(h_lines) + "\n") # Save a scaled preview sheet to inspect orientation transformations visually preview_scale = 4 atlas_rescaled = atlas.resize( (atlas.width * preview_scale, atlas.height * preview_scale), resample=Image.NEAREST ) atlas_rescaled.save("font_preview.png") print(f"Success! Generated stable assets with split character group offset fine-tuning.") if __name__ == "__main__": generate_font_assets()
Функция для рендеринга шрифта на С
С-рендер (пример)
// External display variables referenced by your graphics engine extern uint8_t display_buffer[DISPLAY_WIDTH * DISPLAY_PAGES]; extern uint8_t display_update_needed; uint8_t Display_DrawCharFont(uint8_t x, uint8_t y, char ch) { // 1. Instant out-of-bounds boundary validation if (ch < CRCFONT_FIRSTCHAR || ch > CRCFONT_LASTCHAR || x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) { return 0; } // 2. Pre-calculate flash memory character pointer offset dynamically const uint32_t char_offset = (uint32_t)(ch - CRCFONT_FIRSTCHAR) * CRCFONT_BYTES_PER_CHAR; const uint8_t *font_ptr = &fontCRC[char_offset]; // 3. Compute buffer page mapping offsets and safe drawing boundaries uint8_t start_page = y >> 3; // Fast division by 8 uint8_t bit_shift = y & 0x07; // Fast modulo 8 calculation uint8_t max_cols = (x + CRCFONT_WIDTH <= DISPLAY_WIDTH) ? CRCFONT_WIDTH : (DISPLAY_WIDTH - x); // 4. Generate dynamic master mask based strictly on font height macro (12 bits) uint32_t master_mask = (1UL << CRCFONT_HEIGHT) - 1UL; // 5. Blit vertical pixel strips across screen columns for (uint8_t col = 0; col < max_cols; col++) { // --- FINAL LEFT-RIGHT MIRROR CORRECTION --- // Restore standard left-to-right sequence to achieve final horizontal orientation uint8_t target_col = col; // --- NATIVE LITTLE-ENDIAN WORD ASSEMBLY --- uint32_t glyph_32 = 0; for (uint8_t b = 0; b < CRCFONT_BYTES_PER_COL; b++) { glyph_32 |= ((uint32_t)font_ptr[(target_col * CRCFONT_BYTES_PER_COL) + b]) << (b * 8); } // Clean out any remaining padding artifacts outside the active font height glyph_32 &= master_mask; // --- VERTICAL MIRROR PIPELINE --- // Reverses the bits within the active 12-bit window to keep text right-side up uint32_t flipped_glyph = 0; for (uint8_t i = 0; i < CRCFONT_HEIGHT; i++) { if (glyph_32 & (1UL << i)) { flipped_glyph |= (1UL << (CRCFONT_HEIGHT - 1 - i)); } } glyph_32 = flipped_glyph; // Dynamically align both font strip and mask to coordinate 'y' bit shift uint32_t shifted_glyph = glyph_32 << bit_shift; uint32_t shifted_mask = master_mask << bit_shift; uint16_t base_idx = x + col; uint8_t current_page = start_page; // Loop dynamically through display pages until shifted mask bits are exhausted while (shifted_mask > 0) { if (current_page < DISPLAY_PAGES) { uint8_t p_mask = (uint8_t)(shifted_mask & 0xFFUL); if (p_mask) { uint8_t p_data = (uint8_t)(shifted_glyph & 0xFFUL); uint16_t page_idx = (uint16_t)current_page * DISPLAY_WIDTH + base_idx; display_buffer[page_idx] &= ~p_mask; display_buffer[page_idx] |= p_data; } } else { break; } shifted_glyph >>= 8; shifted_mask >>= 8; current_page++; } } display_update_needed = 1; return (CRCFONT_WIDTH + 1); }
Обратите внимание - можно оптимизировать битовые операции под конкретный шрифт и его свойства, например, кратное 8, 4, 2 итд
Пример битмапа:
//заголовочник #ifndef FONTDEF_H #define FONTDEF_H #include <stdint.h> #define CRCFONT_WIDTH 6 #define CRCFONT_HEIGHT 12 #define CRCFONT_BYTES_PER_COL 2 #define CRCFONT_BYTES_PER_CHAR 12 #define CRCFONT_FIRSTCHAR ((uint8_t)' ') #define CRCFONT_LASTCHAR ((uint8_t)'}') // --- NEW CRITICAL GENERATOR FLAGS --- #define CRCFONT_VALIGN_BOTTOM 1 // 1 if aligned to container bottom, 0 if top #define CRCFONT_VFLIP 0 // 1 if bits flow upside down, 0 if normal #define CRCFONT_HFLIP 0 // 1 if columns flow right-to-left, 0 if normal extern const uint8_t fontCRC []; #endif /* FONTDEF_H */ //данные #include "fontdef.h" #include <stdint.h> /* * Ultimate Optimized Font Data * Configuration - H-Flip: False, V-Flip: False, V-Align: BOTTOM * Packed vertically column-by-column to match display page layout. * Each column takes 2 bytes based on an exact height of 12px. */ const uint8_t fontCRC [] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* " " */ 0x00, 0x00, 0x00, 0x00, 0x7C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* "!" */ 0x80, 0x03, 0x80, 0x00, 0x00, 0x00, 0x80, 0x03, 0x80, 0x00, 0x00, 0x00, /* """ */
Несмотря на фрагмент - данный С-скетч вполне осознаваем ИИ, имеет небольшое количество токенов и достаточен для генерации и коррекции чего бы то ни было.
Паддинг можно настраивать как угодно. Пример вывода файла со сгенерированным шрифтом

Варианты шрифтов

Генерация изображений. Используется преобразование png->С битмап с применением постеризации для ч/б изображения. Вначале RGB->B/W затем всё что больше 127 - белое, меньше - чёрное. Пиксели сохраняются
Генератор изображений
# Саммари кода: генератор bitmap‑изображений для встраиваемых систем ## Назначение Скрипт на Python преобразует изображение в монохромный bitmap и генерирует C‑файлы (`image.h` и `image.c`) для использования в встраиваемых системах (например, на микроконтроллерах). ## Импорты - `os` — проверка существования файла изображения; - `math` — не используется в коде (возможно, планировался к использованию); - `PIL.Image` — работа с изображениями (открытие, масштабирование, конвертация). ## Функция `generate_bitmap` ### Параметры - `image_path` — путь к исходному изображению; - `output_name` (по умолчанию `"my_generated_image"`) — имя выходного объекта в C‑коде; - `max_w` (по умолчанию 128) — максимальная ширина изображения; - `max_h` (по умолчанию 64) — максимальная высота изображения. ### Этапы обработки изображения 1. **Открытие изображения** Изображение открывается через `Image.open()`, получаются его исходные размеры. 2. **Масштабирование** - Рассчитываются коэффициенты масштабирования по ширине (`scale_w`) и высоте (`scale_h`). - Выбирается минимальный коэффициент (`scale_factor`), чтобы сохранить пропорции. - Если `scale_factor < 1.0`, изображение уменьшается с помощью `resize()` (метод Bicubic) до размеров, соответствующих ограничениям. 3. **Конвертация в градации серого** Изображение переводится в режим `'L'` (grayscale). 4. **Бинаризация (чёрно‑белое изображение)** Применяется порог `127`: пиксели ярче — белые (`1`), темнее — чёрные (`0`). Результат — изображение в режиме `'1'`. 5. **Упаковка пикселей в байты** - Пиксели (1 бит каждый) упаковываются в байты (8 бит), начиная со старшего бита (MSB first). - Если общее число пикселей не кратно 8, оставшиеся биты дополняются нулями. 6. **Генерация `image.h`** Создаётся заголовочный файл с: - защитой от множественного включения (`#ifndef IMAGE_H_`); - подключением `<stdint.h>`; - определением структуры `DisplayImage_t` (указатель на данные, ширина, высота); - объявлением внешнего объекта с именем `output_name`. 7. **Генерация `image.c`** Создаётся файл реализации с: - массивом `output_name_data` типа `const uint8_t`, содержащим упакованные данные изображения (в шестнадцатеричном формате, `0xXX`); - инициализацией структуры `DisplayImage_t` с указанием указателя на данные, ширины и высоты. ## Основной блок (`if __name__ == "__main__":`) - Проверяет существование файла `logo.png`. - Если файл найден, вызывает `generate_bitmap()` с ограничениями `max_w=128` и `max_h=64`. - Если файла нет, выводит инструкцию по размещению изображения. ## Результаты выполнения После успешного запуска: - выводятся сообщения о масштабировании (если применимо) и успехе; - создаются файлы `image.h` и `image.c`, готовые к компиляции в проекте для микроконтроллера.
Питон-генератор С-битмапа
import os import math from PIL import Image def generate_bitmap(image_path, output_name="my_generated_image", max_w=128, max_h=64): # 1. Open the image img = Image.open(image_path) orig_w, orig_h = img.size # 2. Calculate proportional scale down factors scale_w = max_w / orig_w scale_h = max_h / orig_h scale_factor = min(scale_w, scale_h) # Only downscale if the image exceeds display limits if scale_factor < 1.0: new_w = int(orig_w * scale_factor) new_h = int(orig_h * scale_factor) # Apply high-quality Bicubic scaling img = img.resize((new_w, new_h), Image.Resampling.BICUBIC) print(f"Rescaled image from {orig_w}x{orig_h} down to {new_w}x{new_h}") # 3. Convert to grayscale ('L' mode) img_gray = img.convert('L') # 4. Posterize by 255/2 threshold (127) to convert to sharp black and white threshold = 127 img_bw = img_gray.point(lambda p: 1 if p > threshold else 0, mode='1') width, height = img_bw.size pixels = list(img_bw.getdata()) # 5. Pack 1-bit pixels into array of bytes (MSB first) byte_array = [] current_byte = 0 bit_count = 0 for pixel in pixels: current_byte = (current_byte << 1) | pixel bit_count += 1 if bit_count == 8: byte_array.append(current_byte) current_byte = 0 bit_count = 0 # Pad trailing bits if total pixel count is not a multiple of 8 if bit_count > 0: current_byte = current_byte << (8 - bit_count) byte_array.append(current_byte) # 6. Generate image.h with open("image.h", "w") as h_file: h_file.write("#ifndef IMAGE_H_\n#define IMAGE_H_\n\n") h_file.write("#include <stdint.h>\n\n") h_file.write("typedef struct {\n const uint8_t *data;\n uint8_t width;\n uint8_t height;\n} DisplayImage_t;\n\n") h_file.write(f"extern const DisplayImage_t {output_name};\n\n") h_file.write("#endif // IMAGE_H_\n") # 7. Generate image.c with open("image.c", "w") as c_file: c_file.write('#include "image.h"\n\n') c_file.write(f"static const uint8_t {output_name}_data[{len(byte_array)}] = {{\n ") for i, byte in enumerate(byte_array): c_file.write(f"0x{byte:02X}, ") if (i + 1) % 12 == 0: c_file.write("\n ") c_file.write("\n};\n\n") c_file.write(f"const DisplayImage_t {output_name} = {{\n") c_file.write(f" .data = {output_name}_data,\n") c_file.write(f" .width = {width},\n") c_file.write(f" .height = {height}\n") c_file.write("};\n") print(f"Success! Generated image.h and image.c for '{image_path}' ({width}x{height})") if __name__ == "__main__": target_image = "logo.png" if os.path.exists(target_image): # Passes target layout max dimensions (128 width, 64 height) generate_bitmap(target_image, max_w=128, max_h=64) else: print(f"Please place your source image as '{target_image}' or edit the script path variable.")
Готовая пиктограммка

Пример С-вывода
//заголовочник #ifndef IMAGE_H_ #define IMAGE_H_ #include <stdint.h> typedef struct { const uint8_t *data; uint8_t width; uint8_t height; } DisplayImage_t; extern const DisplayImage_t my_generated_image; #endif // IMAGE_H_ // реализация #include "image.h" static const uint8_t my_generated_image_data[190] = { 0xEF, 0xF8, 0x29, 0xFF, 0xFF, 0x1C, 0x00, 0x7F, 0xFE, 0xEC, 0x01, 0x67, 0xFF, 0xFE, 0x80, 0x09, 0xFF, 0xBF, 0xD0, 0x12, 0x8E, 0x87, 0xE0, 0x06, 0x96, 0x66, 0x3E, 0x1C, 0x3F, 0xD1, 0x0C, 0x04, 0x15, 0x83, 0x18, 0x04, .... const DisplayImage_t my_generated_image = { .data = my_generated_image_data, .width = 37, .height = 41 }; ... /// функция для рендера пикторграммки
Результат - на первом фото
Далее приведён саммари для драйвера дисплея (полный) который может быть использован для генерации необходимых функций и их согласования с Python-генератором шрифтов.
Саммари по функциям рисования для дисплея
# Саммари по файлу `display_draw.c` Данный файл содержит функции для рисования графических примитивов и вывода текста на монохромный дисплей с разрешением 128x64, управляемый контроллером ST7565R (или совместимым). Функции работают с буфером отображения, который затем отправляется на дисплей через соответствующие функции из `display.c`. ## Общие сведения - **Цель файла**: Реализация функций отрисовки пикселей, линий, прямоугольников, текста и изображений. - **Размеры дисплея**: 128 пикселей в ширину, 64 пикселя в высоту. Данные в памяти организованы по страницам (8 страниц по 8 пикселей в высоту). - **Буферы**: Файл использует внешние буферы `display_buffer` и `last_display_buffer`, определенные в `display.c`. Переменная `display_update_needed` сигнализирует о необходимости обновления дисплея. ## Ключевые функции и алгоритмы ### 1. `Display_SetPixel(x, y, color)` Устанавливает или сбрасывает отдельный пиксель в буфере отображения. - **Алгоритм**: - Проверяет, находится ли координата внутри границ дисплея. - Вычисляет, к какой странице (`page = y / 8`) и какому байту в строке принадлежит пиксель. - Модифицирует соответствующий бит в байте буфера с помощью битовых операций (`|=` для установки, `&=~` для сброса). - Устанавливает флаг `display_update_needed`. ### 2. `Display_DrawString(x, y, str)` Выводит строку символов на дисплей. - **Алгоритм**: - Проверяет корректность входных данных. - Циклически проходит по каждому символу строки. - Для каждого символа вызывает `Display_DrawCharFont`, чтобы нарисовать его и получить его ширину. - Увеличивает координату `x` на ширину символа для вывода следующего символа справа от предыдущего. - Останавливается, если строка выходит за правый край экрана. ### 3. `Display_DrawCharFont(x, y, ch)` Рисует один символ из встроенного шрифта (fontCRC) на экране. Это наиболее сложная функция, реализующая побитовую отрисовку шрифта. - **Алгоритм**: - **Проверка границ**: Проверяет, что символ в пределах допустимого диапазона и что начальная координата `x` в пределах экрана. - **Доступ к данным шрифта**: Рассчитывает смещение в массиве `fontCRC` на основе кода символа. - **Работа со страницами**: Символ может занимать несколько страниц (вертикально). Функция определяет начальную страницу на экране, куда нужно рисовать (`start_page`). - **Вертикальное отражение (Mirroring)**: Данные шрифта в `fontCRC` хранятся в инвертированном по вертикали виде. Перед отрисовкой функция "переворачивает" биты символа, чтобы он отображался правильно. - **Поблочная отрисовка**: Символ рисуется по столбцам. Для каждого столбца из данных шрифта формируется 32-битное слово. - **Сдвиг и наложение**: Слово со сдвинуто на `bit_shift` битов (смещение внутри страницы) и побитово накладывается на соответствующие байты в буфере отображения, используя маску, чтобы не затронуть соседние пиксели. - **Межстрочное расстояние**: Функция всегда возвращает ширину символа плюс один пиксель (пробел), обеспечивая автоматическое расстояние между символами. ### 4. `Display_DrawLine`, `Display_DrawRectangle`, `Display_FillRectangle` Функции для отрисовки линий, контуров и закрашенных прямоугольников. - **Алгоритм**: - `DrawHLine` и `DrawVLine`: Используют цикл и `Display_SetPixel` для установки пикселей вдоль линии. - `DrawRectangle`: Рисует четыре линии (две горизонтальные, две вертикальные). - `FillRectangle`: Закрашивает прямоугольную область, устанавливая каждый пиксель в цикле. ### 5. `Display_DrawImage(img, configuration)` Рисует монохромное изображение с возможностью масштабирования и отражения. - **Алгоритм**: - Рассчитывает начальные координаты `start_x` и `start_y` на основе флагов выравнивания (центр, право, низ и т.д.). - Циклически проходит по каждому пикселю исходного изображения. - Рассчитывает его цвет по битовой маске из массива `img->data`. - Если включены флаги `FLIP_LR` или `FLIP_TB`, координаты пикселя отражаются. - Вызывает `Display_SetPixel` для вывода пикселя в итоговую позицию на экране с учетом `start_x` и `start_y`. ## Особенности реализации - **Оптимизация**: Функции содержат прямые проверки границ для немедленного выхода при выходе за пределы экрана, что улучшает производительность. - **Безопасность**: Используются беззнаковые типы данных и проверки, чтобы избежать ошибок переполнения. - **Зависимости**: Файл зависит от `fontdef.h` (описание шрифта), `display.h` (декларации буферов и функций) и `image.h` (структура изображения).
Саммари для драйвера дисплея и модуля SPI с использованием DMA
# Саммари: display.c Данный файл содержит реализацию драйвера для графического дисплея LCD12864/ER3805/ST7565R на микроконтроллере STM32. Основная особенность реализации — использование технологии DMA для передачи данных дисплею, что позволяет освободить ядро микроконтроллера и повысить общую производительность системы. ## Общие сведения Драйвер управляет дисплеем через интерфейс SPI. Для обеспечения гибкости, код использует двойную буферизацию (`display_buffer` и `last_display_buffer`): * **`display_buffer`**: Текущий буфер кадра, в который рисуются все элементы (линии, строк, изображения). * **`last_display_buffer`**: Буфер предыдущего кадра. Сравнивается с текущим, чтобы определить, какие именно страницы (строки) дисплея изменились. Это позволяет обновлять только измененные области (`differential update`), что ускоряет отрисов��у. Ключевым элементом является функция `Display_Update()`, которая выполняет фазированную передачу данных дисплею. ## Подробное описание работы с DMA DMA (Direct Memory Access) — это механизм, позволяющий периферийным устройствам (в данном случае, SPI) напрямую обращаться к памяти микроконтроллера, минуя CPU. Это критически важно для дисплеев, так как передача всего экрана (1024 байта) по SPI в режиме опроса (polling) занимает слишком много времени и блокирует ядро. ### 1. Инициализация DMA (в `main.c`) Инициализация DMA выполняется в функции `SPI1_DMA_Init()`, которая вызывается из `main()` *до* инициализации дисплея (`Display_Init()`). * **Каналы DMA**: Используются два канала DMA1: * `DMA1_Channel2` — для приема данных от SPI (`SPI1_RX`). * `DMA1_Channel3` — для передачи данных по SPI (`SPI1_TX`). * **Буферы**: Для каждого канала выделены буферы в памяти: * `SPI1_TxBuffer[1024]` — буфер передачи. * `SPI1_RxBuffer[1024]` — буфер приема (в данном случае используется для приема данных от дисплея, если это нужно, или как "dummy" буфер). * **Конфигурация канала передачи (DMA1_Channel3)**: * `DMA_PeripheralBaseAddr`: Указывается адрес регистра данных SPI (`&(SPI1->DR)`). * `DMA_MemoryBaseAddr`: Указывается адрес начала буфера передачи. * `DMA_DIR`: Направление `DMA_DIR_PeripheralDST` (данные из памяти в периферию). * `DMA_BufferSize`: Размер буфера (1024 байта). * `DMA_MemoryInc`: Увеличение адреса памяти включено (`DMA_MemoryInc_Enable`), чтобы последовательно читать весь буфер. * `DMA_PeripheralInc`: Увеличение адреса периферии выключено (`DMA_PeripheralInc_Disable`), так как данные всегда пишутся в один и тот же регистр SPI. * `DMA_Mode`: Установлен в `DMA_Mode_Normal`, что означает передачу одного блока данных. * `DMA_Priority`: Приоритет `High`. * **Включение DMA для SPI**: После конфигурации каналов, включаются запросы DMA для SPI: * `SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE)` — разрешение DMA для передачи. * **Включение прерываний**: Включаются прерывания от DMA по завершению передачи (`DMA_IT_TC`), чтобы CPU мог узнать, когда передача закончена. ### 2. Процесс передачи данных с использованием DMA Процесс обновления дисплея (вызывается из `Display_Update()`) состоит из нескольких этапов: 1. **Настройка позиции (Polling)**: Сначала на дисплее устанавливается позиция (страница и столбец) с помощью команд. Этот этап выполняется в режиме опроса (без DMA), чтобы гарантировать точное позиционирование. 2. **Инициализация DMA-пересылки (DMA)**: Вызывается функция `SPI1_DMA_Transfer()`. Она: * Отключает каналы DMA. * Устанавливает адрес источника данных в памяти (`txData`) как указатель на начало буфера текущей страницы. * Устанавливает размер передачи (128 байт — ширина одной страницы дисплея). * Очищает флаги завершения передачи. * Включает каналы DMA (сначала `DMA1_Channel2`, затем `DMA1_Channel3`). С этого момента передача данных происходит автоматически: DMA-контроллер считывает байт из `display_buffer` и помещает его в регистр `SPI1->DR`. SPI автоматически начинает передачу по шине. Этот процесс выполняется независимо от CPU. 3. **Ожидание завершения (Блокировка)**: Вызывается `SPI1_DMA_WaitForTransfer()`. Эта функция входит в цикл ожидания флага `SPI1_TransferComplete`, который устанавливается в обработчике прерывания `DMA1_Channel3_IRQHandler()` при завершении передачи одного блока данных. После этого, проверяется флаг `SPI_I2S_FLAG_BSY` в регистре статуса SPI, чтобы убедиться, что последний бит физически покинул вывод микроконтроллера. ### 3. Прерывания Прерывания играют ключевую роль в асинхронной работе DMA и SPI: * **`DMA1_Channel3_IRQHandler()`**: Это прерывание срабатывает, когда DMA завершил передачу всего блока данных, указанного в `CNDTR`. В обработчике устанавливается флаг `SPI1_TransferComplete = 1`, который `SPI1_DMA_WaitForTransfer()` использует для выхода из цикла ожидания. * **`DMA1_Channel2_IRQHandler()`**: Обрабатывает прерывание по завершению приема данных (если данные с дисплея читаются через DMA). В данном случае, он просто очищает флаг. * **Конфигурация в `SPI1_DMA_Init()`**: Прерывания для каналов DMA1 Channel2 и Channel3 явно включаются через `NVIC_EnableIRQ(DMA1_Channel2_IRQn)` и `NVIC_EnableIRQ(DMA1_Channel3_IRQn)`. Таким образом, комбинация DMA и прерываний позволяет эффективно управлять высокоскоростным SPI-интерфейсом, освобождая CPU для выполнения других задач, таких как обработка данных с энкодера, мотора или формирование нового кадра для отображения.
В итоге:

Таким образом, ИИ можно смело использовать для разработки кода для микроконтроллеров, при этом, обязательно использовать тесты, включая для целевой платформы, например QEMU, а также ввод-вывод для отладочной информации, включая assertion, контроль регистров, стека, памяти, секций размещения, включая настройки DMA, периферии и др.
Язык Markdown, по всей видимости, станет первой строчкой в рейтингах популярности уже в ближайшем будущем. Также, важны необходимые MCP, промпт-инъекции, методики тестирования, вспомогательные скиллы, текстовые эмбеддеры для кодовой базы и поиска в ней а также нативные средства компилятора для рефакторинга и управления кодом.
Комментарии (24)

jar_ohty
19.05.2026 17:51Напомнило (прошиванием нитками) мой детский "вайб-монтаж" схем на цифровых микросхемах. Плата представляла собой стопку из 3-4-5 перфокарт, прошиваемых с помощью суровой иглы голым проводом 0,25 мм. За счет разделения перфокартами можно было организовывать многоуровневые пересечения проводников. Для впаивания деталей делались два рядом стоящих отверстия, через которые пропускалась пара витков провода. Одно из отверстий предназначалось для пропуска вывода, который припаивался к петле провода, либо использовались оба отверстия для соединения двух выводов кратчайшим путем. Затем все еще прошивалось по краю - проводом или капроновой ниткой. В итоге получалась довольно жесткая и прочная плата, позволявшая перепаивать детали и не страдавшая болезнью "одно припаяли - другое отвалилось".

TimurZhoraev Автор
19.05.2026 17:51да был такой метод, одножильный через шило с ушком или толстая иголка от шприца (тот который стеклянный, многоразовый, раньше такие были и иглы к нему с металлическим штуцером, где проволока не застревала) прокалывался любой материал и переворачивался - микросхемы и что угодно паяли на образующиеся петельки. Чтобы уж совсем не болталось можно было залить парафином от свечки целиком. На пересечении проводов - просто ложилась синяя изолента или на всём пути, медная фольга - на питание. Фактически многослойка. Так был собран ДВ/СВ (сейчас там уже тишина) на КТ315 с выходным каскадом на КТ6хх не помню какой в корпусе который поменьше ТО220 (причём прямиком на динамик, тогда были слабые представления о согласовании сопротивлений) и квадратной батарейке.

KbRadar
19.05.2026 17:51У нас это называется соплестрой. Обычно делался притягиванием нейлоновыми стяжками к куску нефольгированного стеклотекстолита, отверсия сверлятся по месту. Выдерживает годы интенсивной эксплуатации, в том числе в тяжёлых условиях.

jar_ohty
19.05.2026 17:51А если добавить "котлету" из липолек и примотать все синей изолентой - ьудет шахид-девайс))

asoneofus
19.05.2026 17:51Когда-то вполне рабочая промышленная технология подобная. Топология вышивалась. Хех ... год этак 88-90й.

Arhammon
19.05.2026 17:51Самый дешевый принтер стоит в районе 10ки... при должном проектировании DIY может быть серьёзнее заводских изделий. Дендрофекальный DIY уже пора бы признать мертвым

TimurZhoraev Автор
19.05.2026 17:51Проект свёрстан к 18-00. Платы для отладки нужны к 18-30. Залить релиз проверить работоспособность ткнуть пару точек осциллографом, к 20-00 софт уже должен быть готов к использованию на завтра. После чего платы демонтируются ножницами. Какой принтер за полчаса напечатает оснастку в габарите 150 на 200, плюс клипсовый держатель печатных плат. Трапециевидные "выросты"-держатели и прочие защёлки любым пластиком - это сразу на слом, равно как бобышки без рёбер жёсткости, пусть даже формальных, когда слайсер делает дополнительные линии улучшающие прочность. Нарисовать оснастку чтобы она не отламывалась это отдельная история. Да и рисовать как то тоже не очень, это тоже время - разве что агента к FreeCAD прикрутить который будет на Питоне в консоли детали рисовать или что то вроде OpenSCAD. Пример промпта: OpenSCAD дай панель 150 на 200 на которой внутри вырез глубиной 2 мм круг, с торца которого растут крепёжки защёлки клипсы трапеция крючок высота 10 мм 3 штуки.
Результат:Код OpenSCAD
// Параметры панели panel_w = 150; panel_l = 200; panel_h = 5; // Параметры выреза cut_r = 40; // Радиус круглого выреза cut_depth = 2; // Глубина выреза // Параметры защелок clip_h = 10; // Высота защелки от дна выреза clip_base_w = 12; // Ширина основания трапеции clip_top_w = 8; // Ширина верха трапеции clip_thick = 2; // Толщина тела защелки hook_ext = 1.5; // Вылет крючка защелки $fn = 100; // Сглаживание окружностей // 1. Создание основной панели с круглым вырезом difference() { // Основная плита (центрированная по X и Y) cube([panel_w, panel_l, panel_h], center = true); // Круглый вырез (смещен на поверхность панели) translate([0, 0, (panel_h / 2) - cut_depth + 0.01]) cylinder(r = cut_r, h = cut_depth, center = false); } // 2. Добавление 3-х защелок по торцу выреза (расположены под углом 120 градусов) for (angle = [0, 120, 240]) { rotate([0, 0, angle]) translate([cut_r, 0, (panel_h / 2) - cut_depth]) single_clip(); } // Модуль одной защелки-крючка module single_clip() { rotate([0, 0, 0]) { // Смотрит наружу от центра выреза difference() { // Трапециевидное основание защелки rotate([90, 0, 90]) // Поворот для правильной ориентации в пространстве linear_extrude(height = clip_thick) polygon(points = [ [-clip_base_w / 2, 0], [clip_base_w / 2, 0], [clip_top_w / 2, clip_h], [-clip_top_w / 2, clip_h] ]); } // Крючок на вершине защелки (выступает внутрь выреза для фиксации) translate([-clip_thick, -clip_top_w / 2, clip_h - 2]) cube([clip_thick + hook_ext, clip_top_w, 2]); } }
На этот вайб-3D потрачено где-то 45 секунд. Если будет 50, то проще вручную

Arhammon
19.05.2026 17:51Ну вообще из двух частей - плиту "верстака для электроники" разрабатываешь заранее. А крепёжки быстро печатаются индивидуально. Сейчас вяло пытаюсь совместить с ящиком "rugged box", чтоб были сменные панели под дисплеи и управление наверху и кабельные вводы/разъемы/тестпоинты по бокам. В моменте дофига времени потратишь, но потом будешь как белый человек работать...
"выросты"-держатели и прочие защёлки любым пластиком - это сразу на слом,
Делайте "пружину" вдоль слоев и ничего ломаться не будет. Ну и чем больше вы заморачиваетесь и практикуетесь, тем быстрее потом проектируются сложные вещи. Тоже самое вначале страдаешь - потом работаешь как белый человек.

KbRadar
19.05.2026 17:51Сам по себе такой таймлайн должен отсутствовать полностью если хотим что-то сделать.

x89377
19.05.2026 17:51Можете свой ИИ попросить расширить скрипт конвертации ttf в c/h для генерации русских букв (напрмер в кодировке 1251) ?
Скажите ему: Заранее спасибо.
TimurZhoraev Автор
19.05.2026 17:51Да не вопрос! Промпт-инъекция заняла где-то полминуты, далее отладка, ещё раз обратная связь и через 1.5 минуты всё готово! Шрифты берёт из стандартных папок Линукса, можно использовать любые другие с набором Юникод но содержащих всякие смайлики из CP-866, особенно рисование таблиц как в DOS, их можно разместить от 0 до "!" где управляющие последовательности идут.
Вайб-кириллица CP1251
Саммари
# Сводка по gen5.py: Генерация шрифтов CP1251 для LCD-дисплеев Этот скрипт `gen5.py` предназначен для генерации монохромных битовых шрифтов (bitmap fonts) в формате C-массивов для использования на микроконтроллерах, отображающих символы в кодировке CP1251 (кириллица). Он создает как файлы исходного кода, так и визуальные превью. ## Структура данных и алгоритмы ### 1. Основные данные * **`FONT_PATHS_TRY`, `SYMBOL_FONT_PATHS_TRY`**: Списки путей к TrueType-шрифтам (TTF) для основного текста и специальных символов (управляющие коды). Скрипт последовательно пытается загрузить первый доступный шрифт из списка. * **`CONTROL_CHAR_MAP`**: Словарь, сопоставляющий управляющие коды ASCII (0x00-0x20) с визуально понятными Unicode-символами (например, 0x00 -> ∅, 0x08 -> ◘). Это позволяет отображать невидимые управляющие символы на превью и в комментариях. * **`DESCENDER_CHARS`**: Множество символов (латинских и кириллических), которые имеют "нисходящие элементы" (спускающиеся ниже основной линии, как у букв 'g', 'y', 'д', 'щ'). ### 2. Процесс генерации шрифта Алгоритм работает пошагово для каждого из 256 символов (0x00-0xFF): 1. **Загрузка шрифта:** Используя функцию `load_linux_font`, скрипт загружает основной и (опционально) символический шрифт. Если шрифт по указанному пути не найден, он пытается использовать системный шрифт по умолчанию. 2. **Определение символа:** Для каждого кода определяется, какой символ отображать. Для кодов 0x00-0x20 используется `CONTROL_CHAR_MAP`. Для остальных кодов производится попытка декодировать байт в строку CP1251. 3. **Вычисление границ (`textbbox` и `getmask`):** Для определения габаритов символа используются два метода: * `textbbox`: Возвращает ограничивающий прямоугольник на основе метрик шрифта. * `getmask().getbbox()`: Возвращает ограничивающий прямоугольник, основанный на реальных пикселях (маске) символа. Это более точный "пиксель-перфект" метод. При включенном флаге `USE_DUAL_BBOX` предпочтение отдается маске, что обеспечивает более точную центровку. 4. **Выравнивание и смещение:** Символ позиционируется внутри своей ячейки (размером `WIDTH` x `HEIGHT`). * **Горизонтальное выравнивание:** Производится по центру. * **Вертикальное выравнивание:** Определяется константой `V_ALIGN` ("TOP", "CENTER", "BOTTOM"). * **Тонкая настройка:** К символам без спускающегося элемента применяется смещение `NON_DESCENDER_OFFSET`, а к символам с таким элементом — относительное смещение `DESCENDER_RELATIVE_OFFSET`. 5. **Отрисовка:** Символ отрисовывается на изображении с помощью `ImageDraw.Draw.text()`. 6. **Применение инверсии:** Если включены флаги `FLIP_HORIZONTAL` или `FLIP_VERTICAL`, готовое изображение символа отражается по горизонтали или вертикали. Это полезно для адаптации шрифта к аппаратной ориентации дисплея. 7. **Преобразование в байты:** Изображение символа (ширина x высота) преобразуется в последовательность байтов. Каждый байт представляет 8 пикселей, считываемых по столбцам (vertical-major order). Это формирует битовый массив для C-кода. ## Генерация выходных файлов 1. **`display_fonts.c`**: Генерирует массив `const uint8_t fontCRC[]`, содержащий битовые данные для всех 256 символов. Каждая строка содержит байты для одного символа, сопровождаемые комментарием, в котором указан индекс, шестнадцатеричный код и отображаемый символ. 2. **`fontdef.h`**: Генерирует заголовочный файл с константами, описывающими шрифт (ширина, высота, количество байт на столбец, выравнивание, флаги отражения). Это позволяет легко настраивать отображение шрифта в C-коде. 3. **`font_preview.png`**: Создает атлас (atlas) — одно большое изображение, на котором все 256 символов расположены в сетке 16x16. Это позволяет визуально проверить результат генерации. ## Ключевые особенности и маски * **Использование масок (`USE_DUAL_BBOX`):** Этот флаг является ключевым для качества шрифта. Использование маски (`getmask`) вместо метрик шрифта (`textbbox`) позволяет избежать "плавающих" символов и обеспечивает единообразную высоту строки. * **Тонкая настройка позиционирования:** Константы `NON_DESCENDER_OFFSET` и `DESCENDER_RELATIVE_OFFSET` позволяют вручную подгонять вертикальное положение символов, что критично для эстетики моноширинного шрифта. * **Отображение управляющих символов:** Благодаря `CONTROL_CHAR_MAP` и процедурной отрисовке (`draw_procedural_fallback`), управляющие коды (которые в обычном тексте невидимы) получают собственное визуальное представление, что делает превью и C-код более информативными. * **Поддержка аппаратного отражения:** Флаги `FLIP_HORIZONTAL` и `FLIP_VERTICAL` позволяют адаптировать шрифт к дисплею, установленному в нестандартной ориентации, не изменяя при этом C-код отображения. В целом, `gen5.py` представляет собой мощный и гибкий инструмент для создания настраиваемых шрифтов, сочетающий автоматизацию с возможностью точной ручной подстройки.Питон-генератор
import os import math from PIL import Image, ImageFont, ImageDraw # ==================== CONFIGURATION ==================== # Common Linux primary fonts with CP1251 support (Courier, Liberation Mono, DejaVu) FONT_PATHS_TRY = [ "cour.ttf", "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", "/usr/share/fonts/truetype/msttcorefonts/Courier_New.ttf" ] # Common Linux symbol fonts for control characters SYMBOL_FONT_PATHS_TRY = [ "seguiisb.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/freefont/FreeSans.ttf", "/usr/share/fonts/truetype/msttcorefonts/Arial.ttf" ] FONT_SIZE = 12 # Font size to render WIDTH, HEIGHT = 9, 14 # Grid cell width and height COLS = 16 # Number of columns in the PNG preview grid # --- Alignment & Typographical Tuning --- V_ALIGN = "BOTTOM" # Base alignment strategy: "TOP", "CENTER", or "BOTTOM" USE_DUAL_BBOX = True # True: Use pixel-perfect masks | False: Use font-engine metrics # --- Fine-Grained Fine Tuning Controls --- NON_DESCENDER_OFFSET = -2 # Shift normal chars (+ down, - up) DESCENDER_RELATIVE_OFFSET = -1 # Relative shift for descenders (+ down, - up) # CP1251 Descenders (Includes Latin and Cyrillic tails like ц, щ, д) DESCENDER_CHARS = set("gjpqy,;QцщдйЦЩДЙ") # --- Hardware Orientation Inversion Toggles --- FLIP_HORIZONTAL = False # Mirror Left-to-Right FLIP_VERTICAL = False # Mirror Top-to-Bottom # --- Preview Customization --- APPLY_FLIPS_TO_PREVIEW = False # True: preview mimics H/W mirror | False: normal text preview # ======================================================= # Mapping table for ASCII control codes (0x00 to 0x20) to meaningful Unicode symbols/smilies CONTROL_CHAR_MAP = { 0x00: "\u2205", 0x01: "\u263A", 0x02: "\u263B", 0x03: "\u2665", 0x04: "\u2666", 0x05: "\u2663", 0x06: "\u2660", 0x07: "\u2022", 0x08: "\u25D8", 0x09: "\u25CB", 0x0A: "\u25D9", 0x0B: "\u2642", 0x0C: "\u2640", 0x0D: "\u266A", 0x0E: "\u266B", 0x0F: "\u263C", 0x10: "\u25BA", 0x11: "\u25C4", 0x12: "\u2195", 0x13: "\u203C", 0x14: "\u00B6", 0x15: "\u00A7", 0x16: "\u25AC", 0x17: "\u21A8", 0x18: "\u2191", 0x19: "\u2193", 0x1A: "\u2192", 0x1B: "\u2190", 0x1C: "\u221F", 0x1D: "\u2194", 0x1E: "\u25B2", 0x1F: "\u25BC", 0x20: "\u2423" } def load_linux_font(font_paths): for path in font_paths: try: if os.path.exists(path) or "/" not in path: return ImageFont.truetype(path, FONT_SIZE), False except Exception: continue return ImageFont.load_default(), True def draw_procedural_fallback(draw, code, w, h): """Draws pixel-perfect fallbacks for control codes if symbol fonts are missing.""" if code == 0x00 or code == 0x20: draw.rectangle([0, 2, w - 1, h - 3], outline=1) else: draw.line([1, 3, w - 2, h - 4], fill=1) draw.line([1, h - 4, w - 2, 3], fill=1) def build_visual_matrix_comment(): """Generates a text-based 16x16 lookup matrix using the actual mapped characters and symbols.""" matrix_lines = [] matrix_lines.append(" * 0 1 2 3 4 5 6 7 8 9 A B C D E F") matrix_lines.append(" * +-----------------------------------------------") for row in range(16): row_chars = [] for col in range(16): code = row * 16 + col if code <= 0x20: # Use the real symbol mapped for the control character region char_str = CONTROL_CHAR_MAP[code] else: try: char_str = bytes([code]).decode('cp1251') except Exception: char_str = '?' # Clean display properties to keep layout intact while preventing broken comments if char_str in ['/', '*', '\\'] or ord(char_str) < 32: char_str = '?' row_chars.append(f" {char_str} ") matrix_lines.append(f" * {row:X}x |{''.join(row_chars)}") return "\n".join(matrix_lines) def generate_font_assets(): font_primary, primary_is_fallback = load_linux_font(FONT_PATHS_TRY) font_symbol, symbol_is_fallback = load_linux_font(SYMBOL_FONT_PATHS_TRY) if primary_is_fallback: print("Warning: All primary fonts failed. Using low-res default system font.") if symbol_is_fallback and not primary_is_fallback: font_symbol = font_primary print("Notice: Explicit symbol font missing. Overlapping allocations to primary engine.") rows = 16 atlas = Image.new('1', (COLS * WIDTH, rows * HEIGHT), 0) bytes_per_col = math.ceil(HEIGHT / 8) # Generate the visual reference map string using the updated function visual_map = build_visual_matrix_comment() c_lines = [ '#include "fontdef.h"', '#include <stdint.h>', '', '/* ', ' * Linux-Optimized CP1251 Font Data (0-255)', f' * Configuration - H-Flip: {FLIP_HORIZONTAL}, V-Flip: {FLIP_VERTICAL}, V-Align: {V_ALIGN}', ' *', ' * 16x16 SYMBOL TABLE REFERENCE MAP:', visual_map, ' */', 'const uint8_t fontCRC [] = {' ] for code in range(256): char_img = Image.new('1', (WIDTH, HEIGHT), 0) draw = ImageDraw.Draw(char_img) use_procedural = (code <= 0x20) and symbol_is_fallback and primary_is_fallback if use_procedural: draw_procedural_fallback(draw, code, WIDTH, HEIGHT) char_str = CONTROL_CHAR_MAP[code] else: active_font = font_primary if code <= 0x20: char_str = CONTROL_CHAR_MAP[code] active_font = font_symbol else: try: char_str = bytes([code]).decode('cp1251') except UnicodeDecodeError: char_str = '?' m_left, m_top, m_right, m_bottom = draw.textbbox((0, 0), char_str, font=active_font, anchor="lt") mask_tuple = active_font.getmask(char_str).getbbox() if USE_DUAL_BBOX else None if USE_DUAL_BBOX and mask_tuple: b_left, b_top, b_right, b_bottom = mask_tuple else: b_left, b_top, b_right, b_bottom = m_left, m_top, m_right, m_bottom tw = b_right - b_left th = b_bottom - b_top if tw <= 0 or th <= 0: x_off, y_off = 0, 0 else: x_off = math.floor((WIDTH - tw) / 2.0) - b_left align = V_ALIGN.upper() if align == "TOP": base_y = 0 - b_top elif align == "BOTTOM": base_y = (HEIGHT - th) - b_top else: base_y = math.floor((HEIGHT - th) / 2.0) - b_top if char_str in DESCENDER_CHARS: y_off = base_y + DESCENDER_RELATIVE_OFFSET else: y_off = base_y + NON_DESCENDER_OFFSET draw.text((x_off, y_off), char_str, font=active_font, fill=1, anchor="lt") preview_img = char_img.copy() if FLIP_HORIZONTAL: char_img = char_img.transpose(Image.FLIP_LEFT_RIGHT) if FLIP_VERTICAL: char_img = char_img.transpose(Image.FLIP_TOP_BOTTOM) if APPLY_FLIPS_TO_PREVIEW: if FLIP_HORIZONTAL: preview_img = preview_img.transpose(Image.FLIP_LEFT_RIGHT) if FLIP_VERTICAL: preview_img = preview_img.transpose(Image.FLIP_TOP_BOTTOM) atlas.paste(preview_img, ((code % COLS) * WIDTH, (code // COLS) * HEIGHT)) pixels = char_img.load() hex_values = [] for x in range(WIDTH): for b_idx in range(bytes_per_col): byte_val = 0 start_y = b_idx * 8 for bit in range(8): target_y = start_y + bit if target_y < HEIGHT and pixels[x, target_y]: byte_val |= (1 << bit) hex_values.append(f"0x{byte_val:02X}") escaped_char = char_str.replace('\\', '\\\\').replace('/*', '/ *').replace('*/', '* /') c_lines.append(f" {', '.join(hex_values)}, /* Index {code:03d} (0x{code:02X}) -> '{escaped_char}' */") c_lines.append("};") with open("display_fonts.c", "w", encoding="utf-8") as f: f.write("\n".join(c_lines) + "\n") valign_flag = 1 if V_ALIGN.upper() == "BOTTOM" else 0 h_lines = [ "#ifndef FONTDEF_H", "#define FONTDEF_H", "", "#include <stdint.h>", "", f"#define CRCFONT_WIDTH {WIDTH}", f"#define CRCFONT_HEIGHT {HEIGHT}", f"#define CRCFONT_BYTES_PER_COL {bytes_per_col}", f"#define CRCFONT_BYTES_PER_CHAR {WIDTH * bytes_per_col}", "#define CRCFONT_FIRSTCHAR 0", "#define CRCFONT_LASTCHAR 255", "", f"#define CRCFONT_VALIGN_BOTTOM {valign_flag}", f"#define CRCFONT_VFLIP {1 if FLIP_VERTICAL else 0}", f"#define CRCFONT_HFLIP {1 if FLIP_HORIZONTAL else 0}", "", "extern const uint8_t fontCRC [];", "", "#endif" ] with open("fontdef.h", "w", encoding="utf-8") as f: f.write("\n".join(h_lines) + "\n") atlas.resize((atlas.width * 4, atlas.height * 4), resample=Image.NEAREST).save("font_preview.png") print("Success! Mapped array data with matching unicode matrix header comments.") if __name__ == "__main__": generate_font_assets()Вечером проверю уже в железе. Результат:
Сгенерированные шрифты



blackyblack
19.05.2026 17:51Че-то кривовато. Ладно кириллица, но даже у латинских символов размер и выравнивание скачут.

TimurZhoraev Автор
19.05.2026 17:51Это исправимо, чуть позже вот версия с управлением паддингом для каждого символа. Обратите внимание - ИИ сделал всё верно и выделил символы, которые необходимо настроить. Однако делать это надо не оптом а отдельно. Вот исправленная версия с настройкой паддинга для избранных символов.
В принципе можно дать более сложную задачу на уровне распознавания нижней линии, но это уже факультатив. Сейчас пока конвертация идёт просто по bounding box, хотя более правильно - по образующему прямоугольнику без учёта диакритических знаков "Щщ" а также имеющих центральные линии вроде "ф", вообщем это уже особенности конкретной буквы которую не лень поправить вручную.
Делаем промпт: add to gen5 DESCENDER_CHARS so each char have personal Descenders list (q,1), (ц,-1), (ц,0) and so on -1 by default. give it for "gjpqQyY,;дДЦцЩщЙйфФуУ"Исправляем паддинг для отдельных символов
Саммари для генерации Питона
# Сводка по gen5.py: Генерация шрифтов CP1251 для LCD-дисплеев Этот скрипт `gen5.py` предназначен для генерации монохромных битовых шрифтов (bitmap fonts) в формате C-массивов для использования на микроконтроллерах, отображающих символы в кодировке CP1251 (кириллица). Он создает как файлы исходного кода, так и визуальные превью. ## Структура данных и алгоритмы ### 1. Основные данные * **`FONT_PATHS_TRY`, `SYMBOL_FONT_PATHS_TRY`**: Списки путей к TrueType-шрифтам (TTF) для основного текста и специальных символов (управляющие коды). Скрипт последовательно пытается загрузить первый доступный шрифт из списка. * **`CONTROL_CHAR_MAP`**: Словарь, сопоставляющий управляющие коды ASCII (0x00-0x20) с визуально понятными Unicode-символами (например, 0x00 -> ∅, 0x08 -> ◘). Это позволяет отображать невидимые управляющие символы на превью и в комментариях. * **`CHAR_DESCENDER_OFFSETS`**: Словарь, который заменил собой множество `DESCENDER_CHARS`. Каждому символу, имеющему "нисходящий элемент" (спускающийся ниже основной линии), сопоставляется его персональное вертикальное смещение (например, 'g': -1, 'p': 0). Это позволяет точно настроить положение каждого такого символа. ### 2. Процесс генерации шрифта Алгоритм работает пошагово для каждого из 256 символов (0x00-0xFF): 1. **Загрузка шрифта:** Используя функцию `load_linux_font`, скрипт загружает основной и (опционально) символический шрифт. Если шрифт по указанному пути не найден, он пытается использовать системный шрифт по умолчанию. 2. **Определение символа:** Для каждого кода определяется, какой символ отображать. Для кодов 0x00-0x20 используется `CONTROL_CHAR_MAP`. Для остальных кодов производится попытка декодировать байт в строку CP1251. 3. **Вычисление границ (`textbbox` и `getmask`):** Для определения габаритов символа используются два метода: * `textbbox`: Возвращает ограничивающий прямоугольник на основе метрик шрифта. * `getmask().getbbox()`: Возвращает ограничивающий прямоугольник, основанный на реальных пикселях (маске) символа. Это более точный "пиксель-перфект" метод. При включенном флаге `USE_DUAL_BBOX` предпочтение отдается маске, что обеспечивает более точную центровку. 4. **Выравнивание и смещение:** Символ позиционируется внутри своей ячейки (размером `WIDTH` x `HEIGHT`). * **Горизонтальное выравнивание:** Производится по центру. * **Вертикальное выравнивание:** Определяется константой `V_ALIGN` ("TOP", "CENTER", "BOTTOM"). * **Тонкая настройка:** Для символов с "нисходящим элементом" используется индивидуальное смещение из словаря `CHAR_DESCENDER_OFFSETS`. Если символ отсутствует в словаре, применяется смещение по умолчанию (-1). Для символов без такого элемента применяется смещение `NON_DESCENDER_OFFSET`. 5. **Отрисовка:** Символ отрисовывается на изображении с помощью `ImageDraw.Draw.text()`. 6. **Применение инверсии:** Если включены флаги `FLIP_HORIZONTAL` или `FLIP_VERTICAL`, готовое изображение символа отражается по горизонтали или вертикали. Это полезно для адаптации шрифта к аппаратной ориентации дисплея. 7. **Преобразование в байты:** Изображение символа (ширина x высота) преобразуется в последовательность байтов. Каждый байт представляет 8 пикселей, считываемых по столбцам (vertical-major order). Это формирует битовый массив для C-кода. ## Генерация выходных файлов 1. **`display_fonts.c`**: Генерирует массив `const uint8_t fontCRC[]`, содержащий битовые данные для всех 256 символов. Каждая строка содержит байты для одного символа, сопровождаемые комментарием, в котором указан индекс, шестнадцатеричный код и отображаемый символ. 2. **`fontdef.h`**: Генерирует заголовочный файл с константами, описывающими шрифт (ширина, высота, количество байт на столбец, выравнивание, флаги отражения). Это позволяет легко настраивать отображение шрифта в C-коде. 3. **`font_preview.png`**: Создает атлас (atlas) — одно большое изображение, на котором все 256 символов расположены в сетке 16x16. Это позволяет визуально проверить результат генерации. ## Ключевые особенности и маски * **Использование масок (`USE_DUAL_BBOX`):** Этот флаг является ключевым для качества шрифта. Использование маски (`getmask`) вместо метрик шрифта (`textbbox`) позволяет избежать "плавающих" символов и обеспечивает единообразную высоту строки. * **Тонкая настройка позиционирования:** Константы `NON_DESCENDER_OFFSET` и индивидуальные смещения в `CHAR_DESCENDER_OFFSETS` позволяют вручную подгонять вертикальное положение символов с высокой точностью, что критично для эстетики моноширинного шрифта. * **Отображение управляющих символов:** Благодаря `CONTROL_CHAR_MAP` и процедурной отрисовке (`draw_procedural_fallback`), управляющие коды (которые в обычном тексте невидимы) получают собственное визуальное представление, что делает превью и C-код более информативными. * **Поддержка аппаратного отражения:** Флаги `FLIP_HORIZONTAL` и `FLIP_VERTICAL` позволяют адаптировать шрифт к дисплею, установленному в нестандартной ориентации, не изменяя при этом C-код отображения. В целом, `gen5.py` представляет собой мощный и гибкий инструмент для создания настраиваемых шрифтов, сочетающий автоматизацию с возможностью точной ручной подстройки.Mr Питон как есть
import os import math from PIL import Image, ImageFont, ImageDraw # ==================== CONFIGURATION ==================== # Common Linux primary fonts with CP1251 support (Courier, Liberation Mono, DejaVu) FONT_PATHS_TRY = [ "cour.ttf", "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", "/usr/share/fonts/truetype/msttcorefonts/Courier_New.ttf" ] # Common Linux symbol fonts for control characters SYMBOL_FONT_PATHS_TRY = [ "seguiisb.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/freefont/FreeSans.ttf", "/usr/share/fonts/truetype/msttcorefonts/Arial.ttf" ] FONT_SIZE = 12 # Font size to render WIDTH, HEIGHT = 9, 14 # Grid cell width and height COLS = 16 # Number of columns in the PNG preview grid # --- Alignment & Typographical Tuning --- V_ALIGN = "BOTTOM" # Base alignment strategy: "TOP", "CENTER", or "BOTTOM" USE_DUAL_BBOX = True # True: Use pixel-perfect masks | False: Use font-engine metrics # --- Fine-Grained Fine Tuning Controls --- NON_DESCENDER_OFFSET = -3 # Shift normal chars (+ down, - up) DESCENDER_RELATIVE_OFFSET = -1 # Relative shift for descenders (+ down, - up) # Descender characters with their personal vertical offset (applied relative to base alignment) # Default offset for new descenders is -1 CHAR_DESCENDER_OFFSETS = { 'g': -1, 'j': 0, 'p':0, 'q': 0, 'Q': 0, 'y': 0, 'Y': -1, ',': -1, ';': -1, 'Ж': 0, 'ж': 0, 'д': 0, 'Д': 1, 'ц': 0, 'Ц': 1, 'щ': 0, 'Щ': 1, 'Й': -1, 'й': -1, 'ф': 0, 'Ф': -1, 'у': 0, 'р': 0 } # --- Hardware Orientation Inversion Toggles --- FLIP_HORIZONTAL = False # Mirror Left-to-Right FLIP_VERTICAL = False # Mirror Top-to-Bottom # --- Preview Customization --- APPLY_FLIPS_TO_PREVIEW = False # True: preview mimics H/W mirror | False: normal text preview # ======================================================= # Mapping table for ASCII control codes (0x00 to 0x20) to meaningful Unicode symbols/smilies CONTROL_CHAR_MAP = { 0x00: "\u2205", 0x01: "\u263A", 0x02: "\u263B", 0x03: "\u2665", 0x04: "\u2666", 0x05: "\u2663", 0x06: "\u2660", 0x07: "\u2022", 0x08: "\u25D8", 0x09: "\u25CB", 0x0A: "\u25D9", 0x0B: "\u2642", 0x0C: "\u2640", 0x0D: "\u266A", 0x0E: "\u266B", 0x0F: "\u263C", 0x10: "\u25BA", 0x11: "\u25C4", 0x12: "\u2195", 0x13: "\u203C", 0x14: "\u00B6", 0x15: "\u00A7", 0x16: "\u25AC", 0x17: "\u21A8", 0x18: "\u2191", 0x19: "\u2193", 0x1A: "\u2192", 0x1B: "\u2190", 0x1C: "\u221F", 0x1D: "\u2194", 0x1E: "\u25B2", 0x1F: "\u25BC", 0x20: "\u2423" } def load_linux_font(font_paths): for path in font_paths: try: if os.path.exists(path) or "/" not in path: return ImageFont.truetype(path, FONT_SIZE), False except Exception: continue return ImageFont.load_default(), True def draw_procedural_fallback(draw, code, w, h): """Draws pixel-perfect fallbacks for control codes if symbol fonts are missing.""" if code == 0x00 or code == 0x20: draw.rectangle([0, 2, w - 1, h - 3], outline=1) else: draw.line([1, 3, w - 2, h - 4], fill=1) draw.line([1, h - 4, w - 2, 3], fill=1) def build_visual_matrix_comment(): """Generates a text-based 16x16 lookup matrix using the actual mapped characters and symbols.""" matrix_lines = [] matrix_lines.append(" * 0 1 2 3 4 5 6 7 8 9 A B C D E F") matrix_lines.append(" * +-----------------------------------------------") for row in range(16): row_chars = [] for col in range(16): code = row * 16 + col if code <= 0x20: # Use the real symbol mapped for the control character region char_str = CONTROL_CHAR_MAP[code] else: try: char_str = bytes([code]).decode('cp1251') except Exception: char_str = '?' # Clean display properties to keep layout intact while preventing broken comments if char_str in ['/', '*', '\\'] or ord(char_str) < 32: char_str = '?' row_chars.append(f" {char_str} ") matrix_lines.append(f" * {row:X}x |{''.join(row_chars)}") return "\n".join(matrix_lines) def generate_font_assets(): font_primary, primary_is_fallback = load_linux_font(FONT_PATHS_TRY) font_symbol, symbol_is_fallback = load_linux_font(SYMBOL_FONT_PATHS_TRY) if primary_is_fallback: print("Warning: All primary fonts failed. Using low-res default system font.") if symbol_is_fallback and not primary_is_fallback: font_symbol = font_primary print("Notice: Explicit symbol font missing. Overlapping allocations to primary engine.") rows = 16 atlas = Image.new('1', (COLS * WIDTH, rows * HEIGHT), 0) bytes_per_col = math.ceil(HEIGHT / 8) # Generate the visual reference map string using the updated function visual_map = build_visual_matrix_comment() c_lines = [ '#include "fontdef.h"', '#include <stdint.h>', '', '/* ', ' * Linux-Optimized CP1251 Font Data (0-255)', f' * Configuration - H-Flip: {FLIP_HORIZONTAL}, V-Flip: {FLIP_VERTICAL}, V-Align: {V_ALIGN}', ' *', ' * 16x16 SYMBOL TABLE REFERENCE MAP:', visual_map, ' */', 'const uint8_t fontCRC [] = {' ] for code in range(256): char_img = Image.new('1', (WIDTH, HEIGHT), 0) draw = ImageDraw.Draw(char_img) use_procedural = (code <= 0x20) and symbol_is_fallback and primary_is_fallback if use_procedural: draw_procedural_fallback(draw, code, WIDTH, HEIGHT) char_str = CONTROL_CHAR_MAP[code] else: active_font = font_primary if code <= 0x20: char_str = CONTROL_CHAR_MAP[code] active_font = font_symbol else: try: char_str = bytes([code]).decode('cp1251') except UnicodeDecodeError: char_str = '?' m_left, m_top, m_right, m_bottom = draw.textbbox((0, 0), char_str, font=active_font, anchor="lt") mask_tuple = active_font.getmask(char_str).getbbox() if USE_DUAL_BBOX else None if USE_DUAL_BBOX and mask_tuple: b_left, b_top, b_right, b_bottom = mask_tuple else: b_left, b_top, b_right, b_bottom = m_left, m_top, m_right, m_bottom tw = b_right - b_left th = b_bottom - b_top if tw <= 0 or th <= 0: x_off, y_off = 0, 0 else: x_off = math.floor((WIDTH - tw) / 2.0) - b_left align = V_ALIGN.upper() if align == "TOP": base_y = 0 - b_top elif align == "BOTTOM": base_y = (HEIGHT - th) - b_top else: base_y = math.floor((HEIGHT - th) / 2.0) - b_top # Get personal offset if char is in the descender map, otherwise use the default (-1) personal_offset = CHAR_DESCENDER_OFFSETS.get(char_str, -1) y_off = base_y + personal_offset draw.text((x_off, y_off), char_str, font=active_font, fill=1, anchor="lt") preview_img = char_img.copy() if FLIP_HORIZONTAL: char_img = char_img.transpose(Image.FLIP_LEFT_RIGHT) if FLIP_VERTICAL: char_img = char_img.transpose(Image.FLIP_TOP_BOTTOM) if APPLY_FLIPS_TO_PREVIEW: if FLIP_HORIZONTAL: preview_img = preview_img.transpose(Image.FLIP_LEFT_RIGHT) if FLIP_VERTICAL: preview_img = preview_img.transpose(Image.FLIP_TOP_BOTTOM) atlas.paste(preview_img, ((code % COLS) * WIDTH, (code // COLS) * HEIGHT)) pixels = char_img.load() hex_values = [] for x in range(WIDTH): for b_idx in range(bytes_per_col): byte_val = 0 start_y = b_idx * 8 for bit in range(8): target_y = start_y + bit if target_y < HEIGHT and pixels[x, target_y]: byte_val |= (1 << bit) hex_values.append(f"0x{byte_val:02X}") escaped_char = char_str.replace('\\', '\\\\').replace('/*', '/ *').replace('*/', '* /') c_lines.append(f" {', '.join(hex_values)}, /* Index {code:03d} (0x{code:02X}) -> '{escaped_char}' */") c_lines.append("};") with open("display_fonts.c", "w", encoding="utf-8") as f: f.write("\n".join(c_lines) + "\n") valign_flag = 1 if V_ALIGN.upper() == "BOTTOM" else 0 h_lines = [ "#ifndef FONTDEF_H", "#define FONTDEF_H", "", "#include <stdint.h>", "", f"#define CRCFONT_WIDTH {WIDTH}", f"#define CRCFONT_HEIGHT {HEIGHT}", f"#define CRCFONT_BYTES_PER_COL {bytes_per_col}", f"#define CRCFONT_BYTES_PER_CHAR {WIDTH * bytes_per_col}", "#define CRCFONT_FIRSTCHAR 0", "#define CRCFONT_LASTCHAR 255", "", f"#define CRCFONT_VALIGN_BOTTOM {valign_flag}", f"#define CRCFONT_VFLIP {1 if FLIP_VERTICAL else 0}", f"#define CRCFONT_HFLIP {1 if FLIP_HORIZONTAL else 0}", "", "extern const uint8_t fontCRC [];", "", "#endif" ] with open("fontdef.h", "w", encoding="utf-8") as f: f.write("\n".join(h_lines) + "\n") atlas.resize((atlas.width * 4, atlas.height * 4), resample=Image.NEAREST).save("font_preview.png") print("Success! Mapped array data with matching unicode matrix header comments.") if __name__ == "__main__": generate_font_assets()Результат

Теперь символы выровнены.
Следующее - имеются буквы которые прописные но по габаритам как заглавные. Для них можно использовать: 1. Уменьшение размера шрифта для выбранных букв. 2. Сжатие по вертикали и бикубическая интерполяция после растеризации, однако, это может быть кривоватым. 3. Алгоритмическое попиксельное сжатие двоички относительно середины (вырезка пикселей с учётом уголков как в Тетрисе). 4. Симметризатор симметричных символов для чётного количества пикселей (где центральная линия всегда будет смещена). Это и есть настоящий вайб, когда сосредотачиваешься на результате а не то как его накодить.

blackyblack
19.05.2026 17:51Вы вроде как явно указали, что надо выровнять латинскую прописную y. И все равно она не выровнена. q выровнена, но не в ту сторону.
Я тоже пользуюсь агентами для разработки, но если вы что-то на публику выкладываете, причешите хоть маленько.

TimurZhoraev Автор
19.05.2026 17:51Так всё верно, если "q" и "y" ещё ниже опустить она будет кропнутой. Поэтому я и указал выше что помимо паддинга должен быть скейл таких вот символов по вертикали с корректным сжатием относительно выбранной точки, например где у буквы "p" язычок или у буквы "q" засечка. Иначе будет неэффективное использование символ-места на дисплее, когда 3-4 пикселя будут под игры +- паддинг. Это на обычном мониторе 1-й или полуторный интервал - норма, для эмбеддед дисплеев это роскошь. То есть ввести ещё атрибуты в словарь - буква содержит паддинг, скейл по вертикали в виде количества вырезаемых пикселей и смещение по которому производится отрезка. И тогда будет уже подходящий генератор и по вертикали не будет много пустого места. Обычно 2 пикселя более чем достаточно для таких вот сжатых шрифтов в ограниченном пространстве. В любом случае предварительно сгенерить и потом допилить атлас пейнтом проще чем чертить с нуля. Кстати обратное преобразование атласа в битмап - это также того же порядка задачка. Ну или пальнуть Pytorch-ем с обучением оного выделять завитушки и их преобразовывать, датасеты тоже навайбить представлениями об эстетике буквы.
По поводу использования агентов - тут стоит задача максимум - не редактировать код вручную от слова вообще - максимум копипастить в промпт переменные или указывать номер строки где уж явный глюк или не понимание. То есть вся задача обрисовывается скиллами, MCP-шкой, локальным эмбеддером для поиска (RAG), а на выходе код на сотни байт для железа.
P.S. Ну раз уж за красоту - придётся доделать генератор со всеми замечаниями. Плюс ещё вайб-транслятор из Unicode-строк в функцию вывода на дисплей на C, так как не охота ковырять VSCode настройками файлов кодировками иными чем utf-8.
greypo
Очень интересно, но ничего не понятно, о чем вообще речь/суть. П.С. Я бы сделал корпус из картона обмотанный скотчем
TimurZhoraev Автор
А тут интересная статистика дочитываний/скролла когда читают только заголовок и комментарии, поэтому самое интересное - внизу. Те кто занимается работой с дисплейчиками на микроконтроллерах могут взять на вооружение ИИ-код-скетчи и md-саммари в виде скиллов для:
- перебрать в системе все моноширинные шрифты
- преобразовать шрифт в атлас с учётом паддинга для букв, с управляемым смещением
- преобразовать картинку в битмап
- генерация кода для рендеринга шрифта с применением оптимальных масок/загрузок из атласа
- реализовать драйвер дисплея по SPI с использованием DMA для быстрого обмена
- возможность страничной реализации и локального апдейта с курсором дисплея
То есть нет уже необходимости искать какие-то библиотеки, исходники, ИИ - по факту и есть генератор этих библиотек. Более того, при должном подходе вполне может генерировать только то что нужно для периферии на уровне регистров без применения конфигураторов и абстракций уровня языка вроде Cube/HAL (уйдут в прошлое или станут надбавкой к ИИ как tool) Достаточно скиллов, эмбеддеров и MCP/RAG с даташитами + тулкиты. Больше не нужно подтягивать к проекту весь мир библиотек а кодить и извлекать только то что пойдёт в релиз. Вообщем библиотека которая генерирует библиотеку. Вот такие тут идеи.
Albert2009Zi
А зачем Вам искать библиотеку на 12864? Есть ведь даташит. На мой взгляд, даташит на этот экран - это прямо азбука. Идите по шагам, там все расписано, инит, тайминги, два вида интерфейса параллельный и SPI. Если кажется сложновато, то возьмите 1602 или датчик типа dht22, там вообще все довольно понятно и поэтому легко написать свою библиотеку по даташиту на девайс. И если мк STM32, то для проверки работоспособности самописной библиотеки HAL будет в самый раз.
Также, чем Вас всё таки не устраивают копеечные платы для прототипирования, что приходится заниматься тем, что Вы описали в статье?
TimurZhoraev Автор
Всё верно! Можно конвертировать даташит с использованием pdftotext -layout datasheet.pdf output.txt с сохранением лайаута, чтобы агент мог корректно читать таблицы регистров. Заставить его написать заголовочник с командами и описание типов данных + конечный автомат управления. А если уже готовое железо и не охота брать даже простое. Тут уже искусство промпт-инъекций. Я использую вариант по умолчанию baai/bge-m3 + qdrant. Индексирует любой даташит и, что самое главное, эрраты, прошу чтобы не загромождать контекстное окно читать сотню строк. Вообщем мне лично как
кожаному мешку пардончитать доки уже больше практически не приходится, вот такое вот время наступило. Только следить за глюками, которые при должной обёртке тестами выявляются практически мгновенно.Вот это как раз и тестируется, то есть я её делаю своей по результатам тестов кода который даёт агент. Даташиту соответствует? - добро пожаловать во Flash/EEPROM. Там помимо всего прочего - вывод текста (как описано - специально созданного шрифта), графика, инициализация периферии, работа с энкодером и драйвером шагового по протоколу. То есть то на что тратятся дни теперь уже идут минуты. Модем который пишет рабочий код на 9600 (так и есть, десяток токен в секунду). Там ведь ещё и буфер, и DMA и флажки всякие и связь с очередями сообщений, потом ещё менюшка, её логика, так что огород набегает очень быстро в таких разработках.
Так ведь потом эти платы для прототипирования надо ещё прикрепить, да так чтобы они не коротнули соседние. Как раз предлагаемый метод и нужен чтобы захомутать платы без монтажных отверстий и прочие навесные крупногабаритные штуки. А там уже ценник breadboard на десяток тысяч точек уже не назвать копеечным. Двусторонний скотч, картон - не тянут как уже подсказали.
HAL - это боль, оплаканная слезами С, обёрнутая в макросы и комментарии. Это изначально было создано для более скажем так лёгкой работы человека. Агенту это не нужно, наоборот, это размазывает контекст. Поэтому везде где можно - классический SPL на регистрах. Разматывает битовые поля-флажки до комментариев и наоборот. То есть удобство наоборот ушло в простоту теперь чем в абстракции в попытке из С сделать С++ для МК. Для начала нулевых - это супер, сейчас же - зачем учить искусственное когда имеется естественное.
Albert2009Zi
сорри за мою неверную пункутацию, здесь должно было быть "...библиотеки, HAL...". HAL я имел ввиду, как инструмент проверки функционала вашей самописной библиотеки. Вы вот целый абзац, уж простите, воды выдали по поводу HAL. Но у меня прямой вопрос - у Вас реально были проблемы с HAL при работе с 1602, AM2302 или 12864, я сейчас конкретно об этих устройствах говорю???
Очень странно использовать SPL вместо CMSIS. Это суть тот же HAL, только древний и только под STM32. Вот скажите мне, где здесь регистры?:
GPIO_SetBits(GPIOA, GPIO_Pin_5);
и опять же, если Вы сгенерировав в CubeIDE проект под HAL, вдруг захотите прописать
GPIOA->ODR |= GPIO_ODR_OD5;
то всё нормально соберётся и будет работать.
Смотрю на фото Вашего прототипа и вижу максимум 50 (может и меньше, лень считать) навесных проводников. Вот этого
Двухсторонняя печатная плата BoEixun 12x18 см | AliExpress
плюс немного МГТФ и штыревых разъемов 2.54мм - должно за глаза хватить. Пластиковые растерные платы "для Arduino", признаться, меня тоже бесят, если их применять с обычными китайскими Dupon проводами. Однако и для них есть хорошее решение
140 шт. комплект предварительно сформированных перемычек для макетной платы, 14 длинок в ассортименте для схем для прототипирования макетной платы | AliExpress
Обе ссылки на товары приведены в качестве "сферических примеров в вакууме", т.е. для приблизительной оценки уровня цен.
И ещё, одна просьба. У меня давненько зреет желание написать статью для начинающих, на тему самостоятельного написания библиотек и драйверов под простые устройства. Там же приведу пример монтажа для прототипирования. Могу ли я, с Вашего позволения, сослаться на материал Вашей статьи? Удачного дня!
TimurZhoraev Автор
В нынешней ситуации необходимо уточнить номер личности. У первой, человека - никаких проблем нет, а вот у второй, ИИ-помощника - вполне себе иногда проскакивают.
Это соответствует эволюции методик как одно и то же сделать отвёрткой, топором и микроскопом одновременно. Для эксперимента я попытался собрать мега-файл который вбирает в себя всё подряд (гипотетически он потом будет генерироваться Питоном). GPIOA->ODR |= GPIO_ODR_OD5; - это и имелось в виду под регистрами без обёртки функцией со структурой.
Взять к примеру нечто вроде cmsis-device-f1, stm32f1xx-hal-driver, cmsis-core, stm32f1-hal, STM32F10x_StdPeriph_Lib и прочая и прочая. Там есть как HAL так и LL так и ещё что-то. Причём есть версии где внутри файлов реализации есть макросы, которые имеют одинаковые имена (!) но разные значения. Это пригодно для человека но ломает поиск эмбеддером.
Файл stm32f10x_dma.c
/* DMA2 FLAG mask */
#define FLAG_Mask ((uint32_t)0x10000000)
Файл stm32f10x_i2c.c
/* I2C FLAG mask */
#define FLAG_Mask ((uint32_t)0x00FFFFFF)
И как после этого делать поиск по кодовой базе? Неужели нельзя было дописать что-то вразумительное вроде DMA_ или I2C_
Особенно доставляет редефайн макросов для совместимости зоопарка
Вообщем стало понятно что это чьи-то эксперименты по отъёму времени разработчиков.
Поэтому пришлось взять ножницы, клей и сформировать мега-файл microlib.c и к ней гипер-заголовочник microlib.h с соответствующей разметкой из которых можно уже потом неким Питоном генерить то что нужно для bare-metal проекта. Там что-то вроде определения констант SPL
Которые суть CMSIS можно инлайнить или генерировать налету, включая отсутствие нагрузки в виде дебаг-заплаток и статических assertion которые и так понятны самим кодогенератором.
Там же то что требуется для точки входа и прочая для начальной инициализации, включая заглушки и прочий semihosting
Плюсом стандартные заготовки для таблицы векторов а-ля vectors_stm32f103x6.c, sections.ld секции линкёра и по мелочам.
То есть микро-библиотека полностью отвязана от внешних обстоятельств. Там содержится база которая в полной мере понятна агенту и эмбеддеру, из неё он может по месту выкручивать любые функции. Другое дело что апдейт, но это поправимо парсингом того что изливается из недр производителя. Вплоть до переписывания HAL->LL.
CubeID - это статический генератор, у него нет обратной связи из кода в графику. Особенно когда система реконфигурируется в процессе работы. Всё что тут возможно - это MACHINE GENERATED DO NOT EDIT, веет уже четверть вековой давностью. Проще уже описать систему md или промптом чем выставлять флажки вручную - пусть сам думает какой там канал DMA для АЦП или UART и кто с кем конфликтует. ИИ-агент же может эту обратную связь худо-бедно провернуть и оценить как и что инициализируется и в каких местах. Вот для этого я и адаптирую либу чтобы она была как можно более "токен-совместима" чем "человеко-читабельна".
Проблема в том, что железки уже есть как данное. То есть там не нужно ничего паять грубо говоря, только платы с минимальной обвесухой вроде потенциометра. Их необходимо просто закрепить. Образно говоря - расположить на столе и залить строительной пеной/эпоксидкой чтобы не улетало ничего. Задача по монтажу другая - сверхбыстрый объёмный монтаж готовых уже соединённых модулей.
Конечно можно, в Вашем материале дать ссылку на сиё скромное творчество.
Albert2009Zi
Благодарю Вас. Теперь точно придётся писать :)