Привет, Хабровчане! Этот цикл (надеюсь) статей будет посвящён моему пути в создании своего собственного решения по струйной печати. Это будет что-то вроде блога или дневника разработчика в котором постараюсь изучить как же всё таки работает печатающая головка у принтера и как ей можно управлять с помощью микроконтроллера. А также нас ждёт интригующий ответ на вопрос: "Если ли место DIY и OpenSourse в мире струйной печати".

Пролог
Когда-то я решил завести себе хобби - печатать книги на домашнем принтере, а помогал мне в этом уже немолодой HP Deskjet 2630 с приколхоженной подключённой к нему СНПЧ дабы не заправлять картриджи каждый день.
Всем известны типичные проблемы струйных принтеров:
Остановиться посреди печати с ошибкой
Не видеть картриджи, в том числе новые
Засорение дюз и многое другое
Думаю, каждый сможет пополнить парой-тройкой десятков проблем. В какой-то момент терпение лопается и на смену старому принтеру приходит новый вместе с ощутимой потерей веса кошельком, либо мы познаём дзен и учимся жить с тем что есть.
Будучи инженером-робототехником по образованию и программистом по профессии я был не готов мириться с таким положением дел и решил заняться поисками способов кастомизировать свой принтер. Я надеялся найти огромное количество статей в духе "перепрошивка принтера", "open source прошивка для принтера", "струйный принтер на Arduino", "DIY принтер" и т.д. .
Однако, реальность оказалась жестока и я не смог найти ни одного подходящего решения. Были умельцы, которые модифицировали принтер в планшетный для печати на тортах или текстолите, кто-то на основе совсем уж старой печатающей головки делал что-то вроде CNC. Это были как и достойные, так и колхозные работы, но они либо не меняли ПО и железо, либо принтер переставал быть принтером.
Моя же цель заключалась в том чтобы полностью поменять закрытое и неуправляемое ПО домашнего принтера на открытое и гибкое.
§1. Становление идеи
Наткнувшись на статью про Взлом цветного картриджа HP (и основана на блоге) в мою голову закралась мысль, что это можно попробовать воспроизвести. После я нашёл статью на Hackaday где автор, использую начинания предыдущего автора для своего проекта и адаптацию для Arduino от другого автора. Это придало мне некоторую уверенность в что можно попытаться создать свой принтер на базе этих решений.
Безуспешно пытаясь хотя бы запустить код на Arduino UNO (ESP32 просто не было под рукой), я пришёл к выводу что всё будет не так просто и мне придётся начинать свой собственный проект практически с нуля.
§2. Бездна анализа, планирования и ТЗ
Покрутив в голове мысль о собственном проекте, я сформировал для него первые требования к тому что я хочу получить:
Устройство должно подключаться к ПК и определяться им как принтер
Устройство должно управлять картриджами HP123 (такие используются моим принтером и авторами упомянутых выше статей)
У устройства должен быть минимальный HMI - небольшой дисплей для вывода информации и несколько кнопок для управления

