This page documents the basic tools and programming needed to control the LEGO Mindstorms EV3 brick using its native assembly language. The focus is on getting assembly code to compile and run, so that finer points of machine code will not necessarily be included. This is an ongoing project and will be updated as more relevant features are employed in practice.
Compiler Setup Program Structure Working with Data Math and Logic Program Control |
Built-in Brick Output Motor Control Sensor Input Sample Programs Online Documentation |
The EV3 source code is available on Github in two versions: the initial commit corresponding to version 1.03H/E of the firmware,
https://github.com/mindboards/ev3sources
and an update corresponding to version 1.06H/E of the firmware:
https://github.com/mindboards/ev3sources-xtended
There is no guarantee by LEGO that any improvements made to these sets of source code will be incorporated into future firmware, so contribute as you see fit.
There is an assembly language compiler included with the source code that runs in Java, which is available here if not already installed. The compiler consists of the following six files:
assembler.jar | ||
assembler.logo | bytecodes.c | |
fileread.logo | bytecodes.h | |
startup.logo |
The four files in the left-hand column are located in the folders
https://github.com/mindboards/ev3sources/tree/master/lms2012/lmssrc/adk/lmsasm
https://github.com/mindboards/ev3sources-xtended/tree/master/ev3sources/lms2012/lmssrc/adk/lmsasm
and the two in the right-hand column in the folders
https://github.com/mindboards/ev3sources/tree/master/lms2012/lms2012/source
https://github.com/mindboards/ev3sources-xtended/tree/master/ev3sources/lms2012/lms2012/source
depending on which version of the source code is being used. There is no need to download the entire source code: navigate to each file, right click on the “Raw” button and use your browser’s “Save as...” option to put all six files in the same folder.
The compiler is invoked at the command line with
java -jar assembler.jar filename
where the file name can be relative or absolute, but cannot include the required extension .lms
for assembly code. A compiled .rbf
file will appear in the same folder as the assembly code with the same file name, whether or not there are errors in the code. Be on the lookout for error reporting at the command line!
If compiled code with errors is run on the brick, it can cause the brick to crash just like any computer. The brick can then be reset by holding down the upper-left “Back” key, the “Center” key and any one of the other keys surrounding the center key for a couple seconds. The user manual says that one should use the “Left” key for this process, but the three-key combination can currently be completed with the others as well.
Compiled code files can be transferred to the brick using the Memory Browser tool included with the LEGO EV3 software available here. A USB connection is by far the simplest method of communicating between a computer and the brick, but pick your poison. There must be a project folder available on the brick in which to place the compiled program, but that is easily achieved by running any graphical program block in the LEGO software. The compiled program is run from the brick’s menu system just like other programs.
All available assembly language instructions are contained in the file bytecodes.c. They are listed in that file as operation codes: simply ignore the “op” prefix. Enter one instruction per line into a text file with the required .lms
extension.
Instructions and defined parameters must be typed in all capital letters to compile without errors. Instructions without parameters can be followed by opening and closing parentheses but need not. Lines can be terminated with semicolons or commas but need not. Whitespace can be included before and within instructions to enhance readability.
Comments are entered after a double slash “//” as is standard in C-type languages, with the remainder of the line after that marker ignored. As in C-type languages, comments over multiple lines begin with “/*” and end with “*/”.
Every program must begin with an exection thread of the form
vmthread name {
instructions
}
The required word vmthread
must be written in all small letters. The particular name of the thread and its capitalization are arbitrary, with MAIN
, Main
or main
commonly used. Other threads can appear after the first thread without compiler errors, but they are not invoked sequentially.
Subroutines appear after the main execution thread in the form
subcall name(parameters) {
instructions
}
The required word subcall
must be written in all small letters. The capitalization of the routine name is again arbitrary, and passing parameters is optional. Subroutines are invoked with the instruction
CALL(name,parameters)
and program execution returns afterward to the expected location.
The are seven types of data: 8-bit signed integer, 16-bit signed integer, 32-bit signed integer, single-precision float, string, array and an additional unspecified variable type. Variable names can be alphanumeric but not purely numeric.
Numeric variables are declared with the instructions
DATA8 name
DATA16 name
DATA32 name
DATAF name
Additional variable names can appear after the first without compiler errors, comma separated or not, but those additional variables are not properly defined for subsequent program use. Well-defined variables must appear one per line after the appropriate instruction. Variable names are case sensitive.
There are sixteen similar commands for moving numerical data around memory
MOVE8_8 MOVE8_16 MOVE8_32 MOVE8_F |
MOVE16_8 MOVE16_16 MOVE16_32 MOVE16_F |
MOVE32_8 MOVE32_16 MOVE32_32 MOVE32_F |
MOVEF_8 MOVEF_16 MOVEF_32 MOVEF_F |
all with the same usage:
MOVEx_y(source,destination)
These can be used to copy data from a source variable of type x
to a destination variable of type y
. When the source is a number and not a variable name, the instruction initializes the destination variable with that value.
Numerical values for integers are entered as simple numbers, with or without a sign. Floating-point values must have the character “F” appended or they will be interpreted by the compiler as zero. Values in scientific notion can be entered with either a capital letter “E” or a small letter “e”, so that either of these letters followed by a number cannot be used as a variable name without throwing a compiler error.
String variables are best declared with the instruction
DATAS name length
String variables can also be declared with a DATA8
memory reference to the beginning of the string, but then it is unclear how to preserve enough memory space for the desired string length. This form of declaration leads to unpredictable program behavior.
String values must be enclosed in single quotes, as for example 'hello'
. Double quotes around strings are interpreted as part of a variable name and cannot be used to define strings. The value of an existing string variable can be set with the instruction
STRINGS(DUPLICATE,'characters',name)
(array documentation here)
The remaining memory instruction
INIT_BYTES(destination,length,source)
moves a byte stream of specified length from the source variable to the destination DATA8
array.
Algebraic math instructions have the form
OPx(source1,source2,destination)
The sources can be either variable names or numerical values of identical type x
. The instructions available are
ADD8 ADD16 ADD32 ADDF |
SUB8 SUB16 SUB32 SUBF |
MUL8 MUL16 MUL32 MULF |
DIV8 DIV16 DIV32 DIVF |
The instruction sets the value of the destination variable equal to source1 OP source2
, where the operation is addition, subtraction, multiplication or division as indicated.
Random numbers are generated with the instruction
RANDOM(minimum,maximum,destination)
Both inputs and the output are DATA16
integers. The instruction sets the destination variable equal to a random 16-bit integer in the range specified, inclusive of the endpoints. The instruction
RANDOM(0,1,destination)
is equivalent to flipping a coin, returning a random choice of zero or one.
Higher-level math instructions have one of the two forms
MATH(FUNCTION,source1,destination)
MATH(FUNCTION,source1,source2,destination)
The sources can be either variable names or numerical values of type float unless otherwise indicated. The available replacements for FUNCTION
for instructions with a single source are
ABS NEGATE SQRT |
ROUND FLOOR CEIL |
EXP LOG LN |
SIN COS TAN |
ASIN ACOS ATAN |
The meanings of these math functions conform to common usage, with the proviso that LOG
is explicitly base ten. The instruction sets the value of the destination variable equal to the action of the function upon the source.
For powers other than a square root the two-source instruction is
MATH(POW,source1,exponent,destination)
For rounding more precise than nearest integer the instruction is
MATH(TRUNC,source1,precision,destination)
where the precision is an integer from zero to nine. The remaining two-source instructions are the modulo operations of the form
MATH(MODx,source1,source2,destination)
where the available modulo functions are MOD8
, MOD16
and MOD32
for the indicated data types, along with MOD
for arithmetic modulo a float value. The instruction sets the value of the destination variable equal to source1 mod source2
.
Logic instructions have the same form as math instructions:
OPx(source1,source2,destination)
The sources can be either variable names or numerical values of identical type x
. The instructions available are
OR8 OR16 OR32 |
AND8 AND16 AND32 |
XOR8 XOR16 XOR32 |
RL8 RL16 RL32 |
The instruction sets the value of the destination variable equal to source1 OP source2
, where the operation is logical “or”, logical “and”, logical exclusive “or” or rotation left by source2
bits.
Comparison instructions are used in conjunction with a variable to hold the result:
DATA8 flag
CP_OPx(left,right,flag)
The left and right sources are either variable names or numerical values of identical type x
. The instructions available are
CP_LT8 CP_LT16 CP_LT32 CP_LTF |
CP_GT8 CP_GT16 CP_GT32 CP_GTF |
CP_EQ8 CP_EQ16 CP_EQ32 CP_EQF |
CP_NEQ8 CP_NEQ16 CP_NEQ32 CP_NEQF |
CP_LTEQ8 CP_LTEQ16 CP_LTEQ32 CP_LTEQF |
CP_GTEQ8 CP_GTEQ16 CP_GTEQ32 CP_GTEQF |
The instruction evaluates the expression left OP right
, then sets the value of the flag equal to one if the expression is true or equal to zero if the expression is false. The operator names conform to common usage.
Program execution can be halted for an arbitrary number of milliseconds with the instructions
DATA32 name
TIMER_WAIT(milliseconds,name)
TIMER_READY(name)
The variable holding the timer name must be 32-bit: other integer types will compile but lead to a runtime error in the virtual machine. There are two other timer instructions,
DATA32 timer
TIMER_READ(timer)
DATA32 timer
TIMER_READ_US(timer)
The both instructions put the current time since program start into the timer variable. The first of the two reads the timer in milliseconds; the second reads the timer in microseconds, where “US” is an abbreviation for μs.
Program execution can be held until a button is pressed with
UI_BUTTON(WAIT_FOR_PRESS)
The pressed state remains until explicity cleared. A quick way to clear the status of all buttons is with the instruction
UI_BUTTON(FLUSH)
Program branching is performed with jump register instructions. The instruction for unconditional branching is
JR(label)
where the case-sensitive label for the jump destination must be followed by a colon to compile without errors:
label:
instructions
Jump register instructions work just like branching to a labeled line in BASIC with a GOTO statement.
Since jump register instructions are the only way to control program flow in an assembly language, their use can lead to what at first sight appears to be an excess of labels. One might think of using generic label names including numbers to automate the process but this is ungainly to update and ultimately confusing. It is much better to use the label names to indicate what is happening in the program at each jump point so that the program documents itself.
Conditional branching can take either one test value or two. With a declared flag variable, there are two instructions that test the flag value
DATA8 flag
JR_TRUE(flag,label)
DATA8 flag
JR_FALSE(flag,label)
A flag value of one corresponds to true and a value of zero to false. There is also an instruction to test whether a variable of type float is numeric,
DATAF value
JR_NAN(value,label)
where branching occurs if the float value is not a number.
The instructions for conditional branching with two test values are organized just like the comparison instructions. They have the form
JR_OPx(left,right,label)
The left and right sources are either variable names or numerical values of identical type x
. The instructions available are
JR_LT8 JR_LT16 JR_LT32 JR_LTF |
JR_GT8 JR_GT16 JR_GT32 JR_GTF |
JR_EQ8 JR_EQ16 JR_EQ32 JR_EQF |
JR_NEQ8 JR_NEQ16 JR_NEQ32 JR_NEQF |
JR_LTEQ8 JR_LTEQ16 JR_LTEQ32 JR_LTEQF |
JR_GTEQ8 JR_GTEQ16 JR_GTEQ32 JR_GTEQF |
The instruction evaluates the expression left OP right
, then branches if the expression is true. The operator names conform to common usage.
The color of the LED can be controlled with the instruction
UI_WRITE(LED,color)
where the possible colors are
LED_BLACK LED_GREEN LED_RED LED_ORANGE |
LED_GREEN_FLASH LED_RED_FLASH LED_ORANGE_FLASH LED_GREEN_PULSE LED_RED_PULSE LED_ORANGE_PULSE |
The black color turns the LED off. The normal state of the LED during program execution is pulsing green.
When playing sounds, the volume value ranges from zero to one hundred. A single tone can be played with the commands
SOUND(TONE,volume,frequency,milliseconds)
SOUND_READY
The second instruction pauses program execution until the tone is finished playing. Pauses between tones can be achieved by pausing program execution for the desired number of milliseconds.
The EV3 brick cannot synthesize tones below 250 Hz, which is about the B below middle C. If you write a song for the brick using tones below that limit, expect it to sound wrong!
For an example of how to program a tune, here is a song template with a short theme:
vmthread main { DATA16 C4 DATA16 C#4 DATA16 Db4 DATA16 D4 DATA16 D#4 DATA16 Eb4 DATA16 E_4 DATA16 F4 DATA16 F#4 DATA16 Gb4 DATA16 G4 DATA16 G#4 DATA16 Ab4 DATA16 A4 DATA16 A#4 DATA16 Bb4 DATA16 B4 DATA16 C5 DATA16 C#5 DATA16 Db5 DATA16 D5 DATA16 D#5 DATA16 Eb5 DATA16 E_5 DATA16 F5 DATA16 F#5 DATA16 Gb5 DATA16 G5 DATA16 G#5 DATA16 Ab5 DATA16 A5 DATA16 A#5 DATA16 Bb5 DATA16 B5 DATA16 C6 MOVE16_16(262,C4) MOVE16_16(277,C#4) MOVE16_16(277,Db4) MOVE16_16(294,D4) MOVE16_16(311,D#4) MOVE16_16(311,Eb4) MOVE16_16(330,E_4) MOVE16_16(349,F4) MOVE16_16(370,F#4) MOVE16_16(370,Gb4) MOVE16_16(392,G4) MOVE16_16(415,G#4) MOVE16_16(415,Ab4) MOVE16_16(440,A4) MOVE16_16(466,A#4) MOVE16_16(466,Bb4) MOVE16_16(494,B4) MOVE16_16(523,C5) MOVE16_16(554,C#5) MOVE16_16(554,Db5) MOVE16_16(587,D5) MOVE16_16(622,D#5) MOVE16_16(622,Eb5) MOVE16_16(659,E_5) MOVE16_16(698,F5) MOVE16_16(740,F#5) MOVE16_16(740,Gb5) MOVE16_16(784,G5) MOVE16_16(831,G#5) MOVE16_16(831,Ab5) MOVE16_16(880,A5) MOVE16_16(932,A#5) MOVE16_16(932,Bb5) MOVE16_16(988,B5) MOVE16_16(1047,C6) // Begin song SOUND(TONE,50,G4,500) SOUND_READY SOUND(TONE,50,C5,300) SOUND_READY SOUND(TONE,50,G4,100) SOUND_READY SOUND(TONE,50,C5,200) SOUND_READY SOUND(TONE,50,Eb5,600) SOUND_READY SOUND(TONE,50,C5,600) SOUND_READY SOUND(TONE,50,Eb5,300) SOUND_READY SOUND(TONE,50,C5,100) SOUND_READY SOUND(TONE,50,Eb5,200) SOUND_READY SOUND(TONE,50,G5,600) SOUND_READY SOUND(TONE,50,Eb5,600) SOUND_READY SOUND(TONE,50,G5,300) SOUND_READY SOUND(TONE,50,Eb5,100) SOUND_READY SOUND(TONE,50,G5,200) SOUND_READY SOUND(TONE,50,Bb5,600) SOUND_READY SOUND(TONE,50,Bb4,600) SOUND_READY SOUND(TONE,50,Eb5,300) SOUND_READY SOUND(TONE,50,Bb4,100) SOUND_READY SOUND(TONE,50,Eb5,200) SOUND_READY SOUND(TONE,50,G5,2000) SOUND_READY }
The first block of code initializes variables for notes in the two octaves above middle C. The second block of code sets appropriate frequencies for these notes. The third block outputs the song by referring to the note variables.
Sound files used on the brick must be converted to the .rsf
format using the LEGO EV3 software available here. They can be played with the instructions
SOUND(PLAY,volume,filename)
SOUND_READY
where the file name does not include the extension and is enclosed in single quotes. The second instruction pauses program execution until the file is finished playing. To repeat a sound file over and over use
SOUND(REPEAT,volume,filename)
To test whether a sound is currently playing, use the instructions
DATA8 busy
SOUND_TEST(busy)
The value of the busy variable will be set to one when the system is playing a sound. To stop sound playback use
SOUND(BREAK)
An additional instruction SOUND(SERVICE)
is currently undocumented.
The LCD screen on the brick can be cleared with the two instructions
UI_DRAW(FILLWINDOW,0,0,0)
UI_DRAW(UPDATE)
Drawing instructions do not produce output without updating!
Motors are controlled using output instructions. The first parameter for all of these commands is a layer number, an integer from zero to three specifying which brick in a daisy chain of bricks. Zero selects the main brick for control, with one to three selecting daisy-chained bricks in order of physical connection.
The second parameter in most of the output instructions is an integer from one to fifteen specifying which combination of motors is to be controlled. This integer is the base-10 value of a bit field, where the first bit indicates motor A, the second bit motor B, the third C and the fourth D. The motors can thus be selected alone or in eleven combinations:
Motors | Binary Bit Field | Base-10 Integer |
A only | 0001 | 1 |
B only | 0010 | 2 |
C only | 0100 | 4 |
D only | 1000 | 8 |
A and B | 0011 | 3 |
A and C | 0101 | 5 |
A and D | 1001 | 9 |
B and C | 0110 | 6 |
B and D | 1010 | 10 |
C and D | 1100 | 12 |
A, B and C | 0111 | 7 |
A, B and D | 1011 | 11 |
A, C and D | 1101 | 13 |
B, C and D | 1110 | 14 |
A, B, C and D | 1111 | 15 |
The power level of a combination of motors is set with the instruction
OUTPUT_POWER(layer,motors,power)
where the power parameter is an integer from −100 to 100. Once the power level is set, it remains in effect until the brick is rebooted. Power levels can be changed dynamically during program runtime using this instruction.
After setting the power level, a combination of motors is put into motion with the instruction
OUTPUT_START(layer,motors)
and remains running until stopped with the instruction
OUTPUT_STOP(layer,motors,brake)
where the brake parameter is an integer equal to one for braking and zero for coasting to a stop.
Here is a sample program that runs through all combinations of motors in base-10 order, starting and stopping each combination in turn:
vmthread main { DATA32 timer DATA8 counter MOVE8_8(1,counter) OUTPUT_POWER(0,15,100) loop: OUTPUT_START(0,counter) TIMER_WAIT(2000,timer) TIMER_READY(timer) OUTPUT_STOP(0,counter,1) ADD8(1,counter,counter) JR_LT8(counter,16,loop) }
Instead of setting power levels, speed levels can be set using
OUTPUT_SPEED(layer,motors,speed)
where the speed parameter is an integer from −100 to 100. The difference between setting power and speed is that for the latter the brick will automatically adjust the power levels to maintain the specified speed.
By default postive values for power and speed run motors in a forward direction and negative values in a backward direction. The sense of “forward” and “backward” can be altered as needed by changing the polarity of a motor with the instruction
OUTPUT_POLARITY(layer,motors,polarity)
The polarity parameter is an integer that takes one of three values: plus one, minus one or zero. When it is plus one the motor will have the default behavior. When it is minus one the motor will behave opposite to its default: positive values for speed or power will run the motor backward and negative values run it forward. From a calculational perspective, a specified power or speed is multiplied by the motor polarity for the value actually applied to the motor. The final polarity option of zero toggles between the positive and negative settings.
Motors can be set to run for a specified amount of time with the instructions
OUTPUT_TIME_POWER(layer,motors,power,time up,time run,time down,brake)
OUTPUT_TIME_SPEED(layer,motors,speed,time up,time run,time down,brake)
The added parameters here are the time in milliseconds for each part of the action: the time to bring the motor up to the specified power or speed, the time to run at that power or speed, and the time to take the motor back down to zero. The instruction starts and stops the motor combination specified without any additional instructions, braking or not as specified at the end.
The is another way to run motors for a specified amount: tachometer count. There are 360 tachometer counts in a complete rotatoin, one tachometer count per degree. The instructions that implement motor run by tachometer count are
OUTPUT_STEP_POWER(layer,motors,power,count up,count run,count down,brake)
OUTPUT_STEP_SPEED(layer,motors,speed,count up,count run,count down,brake)
The added parameters here are for the number to tachometer counts to bring the motor up to the specified power or speed, the tachometer counts to run at that power or speed, and the tachometer counts to take the motor back down to zero. The instruction starts and stops the motor combination specified without any additional instructions, braking or not as specified at the end.
When using instructions to run motors for a specified time or tachometer count, program execution does not automatically pause during implementation. This is supposed to be handled by the instruction
OUTPUT_READY(layer,motors)
but in practice this currently pauses program execution indefinitely. One workaround is to use a timer to pause program execution long enough for the motor movement to conclude.
Sensors are read using input instructions which store the returned value in a named variable. A typical pair of instructions is
DATA8 percent
INPUT_READ(layer,port,type,mode,percent)
The layer parameter is the same as for output instructions: zero for the main brick and one to three for daisy-chained bricks in order of phyiscal connection. The port number is one less than the physical numbering on the brick, with a value of zero for the first sensor port and three for the fourth port.
The value returned by INPUT_READ
is of type DATA8
representing a normalized percentage between the minimum and maximum raw values of the sensor.
The values for sensor type and mode are built into the firmware to allow the brick to process the raw input appropriately. The detailed assignments are give in the files typedata.rcf and typedata.rcf for the two versions of the source code. The assignments for common sensors are as follows:
Type Description | Type | Mode | Mode Description |
NXT Touch | 1 | 0 | Touch |
1 | Bump | ||
NXT Light | 2 | 0 | Reflected |
1 | Ambient | ||
NXT Sound | 3 | 0 | Decibels |
1 | A-Weighted Decibels | ||
NXT Color | 4 | 0 | Reflected |
1 | Ambient | ||
3 | Color | ||
3 | Green | ||
4 | Blue | ||
5 | Raw | ||
NXT Ultrasonic | 5 | 0 | Centimeters |
1 | Inches | ||
NXT Temperature | 6 | 0 | Celsius |
1 | Fahrenheit | ||
Large Motor | 7 | 0 | Degree |
1 | Rotation | ||
2 | Speed | ||
Medium Motor | 8 | 0 | Degree |
1 | Rotation | ||
2 | Speed | ||
EV3 Touch | 16 | 0 | Touch |
1 | Bump | ||
EV3 Color | 29 | 0 | Reflected |
1 | Ambient | ||
2 | Color | ||
3 | Reflected Raw | ||
4 | RGB Raw | ||
5 | Calibration | ||
EV3 Ultrasonic | 30 | 0 | Centimeters |
1 | Inches | ||
2 | Listen | ||
3 | SI Centimeters | ||
4 | SI Inches | ||
5 | CD Centimeters | ||
6 | SI Inches | ||
EV3 Gyro | 32 | 0 | Angle |
1 | Rate | ||
2 | Fast | ||
3 | Rate & Angle | ||
4 | Calibration | ||
EV3 Infrared | 33 | 0 | Proximity |
1 | Seeker | ||
2 | Remote | ||
3 | Remote Advanced | ||
4 | Seeker Alternate | ||
5 | Calibration | ||
I2C Sensors | 100 | 0 | Byte |
1 | Word |
There are additional types left free or reserved for third-party sensors. Details of each mode remain to be explored.
Sample programs on this website:
Motor Combinations
Song Template
A simple robot with motorized tracks connected to outputs B and C, plus a forward-pointing infrared sensor on input 4, can be given semi-autonomy with the following short program:
vmthread main { DATA32 timer DATA8 percent DATA16 motor OUTPUT_SPEED(0,6,80) OUTPUT_START(0,6) check: INPUT_READ(0,3,33,0,percent) JR_LT8(percent,50,reverse) TIMER_WAIT(500,timer) TIMER_READY(timer) JR(check) reverse: RANDOM(1,2,motor) MUL16(motor,2,motor) OUTPUT_SPEED(0,6,-50) OUTPUT_SPEED(0,motor,-25) TIMER_WAIT(1000,timer) TIMER_READY(timer) OUTPUT_SPEED(0,6,80) JR(check) }
This program checks every half second to see if the infrared sensor has fallen below a 50% reading, and if so backs up randomly right or left before continuing on straight.
For readers of The LEGO Mindstorms EV3 Laboratory, here is an assembly language version of the program for WATCHGOOZ3:
vmthread main { DATA8 pushed DATA8 percent DATA32 timer shiftWeight: OUTPUT_POWER(0,1,50) OUTPUT_START(0,1) buttonPushed: INPUT_READ(0,0,1,0,pushed) JR_FALSE(pushed,buttonNotPushed) JR(buttonPushed) buttonNotPushed: INPUT_READ(0,0,1,0,pushed) JR_TRUE(pushed,turnBody) JR(buttonNotPushed) turnBody: OUTPUT_STOP(0,1,0) OUTPUT_POWER(0,8,-100) OUTPUT_START(0,8) TIMER_WAIT(1000,timer) TIMER_READY(timer) tooClose: INPUT_READ(0,3,33,0,percent) JR_GT8(percent,50,farEnough) JR(tooClose) farEnough: OUTPUT_STOP(0,8,1) // reverse polarities for mirror process OUTPUT_POLARITY(0,1,0) OUTPUT_POLARITY(0,8,0) JR(shiftWeight) }
The labels for the jump instructions have deliberately been used to document what is mechanically occurring to the robot during execution of the program. Since the process for each subsequent step is the mirror image of the previous, the length of the program is cut in half by merely reversing motor polarities before looping.
The two sets of source code contain documentation for the firmware, but searching within Github is not particularly efficient. The documentation can be extracted using Doxygen, and there is one instance available online:
http://ev3.fantastic.computer/doxygen/
The set appears to have been generated using the file lists here or here in the source code. The table of contents is not particularly helpful, but Googling within the subdomain usually produces an answer to a specific question. There is another set of documentation
http://ev3.fantastic.computer/doxygen-all/
that appears to have been generated using all files in the source code. This set includes a search function that is useful, but searches return a great many C-language source files not relevant to assembly language.
There is also a document entitled “Firmware Developer Kit” available in a ZIP file hosted by Xander Soldaat of Bot Bench. This document is clearly a rough draft of the final EV3 specification, as it contains slightly different instructions and incomplete information for their execution.
Uploaded 2014.10.10 — Updated 2017.01.08 analyticphysics.com