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.

Contents

Compiler Setup
Program Structure
Working with Data
Math and Logic
Program Control
Built-in Brick Output
Motor Control
Sensor Input
Sample Programs
Online Documentation

Compiler Setup

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.logobytecodes.c
fileread.logobytecodes.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.


Program Structure

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.


Working with Data

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.


Math and Logic

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 Control

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.


Built-in Brick Output

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!


Motor Control

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:

MotorsBinary Bit FieldBase-10 Integer
 
A only00011
B only00102
C only01004
D only10008
A and B00113
A and C01015
A and D10019
B and C01106
B and D101010
C and D110012
A, B and C01117
A, B and D101111
A, C and D110113
B, C and D111014
A, B, C and D111115

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.


Sensor Input

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 DescriptionType ModeMode Description
 
NXT Touch10Touch
1Bump
 
NXT Light20Reflected
1Ambient
 
NXT Sound30Decibels
1 A-Weighted Decibels
 
NXT Color40Reflected
1Ambient
3Color
3Green
4Blue
5Raw
 
NXT Ultrasonic50Centimeters
1Inches
 
NXT Temperature60Celsius
1Fahrenheit
 
Large Motor70Degree
1Rotation
2Speed
 
Medium Motor80Degree
1Rotation
2Speed
 
EV3 Touch160Touch
1Bump
 
EV3 Color290Reflected
1Ambient
2Color
3Reflected Raw
4RGB Raw
5Calibration
 
EV3 Ultrasonic300Centimeters
1Inches
2Listen
3SI Centimeters
4SI Inches
5CD Centimeters
6SI Inches
 
EV3 Gyro320Angle
1Rate
2Fast
3Rate & Angle
4Calibration
 
EV3 Infrared330Proximity
1Seeker
2Remote
3Remote Advanced
4Seeker Alternate
5Calibration
 
I2C Sensors1000Byte
1Word

There are additional types left free or reserved for third-party sensors. Details of each mode remain to be explored.


Sample Programs

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.


Online Documentation

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