Изменения

Материал из Animus
Перейти к навигацииПерейти к поиску
27 106 байт добавлено ,  в пятницу в 18:02
Новая страница: «'''MCU''' (Microcontroller Unit) — программируемые микроконтроллеры, позволяющие игрокам создавать а...»
'''MCU''' (Microcontroller Unit) — программируемые микроконтроллеры, позволяющие игрокам создавать автоматизированные устройства с произвольной логикой. MCU используют полноценный интерпретатор 32-битного процессора '''RISC-V''' с расширениями <code>IMFZicsrZicntrZbbZba</code> и уровнями привилегий M+U.

== Обзор ==

MCU представляют собой компактные вычислительные платы, которые можно программировать с помощью загрузки ELF-файлов через JTAG-программатор. К MCU подключаются различные модули расширения через PCI-слоты, позволяя взаимодействовать с окружающим миром: синтезировать речь, отправлять радиосигналы, выводить отладочную информацию и многое другое.

=== Ключевые особенности ===

* '''Реальная архитектура RISC-V''' — код компилируется стандартными тулчейнами (GCC, Clang)
* '''Модульная система''' — расширение функциональности через PCI-модули
* '''Реалистичная термодинамика''' — перегрев, троттлинг, аварийное отключение
* '''Разгон''' — возможность превысить штатные частоты с риском нестабильности
* '''Радиационная устойчивость''' — различные уровни защиты для работы вблизи суперматерии

== Базовое использование ==

=== Начало работы ===

# '''Получите MCU''' — изготовьте на принтере схем или найдите готовую плату
# '''Установите батарею''' — используйте любую совместимую ячейку питания
# '''Подключите модули''' — вставьте нужные PCI-модули в плату
# '''Загрузите прошивку''' — используйте JTAG-программатор для загрузки ELF-файла
# '''Включите плату''' — используйте команду "Turn On" в контекстном меню

=== Управление ===

{| class="wikitable"
! Действие !! Инструмент !! Описание
|-
| Загрузка прошивки || JTAG-программатор || Загружает ELF-файл в память MCU (плата должна быть выключена)
|-
| Настройка частоты || Мультитул || Позволяет задать рабочую частоту в допустимых пределах
|-
| Переключение OC || Отвёртка || Включает/выключает режим разгона
|-
| Блокировка прошивки || Сварочный аппарат || '''НЕОБРАТИМО''' прожигает OTP-предохранитель, запрещая перепрограммирование
|-
| Установка батареи || Батарея (в руке) || Вставляет источник питания
|-
| Установка модуля || PCI-модуль (в руке) || Подключает модуль расширения
|}

=== Контекстное меню ===

* '''Turn On''' — включить плату
* '''Turn Off''' — выключить плату
* '''Eject Battery''' — извлечь батарею
* '''Eject Module''' — извлечь выбранный PCI-модуль

== Модели MCU ==

=== Стандартная линейка (Nanotrasen Cybernetics) ===

{| class="wikitable"
! Модель !! RAM !! Частота !! PCI !! Особенности
|-
| '''NCR-1000''' || 64 KB || 0.25–2 МГц || 4 || Базовая модель для повседневных задач
|-
| '''NCR-2000''' || 256 KB || 0.5–4 МГц || 8 || Удвоенная память, популярна в промышленности
|-
| '''NCR-4000 Pro''' || 1 MB || 1–8 МГц || 16 || Профессиональный уровень, лёгкая защита от радиации
|}

=== Энергоэффективная линейка (Whisper) ===

{| class="wikitable"
! Модель !! RAM !! Частота !! PCI !! Особенности
|-
| '''Whisper-LP8''' || 32 KB || 0.125–1 МГц || 2 || Ультранизкое потребление, идеален для датчиков
|-
| '''Whisper-LP16''' || 64 KB || 0.1–1.5 МГц || 4 || Расширенная автономность
|-
| '''Whisper-LP32i''' || 128 KB || 0.25–2 МГц || 6 || Промышленный класс, повышенная радиационная защита
|}

=== Оверклокерская линейка (Cybersun Fury) ===

{| class="wikitable"
! Модель !! RAM !! Частота !! OC предел !! PCI !! Особенности
|-
| '''Fury-S1 Starter''' || 64 KB || 0.75–3 МГц || 4.5 МГц || 4 || Начальный уровень, защита RAM при OC
|-
| '''Fury-X1''' || 256 KB || 1–4 МГц || 6 МГц || 8 || Разблокированные множители
|-
| '''Fury-X2 Extreme''' || 1 MB || 2–8 МГц || 12 МГц || 16 || Флагман линейки, требует серьёзного охлаждения
|}

