О проекте: Пишем один код - собираем на разные 8 бит МК!
https://vm5277.ru- это универсальное решение для embedded-разработки, которое позволяет сократить время создания прошивки для 8 бит микроконтроллеров в разы.
Как это работает:
- Пишешь код на Java подобном языке (чистое ООП, без головной боли с указателями и не читабельным кодом) 
- Компилятор автоматически генерирует оптимизированный ассемблерный код под выбранную платформу 
- Код работает поверх легковесной RTOS, написанной на ассемблере для максимальной производительности 
- Ассемблер-сборщик финализирует проект в бинарный файл прошивки 
Что входит в решение:
- Высокоуровневый компилятор с Java подобным синтаксисом 
- Максимально оптимизированная RTOS, целиком на ассемблере для каждой платформы 
- Унифицированные драйвера - один API для UART, SPI, I2C, GPIO на всех МК 
- Стандартные библиотеки (Runtime): Базовые типы данных (Float, String), математические функции (Math), работа с памятью и другие абстракции, не зависящие от платформы. 
- Драйверы верхнего уровня (High-Level Drivers): Унифицированные API для работы с периферийными устройствами: датчиками (I2C/SPI), дисплеями, SD-картами, беспроводными модулями (ESP8266, Bluetooth). 
Ключевые преимущества:
- Скорость разработки: Создавайте функционал на высокоуровневом языке быстрее, чем на Си 
- Производительность: RTOS и драйверы оптимизированы до уровня чистого ассемблера, экономя каждый байт и такт 
- Переносимость: Переход с AVR на PIC или STM8 - дело нескольких правок, а не изучение и адаптация библиотек 
Проект находится на ранней стадии, но я активно над ним работаю. Уже можно видеть, как высокоуровневый код на Java-подобном языке превращается в чистый и эффективный ассемблер! Это ещё не итоговый вариант, но прогресс уже есть.
Что уже работает в этом примере:
- Наследование интерфейсов: Test implements Number 
- Динамическое выделение памяти: оператор new 
- Полиморфизм: вызов метода через переменную интерфейсного типа (Number.toByte()) 
- Проверка типа во время выполнения: оператор is (аналог instanceof) 
- Полноценная работа с объектами: конструкторы, методы, поля. 
- Интеграция с RTOS: вызов системных функций (System.out). 
Исходный код custom.j8b:
import rtos.System;
import rtos.RTOSParam;
class Main {
	interface Number {
		byte toByte();
	}
	interface Test implements Number {
		byte toByte();
		void test();
	}
	public class Byte implements Test {
		private	byte	value;
		public Byte(byte value) {
			this.value = value;
		}
		public byte toByte() {
			return value;
		}
		public void test() {
		}
	}
    public static void main() {
		System.setParam(RTOSParam.STDOUT_PORT, 0x12);
		Number b1 = new Byte(0x08);
		if(b1 is Byte) {
			System.out(b1.toByte());
		}
	}
}
Итоговый ассемблер код(черновой, могут быть ошибки)
.equ stdout_port = 18
.set OS_FT_DRAM = 1
.set OS_FT_STDOUT = 1
.include "devices/atmega328p.def"
.include "core/core.asm"
.include "dmem/dram.asm"
.include "j8b/inc_refcount.asm"
.include "j8b/dec_refcount.asm"
.include "j8b/instanceof.asm"
.include "j8b/clear_fields.asm"
.include "j8b/invoke_method.asm"
.include "stdio/out_num8.asm"
Main:
	jmp j8bCMainMmain
;======== enter CLASS Main ========================
;======== enter CLASS Byte ========================
_j8b_meta10:
	.db 15,2,13,1,14,2
	.dw j8bC14CByteMtoByte,j8bC14CByteMtoByte,0
j8bC11CByteMByte:
	ldi r16,6
	ldi r17,0
	mcall os_dram_alloc
	std z+0,r16
	std z+1,r17
	std z+2,c0x00
	ldi r16,low(_j8b_meta10*2)
	std z+3,r16
	ldi r16,high(_j8b_meta10*2)
	std z+4,r16
	mcall j8bproc_clear_fields_nr
