🇺🇸 English | 🇺🇦 Українська
This project implements a simple stack-based programming language (inspired by MOUSE) on an Arduino UNO. It provides a basic interactive environment with a text editor, low-resolution graphics, and program storage in the internal EEPROM.
Mouse is a stack-based language using Reverse Polish Notation (RPN), characterized by its minimalist syntax. It was designed for microcomputers with limited resources. Commands are primarily single characters that perform operations on the stack, variables, input/output, control flow, and macros. Mouse was created by Dr. Peter Grogono in the late 1970s and early 1980s, inspired by the MUSYS language used in electronic music studios. Its main purpose was education and operation on resource-constrained devices.
This specific implementation for Arduino UNO aims for a high degree of compatibility with the core concepts and command set of the original MOUSE language, making many original MOUSE examples runnable with minor or no modifications, while adding hardware-specific features like graphics and EEPROM storage. The creator of this Arduino adaptation is Ivan Svarkovsky (2025).
- Interpreter for a minimalist stack-based programming language.
- Built-in line editor for writing and modifying programs directly on the screen.
- Low-resolution graphics (128x96 pixels) with drawing primitives (pixel, line, circle, rectangle, triangle).
- Program storage in the internal EEPROM of the Arduino UNO (up to 31 lines of code).
- Support for PS/2 keyboard input and composite video output (NTSC) via the TVout library.
- Basic arithmetic, comparison, variable access, and control flow commands.
- Simple macro system for defining reusable code blocks.
- Sound feedback for key presses and actions.
This project serves as an excellent educational tool for understanding:
- How stack-based languages work: By writing and executing code, you directly interact with the stack, gaining insight into this fundamental programming paradigm used in languages like Forth, PostScript, or even virtual machines.
- Basic programming concepts in a constrained environment: Learn how to implement logic, loops, and subroutines using a limited set of commands and memory.
- Direct hardware interaction: Explore how simple commands can directly control graphics output and handle user input at a low level.
- Memory limitations and optimization: The project's design, with its custom compact functions and EEPROM usage, highlights the challenges and techniques for working with limited microcontroller resources.
The project is designed for the Arduino UNO, which features:
- Microcontroller: ATmega328P
- Clock Speed: 16 MHz
- Flash Memory: 32 KB (0.5 KB used by bootloader) - Used for storing the interpreter code.
- SRAM: 2 KB - Used for the stack, variables, line buffer, call stack, etc. This is a critical limitation.
- EEPROM: 1 KB - Used for storing user programs and macro addresses.
These limited resources necessitate the highly optimized nature of the UNO MOUSE interpreter and its language design.
To connect the necessary peripherals to your Arduino UNO:
- PS/2 Keyboard (using PS2uartKeyboard library):
- PS/2 Data Pin -> Arduino Digital Pin 0 (RX)
- PS/2 Clock Pin -> Arduino Digital Pin 4
- PS/2 5V -> Arduino 5V
- PS/2 GND -> Arduino GND
- Composite Video Display (NTSC, using TVout library):
- Connect to a CRT TV or monitor with a composite video input.
- Video Signal -> Arduino Digital Pin 7
- Sync Signal -> Arduino Digital Pin 9
- Video/Sync GND -> Arduino GND
- Audio Output (using TVout library):
- Audio Signal -> Arduino Digital Pin 11
- Connect to pin 11 through a series-connected resistor (1 kΩ) and a film capacitor (0.1 µF) to your audio input (e.g., speaker or amplifier).
To build and run this project, you will need:
- Hardware:
- Arduino UNO (or a compatible AVR board like Nano, Pro Mini with sufficient memory).
- PS/2 Keyboard.
- CRT TV or monitor with composite video input.
- Speaker or amplifier for audio (optional).
- Necessary wiring as described above (and potentially a specific circuit for Pin 9 and the audio filter).
- Software:
- Arduino IDE 1.8.19 (or potentially other 1.8.x versions, but 1.8.19 is specified).
- avr-gcc compiler (version 5.4 is specified, usually included with the recommended IDE version).
- Libraries:
- TVout: For composite video output and audio. Installable via the Arduino Library Manager.
- PS2uartKeyboard: For PS/2 keyboard input. Installable via the Arduino Library Manager.
- Install the required libraries (TVout, PS2uartKeyboard) via the Arduino IDE Library Manager (
Sketch > Include Library > Manage Libraries...
). - Open the
UNO_MOUSE.ino
file in Arduino IDE 1.8.19. - Select the correct board (
Tools > Board > Arduino Uno
) and port (Tools > Port
). - Important Build Flag: This project was tested with avr-gcc 5.4 and might require disabling Link Time Optimization (LTO) if you encounter compilation or linking errors. In Arduino IDE 1.8.x, LTO is often controlled by the
-flto
flag in theplatform.txt
file for your board. If you face issues, you might need to edit this file (located in your Arduino installation directory underhardware/arduino/avr/
) and remove or comment out the-flto
flag from thecompiler.c.flags
andcompiler.cpp.flags
lines. Be cautious when editing platform files. - Compile and upload the sketch to your Arduino UNO (
Sketch > Upload
).
- Connect the PS/2 keyboard, composite video display, and optional audio output to your Arduino UNO.
- Power on the Arduino.
- The system will boot, play a short melody, and enter the built-in line editor.
- Type your program lines. Use the arrow keys (UP, DOWN, LEFT, RIGHT, HOME, END) to navigate and edit. Backspace and Delete work as expected.
- Press
Enter
to save the current line to EEPROM and move to the next line. - Type special commands (like
RUN
,SAV
,ERS
,FREERAM
,TEST
) on a line and press Enter to execute them. - Type
RUN
and press Enter to execute the program stored in EEPROM. - Type
TEST
and press Enter to load and run the built-in example program.
Commands operate on the stack (8-bit signed integers, -128 to 127).
- Numbers: Pushes the number onto the stack.
- Syntax:
[number]
- Example:
42
- Stack after:
[42]
- Syntax:
- Variables (A-Z): Pushes the index (0-25) of the variable onto the stack.
- Syntax:
[letter]
- Example:
A
- Stack after:
[0]
(if A is the first variable)
- Syntax:
.
: Get variable value. Takes a variable index from the stack, pushes its value.- Syntax:
[variable_index] .
(or[letter] .
) - Example:
A .
(if variable A holds value 10) - Stack after:
[10]
(index removed, value added)
- Syntax:
+
: Add. Takes the top two numbers, adds them, pushes the result.- Syntax:
[number1] [number2] +
- Example:
5 3 +
- Stack after:
[8]
(5 and 3 removed, 8 added)
- Syntax:
-
: Subtract. Takes the top two numbers, subtracts the top from the second from top, pushes the result.- Syntax:
[number1] [number2] -
- Example:
10 4 -
- Stack after:
[6]
(10 and 4 removed, 6 added)
- Syntax:
*
: Multiply. Takes the top two numbers, multiplies them, pushes the result.- Syntax:
[number1] [number2] *
- Example:
6 7 *
- Stack after:
[42]
(6 and 7 removed, 42 added)
- Syntax:
/
: Divide (quotient). Takes the top two numbers, divides the second from top by the top, pushes the quotient.- Syntax:
[number1] [number2] /
- Example:
20 5 /
- Stack after:
[4]
(20 and 5 removed, 4 added)
- Syntax:
\
: Modulo (remainder). Takes the top two numbers, computes the remainder of the second from top divided by the top, pushes the result.- Syntax:
[number1] [number2] \
- Example:
17 5 \
- Stack after:
[2]
(17 and 5 removed, 2 added)
- Syntax:
=
: Assign. Takes a value and a variable index from the stack, assigns the value to the variable.- Syntax:
[value] [variable_index] =
(or[value] [letter] =
) - Example:
10 A =
- Stack after:
[]
(10 and index A removed, variable A set to 10)
- Syntax:
<
: Less than. Takes the top two numbers, pushes1
if the second from top is less than the top, otherwise0
.- Syntax:
[number1] [number2] <
- Example:
5 10 <
- Stack after:
[1]
(5 and 10 removed, 1 added)
- Syntax:
>
: Greater than. Takes the top two numbers, pushes1
if the second from top is greater than the top, otherwise0
.- Syntax:
[number1] [number2] >
- Example:
10 5 >
- Stack after:
[1]
(10 and 5 removed, 1 added)
- Syntax:
!
: Print number. Takes a number from the stack, prints it to the screen.- Syntax:
[number] !
- Example:
42 !
- Stack after:
[]
(42 removed) - Output:
42
- Syntax:
?
: Input number. Prompts for input from the keyboard, pushes the entered number onto the stack.- Syntax:
?
- Example:
?
(user enters 123) - Stack after:
[123]
- Output:
? 123
(followed by a newline)
- Syntax:
"
: Print string. Prints the text enclosed in double quotes.!
inside the string prints a newline.- Syntax:
"Text"
- Example:
"HELLO!"
- Stack after:
[]
(stack not affected) - Output:
HELLO!
- Syntax:
[
...]
: Conditional block. Takes a value from the stack. Executes the code inside[]
if the value is greater than 0.- Syntax:
[condition] [ code ]
- Example:
5 10 < [ "5 is less than 10" ! ]
- Stack after
5 10 <
:[1]
- Stack after
[
(consumes 1):[]
- Output:
5 is less than 10
- Syntax:
(
...)
: Loop/Subroutine. Saves the current position. Executes the code inside()
.)
returns execution to the saved position.- Syntax:
( code )
- Example:
( 1 ! )
- Stack after:
[]
(stack not affected by()
, only by code inside) - Action: Starts loop/call.
- Syntax:
^
: Break/Return. Takes a value from the stack. Exits the current()
or#
block if the value is less than or equal to 0.- Syntax:
[condition] ^
- Example:
( 5 ! 0 ^ )
- Stack after
0
:[0]
- Stack after
^
(consumes 0):[]
- Action: Exits the
()
loop. - Output:
5
- Syntax:
$C
: Macro definition.$A code
. Marks the line as a macro definition with name C (A-Z). This is a parser directive, not an e 9E19 xecutable command during normal run.- Syntax:
$A macro code
- Example:
$M 10 20 + !
(defines macro M)
- Syntax:
#C
: Macro call.#A
. Calls macro A. Saves the current position on the call stack.- Syntax:
#A
- Example:
#M
(calls macro M) - Stack after:
[]
(stack not affected by#
, only by macro code) - Action: Jumps to macro code.
- Syntax:
@
: Macro return.@
. Returns from a macro to the position saved by the last#
.- Syntax:
@
- Example:
@
- Stack after:
[]
(stack not affected) - Action: Returns from macro.
- Syntax:
'
: Comment.' text
. Ignores the rest of the line.- Syntax:
' Any text
- Example:
5 ! ' This is a comment
- Stack after:
[]
(stack not affected) - Output:
5
- Syntax:
$$
: Program end.$$
. Stops program execution.- Syntax:
$$
- Example:
1 ! $$ 2 !
- Stack after:
[]
(stack not affected) - Action: Halts program. Output:
1
(2 is not printed)
- Syntax:
{
: Trace on.{
. Enables stack tracing (prints stack after most commands).- Syntax:
{
- Example:
{
- Stack after:
[]
(stack not affected) - Action: Enables tracing.
- Syntax:
}
: Trace off.}
. Disables stack tracing.- Syntax:
}
- Example:
}
- Stack after:
[]
(stack not affected) - Action: Disables tracing.
- Syntax:
- Graphics Commands (consume operands from stack):
[x] [y] P
: Draw Pixel at (x, y). Stack after:[]
.[x1] [y1] [x2] [y2] L
: Draw Line from (x1, y1) to (x2, y2). Stack after:[]
.[x] [y] [r] C
: Draw Circle at (x, y) with radius r. Uses fill flag. Stack after:[]
.[x] [y] [w] [h] R
: Draw Rectangle at (x, y) with width w and height h. Uses fill flag. Stack after:[]
.[x1] [y1] [x2] [y2] [x3] [y3] T
: Draw Triangle with vertices (x1, y1), (x2, y2), (x3, y3). Stack after:[]
.[value] F
: Set Fill flag. If value is non-zero, enables fill for C and R. If 0, disables. Stack after:[]
.E
: Clear Screen. Stack after:[]
.
- Editor Commands (type on a line and press Enter):
RUN
: Execute the program in EEPROM.SAV
: Save current line buffer to EEPROM (automatic on Enter/arrow keys). Shows OK.ERS
: Erase all program lines and macros from EEPROM.FREERAM
: Display available SRAM.TEST
: Load and run the built-in test program from Flash.
This project is released under the MIT License. See the source code file (UNO_MOUSE.ino
) for the full license text.
This project is based on the work by Ivan Svarkovsky (2025) ivansvarkovsky@gmail.com
Analysis Code
The code is an implementation of a Mouse programming language interpreter for the Arduino UNO, utilizing the TVout library for graphics and text output and the PS2uartKeyboard library for keyboard input. The code employs numerous low-level techniques and optimizations to minimize RAM usage, critical for microcontrollers like the ATmega328P with only 2 KB of RAM. Below is a detailed breakdown of these approaches, focusing on RAM efficiency and unconventional programming techniques.To minimize RAM usage and reduce dependency on the C standard library (libc
), the code replaces standard functions with compact, custom implementations, reducing memory overhead and optimizing performance.
-
Custom String Functions:
- Standard
<string.h>
functions are replaced withmy_memset
,my_strcmp
,my_strlen
,my_strcat
, andmy_streq
, optimized for minimal stack and RAM usage:my_memset
: A simple loop-based implementation for memory filling, avoiding the complexity of standardmemset
.my_strcmp
: Returnsint8_t
instead ofint
, saving 1 byte per return value. Uses byte-by-byte comparison withunsigned char
casting for correct handling of signed chars.my_strlen
: Returnsuint8_t
instead ofsize_t
, sufficient forMAX_LINE_LENGTH
(30 characters), fitting in 8 bits.my_strcat
: A minimal concatenation function, avoiding temporary buffers.my_streq
: An optimized equality check, potentially faster thanmy_strcmp == 0
for equal strings due to early termination.
- RAM Savings: Eliminating
<string.h>
reduces code size and avoids library overhead. Custom functions minimize stack usage by avoiding large local arrays.
- Standard
-
Custom Character and Number Functions:
- Instead of
<ctype.h>
, functions likemy_isdigit
,my_isprint
,my_isspace
, andmy_toupper
are implemented:my_isdigit
: Uses subtraction to check the'0'
–'9'
range, faster and smaller than standardisdigit
.my_isprint
: Checks printable characters (' '
to'~'
) without lookup tables.my_isspace
: Handles whitespace (' '
,'\t'
,'\n'
,'\v'
,'\f'
,'\r'
) with a compact condition.my_toupper
: Converts lowercase to uppercase by subtracting 32, avoiding conversion tables.
- RAM Savings: These
inline
functions are inlined by the compiler, eliminating function call overhead (register push/pop). Removing<ctype.h>
saves code space.
- Instead of
-
Custom Number Parsing and Formatting:
- Instead of
<stdlib.h>
,my_atol
andmy_itoa
are implemented:my_atol
: Parses strings to numbers usingint16_t
instead oflong
, saving 2 bytes per variable, as Mouse numbers are limited toint8_t
(-128..127).my_itoa
: Convertsint8_t
to strings with a 5-byte temporary buffer (for"-128\0"
), supporting only base 10 to simplify logic.
- RAM Savings: Using
int16_t
and removing<stdlib.h>
reduces memory usage and avoids hidden allocations.
- Instead of
To conserve RAM, all immutable strings and test programs are stored in flash memory (PROGMEM) instead of RAM.
-
String Storage:
- Constants like
welcome_msg
,syntax_error_msg
, andtest_program_flash
are defined withPROGMEM
, stored in the 32 KB flash memory. - Access is handled via
pgm_read_byte
orpgm_read_byte_near
for byte-by-byte reading from flash. - A custom
my_strlen_P
function calculates PROGMEM string lengths efficiently.
- Constants like
-
Test Program:
- The
test_program_flash
is stored in PROGMEM and loaded into EEPROM during theTEST
command, avoiding RAM usage.
- The
-
RAM Savings: Storing strings and data in flash frees hundreds of bytes of RAM. For example,
test_program_flash
(~100 bytes) and error messages (~150 bytes) do not occupy RAM.
The code uses compact data structures tailored for minimal RAM consumption.
-
Stack and Variables:
- The interpreter stack (
stack
) is anint8_t
array ofSTACK_SIZE
(20 elements), using 20 bytes.int8_t
saves 1–2 bytes per element compared toint
orint16_t
. - The variables array (
vars
) usesint8_t
for 26 variables (A–Z), occupying 26 bytes, matching Mouse’s range constraints. - The call stack (
call_stack
) stores line and position for nested loops/macros, sizedMAX_NESTING
(3) × 2uint8_t
, using 6 bytes.
- The interpreter stack (
-
Buffers:
- The line buffer (
line_buffer
) isMAX_LINE_LENGTH + 1
(31 bytes), sufficient for 30 characters plus a null terminator. - The input buffer (
input_buffer
) is 10 bytes, adequate forint8_t
numbers (e.g.,"-128\0"
). - The temporary buffer in
my_itoa
(tmp
) is 5 bytes, minimal for"-128\0"
.
- The line buffer (
-
RAM Savings: Using
int8_t
/uint8_t
saves bytes per element. Compact buffers reduce stack and static memory usage.
A structure with bit fields is used to store state flags, minimizing RAM usage.
-
Flags Structure:
struct { uint8_t cursor_visible : 1; uint8_t running : 1; uint8_t tracing : 1; uint8_t stack_overflow : 1; } flags = {0, 0, 0, 0};
- Four flags are packed into a single byte (4 bits used), instead of four
bool
variables (4 bytes).
- Four flags are packed into a single byte (4 bits used), instead of four
-
RAM Savings: Bit fields save 3 bytes compared to separate
bool
variables.
EEPROM stores the program (up to 31 lines of 30 characters) and macros efficiently.
-
Program Storage:
- Each line uses
MAX_LINE_LENGTH + 1
(31 bytes): 30 for characters, 1 for length (buffer_len
). Total:31 × 31 = 961 bytes
, fitting within the 1 KB EEPROM. - Storing length avoids null-termination for short strings.
- Each line uses
-
Macro Storage:
- Macros are stored starting at
EEPROM_MACRO_DATA_START
(962), with 2-byte addresses (uint16_t
) for each of 26 macros, using26 × 2 = 52 bytes
. - A magic value (
EEPROM_MACRO_MAGIC_VALUE
) ensures initialization, preventing invalid data reads.
- Macros are stored starting at
-
Optimized Operations:
EEPROM.update
is used instead ofEEPROM.write
to minimize EEPROM wear.scanForMacros
scans EEPROM for macro definitions ($A
,$B
, etc.), storing addresses for quick access.
-
RAM Savings: Storing programs and macros in EEPROM frees RAM. Temporary buffers (
line_buffer
,scan_buffer
) are reused to avoid additional allocations.
Small functions like my_isdigit
, my_isprint
, my_isspace
, my_toupper
, my_atol
, and my_itoa
are marked inline
, allowing the compiler to embed their code, avoiding function call overhead.
- RAM and Stack Savings: Inlining eliminates stack usage for register push/pop, critical for frequently called functions like
my_isdigit
.
Temporary buffers are kept small and short-lived to reduce RAM usage.
-
In
my_itoa
:- A 5-byte
tmp
buffer is created on the stack and freed immediately, avoiding global storage. - Digits are generated in reverse, then copied to the output buffer, minimizing computation.
- A 5-byte
-
In
executeCommand
:- A 5-byte
num_buf
is used for number parsing, created only when needed.
- A 5-byte
-
RAM Savings: Small, stack-based buffers reduce peak stack usage.
The interpreter and call stacks are designed for minimal memory use.
-
Interpreter Stack:
- Limited to
STACK_SIZE = 20
, using 20 bytes.push
andpop
check bounds, settingstack_overflow
to prevent errors.
- Limited to
-
Call Stack:
- Limited to
MAX_NESTING = 3
, storing line and position (uint8_t
), using 6 bytes.
- Limited to
-
RAM Savings: Small stack sizes and
uint8_t
usage minimize memory consumption.
The code employs several non-standard approaches:
-
Direct
pgm_read_byte_near
andpgm_read_ptr
Usage:- Low-level flash memory access for PROGMEM data (strings, command table) avoids high-level wrappers.
-
Command Table in PROGMEM:
- The
cmd_table
stores{char, function_pointer}
pairs in flash, enabling efficient command dispatching without RAM usage.
- The
-
Negative Number Handling:
- In
executeCommand
, the parser distinguishes between'-'
as a subtraction operator and part of a number (e.g.,-123
), revertingpc
if not followed by a digit.
- In
-
Fill Flag (
fill_flag
):- A global
fill_flag
is set by theF
command and reset after drawing, avoiding stack-based parameter passing.
- A global
-
Dynamic Macro Scanning:
scanForMacros
locates macro definitions in EEPROM, storing their addresses for efficient invocation.
-
Minimal Global Variables:
- Global variables are limited to ~104 bytes, including
flags
(1 byte),last_blink
(4 bytes),line_buffer
(31 bytes), and others.
- Global variables are limited to ~104 bytes, including
Graphics commands (P
, L
, C
, R
, T
, E
) are implemented efficiently:
-
Compact Commands:
- Use the interpreter stack for parameters, minimizing temporary variables.
- Boundary checks (0 ≤ x < 128, 0 ≤ y < 96) are selective to reduce computation.
-
Triangle Drawing:
- The
T
command draws triangles as three lines, avoiding complex fill algorithms to save RAM.
- The
-
Screen Clearing:
- The
E
command clears the screen without parameters, minimizing stack usage.
- The
-
RAM Savings: Graphics commands reuse the stack, with minimal local variables.
Keyboard and screen handling are optimized:
-
Input Handling:
cmd_input
uses a 10-byteinput_buffer
for numbers, withmy_isdigit
andmy_isprint
for validation.- Audio feedback via
playTone
uses different frequencies/durations for actions, enhancing UX without RAM cost.
-
Cursor Blinking:
handleCursorBlink
usesmillis()
with a singlelast_blink
variable (4 bytes).
-
RAM Savings: Small buffers and reused
line_buffer
minimize memory usage.
The getFreeRam
function provides insight into available RAM:
int getFreeRam() {
extern int __heap_start, *__brkval;
int v;
return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}
-
Calculates free RAM by comparing the stack pointer to the heap boundary.
-
RAM Savings: Uses only one 2-byte local variable.
The test_program_flash
demonstrates Mouse capabilities:
const char test_program_flash[] PROGMEM =
"E\n" // Clear screen
"30 40 25 C\n" // Circle: center (30,40), radius 25
"10 15 10 50 40 50 T\n" // Triangle: vertices (30,15), (10,60), (50,60)
"60 20 40 40 R\n" // Rectangle: corner (60,20), 40x40
"1 F 70 30 20 20 R\n" // Filled rectangle: corner (70,30), 20x20
...
"$$\n" // End program
"RUN\n";
- Stored in PROGMEM, loaded into EEPROM for execution.
- Tracing (
{
,}
) outputs stack contents without additional buffers.
Errors are handled efficiently:
- Messages (
SYNTAX ERROR
,UNMATCHED BRACKET
, etc.) are stored in PROGMEM. - Output includes line number and message, minimizing RAM usage.
- Global Variables: ~104 bytes.
- Library Objects: ~1612–1688 bytes (TVout, PS2uartKeyboard).
- Stack/Temporary: ~91–151 bytes.
- Free RAM: ~105–241 bytes (verified by
getFreeRam
).
The UNO_MOUSE.ino
code exemplifies low-level programming for resource-constrained microcontrollers. Key techniques include custom library functions, PROGMEM usage, compact data structures, bit fields, inline functions, and optimized EEPROM handling. Unconventional approaches like dynamic macro scanning and minimal graphics commands highlight the focus on resource efficiency, making the Mouse interpreter viable on the Arduino UNO despite its 2 KB RAM limit.
PS2 Pin | Arduino Pin |
---|---|
data | RX (D0) |
clock | XCK (D4) |
ground | Ground |
5V | 5V |
TVout Pin | Arduino Pin |
---|---|
sync | D9 |
video | D7 |
audio | D11 |
ground | Ground |
🇺🇸 English | 🇺🇦 Українська
Цей проєкт реалізує просту стекову мову програмування (на основі MOUSE) на Arduino UNO. Він надає базове інтерактивне середовище з текстовим редактором, низькороздільною графікою та можливістю зберігання програм у внутрішній EEPROM.
Mouse — це стекова мова, що використовує зворотну польську нотацію (RPN), яка характеризується мінімалістичним синтаксисом. Вона була розроблена для мікрокомп'ютерів з обмеженими ресурсами. Команди в основному представляють собою одиночні символи, що виконують операції над стеком, змінними, вводом/виводом, керуванням потоком та макросами. Mouse був створений доктором Пітером Грогоно наприкінці 1970-х — на початку 1980-х років, натхненний мовою MUSYS, що використовувалася в електронних музичних студіях. Основне призначення — навчання та робота на пристроях з обмеженими ресурсами.
Ця конкретна реалізація для Arduino UNO прагне високого ступеня сумісності з основними концепціями та набором команд оригінальної мови MOUSE, що дозволяє запускати багато оригінальних прикладів MOUSE з незначними змінами або без них, додаючи при цьому апаратно-специфічні можливості, такі як графіка та зберігання в EEPROM. Автором цієї адаптації для Arduino є Іван Сварковський (2025).
- Інтерпретатор мінімалістичної стекової мови програмування.
- Вбудований рядковий редактор для написання та редагування програм безпосередньо на екрані.
- Низькороздільна графіка (128x96 пікселів) з графічними примітивами (піксель, лінія, коло, прямокутник, трикутник).
- Зберігання програм у внутрішній EEPROM Arduino UNO (до 31 рядка коду).
- Підтримка вводу з клавіатури PS/2 та композитного відеовиходу (NTSC) через бібліотеку TVout.
- Базові команди для арифметики, порівняння, доступу до змінних та керування потоком виконання.
- Проста система макросів для визначення блоків коду, що можуть повторно використовуватись.
- Звуковий супровід для натискань клавіш та дій.
Цей проєкт слугує чудовим освітнім інструментом для розуміння:
- Принципів роботи стекових мов програмування: Пишучи та виконуючи код, ви безпосередньо взаємодієте зі стеком, отримуючи уявлення про цю фундаментальну парадигму програмування, що використовується в таких мовах, як Forth, PostScript або навіть віртуальні машини.
- Базових концепцій програмування в умовах обмежених ресурсів: Навчіться реалізовувати логіку, цикли та підпрограми, використовуючи обмежений набір команд та пам'яті.
- Безпосередньої взаємодії з апаратним забезпеченням: Дослідіть, як прості команди можуть безпосередньо керувати виведенням графіки та обробляти ввід користувача на низькому рівні.
- Обмежень пам'яті та оптимізації: Дизайн проєкту, з його власними компактними функціями та використанням EEPROM, підкреслює виклики та техніки роботи з обмеженими ресурсами мікроконтролерів.
Проєкт розроблено для Arduino UNO, яка має такі характеристики:
- Мікроконтролер: ATmega328P
- Тактова частота: 16 МГц
- Flash-пам'ять: 32 КБ (0.5 КБ використовується завантажувачем) - Використовується для зберігання коду інтерпретатора.
- SRAM: 2 КБ - Використовується для стеку, змінних, буфера рядка, стеку викликів тощо. Це критичне обмеження.
- EEPROM: 1 КБ - Використовується для зберігання програм користувача та адрес макросів.
Ці обмежені ресурси вимагають високої оптимізації інтерпретатора UNO MOUSE та дизайну його мови.
Для підключення необхідної периферії до вашої Arduino UNO:
- Клавіатура PS/2 (використовує бібліотеку PS2uartKeyboard):
- Пін даних PS/2 -> Цифровий пін Arduino 0 (RX)
- Пін тактування PS/2 -> Цифровий пін Arduino 4
- PS/2 5V -> Arduino 5V
- PS/2 GND -> Arduino GND
- Композитний відеомонітор (NTSC, використовує бібліотеку TVout):
- Підключіть до ЕПТ телевізора або монітора з композитним відеовходом.
- Відеосигнал -> Цифровий пін Arduino 7
- Сигнал синхронізації -> Цифровий пін Arduino 9
- GND відео/синхронізації -> Arduino GND
- Аудіовихід (використовує бібліотеку TVout):
- Аудіосигнал -> Цифровий пін Arduino 11
- Підключіть до піну 11 через послідовно з'єднані резистор (1 кОм) та плівковий конденсатор (0.1 мкФ) до вашого аудіовходу (наприклад, динаміка або підсилювача).
Для збірки та запуску цього проєкту вам знадобиться:
- Апаратне забезпечення:
- Arduino UNO (або сумісна плата на AVR, як Nano, Pro Mini з достатнім обсягом пам'яті).
- Клавіатура PS/2.
- ЕПТ телевізор або монітор з композитним відеовходом.
- Динамік або підсилювач для аудіо (опціонально).
- Необхідне підключення, як описано вище (та, можливо, специфічна схема для піна 9 та аудіофільтра).
- Програмне забезпечення:
- Arduino IDE 1.8.19 (або, можливо, інші версії 1.8.x, але вказано 1.8.19).
- Компілятор avr-gcc (вказано версію 5.4, зазвичай входить до рекомендованої версії IDE).
- Бібліотеки:
- TVout: Для композитного відеовиходу та аудіо. Можна встановити через менеджер бібліотек Arduino IDE.
- PS2uartKeyboard: Для вводу з клавіатури PS/2. Можна встановити через менеджер бібліотек Arduino IDE.
- Встановіть необхідні бібліотеки (TVout, PS2uartKeyboard) через менеджер бібліотек Arduino IDE (
Sketch > Include Library > Manage Libraries...
). - Відкрийте файл
UNO_MOUSE.ino
в Arduino IDE 1.8.19. - Оберіть правильну плату (
Tools > Board > Arduino Uno
) та порт (Tools > Port
). - Важливий прапорець збірки: Цей проєкт був протестований з avr-gcc 5.4 і може вимагати вимкнення оптимізації часу лінкування (LTO), якщо ви зіткнетеся з помилками компіляції або лінкування. У Arduino IDE 1.8.x LTO часто контролюється прапорцем
-flto
у файліplatform.txt
для вашої плати. Якщо у вас виникнуть проблеми, можливо, вам доведеться відредагувати цей файл (розташований у директорії встановлення Arduino за шляхомhardware/arduino/avr/
) та видалити або закоментувати прапорець-flto
з рядківcompiler.c.flags
таcompiler.cpp.flags
. Будьте обережні при редагуванні системних файлів. - Скомпілюйте та завантажте скетч на вашу Arduino UNO (
Sketch > Upload
).
- Підключіть клавіатуру PS/2, композитний відеомонітор та опціональний аудіовихід до вашої Arduino UNO.
- Увімкніть живлення Arduino.
- Система завантажиться, програє коротку мелодію та увійде у вбудований рядковий редактор.
- Вводьте рядки вашої програми. Використовуйте клавіші зі стрілками (ВГОРУ, ВНИЗ, ВЛІВО, ВПРАВО, HOME, END) для навігації та редагування. Backspace та Delete працюють як очікується.
- Натисніть
Enter
, щоб зберегти поточний рядок в EEPROM та перейти до наступного рядка. - Вводьте спеціальні команди (наприклад,
RUN
,SAV
,ERS
,FREERAM
,TEST
) на окремому рядку та натискайте Enter для їх виконання. - Введіть
RUN
та натисніть Enter для виконання програми, збереженої в EEPROM. - Введіть
TEST
та натисніть Enter для завантаження та запуску вбудованої прикладної програми.
Команди працюють зі стеком (8-бітні знакові цілі числа, від -128 до 127).
- Числа: Розміщує число на стек.
- Синтаксис:
[число]
- Приклад:
42
- Стек після:
[42]
- Синтаксис:
- Змінні (A-Z): Розміщує індекс (0-25) змінної на стек.
- Синтаксис:
[літера]
- Приклад:
A
- Стек після:
[0]
(якщо A - перша змінна)
- Синтаксис:
.
: Отримати значення змінної. Бере індекс змінної зі стеку, розміщує її значення.- Синтаксис:
[індекс_змінної] .
(або[літера] .
) - Приклад:
A .
(якщо змінна A містить значення 10) - Стек після:
[10]
(індекс видалено, значення додано)
- Синтаксис:
+
: Додавання. Бере два верхні числа, додає їх, розміщує результат.- Синтаксис:
[число1] [число2] +
- Приклад:
5 3 +
- Стек після:
[8]
(5 і 3 видалено, 8 додано)
- Синтаксис:
-
: Віднімання. Бере два верхні числа, віднімає верхнє від передостаннього, розміщує результат.- Синтаксис:
[число1] [число2] -
- Приклад:
10 4 -
- Стек після:
[6]
(10 і 4 видалено, 6 додано)
- Синтаксис:
*
: Множення. Бере два верхні числа, множить їх, розміщує результат.- Синтаксис:
[число1] [число2] *
- Приклад:
6 7 *
- Стек після:
[42]
(6 і 7 видалено, 42 додано)
- Синтаксис:
/
: Ділення (частка). Бере два верхні числа, ділить передостаннє на верхнє, розміщує частку.- Синтаксис:
[число1] [число2] /
- Приклад:
20 5 /
- Стек після:
[4]
(20 і 5 видалено, 4 додано)
- Синтаксис:
\
: Остача від ділення. Бере два верхні числа, обчислює остачу від ділення передостаннього на верхнє, розміщує результат.- Синтаксис:
[число1] [число2] \
- Приклад:
17 5 \
- Стек після:
[2]
(17 і 5 видалено, 2 додано)
- Синтаксис:
=
: Присвоїти. Бере значення та індекс змінної зі стеку, присвоює значення змінній.- Синтаксис:
[значення] [індекс_змінної] =
(або[значення] [літера] =
) - Приклад:
10 A =
- Стек після:
[]
(10 та індекс A видалено, змінній A присвоєно 10)
- Синтаксис:
<
: Менше. Бере два верхні числа, розміщує1
, якщо передостаннє менше верхнього, інакше0
.- Синтаксис:
[число1] [число2] <
- Приклад:
5 10 <
- Стек після:
[1]
(5 і 10 видалено, 1 додано)
- Синтаксис:
>
: Більше. Бере два верхні числа, розміщує1
, якщо передостаннє більше верхнього, інакше0
.- Синтаксис:
[число1] [число2] >
- Приклад:
10 5 >
- Стек після:
[1]
(10 і 5 видалено, 1 додано)
- Синтаксис:
!
: Вивести число. Бере число зі стеку, виводить його на екран.- Синтаксис:
[число] !
- Приклад:
42 !
- Стек після:
[]
(42 видалено) - Вивід:
42
- Синтаксис:
?
: Ввести число. Запитує ввід з клавіатури, розміщує введене число на стек.- Синтаксис:
?
- Приклад:
?
(користувач вводить 123) - Стек після:
[123]
- Вивід:
? 123
(з переходом на новий рядок)
- Синтаксис:
"
: Вивести рядок. Виводить текст у подвійних лапках.!
всередині рядка виводить перехід на новий рядок.- Синтаксис:
"Текст"
- Приклад:
"HELLO!"
- Стек після:
[]
(стек не змінюється) - Вивід:
HELLO!
- Синтаксис:
[
...]
: Умовний блок. Бере значення зі стеку. Виконує код всередині[]
якщо значення більше 0.- Синтаксис:
[умова] [ код ]
- Приклад:
5 10 < [ "5 менше 10" ! ]
- Стек після
5 10 <
:[1]
- Стек після
[
(споживає 1):[]
- Вивід:
5 менше 10
- Синтаксис:
(
...)
: Цикл/Підпрограма. Зберігає поточну позицію. Виконує код всередині()
.)
повертає виконання на збережену позицію.- Синтаксис:
( код )
- Приклад:
( 1 ! )
- Стек після:
[]
(стек не змінюється самою командою()
, змінюється кодом всередині) - Дія: Починає цикл/виклик.
- Синтаксис:
^
: Вихід/Повернення. Бере значення зі стеку. Виходить з поточного блоку()
або#
, якщо значення менше або дорівнює 0.- Синтаксис:
[умова] ^
- Приклад:
( 5 ! 0 ^ )
- Стек після
0
:[0]
- Стек після
^
(споживає 0):[]
- Дія: Виходить з циклу
()
. - Вивід:
5
- Синтаксис:
$C
: Визначення макросу.$A код
. Позначає рядок як визначення макросу з іменем C (A-Z). Це директива парсера, не виконувана команда під час звичайного запуску.- Синтаксис:
$A код макросу
- Приклад:
$M 10 20 + !
(визначає макрос M)
- Синтаксис:
#C
: Виклик макросу.#A
. Викликає макрос A. Зберігає поточну позицію в стеку викликів.- Синтаксис:
#A
- Приклад:
#M
(викликає макрос M) - Стек після:
[]
(стек не змінюється самою командою#
, змінюється кодом макросу) - Дія: Переходить до коду макросу.
- Синтаксис:
@
: Повернення з макросу.@
. Повертається з макросу на позицію, збережену останнім#
.- Синтаксис:
@
- Пример:
@
- Стек после:
[]
(стек не змінюється) - Дія: Повертається з макросу.
- Синтаксис:
'
: Коментар.' текст
. Ігнорує решту рядка.- Синтаксис:
' Будь-який текст
- Приклад:
5 ! ' Це коментар
- Стек після:
[]
(стек не змінюється) - Вивід:
5
- Синтаксис:
$$
: Кінець програми.$$
. Зупиняє виконання програми.- Синтаксис:
$$
- Приклад:
1 ! $$ 2 !
- Стек після:
[]
(стек не змінюється) - Дія: Зупиняє програму. Вивід:
1
(2 не виводиться)
- Синтаксис:
{
: Увімкнути трасування.{
. Вмикає трасування стеку (виводить стек після більшості команд).- Синтаксис:
{
- Приклад:
{
- Стек після:
[]
(стек не змінюється) - Дія: Вмикає трасування.
- Синтаксис:
}
: Вимкнути трасування.}
. Вимикає трасування стеку.- Синтаксис:
}
- Приклад:
}
- Стек після:
[]
(стек не змінюється) - Дія: Вимикає трасування.
- Синтаксис:
- Команди Графіки (споживають операнди зі стеку):
[x] [y] P
: Намалювати Піксель в (x, y). Стек після:[]
.[x1] [y1] [x2] [y2] L
: Намалювати Лінію з (x1, y1) до (x2, y2). Стек після:[]
.[x] [y] [r] C
: Намалювати Коло в (x, y) радіусом r. Використовує прапорець заливки. Стек після:[]
.[x] [y] [w] [h] R
: Намалювати Прямокутник в (x, y) шириною w та висотою h. Використовує прапорець заливки. Стек після:[]
.[x1] [y1] [x2] [y2] [x3] [y3] T
: Намалювати Трикутник з вершинами (x1, y1), (x2, y2), (x3, y3). Стек після:[]
.[значення] F
: Встановити прапорець Заливки. Якщо значення не нульове, вмикає заливку для C та R. Якщо 0, вимикає. Стек після:[]
.E
: Очистити Екран. Стек після:[]
.
- Команди Редактора (вводяться на рядку та натискається Enter):
RUN
: Виконати програму з EEPROM.SAV
: Зберегти поточний буфер рядка в EEPROM (автоматично при Enter/стрілках). Показує OK.ERS
: Стерти всі рядки програми та макроси з EEPROM.FREERAM
: Показати доступну SRAM.TEST
: Завантажити та запустити вбудовану тестову програму з Flash.
Цей проєкт випущено під ліцензією MIT. Повний текст ліцензії дивіться у вихідному файлі (UNO_MOUSE.ino
).
Цей проєкт базується на роботі Івана Сварковського (2025) ivansvarkovsky@gmail.com
Аналіз Коду
Код є реалізацією інтерпретатора мови програмування Mouse для Arduino UNO, використовуючи бібліотеку TVout для виведення графіки та тексту на екран і бібліотеку PS2uartKeyboard для обробки вводу з клавіатури. Код застосовує численні низькорівневі техніки та оптимізації для мінімізації використання RAM, що критично важливо для мікроконтролерів з обмеженими ресурсами, таких як ATmega328P з 2 КБ оперативної пам’яті. Нижче наведено детальний розбір цих підходів із акцентом на економію RAM та нестандартні програмні техніки.Для зменшення використання RAM та залежності від стандартної бібліотеки C (libc
), автор замінив стандартні функції на власні компактні реалізації, що зменшують витрати пам’яті та оптимізують продуктивність.
-
Кастомні функції для обробки рядків:
- Замість
<string.h>
реалізовано функціїmy_memset
,my_strcmp
,my_strlen
,my_strcat
,my_streq
, оптимізовані для мінімального використання стека та RAM:my_memset
: Проста реалізація на основі циклу для заповнення пам’яті, що уникає складності стандартноїmemset
.my_strcmp
: Повертаєint8_t
замістьint
, заощаджуючи 1 байт на повернене значення. Використовує побайтове порівняння з приведенням доunsigned char
для коректної роботи зі знаковими символами.my_strlen
: Повертаєuint8_t
замістьsize_t
, що достатньо дляMAX_LINE_LENGTH
(30 символів), вміщаючись у 8 бітів.my_strcat
: Мінімальна функція конкатенації рядків, що уникає використання тимчасових буферів.my_streq
: Оптимізоване порівняння рядків на рівність, яке може бути швидшим заmy_strcmp == 0
завдяки ранньому завершенню при збігу символів.
- Економія RAM: Виключення
<string.h>
зменшує розмір коду та усуває накладні витрати бібліотеки. Кастомні функції мінімізують використання стека, уникаючи великих локальних масивів.
- Замість
-
Кастомні функції для обробки символів і чисел:
- Замість
<ctype.h>
реалізованоmy_isdigit
,my_isprint
,my_isspace
,my_toupper
:my_isdigit
: Використовує віднімання для перевірки діапазону'0'
–'9'
, що швидше та компактніше за стандартнуisdigit
.my_isprint
: Перевіряє друковані символи (' '
до'~'
) без використання таблиць символів.my_isspace
: Обробляє пробільні символи (' '
,'\t'
,'\n'
,'\v'
,'\f'
,'\r'
) за допомогою компактної умови.my_toupper
: Перетворює символи'a'
–'z'
у верхній регістр відніманням 32, уникаючи таблиць перетворення.
- Економія RAM: Ці функції позначені як
inline
, що дозволяє компілятору вбудовувати їх код у місце виклику, усуваючи накладні витрати на виклик функції (push/pop регістрів). Виключення<ctype.h>
заощаджує місце в кодовій пам’яті.
- Замість
-
Кастомні функції для роботи з числами:
- Замість
<stdlib.h>
реалізованоmy_atol
таmy_itoa
:my_atol
: Парсить рядки в числа, використовуючиint16_t
замістьlong
, заощаджуючи 2 байти на змінну, оскільки числа в Mouse обмежені діапазономint8_t
(-128..127).my_itoa
: Перетворюєint8_t
у рядок із тимчасовим буфером на 5 байтів (для"-128\0"
), підтримуючи лише основу 10 для спрощення логіки.
- Економія RAM: Використання
int16_t
та виключення<stdlib.h>
зменшують споживання пам’яті та усувають приховані аллокації.
- Замість
Для економії RAM усі незмінні рядки та тестові програми зберігаються у флеш-пам’яті (PROGMEM), а не в оперативній пам’яті.
-
Зберігання рядків у PROGMEM:
- Константи, такі як
welcome_msg
,syntax_error_msg
,test_program_flash
, визначено з модифікаторомPROGMEM
, що зберігає їх у 32 КБ флеш-пам’яті. - Доступ до рядків здійснюється через
pgm_read_byte
абоpgm_read_byte_near
для побайтового читання з флеш-пам’яті. - Реалізовано функцію
my_strlen_P
для ефективного обчислення довжини рядків у PROGMEM.
- Константи, такі як
-
Тестова програма:
- Тестова програма (
test_program_flash
) зберігається в PROGMEM і завантажується в EEPROM під час виконання командиTEST
, що уникає використання RAM.
- Тестова програма (
-
Економія RAM: Зберігання рядків і даних у флеш-пам’яті звільняє сотні байтів RAM. Наприклад,
test_program_flash
(~100 байтів) та повідомлення про помилки (~150 байтів) не займають RAM.
Код використовує компактні структури даних, оптимізовані для мінімального споживання RAM.
-
Стек і змінні:
- Стек інтерпретатора (
stack
) — це масивint8_t
розміромSTACK_SIZE
(20 елементів), що займає 20 байтів. Використанняint8_t
заощаджує 1–2 байти на елемент порівняно зint
чиint16_t
. - Масив змінних (
vars
) використовуєint8_t
для 26 змінних (A–Z), займаючи 26 байтів, що відповідає діапазону Mouse. - Стек викликів (
call_stack
) зберігає координати (рядок, позиція) для вкладених циклів і макросів, розміромMAX_NESTING
(3) × 2uint8_t
, що становить 6 байтів.
- Стек інтерпретатора (
-
Буфери:
- Буфер рядка (
line_buffer
) має розмірMAX_LINE_LENGTH + 1
(31 байт), достатній для 30 символів плюс нульовий термінатор. - Буфер вводу (
input_buffer
) обмежений 10 байтами, що достатньо для чиселint8_t
(наприклад,"-128\0"
). - Тимчасовий буфер у
my_itoa
(tmp
) має розмір 5 байтів, мінімальний для"-128\0"
.
- Буфер рядка (
-
Економія RAM: Використання
int8_t
/uint8_t
заощаджує байти на елемент. Компактні буфери зменшують використання стека та статичної пам’яті.
Для зберігання флагів стану використовується структура з бітами, що мінімізує використання RAM.
-
Структура
flags
:struct { uint8_t cursor_visible : 1; uint8_t running : 1; uint8_t tracing : 1; uint8_t stack_overflow : 1; } flags = {0, 0, 0, 0};
- Чотири флаги пакуються в один байт (використовується 4 біти), замість чотирьох змінних
bool
(4 байти).
- Чотири флаги пакуються в один байт (використовується 4 біти), замість чотирьох змінних
-
Економія RAM: Бітові поля заощаджують 3 байти порівняно з окремими змінними
bool
.
EEPROM використовується для зберігання програми (до 31 рядка по 30 символів) і макросів з ефективним підходом до економії пам’яті.
-
Зберігання програми:
- Кожен рядок займає
MAX_LINE_LENGTH + 1
(31 байт) у EEPROM: 30 байтів для символів, 1 байт для довжини (buffer_len
). Загалом:31 × 31 = 961 байтів
, що вміщається в 1 КБ EEPROM. - Зберігання довжини уникає нульового термінатора для коротких рядків.
- Кожен рядок займає
-
Зберігання макросів:
- Макроси зберігаються з адреси
EEPROM_MACRO_DATA_START
(962), з 2-б C849 йтними адресами (uint16_t
) для 26 макросів, займаючи26 × 2 = 52 байти
. - Магічне значення (
EEPROM_MACRO_MAGIC_VALUE
) перевіряє ініціалізацію макросів, уникаючи читання некоректних даних.
- Макроси зберігаються з адреси
-
Оптимізовані операції:
- Використовується
EEPROM.update
замістьEEPROM.write
, щоб мінімізувати знос EEPROM. - Функція
scanForMacros
сканує EEPROM для пошуку визначень макросів ($A
,$B
тощо), зберігаючи їх адреси для швидкого доступу.
- Використовується
-
Економія RAM: Зберігання програми та макросів у EEPROM звільняє оперативну пам’ять. Тимчасові буфери (
line_buffer
,scan_buffer
) використовуються повторно, уникаючи додаткових аллокацій.
Маленькі функції, такі як my_isdigit
, my_isprint
, my_isspace
, my_toupper
, my_atol
, my_itoa
, позначені як inline
, що дозволяє компілятору вбудовувати їх код, уникаючи накладних витрат на виклик функції.
- Економія RAM і стека: Вбудовування усуває використання стека для push/pop регістрів, що критично для часто викликаних функцій, таких як
my_isdigit
.
Тимчасові буфери мають малий розмір і короткий час життя для зменшення використання RAM.
-
У
my_itoa
:- Тимчасовий буфер
tmp
на 5 байтів створюється на стеку та звільняється одразу, уникаючи глобального зберігання. - Цифри генеруються в зворотному порядку, потім копіюються в вихідний буфер, мінімізуючи обчислення.
- Тимчасовий буфер
-
У
executeCommand
:- Буфер
num_buf
на 5 байтів використовується для парсингу чисел, створюючись лише за потреби.
- Буфер
-
Економія RAM: Маленькі стекові буфери зменшують пікове використання стека.
Стек інтерпретатора та стек викликів розроблені для мінімального використання пам’яті.
-
Стек інтерпретатора:
- Обмежений
STACK_SIZE = 20
, займає 20 байтів. Функціїpush
іpop
перевіряють межі, встановлюючиstack_overflow
для запобігання помилкам.
- Обмежений
-
Стек викликів:
- Обмежений
MAX_NESTING = 3
, зберігає рядок і позицію (uint8_t
), займаючи 6 байтів.
- Обмежений
-
Економія RAM: Маленькі розміри стеків і використання
uint8_t
мінімізують споживання пам’яті.
Код містить кілька нестандартних підходів:
-
Пряме використання
pgm_read_byte_near
іpgm_read_ptr
:- Низькорівневий доступ до флеш-пам’яті для даних PROGMEM (рядки, таблиця команд) уникає високорівневих обгорток.
-
Таблиця команд у PROGMEM:
cmd_table
зберігає пари{символ, вказівник_на_функцію}
у флеш-пам’яті, забезпечуючи ефективне виконання команд без використання RAM.
-
Обробка від’ємних чисел:
- У
executeCommand
парсер розрізняє'-'
як оператор віднімання та як частину числа (наприклад,-123
), повертаючиpc
назад, якщо за'-'
не слідує цифра.
- У
-
Флаг заливки (
fill_flag
):- Глобальний
fill_flag
встановлюється командоюF
і скидається після малювання, уникаючи передачі параметра через стек.
- Глобальний
-
Динамічне сканування макросів:
scanForMacros
знаходить визначення макросів у EEPROM, зберігаючи їх адреси для ефективного виклику.
-
Мінімальні глобальні змінні:
- Глобальні змінні обмежені ~104 байтами, включно з
flags
(1 байт),last_blink
(4 байти),line_buffer
(31 байт) тощо.
- Глобальні змінні обмежені ~104 байтами, включно з
Графічні команди (P
, L
, C
, R
, T
, E
) реалізовані ефективно:
-
Компактні команди:
- Використовують стек інтерпретатора для параметрів, мінімізуючи тимчасові змінні.
- Перевірка меж координат (0 ≤ x < 128, 0 ≤ y < 96) виконується вибірково для зменшення обчислень.
-
Малювання трикутника:
- Команда
T
малює трикутник як три лінії, уникаючи складних алгоритмів заливки для економії RAM.
- Команда
-
Очищення екрана:
- Команда
E
очищає екран без параметрів, мінімізуючи використання стека.
- Команда
-
Економія RAM: Графічні команди використовують стек, з мінімальними локальними змінними.
Обробка клавіатури та екрана також оптимізована:
-
Обробка вводу:
cmd_input
використовує 10-байтнийinput_buffer
для чисел, з перевіркою черезmy_isdigit
іmy_isprint
.- Звуковий зворотний зв’язок через
playTone
використовує різні частоти/тривалості для дій, покращуючи UX без витрат RAM.
-
Миготіння курсору:
handleCursorBlink
використовуєmillis()
з єдиною змінноюlast_blink
(4 байти).
-
Економія RAM: Маленькі буфери та повторне використання
line_buffer
мінімізують споживання пам’яті.
Функція getFreeRam
надає інформацію про доступну RAM:
int getFreeRam() {
extern int __heap_start, *__brkval;
int v;
return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}
-
Обчислює вільну RAM, порівнюючи вказівник стека з межею купи.
-
Економія RAM: Використовує лише одну локальну змінну на 2 байти.
Тестова програма test_program_flash
демонструє можливості Mouse:
const char test_program_flash[] PROGMEM =
"E\n" // Очищення екрана
"30 40 25 C\n" // Коло: центр (30,40), радіус 25
"10 15 10 50 40 50 T\n" // Трикутник: вершини (30,15), (10,60), (50,60)
"60 20 40 40 R\n" // Прямокутник: кут (60,20), 40x40
"1 F 70 30 20 20 R\n" // Заповнений прямокутник: кут (70,30), 20x20
...
"$$\n" // Кінець програми
"RUN\n";
- Зберігається в PROGMEM, завантажується в EEPROM для виконання.
- Трасування (
{
,}
) виводить вміст стека без додаткових буферів.
Помилки обробляються ефективно:
- Повідомлення (
SYNTAX ERROR
,UNMATCHED BRACKET
тощо) зберігаються в PROGMEM. - Вивід включає номер рядка та повідомлення, мінімізуючи використання RAM.
- Глобальні змінні: ~104 байти.
- Об’єкти бібліотек: ~1612–1688 байтів (TVout, PS2uartKeyboard).
- Стек/тимчасові: ~91–151 байтів.
- Вільна RAM: ~105–241 байтів (перевірено через
getFreeRam
).
Код є прикладом низькорівневого програмування для мікроконтролерів з обмеженими ресурсами. Ключові техніки включають кастомні бібліотечні функції, використання PROGMEM, компактні структури даних, бітові поля, inline-функції та оптимізовану роботу з EEPROM. Нестандартні підходи, такі як динамічне сканування макросів і мінімалістичні графічні команди, підкреслюють увагу до економії ресурсів, роблячи інтерпретатор Mouse придатним для Arduino UNO попри обмеження в 2 КБ RAM.
Links to resources