''Линейка Fury поставляется с включённым режимом разгона и защитой RAM. Превышение штатных частот значительно увеличивает тепловыделение и может привести к нестабильности!''

=== Гибкая линейка (Flex) ===

{| class="wikitable"
! Модель !! RAM !! Частота !! PCI !! Особенности
|-
| '''Flex-V1''' || 64 KB || 0.1–4 МГц || 6 || Широкий диапазон частот
|-
| '''Flex-V2 Pro''' || 256 KB || 0.125–6 МГц || 12 || Адаптивные системы
|-
| '''Flex-V3 Max''' || 768 KB || 0.0625–8 МГц || 18 || Максимальная гибкость, защита от EMP
|}

=== Специализированные модели ===

{| class="wikitable"
! Модель !! RAM !! Частота !! PCI !! Особенности
|-
| '''Aegis-H1''' || 128 KB || 0.5–2 МГц || 4 || 90% защита от радиации, работает у суперматерии
|-
| '''RetroTech Z80-NT''' || 32 KB || 0.25–0.75 МГц || 2 || Ностальгия для энтузиастов, минимальное потребление
|}

== PCI-модули ==

=== Text-to-Speech Module ===

Синтезирует речь из текста. MCU может "говорить" в игровой чат.

=== Serial Terminal Module ===

Терминал для отладки и взаимодействия с MCU. Открывается при использовании модуля в руке (при подключении к MCU). Позволяет просматривать вывод программы и отправлять данные.

=== Signaler Module ===

Радиомодуль для отправки и приёма сигналов. Позволяет удалённо активировать другие устройства или получать команды.

== Термальная система ==

MCU генерируют тепло при работе. Система охлаждения учитывает:

* '''Частоту работы''' — выше частота = больше тепла
* '''Загрузку процессора''' — активные вычисления греют сильнее
* '''Окружающую среду''' — в вакууме охлаждение практически отсутствует
* '''Разгон''' — потребление растёт квадратично относительно превышения частоты

=== Температурные пороги ===

Каждая модель имеет свои пороги (указаны типичные значения):

{| class="wikitable"
! Порог !! Температура !! Эффект
|-
| Троттлинг || ~65°C || Частота снижается вдвое
|-
| Аварийное отключение || ~90°C || MCU выключается
|-
| Повреждение || ~95°C || Начинается повреждение памяти
|}

''При включённом режиме разгона (OC) аварийное отключение при перегреве '''отключено''' — плата продолжит работать до физического повреждения!''

=== Индикация состояния ===

При осмотре MCU:
* '''Зелёный LED''' — нормальная работа
* '''Оранжевый мигающий LED''' — троттлинг активен
* '''Красный мигающий LED''' — требуется охлаждение перед запуском
* '''LED выключен''' — плата не работает

== Разгон (Overclocking) ==

Для разгона:

# Переключите OC-джампер отвёрткой
# Установите частоту выше штатного максимума (до 150% от max)

=== Риски разгона ===

* '''Повышенное энергопотребление''' — батарея разряжается быстрее
* '''Увеличенное тепловыделение''' — требуется внешнее охлаждение
* '''Нестабильность памяти''' — случайное повреждение данных в RAM
* '''Отсутствие защиты от перегрева''' — плата не выключится автоматически

''Модели без флага <code>oc_ram_protection</code> будут испытывать случайные ошибки памяти при разгоне!''

== Радиация ==

MCU подвержены воздействию ионизирующего излучения:

=== Мгновенные эффекты (при работе) ===

* '''SEU''' (Single Event Upset) — случайное изменение битов в памяти
* '''SEL''' (Single Event Latchup) — короткое замыкание с аварийным отключением и искрами

=== Накопительный эффект (TID) ===

Total Ionizing Dose накапливается даже при выключенной плате:

{| class="wikitable"
! Уровень TID !! Эффект
|-
| 50% лимита || Лёгкое обесцвечивание, ×1.5 к вероятности ошибок
|-
| 75% лимита || Заметное обесцвечивание, ×2 к вероятности ошибок
|-
| 100% лимита || '''Необратимый выход из строя'''
|}

=== Защита от радиации ===