_j8b_cinit12:
	ldd r16,y+0
	std z+5,r16
_j8b_methodend13:
	ret
j8bC14CByteMtoByte:
	ldd r16,z+5
	jmp _j8b_methodend15
_j8b_methodend15:
	ret
;======== leave CLASS Byte ========================
j8bCmainMmain:
	push_z
	ldi zl,low(_j8b_retpoint20)
	push zl
	ldi zl,high(_j8b_retpoint20)
	push zl
	ldi zl,8
	push zl
	jmp j8bC11CByteMByte
_j8b_retpoint20:
	pop_z
	mov r20,r16
	mov r21,r17
_j8b_ifbegin22:
	push_z
	mov r30,r20
	mov r31,r21
	ldi r17,15
	mcall j8bproc_instanceof_nr
	pop_z
	brne _j8b_ifend25
_j8b_ifthen23:
	push_z
	ldi zl,low(_j8b_retpoint21)
	push zl
	ldi zl,high(_j8b_retpoint21)
	push zl
	mov r30,r20
	mov r31,r21
	ldi r16,13
	ldi r17,0
	jmp j8bproc_invoke_method_nr
_j8b_retpoint21:
	pop_z
	mcall os_out_num8
_j8b_methodend19:
_j8b_ifend25:
_j8b_methodend18:
	ret
;======== leave CLASS Main ========================
Одна из функций RTOS(код сырой, может содержать ошибки)
.IFNDEF J8BPROC_INVOKE_METHOD_NR
;-----------------------------------------------------------
J8BPROC_INVOKE_METHOD_NR:									;NR-NO_RESTORE - не восстанавливаю регистры
;-----------------------------------------------------------
;Переходим на код метода
;IN: Z-адрес HEAP,ACCUM_L-ид интерфейса, ACCUM_H-порядковый
;номер метода в интерфейсе
;-----------------------------------------------------------
	PUSH_Z
	ADIW ZL,0x03
	LD ACCUM_EH,Z+
	LD ZH,Z
	MOV ZL,ACCUM_EH
	ADIW ZL,0x01											;Пропускаем ид типа класса
	LPM ACCUM_EH,Z+											;Получаем количество пар(ид интерфейс + кол-во методов)
	MOV YL,ACCUM_EH											;Вычисляю адрес блока адресов методов
	LDI YH,0x00
	LSL YL
	ROL YH
	ADD YL,ZL
	ADC YH,ZH
	LDI TEMP_L,0x00											;Порядковый номер метода в классе
_J8BPROC_INVOKE_METHOD_NR__IFACEIDS_LOOP:
	LPM ACCUM_EL,Z+											;Получаю id интерфейса
	CP ACCUM_EL,ACCUM_L
	BREQ _J8BPROC_INVOKE_METHOD_NR__GOT_IFACE
	LPM ACCUM_EL,Z+											;Получаю количество методов в интерфейсе
	ADD ACCUM_H,ACCUM_EL
	DEC ACCUM_EH
	BRNE _J8BPROC_INVOKE_METHOD_NR__IFACEIDS_LOOP
_J8BPROC_INVOKE_METHOD_NR__GOT_IFACE:
	POP_Z
	LSL ACCUM_H												;Смещаюсь на адрес с учетом порядкового номера метода в классе
	ADD YL,ACCUM_H
	ADC YH,C0x00
	PUSH_Y
	RET
.ENDIF
Ключевые фрагменты сгенерированного ассемблерного кода:
1. Метаданные класса:
 Компилятор автоматически формирует структуру для поддержки RTTI (Run-Time Type Information), необходимую для instanceof.
_j8b_meta10:
	.db 15,2,13,1,14,2 ; <-- Вот эти метаданные класса `Byte`
	.dw j8bC14CByteMtoByte, j8bC14CByteMtoByte, 0
2. Динамическое создание объекта в куче:
 Код конструктора new Byte(0x08) транслируется в вызов менеджера динамической памяти (os_dram_alloc) и инициализацию полей.
j8bC11CByteMByte:
	ldi r16,6         ; Размер объекта: 6 байт!
	mcall os_dram_alloc
	std z+5, r16      ; Инициализация поля `value`
