Что, если вы не хотите использовать C++ или ассемблер? Что, если вы хотите создать свой собственный язык, который будет идеально подходить для ваших задач? Мы создадим интерпретатор байт-кода, который будет выполняться на ATmega328P.
Архитектура виртуальной машины:
// Байт-код виртуальной машины
#define OP_NOP 0x00
#define OP_LOAD 0x01 // Загрузить константу
#define OP_STORE 0x02 // Сохранить в переменную
#define OP_ADD 0x03 // Сложение
#define OP_SUB 0x04 // Вычитание
#define OP_MUL 0x05 // Умножение
#define OP_DIV 0x06 // Деление
#define OP_CMP 0x07 // Сравнение
#define OP_JMP 0x08 // Безусловный переход
#define OP_JZ 0x09 // Переход если ноль
#define OP_CALL 0x0A // Вызов функции
#define OP_RET 0x0B // Возврат
#define OP_PRINT 0x0C // Вывод
#define OP_READ 0x0D // Чтение с пина
#define OP_WRITE 0x0E // Запись на пин
#define OP_DELAY 0x0F // Задержка
// Виртуальная машина
class VirtualMachine {
private:
uint8_t memory[256]; // 256 байт оперативной памяти
uint8_t regs[16]; // 16 регистров
uint16_t pc = 0; // Счетчик команд
uint8_t sp = 0; // Указатель стека
uint8_t stack[16]; // Стек вызовов
bool running = true;
public:
void loadProgram(const uint8_t* program, uint16_t size) {
memcpy(memory, program, size);
}
void execute() {
while (running) {
uint8_t opcode = memory[pc++];
switch (opcode) {
case OP_LOAD: {
uint8_t reg = memory[pc++];
uint8_t value = memory[pc++];
regs[reg] = value;
break;
}
case OP_ADD: {
uint8_t reg1 = memory[pc++];
uint8_t reg2 = memory[pc++];
uint8_t dest = memory[pc++];
regs[dest] = regs[reg1] + regs[reg2];
break;
}
case OP_PRINT: {
uint8_t reg = memory[pc++];
Serial.println(regs[reg]);
break;
}
case OP_WRITE: {
uint8_t pin = memory[pc++];
uint8_t reg = memory[pc++];
digitalWrite(pin, regs[reg]);
break;
}
case OP_READ: {
uint8_t pin = memory[pc++];
uint8_t reg = memory[pc++];
regs[reg] = digitalRead(pin);
break;
}
case OP_DELAY: {
uint8_t reg = memory[pc++];
delay(regs[reg]);
break;
}
case OP_JMP: {
uint16_t addr = memory[pc] | (memory[pc+1] << 8);
pc = addr;
break;
}
case OP_JZ: {
uint8_t reg = memory[pc++];
uint16_t addr = memory[pc] | (memory[pc+1] << 8);
if (regs[reg] == 0) {
pc = addr;
}
break;
}
case OP_HALT:
running = false;
break;
}
}
}
};
// Пример программы на нашем языке (мигаем LED)
const uint8_t blinkProgram[] = {
OP_LOAD, 0, 1, // reg0 = 1
OP_LOAD, 1, 13, // reg1 = 13 (пин)
OP_WRITE, 1, 0, // digitalWrite(13, reg0)
OP_DELAY, 0, // delay(reg0)
OP_LOAD, 0, 0, // reg0 = 0
OP_WRITE, 1, 0, // digitalWrite(13, 0)
OP_DELAY, 0, // delay(1)
OP_JMP, 0, 0, // бесконечный цикл
OP_HALT
};
void setup() {
Serial.begin(115200);
pinMode(13, OUTPUT);
VirtualMachine vm;
vm.loadProgram(blinkProgram, sizeof(blinkProgram));
vm.execute();
}
void loop() {}
Компилятор для нашего языка (на Python):
# compiler.py - конвертирует наш язык в байт-код
import sys
opcodes = {
'LOAD': 0x01, 'STORE': 0x02, 'ADD': 0x03,
'SUB': 0x04, 'MUL': 0x05, 'DIV': 0x06,
'CMP': 0x07, 'JMP': 0x08, 'JZ': 0x09,
'CALL': 0x0A, 'RET': 0x0B, 'PRINT': 0x0C,
'READ': 0x0D, 'WRITE': 0x0E, 'DELAY': 0x0F,
'HALT': 0x10
}
def compile(source):
bytecode = []
lines = source.strip().split('\n')
for line in lines:
parts = line.strip().split()
if not parts:
continue
op = parts[0]
bytecode.append(opcodes[op])
for arg in parts[1:]:
bytecode.append(int(arg))
return bytes(bytecode)
# Пример программы на нашем языке
source_code = """
LOAD 0 1
LOAD 1 13
WRITE 1 0
DELAY 0
LOAD 0 0
WRITE 1 0
DELAY 0
JMP 0
HALT
"""
bytecode = compile(source_code)
# Сохраняем в C-массив
print("const uint8_t program[] = {")
for i, b in enumerate(bytecode):
if i % 8 == 0:
print(" ", end="")
print(f"0x{b:02X}", end="")
if i < len(bytecode) - 1:
print(", ", end="")
if i % 8 == 7:
print()
print("};")