{| class="wikitable"
! Модель !! Защита !! TID лимит
|-
| Стандартные || 0–5% || 100
|-
| Whisper-LP32i || 50% || 250
|-
| Aegis-H1 || 90% || 1000
|}

== EMP ==

Электромагнитные импульсы повреждают MCU в зависимости от силы импульса и уровня защиты (<code>emp_hardening</code>):

{| class="wikitable"
! Модель !! Защита EMP
|-
| Базовые модели || 0 (уязвимы)
|-
| NCR-4000 Pro, Whisper-LP || 1
|-
| Whisper-LP32i || 2
|-
| Aegis-H1 || 4
|}

При превышении защиты — '''необратимый выход из строя'''.

== Физические повреждения ==

MCU разрушаются от:
* Попадания снарядов
* Взрывов
* Падения/ударов
* Расплавления (экстремальные температуры)

При разрушении батарея и модули могут уцелеть (50% шанс) и разлететься в стороны.

== Защита прошивки ==

OTP (One-Time Programmable) предохранитель можно прожечь сварочным аппаратом:

''Это действие '''НЕОБРАТИМО'''! После прожига MCU невозможно перепрограммировать.''

Используется для:
* Защиты критических систем от саботажа
* Предотвращения несанкционированных модификаций
* Создания "запечатанных" устройств

== Изготовление ==

Все MCU и модули изготавливаются на '''принтере схем''' (Imprinter) и '''протолате'''. Требуемые технологии и материалы зависят от сложности устройства.

== Советы ==

* Начинайте с NCR-1000 или Whisper-LP8 для изучения системы
* Всегда тестируйте прошивку перед прожигом OTP
* Для работы у суперматерии используйте '''только''' Aegis-H1
* Serial Terminal незаменим для отладки
* В вакууме MCU перегревается почти мгновенно — обеспечьте атмосферу
* Линейка Flex идеальна для устройств с переменной нагрузкой

== Разработка прошивок ==

Эта секция предназначена для разработчиков, желающих создавать собственные прошивки для MCU.

=== Архитектура ===

MCU используют 32-битный процессор '''RISC-V''' со следующими расширениями:

{| class="wikitable"
! Расширение !! Описание
|-
| '''I''' || Базовый целочисленный набор инструкций
|-
| '''M''' || Умножение и деление
|-
| '''F''' || Операции с плавающей точкой одинарной точности
|-
| '''Zicsr''' || Инструкции для работы с CSR (Control and Status Registers)
|-
| '''Zicntr''' || Базовые счётчики производительности
|-
| '''Zbb''' || Базовые битовые манипуляции
|-
| '''Zba''' || Адресные битовые манипуляции
|}

Уровни привилегий: '''M''' (Machine) + '''U''' (User).

=== SDK ===

Официальный SDK написан на языке '''Zig''' и доступен на GitHub:

https://github.com/igorsaux/mcu-sdk

SDK содержит:
* Определения всех memory-mapped регистров
* Драйверы для всех PCI-устройств
* Утилиты для работы с таймерами, DMA, PRNG
* '''Примеры использования всех модулей'''

=== Карта памяти ===

{| class="wikitable"
! Адрес !! Размер !! Описание
|-
| <code>0x0000_1000</code> || 16 байт || Boot Info (частота CPU, размер RAM)
|-
| <code>0x0000_2000</code> || 8 байт || Сенсоры (температура, энергопотребление, флаги)
|-
| <code>0x0200_0000</code> || — || CLINT (таймер, прерывания)
|-
| <code>0x0C00_2000</code> || 1 байт || PRNG (аппаратный генератор случайных чисел)
|-
| <code>0x1000_0000</code> || — || UART
|-
| <code>0x1000_1000</code> || — || DMA контроллер
|-
| <code>0x1000_2000</code> || — || PCI контроллер
|-
| <code>0x2000_0000</code> || — || Адресное пространство PCI-устройств
|-
| <code>0x8000_0000</code> || varies || RAM (начало)
|}

=== Структуры данных ===

==== Boot Info ====

<pre>
pub const BootInfo = extern struct {
cpu_frequency: u64, // Частота CPU в Hz
ram_size: u32, // Размер RAM в байтах
free_ram_start: u32, // Начало свободной RAM
};
</pre>

==== Sensors ====

<pre>
pub const Sensors = extern struct {
pub const Flags = packed struct(u8) {
overheat: bool = false, // Температура >= shutdown_temp
throttled: bool = false, // Частота снижена
_pad: u6 = 0,
};

temperature: i16 = 0, // Температура в °C
power_usage: u16 = 0, // Потребление в mWh/мин
flags: Flags = .{},
};
</pre>