3. Проверка типа (is / instanceof):
 Оператор if(b1 is Byte) компилируется в вызов процедуры j8bproc_instanceof_nr, которая проверяет метаданные объекта.
mcall j8bproc_instanceof_nr ; Магия проверки типа происходит здесь!
brne _j8b_ifend25           ; Условный переход на основе результата
4. Полиморфный вызов метода:
 Вызов b1.toByte() через интерфейс Number преобразуется в универсальный механизм поиска и диспетчеризации метода.
; invokeInterfaceMethod byte Number.toByte
ldi r16, 13
ldi r17, 0
jmp j8bproc_invoke_method_nr ; Динамический вызов метода!
5. Интеграция с системными сервисами:
 Вывод в "консоль" (System.out) — это вызов системного сервиса ОСРВ.
mcall os_out_num8 ; Вывод числа через системный вызов RTOS
Что это значит?
 Это доказывает, что подход vm5277 работоспособен. Мы можем писать на высокоуровневом ООП-языке, а под капотом получать код, который:
- Эффективно использует память: объекты размещаются в куче, метаданные компактны. 
- Сохраняет производительность: ключевые операции (выделение памяти, проверка типов) вынесены в оптимизированные ассемблерные процедуры. 
- Является переносимым: этот же Java-like-код, после завершения работы над платформами, сможет работать на PIC и STM8 (ограничений почти нет). 
Комментарии (26)
 - MasterMentor30.08.2025 10:55- Статья/карма/подписка/Github: +/+/+/+. - Плюсы: + амбициозный проект; + это подлинное программирование. - Минусы: - маленькая аудитория (но в этом минусе плюс, что она хотя бы есть один пользователь проекта - сам автор); - нужно тянуть java, а это платформа с весьма "специфичным" "характером". 
 - LaoWai200030.08.2025 10:55- Автору — респект. - Вопрос — в чем преимущества применения восьмибитных архитектур при дешевизне 32 битных?  - 5277 Автор30.08.2025 10:55- Я могу ошибаться в деталях, но в общем 8 бит мк, по моему личному мнению, имеют следующие преимущества: - дешевле, в больших партиях эта разница существенна. 
- меньше размерами 
- потребляют меньше тока 
- более устойчивы к ESD 
- Предиктивны (можно прогнозировать количество потраченных тактов/времени) 
- Менее сложные (меньше периферии, легче реализация функционала - например инициализация периферии или тактирования) 
  - Sergey_1234530.08.2025 10:55- 5 вольтовых входы выходы. 
- 
Больший ток входов выходов Иногда это критично 
 
 
  - svitoglad30.08.2025 10:55- Стабильно есть в наличии и совместимость pin-to-pin даже переферии в пределах серии. Например в STM32 во времена кризиса чипов это было проблемой. 
 
 - firehacker30.08.2025 10:55- Господи, RTTI на 8-битках... где SRAM может доходить до нескольких десятков байт...  - 5277 Автор30.08.2025 10:55- Очевидно, что высокоуровневый ООП язык на устройстве с десятками байт невозможен. Однако в проекте будет режим функций(без ООП). Проект также может быть полезен для реализации ASM программы. Так как включает в себя ассемблер-сборщик и наработанные библиотеки(в том числе и RTOS). Подключение кода будет только по необходимости. - Также прошу обратить внимание, что в моем решении минимальное потребление памяти для RTTI - *проект находится на ранней стадии разработки. 
 
 - easimonenko30.08.2025 10:55- Не очень понятны две вещи: - Зачем проверка типов во время исполнения кода? 
- Зачем полиморфизм также во время исполнения кода? 
 - Обычно такие вещи в runtime присутствуют либо в языках с виртуальной машиной и интерпретатором, либо: - информация о типах удаляется, так как код прошёл все проверки; 