И я стал думать над базой. Если вышеупомянутые проекты несли скорее исследовательский и развлекательный характер, то мне хотелось создать более комплексное решение, так что я отказался от использования ESP32 и Arduino. Мой взгляд пал на давно пылящуюся в закромах NUCLEO-411RE в качестве основы - 512 Кбайт Flash и 128 Кбайт RAM дают пространство для разворота, а DMA и 72 МГц максимальной частоты поможет меньше задумываться о производительности, не говоря уже об огромном количестве возможной периферии.
Теперь можно добавить чуть больше конкретики для начала прототипирования:
NUCLEO-411RE как основа
Проект должен быть открытым для желающих повторить, что-то улучшить или же создать что-то своё на базе. Плохо знаком с лицензиями, но, думаю Apache 2.0 подходит
Необходимо оставить, по крайней мере 10-15 пинов ШИМ чтобы управлять одной печатающей головкой (я ещё не разобрался с подробностями управления, пока будем держать в голове такие числа)
Используем просто OLED дисплей 128x64 для вывода информации. Он работает по I2C и уже есть библиотеки для работы с ним
Больше мосфетов богу мосфетов. Пины печатающей головки подключаются к нагревательному элементу (немного подробнее описано тут), а значит могут потреблять значительный ток (где-то встречал значение до 2.5А). Таким образом, как было отмечено в статьях ранее, банального преобразователя уровня недостаточно, необходимо хорошее усиление
В качестве основного ввода можно воспользоваться стиком для Arduino и свести 5 кнопок в 3 пина + подключим модуль для расширения GPIO по I2C дабы иметь больше кнопок (включение, отмена, информация и т.д.) и не трогать ценные пины контроллера
Будет хорошей идеей подключить SD карту, её можно будет использовать в качестве источника для задания печати, писать логи и (в крайнем случае) хранить ресурсы для дисплея, возможно появятся ещё идеи. Можно было бы воспользоваться модулем с SDIO, но у меня уже имеется SPI модуль - воспользуемся тем что есть
Нужно будет перемещать печатающую головку и бумагу. Самое логичное решение - CNC Shield для Arduino + набор шаговых двигателей. Просто и всем знакомо по 3D принтерам. Будем считать шаги для определения положения и концевики для крайних положений
Необходимо хранить профили коррекции цвета, возможно для этого подойдёт модуль EEPROM, который также можно подключить по I2C
Логгер. Вывод в консоль через UART и в файл жизненно необходим
Разобраться с подключением контроллера как принтера
Предстоит много коммуникации и периферии так что можно воспользоваться FreeRTOS. Давно хотел
потыкать её палкойпоработать с ней, но не мог придумать подходящей задачиИспользуем DMA везде где это возможно: SPI, I2C, UART
Простой обработчик событий. Да FreeRTOS имеет механизм сообщений, но он кажется слишком громоздким для простого события нажатия кнопки
Тем самым вырисовываются первые функциональные модули, которые могут работать в отдельных потоках:
модуль для работы с дисплеем,
модуль для "прямой" работы с пинами ввода-вывода. Вынесем сюда обработку GPIO, в том числе ADC и работу с I2C расширением
модуль логирования - без комментариев
простой обработчик событий. Да FreeRTOS имеет механизм сообщений, но он кажется слишком громоздким для простого события нажатия кнопки
модуль обработки событий. Для начала ничего сложного - вызываем событие с параметром в виде enum'а, а модуль будет вызывать подписанные обработчики (они сами решат какие события обрабатывают)
модуль для управления шаговыми двигателями
модуль для управления печатающей головкой
модуль для контроля печати. Будем контролировать прогресс печати, преобразовывать задание печати для управления моторами и печатающей головкой (возможно объединение с управлением моторами и печатающей головкой) + контроль уровня чернил (датчик уровня для СНПЧ и счётчик со сбросом для картриджей)
Таким образом у меня уже есть что-то похожее на план от которого можно отталкиваться при разработке ПО.
§3. Проблемы и вопросы
Начнём с главной проблемы: все печатающие головки проприетарные и на них нет документации в открытом доступе. Так что мне пригодятся начиная связанные с блогом.
Исходя из этой проблемы вытекает следующая: в сети полно статей как работать с принтером, в основном про принт-серверы, но практически ни одной статьи как это подключение устроено с другой стороны. C похожей проблемой столкнулся автор этой статьи. Несмотря на наличие спецификации для USB Printer Class мне так и не удалось найти ни одной реализации, даже пример реализации от STM найти удалось далеко не сразу.
В связи с этим на поверхности появляется уже появляются, например:
вагон протоколов LPR/LPD, RAW Printing, IPP, SNMP и т.д.
можно ли расширять эти протоколы своими командами? Например, можно написать свою программу для ПК, которая будет способна отправлять команды на прочистку дюз, калибровку и много другое
В каком виде приходит задание на печать? Какие преобразования должны происходить? В конце концов это должен быть набор пикселей в CMYK, который должен быть преобразован в управляющие сигналы для печатающей головки
Отличается ли задание на печать текста от изображения?
Ответы на них можно будет получить при дальнейшем изучении в процессе работы над конкретным функционалом.
§4. А где писать код? Или Cmake для STM32
Вопрос в заголовке кажется глупым и очевидным, но тем ни менее настройка среды для разработки довольно важным моментом в работе. Кто-то поспорят что я всё усложняю, но кто-то найдёт мои следующие рассуждения весьма занимательными.
Для STM32 наиболее простым решением будет ST32 CubeIDE. В него уже интегрирован CubeMX для конфигурации микроконтроллера, а так как у меня дефицит опыта работы с STM32, то это идеальный вариант. Однако, редактор кода основан на Eclipse, который не отличается своим удобством и дружелюбностью. Я провёл достаточно времени в Visual Studio, Visual Studio Code, Android Studio и прочих IDE от JetBrains и ни один переход в другую IDE не вызывает такой боли как переход в Eclipse.
Самой удобной для меня всегда оказывался VS Code, будь то Embeddeed или Web проекты. Однако нельзя просто так взять и создать проект для STM32 в VS Code, а устанавливать и настраивать вагон расширений, который работает с CubeIDE зависимостями просто не хочется.
Многие STM32 разработчики привыкли к CubeIDE, так что я обязан сохранить работу с ней если хочу чтобы в будущем проект мог поддерживаться другими людьми, но при этом для свой собственной эффективности мне нужен VS Code. Выход из этой ситуации простой - использовать CMake.
Идея заключается в том чтобы положить рядом с ioc-файлом CMakeLists.txt
, благо, CubeIDE тоже умеет их создавать (теряя при этом ioc-файл), так что мы воспользуемся одним из таких сгенерированных CMakeLists.txt
и немного доработаем:
CmakeLists.txt
#############################################################################################################################
# file: CMakeLists.txt
# brief: Template "CMakeLists.txt" for building of executables and static libraries.
#
# usage: Edit "VARIABLES"-section to suit project requirements.
# For debug build:
# cmake -DCMAKE_TOOLCHAIN_FILE=cubeide-gcc.cmake -S ./ -B Debug -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug
# make -C Debug VERBOSE=1 -j
# For release build:
# cmake -DCMAKE_TOOLCHAIN_FILE=cubeide-gcc.cmake -S ./ -B Release -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
# make -C Release VERBOSE=1 -j
#############################################################################################################################
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
cmake_minimum_required(VERSION 3.20)
###################### CONSTANTS ######################################
set (PROJECT_TYPE_EXECUTABLE "exe")
set (PROJECT_TYPE_STATIC_LIBRARY "static-lib")
set (MCPU_CORTEX_M0 "-mcpu=cortex-m0")
set (MCPU_CORTEX_M0PLUS "-mcpu=cortex-m0plus")
set (MCPU_CORTEX_M3 "-mcpu=cortex-m3")
set (MCPU_CORTEX_M4 "-mcpu=cortex-m4")
set (MCPU_CORTEX_M7 "-mcpu=cortex-m7")
set (MCPU_CORTEX_M33 "-mcpu=cortex-m33")
set (MCPU_CORTEX_M55 "-mcpu=cortex-m55")
set (MCPU_CORTEX_M85 "-mcpu=cortex-m85")
set (MFPU_FPV4_SP_D16 "-mfpu=fpv4-sp-d16")
set (MFPU_FPV4 "-mfpu=vfpv4")
set (MFPU_FPV5_D16 "-mfpu=fpv5-d16")
set (RUNTIME_LIBRARY_REDUCED_C "--specs=nano.specs")
set (RUNTIME_LIBRARY_STD_C "")
set (RUNTIME_LIBRARY_SYSCALLS_MINIMAL "--specs=nosys.specs")
set (RUNTIME_LIBRARY_SYSCALLS_NONE "")
set (MFLOAT_ABI_SOFTWARE "-mfloat-abi=soft")
set (MFLOAT_ABI_HARDWARE "-mfloat-abi=hard")
set (MFLOAT_ABI_MIX "-mfloat-abi=softfp")
#######################################################################
###################### VARIABLES ######################################
set (PROJECT_NAME "test_F411RE")
set (PROJECT_TYPE "exe")
set (LINKER_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/STM32F411RETX_FLASH.ld")
set (MCPU "-mcpu=Cortex-M4")
set (MFLOAT_ABI "")
set (RUNTIME_LIBRARY "--specs=nano.specs")
set (RUNTIME_LIBRARY_SYSCALLS "--specs=nosys.specs")
file(GLOB_RECURSE SOURCES
Drivers/STM32F4xx_HAL_Driver/Src/*.*
Core/Src/*.*
)
set (PROJECT_SOURCES
# LIST SOURCE FILES HERE
Core/Startup/startup_stm32f411retx.s
${SOURCES}
)
set (PROJECT_DEFINES
# LIST COMPILER DEFINITIONS HERE
STM32F411xE
)
set (PROJECT_INCLUDES
# LIST INCLUDE DIRECTORIES HERE
Core/Inc
Drivers/STM32F4xx_HAL_Driver/Inc
Drivers/CMSIS/Include
Drivers/CMSIS/Device/ST/STM32F4xx/Include
)
# specify cross-compilers and tools
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_AR arm-none-eabi-ar)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(SIZE arm-none-eabi-size)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
############ MODIFY ACCORDING TO REQUIREMENTS) ########################
#######################################################################
################## PROJECT SETUP ######################################
project(${PROJECT_NAME})
enable_language(ASM)
add_executable(${PROJECT_NAME} ${PROJECT_SOURCES} ${GLOB_RECURSE})
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${CMAKE_PROJECT_NAME}>)
add_compile_definitions (${PROJECT_DEFINES})
include_directories (${PROJECT_INCLUDES})
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type")
set (CMAKE_EXECUTABLE_SUFFIX ".elf")
set (CMAKE_STATIC_LIBRARY_SUFFIX ".a")
set (CMAKE_C_FLAGS "${MCPU} -std=gnu11 ${MFPU} ${MFLOAT_ABI} ${RUNTIME_LIBRARY} -mthumb -Wall -Werror")
set (CMAKE_EXE_LINKER_FLAGS "-T${LINKER_SCRIPT} ${RUNTIME_LIBRARY_SYSCALLS} -Wl,-Map=test_F411RE.map -Wl,--gc-sections -static -Wl,--start-group -lc -lm -Wl,--end-group")
set (CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp")
Используем PROJECT_SOURCES
для всех .с
файлов, PROJECT_INCLUDES
для всех директорий с .h
файлами. В целом, он выглядит как классический CMakeLists.txt
для исполняемого файла с некоторыми дополнениями от CubeIDE, такими как константы:
###################### CONSTANTS ######################################
set (PROJECT_TYPE_EXECUTABLE "exe")
set (PROJECT_TYPE_STATIC_LIBRARY "static-lib")
set (MCPU_CORTEX_M0 "-mcpu=cortex-m0")
set (MCPU_CORTEX_M0PLUS "-mcpu=cortex-m0plus")
set (MCPU_CORTEX_M3 "-mcpu=cortex-m3")
set (MCPU_CORTEX_M4 "-mcpu=cortex-m4")
set (MCPU_CORTEX_M7 "-mcpu=cortex-m7")
set (MCPU_CORTEX_M33 "-mcpu=cortex-m33")
set (MCPU_CORTEX_M55 "-mcpu=cortex-m55")
set (MCPU_CORTEX_M85 "-mcpu=cortex-m85")
set (MFPU_FPV4_SP_D16 "-mfpu=fpv4-sp-d16")
set (MFPU_FPV4 "-mfpu=vfpv4")
set (MFPU_FPV5_D16 "-mfpu=fpv5-d16")
set (RUNTIME_LIBRARY_REDUCED_C "--specs=nano.specs")
set (RUNTIME_LIBRARY_STD_C "")
set (RUNTIME_LIBRARY_SYSCALLS_MINIMAL "--specs=nosys.specs")
set (RUNTIME_LIBRARY_SYSCALLS_NONE "")
set (MFLOAT_ABI_SOFTWARE "-mfloat-abi=soft")
set (MFLOAT_ABI_HARDWARE "-mfloat-abi=hard")
set (MFLOAT_ABI_MIX "-mfloat-abi=softfp")
#######################################################################
и ARM тулчейн
# specify cross-compilers and tools
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_AR arm-none-eabi-ar)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(SIZE arm-none-eabi-size)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
Таким образом я имею удобный конфигуратор в CubeIDE (который можно заменить на CubeMX) и могу писать и компилировать код в VS Code с помощью CMake, а так же это открывает возможность сборки в CI/CD.
§4. А как же отладка?
Одним из преимуществ STM32 является возможность отладки через SWD интерфейс. CubeIDE поддерживает такую возможность, а вот для VS Code придётся напрячься.
Во-первых нужны расширения, позволяющие запускать компилировать наш код и запускать отладчик, я составил для себя следующий список, который можно поместить в .vscode/extensions.json:
extensions.json
{
"recommendations": [
"dan-c-underwood.arm",
"marus25.cortex-debug",
"trond-snekvik.gnu-mapfiles",
"mcu-debug.memory-view",
"twxs.cmake",
"josetr.cmake-language-support-vscode",
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools-themes"
]
}
Ключевым тут является marus25.cortex-debug
, т.к. именно это расширение делает всё необходимое, но ему надо помочь написав правильный launch.json
. Но перед этим я поставлю OpenOCD, который будет поднимать Debug Server для VS Code. и помещу путь к нему в settings.json
:
{
"cmake.useCMakePresets": "always",
"cortex-debug.variableUseNaturalFormat": false,
"cortex-debug.openocdPath": "${config:openOCD_dir}/openocd.exe",
// Local environment
"openOCD_dir" : "C:/tools/stmOpenOCD",
"openOCD_CFG": "${workspaceFolder}/test.cfg",
"openOCD_scripts": "${config:openOCD_dir}/st_scripts",
"plink": "C:/Program Files/PuTTY/plink.exe"
}
Для правильной работы OpenOCD нужен правильный конфиг test.cfg
:
test.cfg
# This is an genericBoard board with a single STM32F411RETx chip
#
# Generated by STM32CubeIDE
# Take care that such file, as generated, may be overridden without any early notice. Please have a look to debug launch configuration setup(s)
source [find interface/stlink-dap.cfg]
set WORKAREASIZE 0x8000
transport select "dapdirect_swd"
set CHIPNAME STM32F411RETx
set BOARDNAME genericBoard
# Enable debug when in low power modes
set ENABLE_LOW_POWER 1
# Stop Watchdog counters when halt
set STOP_WATCHDOG 1
# STlink Debug clock frequency
set CLOCK_FREQ 8000
# Reset configuration
# use hardware reset, connect under reset
# connect_assert_srst needed if low power mode application running (WFI...)
reset_config srst_only srst_nogate connect_assert_srst
set CONNECT_UNDER_RESET 1
set CORE_RESET 0
# ACCESS PORT NUMBER
set AP_NUM 0
# GDB PORT
set GDB_PORT 3333
# BCTM CPU variables
source [find target/stm32f4x.cfg]
# SWV trace
set USE_SWO 0
set swv_cmd "-protocol uart -output :3344 -traceclk 16000000"
source [find board/swv.tcl]
# No way to set port, probably it is hardcoded to 3344
swv start 8 -port 3344 -traceclk 32000000
# The following commands must be sent manually
# itm port 0 on
# itm port start
Но есть несколько нюансов:
source
[find target/stm32f4x.cfg]
отвечает за то какой контроллер мы собираемся прошивать и отлаживать. При смене MCU эта строчка должна быть обновленаЕсли используется SWO порт для отладки, то необходимо использовать OpenOCD из STM32CubeIDE, в противном случае всё что после строчки из пункта может быть удалено
Запуск терминала для вывода сообщений отладки - об этом ниже
Самый короткий путь для получения отладки это запуск putty/plink на прослушивание нужного порта. А так как удобство превыше всего, то автоматизируем это.
Создадим скрипт для запуска plink в консоли:
swo.bat
@echo off
set host=localhost
set port=3344
chcp 65001
cls
:loop
:: check if port available
>nul 2>&1 (echo >\\.\%host%:%port%)
if errorlevel 1 (
echo Port %port% is unavailable — wait...
timeout /T 2 /NOBREAK >nul
) else (
%1 -raw %host% -P %port%
)
goto loop
И сделаем task для его запуска в терминал VS Code и (чисто формальную) остановку:
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "SWO",
"type": "shell",
"command": "${workspaceFolder}/swo.bat",
"args": ["${config:plink}"],
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"clear": true,
},
"problemMatcher": [],
},
{
"label": "Stop SWO",
"type": "shell",
"command": "taskkill",
"args": ["/IM", "plink.exe", "/F"],
"problemMatcher": [],
"hide": true,
"presentation": {
"reveal": "never",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"echo": false,
"clear": false,
"close": true
},
}
],
}
Теперь создадим конфигурацию "Deploy & Start" для запуска отладки вместе с терминалом:
Конфигурация в launch.json
..........
{
"name": "Deploy & Start",
"type":"cortex-debug",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "openocd",
"gdbPath" : "arm-none-eabi-gdb",
// ============== OpenOCD Config BEGIN ==============
"interface": "swd",
"configFiles": [
"${config:openOCD_CFG}"
],
"serverArgs": [
"-s", "${config:openOCD_scripts}",
"-c", "itm port 0 on",
],
// ============== OpenOCD Config END ==============
"runToEntryPoint": "main",
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
],
// Work around for debugging via SWO
"preLaunchTask": "SWO",
"postDebugTask": "Stop SWO"
},
.............
Теперь можно запускать отладку прямо в VS Code нажатием F5 и пользоваться всеми благами отладки вроде Watch, CallStack и т.д.
Для будущих проектов я создал шаблон на Github, и уже попробовал модифицировать его для STM32F103C8Tx.
OpenOCD кажется ненужным, т.к. ST-Link уже подключен и можно использовать его тулчейн, однако, OpenOCD, помимо прочего, открывает возможности удалённой отладки, которой я бы хотел воспользоваться в других проектах.
После всего выше описанного можно приступить к работе и начать то для чего всё затевалось. Продолжение следует...
Эпилог
В итоге я имею план для написания своего ПО для принтера и готовую среду для разработки. Определился с компонентной базой и чем-то похожим на архитектуру.
На момент написания статьи транзисторы для управления печатающей головкой ещё в пути и у меня есть время на небольшое изучение FreeRTOS и погружение в С на STM32, так что уже есть репозиторий с рабочим названием PrintSpider_stm32, которое впоследствии будет изменено (буду рад хорошим идеям, пока в голове только HackInkTer образованное как сокращение от Hacked Inkjet Printer), наладил CI/CD для будущих самодельцев, создал заготовку под модульные тесты на Google Tests и я начал разработку первых модулей:
модуль для работы с GPIO расширением и обработкой стика
модуль OLED дисплея и простое меню
абстракция над FATFS, обрабатывающая команды в отдельном потоке
обработчик событий
логгер с выводом в UART
Уже есть желание перевести проект на C++, т.к. написание даже простого UI пока ещё кажется слишком громоздким. Не знаю кого больше в сообществе любителей и знатоков C или C++, но пока нужно добиться MVP на C.
Возможно я избавлюсь от мусора, приведу в порядок стиль кода и какие-то из следующих частей будут посвящены их разбору уже написанных модулей. А пока всем спасибо за внимание и до следующей части!