=== PCI-устройства ===

==== Обнаружение устройств ====

<pre>
pub const Pci = extern struct {
pub const MAX_DEVICES: usize = 18;

pub const DeviceType = enum(u8) {
none = 0,
tts = 1,
serial_terminal = 2,
signaler = 3,
_,
};

pub const Entry = extern struct {
address: u32 = 0, // MMIO адрес устройства
len: u32 = 0, // Размер адресного пространства
ty: DeviceType = .none,
};
};
</pre>

Для поиска устройства по типу:

<pre>
inline fn findDevice(comptime T: type, comptime pci_type: sdk.Pci.DeviceType) ?T {
for (&sdk.pci.status().entries, 0..) |entry, slot| {
if (entry.ty == pci_type) {
return .{ .entry = entry, .slot = @intCast(slot) };
}
}
return null;
}
</pre>

==== Text-to-Speech (TTS) ====

<pre>
pub const Tts = extern struct {
pub const BUFFER_SIZE: usize = 128;

// Проверка готовности
pub inline fn isReady(this: *volatile Tts) bool;

// Произнести текст (предварительно записанный через DMA)
pub inline fn say(this: *volatile Tts) void;

// Подтвердить обработку события
pub inline fn ack(this: *volatile Tts) void;
};
</pre>

==== Serial Terminal ====

<pre>
pub const SerialTerminal = extern struct {
pub const INPUT_BUFFER_SIZE: usize = 1024;
pub const OUTPUT_BUFFER_SIZE: usize = 1024;

// Количество байт во входном буфере
pub inline fn len(this: *volatile SerialTerminal) u16;

// Отправить выходной буфер
pub inline fn flush(this: *volatile SerialTerminal) void;

// Включить прерывание при получении данных
// this.interrupts().on_new_data = true;
};
</pre>

==== Signaler ====

<pre>
pub const Signaler = extern struct {
// Установить частоту и код
pub inline fn set(this: *volatile Signaler, frequency: u16, code: u8) void;

// Отправить сигнал
pub inline fn send(this: *volatile Signaler) void;

// Проверка готовности (есть cooldown между отправками)
pub inline fn ready(this: *volatile Signaler) bool;

// Включить прерывания
// this.interrupts().on_pulse = true; // При получении сигнала
// this.interrupts().on_ready = true; // Когда готов к отправке
};
</pre>

=== DMA ===

DMA позволяет эффективно передавать данные между RAM и устройствами:

<pre>
// Чтение из устройства в RAM
sdk.dma.read(channel, device_address, destination_slice);

// Запись из RAM в устройство
sdk.dma.write(channel, device_address, source_slice);

// Заполнение паттерном
sdk.dma.fill(channel, device_address, pattern_slice, total_length);

// Заполнение одним байтом (memset)
sdk.dma.memset(channel, device_address, byte_value, length);
</pre>

=== CLINT (таймер) ===

<pre>
// Прочитать текущее время в тиках
const ticks = sdk.clint.readMtime();

// Прочитать текущее время в наносекундах
const ns = sdk.clint.readMtimeNs();

// Установить прерывание через N тиков
sdk.clint.interruptAfter(ticks);

// Установить прерывание через N наносекунд
sdk.clint.interruptAfterNs(nanoseconds);

// Ожидание прерывания (энергосбережение)
sdk.arch.wfi();
</pre>

=== PRNG ===

Аппаратный генератор случайных чисел:

<pre>
// Получить один случайный байт
const byte = sdk.prng.status().value;

// Использовать как std.Random интерфейс
const random = sdk.prng.interface();
const value = random.int(u32);
</pre>

=== Пример: Sub-GHz передатчик ===

Разберём пример прошивки, реализующей интерактивный терминал для управления Signaler-модулем.

==== Структура программы ====

'''1. Обёртки для PCI-устройств'''

<pre>
const SerialTerminal = struct {
entry: sdk.Pci.Entry,
slot: u8 = 0,

pub inline fn mmio(this: SerialTerminal) *volatile sdk.SerialTerminal {
return @ptrFromInt(this.entry.address);
}
};

const Signaler = struct {
entry: sdk.Pci.Entry,
slot: u8 = 0,

pub inline fn mmio(this: Signaler) *volatile sdk.Signaler {
return @ptrFromInt(this.entry.address);
}
};
</pre>