- обобщённый код заменяется на специализированные варианты во время компиляции. 
  - 5277 Автор30.08.2025 10:55- import Animal { void makeSound(); } class Dog implements Animal { void makeSound() {...} void toWag() {...} } class Cat implements Animal { void makeSound() {...} } Animal myPet = getRandomAnimal(); // Может вернуть и Dog, и Cat myPet.makeSound(); // Какая реализация метода должна быть вызвана? if(myPet is Dog as dog) { dog.toWag(); }- Проверка типов во время выполнения нужна для работы с полиморфными объектами, конкретный тип которых неизвестен на этапе компиляции и определяется динамически (по данным с датчиков, от пользователя, из сети и т.д.). - Runtime-полиморфизм нужен для единообразной работы с объектами через их общий интерфейс, без необходимости знать их конкретный тип.  - easimonenko30.08.2025 10:55- Всё ещё не понятно: у вас статическая типизация? Значит все типы переменных известны заранее. А если что-то придёт из-вне неизвестное, то всё что вы можете сделать -- проигнорировать или упасть. - Пример с кошечками и собачками сомнительный: какой тип у getRandomAnimal()? Animal? Тогда будет вызываться Animal.makeSound(). Так?  - 5277 Автор30.08.2025 10:55- Это не чисто статическая система. У интерфейса Animal нет реализации метода makeSound(). Конкретный метод(класса Dog или Cat) выбирается в runtime. - Это не интерпретация, а компиляция с сохранением метаинформации для динамической диспетчеризации.  - easimonenko30.08.2025 10:55- Если я правильно понял, то это всё напоминает C++ с его абстрактными классами и виртуальными методами.  - 5277 Автор30.08.2025 10:55- Я ориентируюсь на Java, но более упрощенную(без наследования классов, но с интерфейсами)  - easimonenko30.08.2025 10:55- Мне, собственно, так же не хватает интерфейсов и их реализации в языках для встраиваемых систем. А название class сбивает с толку и направляет мышление в сторону ООП с классами...  - 5277 Автор30.08.2025 10:55- Да, я использую синтаксис, похожий на Java, но с ключевым отличием: в моём языке нет наследования классов (нет extends для классов, кроме extends для интерфейсов). Наследование классов - слишком избыточно для 8 бит. - Поэтому слово class здесь означает ровно то же, что и final class в Java: 
 Это конечный (final) тип данных, который нельзя наследовать.
 Это реализация состояния (поля) и поведения (методов).
 Это единственный способ реализовать интерфейс (implements).- Вся полиморфная работа, ведётся только через интерфейсы. Класс же — это всего лишь «фабрика» для создания объектов, которые могут реализовывать эти интерфейсы. - Таким образом, это не «полноценное ООП с наследованием», а скорее «ООП в стиле композиции», где интерфейсы определяют контракты, а классы — их конкретные реализации. Название class я использую для привычности и краткости, понимая его именно в этом, более узком смысле. 
 
 
 
 
  - 5277 Автор30.08.2025 10:55- И главное. Я практик, а Вы меня затягиваете на поле спора в теорию. Посмотрите на приведенный в статье код, он буквально объясняет как это работает и зачем это нужно. 
 
 
 
 - DungeonLords30.08.2025 10:55- Оффтоп. Почему под atmega для описания режима работы переферии не используется Device Tree? Идеальный был бы подход, нет? 
 
           
 


AnthonyDS
А как это всё запускать, собирать? Есть пример, инструкция?
kotenev
5277 Автор
Да, именно так, ранняя стадия разработки.
Проект пишу ~4 месяца с чистого листа. За этот срок проделан значительный объем работ. Планирую основные работы по компилятору закончить в конце сентября. После чего накидаю минимум для RTOS(AVR) и высокоуровневый runtime. Даже в конце года проект будет максимум на стадии MVP(пока с поддержкой только AVR). Это слишком большой объем работ для одного человека. Я это осознаю, буду двигаться постепенно.
5277 Автор
:) Немного торопитесь, проект на ранней стадии разработки. Пока не закончен кодогенератор(он оказался немного сложнее, чем я ожидал, тем не менее я надеюсь уложиться в срок и к концу сентября закончить кодогенерацию и навести порядок в семантике.
К примеру сейчас реализую кодогенерацию(для AVR) логических операций и операций сравнения для AVR в конструкции условных операторов
Я обязательно займусь документацией, примерами и тестами производительности, но скорее всего к концу года.
Rigidus
Ждем расширения поддерживаемых архитектур на MSP430
svitoglad
Новые AVR тоже будут?