Каждое устройство хранит свой PCI Entry и номер слота. Метод <code>mmio()</code> возвращает указатель на memory-mapped регистры устройства.

'''2. Инициализация'''

<pre>
pub fn main() void {
// Поиск необходимых устройств
const terminal = findDevice(SerialTerminal, .serial_terminal) orelse return;
const signaler = findDevice(Signaler, .signaler) orelse return;

var shell = Shell.init(terminal, signaler);
shell.run();
}
</pre>

Если устройства не найдены — программа завершается.

'''3. Главный цикл'''

<pre>
pub fn run(this: *Shell) void {
// Включаем внешние прерывания
sdk.arch.Mie.setMeie();

// Подписываемся на события
this.terminal.mmio().interrupts().on_new_data = true;
this.signaler.mmio().interrupts().on_pulse = true;

this.printBanner();

while (true) {
// Проверяем события терминала
if (this.terminal.mmio().lastEvent()) |event| {
this.terminal.mmio().ack();
if (event.ty == .new_data) {
this.handleInput();
}
}

// Проверяем события сигналера
if (this.signaler.mmio().lastEvent()) |event| {
this.signaler.mmio().ack();
if (event.ty == .pulse) {
// Получен входящий сигнал!
this.print("\n");
this.printBox("SIGNAL RECEIVED", C.bgreen);
this.printPrompt();
this.flush();
}
}

// Ждём следующего прерывания (экономия энергии)
sdk.arch.wfi();
}
}
</pre>

''Вызов <code>wfi()</code> (Wait For Interrupt) переводит CPU в режим ожидания, значительно снижая энергопотребление.''

'''4. Обработка ввода'''

<pre>
fn handleInput(this: *Shell) void {
const bytes = this.terminal.mmio().len();
var input_buffer: [sdk.SerialTerminal.INPUT_BUFFER_SIZE]u8 = undefined;

// Читаем данные через DMA
sdk.dma.read(this.terminal.slot, 0, input_buffer[0..bytes]);

var input = input_buffer[0..bytes];

// Убираем trailing newlines
while (input.len > 0 and (input[input.len - 1] == '\n' or input[input.len - 1] == '\r')) {
input = input[0 .. input.len - 1];
}

this.executeCommand(input);
}
</pre>

'''5. Отправка сигнала'''

<pre>
fn cmdSend(this: *Shell) void {
if (this.cur_freq == 0) {
this.print("No frequency configured\n");
return;
}

// Проверяем cooldown
if (!this.signaler.mmio().ready()) {
this.print("Cooldown, wait...\n");
return;
}

// Устанавливаем параметры и отправляем
this.signaler.mmio().set(this.cur_freq, this.cur_code);
this.signaler.mmio().send();
this.send_count += 1;
}
</pre>

'''6. Entry Point'''

<pre>
comptime {
_ = sdk.utils.EntryPoint(.{
.stack_size = std.math.pow(u32, 2, 14), // 16 KB стека
});
}
</pre>

Макрос <code>EntryPoint</code> генерирует стартовый код, настраивает стек и вызывает <code>main()</code>.

==== ANSI-цвета в терминале ====

Serial Terminal поддерживает ANSI escape-последовательности:

<pre>
const C = struct {
const reset = "\x1b[0m";
const bold = "\x1b[1m";
const dim = "\x1b[2m";

const orange = "\x1b[38;5;208m";
const green = "\x1b[32m";
const red = "\x1b[31m";
// ...
};
</pre>

=== Компиляция ===

# Установите [https://ziglang.org/download/ Zig] (версия 0.15.2)
# Клонируйте SDK: <code>git clone https://github.com/igorsaux/mcu-sdk</code>
# Соберите пример: <code>zig build</code>
# Загрузите <code>zig-out/bin/firmware.elf</code> через JTAG-программатор

=== Отладка ===

* Используйте '''Serial Terminal''' для вывода отладочной информации
* Проверяйте '''Sensors''' для мониторинга температуры и потребления
* Используйте <code>sdk.boot_info</code> для получения информации о системе

=== Ограничения ===

* Максимальный размер ELF-файла ограничен конфигурацией сервера
* Доступ к памяти вне разрешённых регионов вызывает trap
* Некоторые syscall'ы имеют cooldown (TTS, Signaler)
* RAM не сохраняется между перезагрузками

{{GuideMenu}}

Навигация