CREATOR Arduino module (CREATino)
CREATino allows the user to add custom Arduino libraries to their programs.
[!IMPORTANT] This library only works with the ESP32 gateway and the supported ESP32 boards. You must set up the gateway before using this module.
Using CREATino library functions
In the Editor view, go to Library → Load Arduino Library. The available functions will be displayed on the right.

[!TIP] Except the
printffunction (which is excusive for Espressif Arduino devices) all the functions displayed can be found in Arduino's documentation and in Help → Creatino Help.
Creating your first program
As in the original Arduino sketches, CREATino programs must have a structure composed by a "setup" and a "loop" function. CREATOR provides an example template in Help → Examples → Example 1: Template for new examples.

[!IMPORTANT] Before executing your program in the device, make sure to select the Arduino Support checkbox to enable CREATino.
Aspects to consider when using this library
- The supported ESP32 boards do not support have floating point operations. Arduino functions with
floatordoubleoutputs will fail. Avoid using the following GPIO pins:
GPIO8: BOOT MODE pin. It might show the following error:
Serial port /dev/ttyUSB0 Connecting...................................... A fatal error occurred: Failed to connect to ESP32-C3: Wrong boot mode detected (0x0)! The chip needs to be in download mode. For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html- GPIO 18 and 19: Debug pins
Examples
Example 1: Internal LED Blink
This example is used to check if GPIO functions work correctly inside a ESP32-C3 board.
Components:
- ESP32-C3-DevKitC-02 board
Step 1: Setup
In the ESP32-C3-DevKitC-02, the internal LED pin corresponds to pin 30, and it must be tagged as output. We use pinMode function for this.
The possible values for the mode are:
| Mode | Value | Usage |
|---|---|---|
INPUT |
0x01 |
Digital input mode |
OUTPUT |
0x03 |
Digital output mode |
PULLUP |
0x04 |
Enables the internal pull-up resistor on an input pin (if the pin is disconnected, it will read HIGH). Buttons connected to ground |
INPUT_PULLUP |
0x05 |
Combines input configuration (0x01) with pull-up (0x04) |
PULLDOWN |
0x08 |
Ensures the pin reads LOW when disconnected |
INPUT_PULLDOWN |
0x09 |
Ensures the pin reads LOW when disconnected |
OPEN_DRAIN (Data buses) |
0x10 |
Output can only pull to ground; reading HIGH requires an external resistor or pull-up |
OUTPUT_OPEN_DRAIN (Buses) |
0x13 |
Combines digital output with open-drain mode |
ANALOG |
0xC0 |
Configures the pin for analog input (ADC) |
In this case, our setup function will look like this:
setup:
# pinMode(30, OUTPUT);
li a0, 30
li a1, 0x03
addi sp, sp, -4
sw ra, 0(sp)
jal ra, pinMode
lw ra, 0(sp)
addi sp, sp, 4
jr ra
Step 2: Loop
The LED has been set up; now it is we can try to make it turn on and off, creating a blinking effect. For this, we'll use the digitalWrite function.
This function needs the number of the pin used (pin 30) and the state to write (in this case 0x1 or HIGH to turn on the light and 0x0 to turn it off)
We will also use a delay function to generate a delay, in milliseconds, between changes of state of the LED. For this example, we'll store the length of the delay in memory.
The result is:
.data
time:
.word 1000
.text
loop:
# digitalWrite(LED_BUILTIN, HIGH);
li a0,30
li a1, 0x1
addi sp, sp, -4
sw ra, 0(sp)
jal ra, digitalWrite
lw ra, 0(sp)
addi sp, sp, 4
# delay(time);
la a0, time
lw a0, 0(a0)
addi sp, sp, -4
sw ra, 0(sp)
jal ra, delay
lw ra, 0(sp)
addi sp, sp, 4
# turn off
# digitalWrite(LED_BUILTIN, LOW);
li a0,30
li a1, 0x0
addi sp, sp, -4
sw ra, 0(sp)
jal ra, digitalWrite
lw ra, 0(sp)
addi sp, sp, 4
# delay(time);
la a0, time
lw a0, 0(a0)
addi sp, sp, -4
sw ra, 0(sp)
jal ra, delay
lw ra, 0(sp)
addi sp, sp, 4
j loop
Now the LED will start blinking.

Example 2: Button + LED
In this example we'll turn an LED on when the button is pressed.
Components:
- ESP32-C3-DevKitC-02 board
- Button (in GPIO 6)
- LED (in GPIO 4)

There are two ways to do this: synchronously, using an infinite loop, and asynchronously, using interrupts.
Using an infinite loop
Step 1: Setup
First we'll establish the LED as an output and the button as input. As shown in Use example 1, you need to configure the pin mode with pinMode before using the pins:
.data
buttonPin: .word 6
ledpin: .word 4
.text
setup:
# pinMode(buttonPin, INPUT_PULLUP);
la a0, buttonPin
lw a0, 0(a0)
li a1, 0x05 # INPUT_PULLUP
addi sp, sp, -4
sw ra, 0(sp)
jal ra, pinMode
lw ra, 0(sp)
addi sp, sp, 4
# pinMode(ledpin, OUTPUT);
la a0, ledpin
lw a0, 0(a0)
li a1, 0x03 # OUTPUT
addi sp, sp, -4
sw ra, 0(sp)
jal ra, pinMode
lw ra, 0(sp)
addi sp, sp, 4
jr ra
Step 2: Reading the button
Once set up, we start the infinite loop by reading the button state using digitalRead, to which we only need to pass the button’s pin (in this case, pin 6).
In our case, if it detects that the button is pressed, the button_pressed function will be called. Otherwise, the LED will remain off. A small delay is added to avoid overloading the system:
.data
time: .word 100
.text
loop:
la a0, buttonPin
lw a0, 0(a0)
addi sp, sp, -4
sw ra, 0(sp)
jal ra, digitalRead
lw ra, 0(sp)
addi sp, sp, 4
mv t0,a0
li t1 ,0 # LOW
beq t0, t1, button_pressed
la a0, ledpin
lw a0, 0(a0)
li a1, 0x0
jal ra, digitalWrite
la a0, time
lw a0, 0(a0)
addi sp, sp, -4
sw ra, 0(sp)
jal ra, delay
lw ra, 0(sp)
addi sp, sp, 4
j loop
Step 3: Turning the LED on
As shown in Example 1, we will turn on the LED when button is pressed:
button_pressed:
la a0, ledpin
lw a0, 0(a0)
li a1, 0x1
addi sp, sp, -4
sw ra, 0(sp)
jal ra, digitalWrite
lw ra, 0(sp)
addi sp, 4
la a0, time
lw a0, 0(a0)
addi sp, sp, -4
sw ra, 0(sp)
jal ra, delay
lw ra, 0(sp)
addi sp, 4
jr ra
Using interrupts
Another way to achieve this project is by using GPIO interrupts, which are much more immediate but more complex to program.
In this case, we will use the attachInterrupt and digitalPinToInterrupt Arduino functions to obtain the interrupt number that can be assigned to the interrupt service routine for that pin.

Step 1: Setup
As shown in Example 1, you need to configure the pin mode with pinMode before using the pins.
Then we connect the button pin to an interrupt routine or ISR (which we will call blink) that runs automatically when the button is pressed. For this we will use attachInterrupt function.
The attachInterrupt function takes the following parameters:
- The interrupt position corresponding to the pin for which we want to detect the interrupt (we use
digitalPinToInterrupt(pin)) - The memory address where the interrupt service routine is located (in this case, we call it
blink) - The interrupt mode, that can be one of the following:
| Mode | Value | Usage |
|---|---|---|
DISABLED |
0x00 |
Interrupt disabled |
RISING |
0x01 |
Interrupt triggered on the rising edge (when the pin changes from LOW to HIGH) |
FALLING |
0x02 |
Interrupt triggered on the falling edge (when the pin changes from HIGH to LOW) |
CHANGE |
0x03 |
Interrupt triggered on any change of the pin state (both LOW→HIGH and HIGH→LOW) |
ONLOW |
0x04 |
Interrupt triggered while the pin remains LOW |
ONHIGH |
0x05 |
Interrupt triggered while the pin remains HIGH |
ONLOW_WE |
0x06 |
Same as ONLOW, but with write enable — allows modifications or writing to related registers while the pin is LOW |
ONHIGH_WE |
0x07 |
Same as ONHIGH, but with write enable |
In this case, as we want the interruption when the button is pressed, we choose the ON_LOW mode.
This is how the setup function will look:
.data
ledPin: .byte 4
interruptpin: .byte 6
state: .byte 0 #LOW
on_low: .byte 0x04
.text
setup:
# pinMode(ledPin, OUTPUT);
la t1, ledPin
lb a0, 0(t1)
li a1, 0x03 # OUTPUT
addi sp, sp, -4
sw ra,0(sp)
jal ra, pinMode
lw ra,0(sp)
addi sp, sp, 4
# pinMode(ledPin, INPUT_PULLUP);
la t1, interruptpin
lb a0, 0(t1)
li a1, 0x05 # INPUT_PULLUP
addi sp, sp, -4
sw ra,0(sp)
jal ra, pinMode
lw ra,0(sp)
addi sp, sp, 4
# digitalPinToInterrupt(interruptpin);
la t1, interruptpin
lb a0, 0(t1)
addi sp, sp, -4
sw ra,0(sp)
jal ra, digitalPinToInterrupt
lw ra,0(sp)
addi sp, sp, 4
# attachInterrupt(digitalPinToInterrupt(interruptPin), blink, ON_LOW);
la a1, blink
la t1, on_low
lb a2, 0(t1)
addi sp, sp, -4
sw ra,0(sp)
jal ra, attachInterrupt
lw ra,0(sp)
addi sp, sp, 4
jr ra
Step 2: ISR definition.
We want to change the LED state when an interrupt is detected (the button's state changes). For that, we'll modify a variable stored in memory that indicates the LED’s state, as follows:
blink:
addi sp, sp, -8
sw ra, 4(sp)
sw t0, 0(sp)
la t0, state
lb a0, 0(t0)
xori a0, a0, 1 # 0->1, 1->0
sb a0, 0(t0)
lw t0, 0(sp)
lw ra, 4(sp)
addi sp, sp, 8
jr ra
Step 3: Loop
The LED will be turned off until the button is pressed, as shown in the following code:
loop:
# digitalWrite(ledPin, state);
la t1, ledPin
lb a0, 0(t1)
li a1,0
addi sp, sp, -4
sw ra,0(sp)
jal ra, digitalWrite
lw ra,0(sp)
addi sp, sp, 4
# delay(100)
li a0, 100
addi sp, sp, -4
sw ra,0(sp)
jal ra, delay
lw ra,0(sp)
addi sp, sp, 4
j loop
setup:
# pinMode(ledPin, OUTPUT);
la t1, ledPin
lb a0, 0(t1)
li a1, 0x03 #OUTPUT
addi sp, sp, -4
sw ra,0(sp)
jal ra, pinMode
lw ra,0(sp)
addi sp, sp, 4
# pinMode(ledPin, INPUT_PULLUP);
la t1, interruptpin
lb a0, 0(t1)
li a1, 0x05 # INPUT_PULLUP
addi sp, sp, -4
sw ra,0(sp)
jal ra, pinMode
lw ra,0(sp)
addi sp, sp, 4
# digitalPinToInterrupt(interruptpin);
la t1, interruptpin
lb a0, 0(t1)
addi sp, sp, -4
sw ra,0(sp)
jal ra, digitalPinToInterrupt
lw ra,0(sp)
addi sp, sp, 4
# attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
la a1, blink
la t1, change
lb a2, 0(t1)
addi sp, sp, -4
sw ra,0(sp)
jal ra, attachInterrupt
lw ra,0(sp)
addi sp, sp, 4
jr ra
Example 3: Text input/output using serial output
For this use case, we are going to make a little use of basic functions from the Arduino Serial library. For more information, here is the official documentation (not all functions are available in this library, as they are not the most suitable for the environment in which we are working).
[!WARNING] By definition, serial input functions are very fast (they do not wait for you to press Enter) and do not display a callback of what has been written. The purpose of this library is to familiarize yourself with these functions; therefore, it is the student's responsibility to monitor each step of what is being done.
Just as we previously used the ecall instruction to print messages in assembly CREATOR programs, here we will use the serial_printf function, which is part of Espressif's Arduino component.
Unlike higher-level functions like Serial.print and Serial.println, serial_printf requires explicit format specifiers to indicate the data types being printed; otherwise, the output is interpreted as a string.
On the other hand, we will use the serial_readBytes function to see how text input works. There is the serial_read variant (which only takes 1 character), serial_readBytesUntil (which stops storing characters when it finds the specified character), and those that parse, such as serial_parseInt (which only takes numbers).
Components: ESP32-C3-DevKitC-02 board
Data: Save messages to print in memory. For this use case we are going to declare in data section:
- The initial message to be displayed.
- The buffer allocated to store the input text.
- The message that includes a placeholder for outputting the entered text.
- An auxiliary callback used to display the entered number.
This would be reflected in the code as follows:
.data space: .zero 100 #Buffer to place the string initial: .string "Introduce number of letters:\n" aux: .string "%d\nType your message\n" print: .string "Your message: %s\n"Serial: Start terminal output and input. On most boards, such as Arduino and Espressif, in order to use the terminal, it needs to be initialized with a specific frequency. In this case, we will use the
serial_beginfunction with a baud rate of 115200.If a value other than the one given is entered, strange characters may be printed or nothing may be printed at all.
It is declared in the code as follows:
setup: li a0,115200 addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_begin lw ra, 0(sp) addi sp, sp,4 jr raLoop: Check if the terminal is correctly open. A good practice that can be seen in Arduino is to use serial_available to see if the terminal has opened correctly.
If it has a value greater than 0, it means that it is operational and ready to use.
[!NOTE] For simplicity's sake, you will often find that this check is not performed... but using
serial_availableis a very good error control.So, we start our loop as follows:
loop: addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_available lw ra, 0(sp) addi sp, sp, 4 beqz a0,aux_print j loopaux_printis an auxiliary function to indicate to the user that they should enter the number only once. This is purely aesthetic but can provide clarity to the user.aux_print: #serialPrintf la a0,initial addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_printf lw ra, 0(sp) addi sp, 4 j read_numread_num: reads a number from terminalIn this case, we use the
serial_parseIntfunction to get a number per terminal.[!NOTE] Why don't we use
serial_read? Becauseserial_read, although it returns a number, interprets the input in ASCII code. ️If we enter the number
4via the terminal, the values change:- In
serial_read: 52 - In
serial_parseInt: 4
Since
serial_readis very fast, we will not move on to the next step if there is no number greater than 0 collected in a0, leaving the following code snippet:read_num: addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_parseInt lw ra, 0(sp) addi sp, sp,4mv t0, a0 bnez t0, print_int j read_numA small auxiliary function called
print_intwas created to view the callback of the value specified as length, as follows.print_int: la a0, aux mv a1,t0 addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_printf lw ra, 0(sp) addi sp, 4 j read_functionThe
read_functionreads the text of the requested length. Once we have the length of our string, we collect the text we want to add withserial_readBytes, which is blocking until the requested length of characters has been reached.We would end up with an implementation like this:
read_function: la a0, space mv a1, t0 # number of letters it will have addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_readBytes lw ra, 0(sp) addi sp, sp, 4 bne t0, a0, read_function la a0, print la a1, space addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_printf lw ra, 0(sp) addi sp, 4 jr ra- In
And this is the result!

Example 4: Daytime running lights with analogRead.
This time, we are going to make a small LED that lights up when it detects low light, using an LDR.
[!WARNING] The sensitivity values of the sensor may vary depending on the location of the student and their device. We recommend having a flashlight or something that gives off light handy and using the debugger or prints.
Components:
- ESP32-C3-DevKitC-02 board
- LDR or photoresistor (in GPIO 2)
- LED (in GPIO 4)
- 10kΩ resistor

To find resistors with the correct value, look at the color code on their bands. You can either look at the color guide, use this calculator, or simply look for a resistor that matches the one in the photo.

Setup: Initialize LDR, pin, and serial output. To use the sensor, we must initialize it with
pinModeto the value of INPUT (0x01) as we saw in Figure 2.1. The setup code would look like this:.data lightSensorPin: .word 2 ledPin: .word 4 time: .word 100 aux_msg: .string "LDR value: %d\n" .text setup: # Serial li a0,115200 addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_begin lw ra, 0(sp) addi sp, sp,4 # PinMode LED la t1, ledPin lw a0, 0(t1) li a1, 0x03 # OUTPUT addi sp, sp, -4 sw ra,0(sp) jal ra, pinMode #pinMode(ledPin, OUTPUT); lw ra,0(sp) addi sp, sp, 4 # Light sensor pin la t1, lightSensorPin lw a0, 0(t1) li a1, 0x01 addi sp, sp, -4 sw ra,0(sp) jal ra, pinMode# pinMode(lightSensorPin, INPUT); lw ra,0(sp) addi sp, sp, 4 jr raLoop: Analog sensor reading Once everything is up and running, we read the light level in the room using
analogRead.In this tutorial, after a few tests, we found that the values reached were between 1100 and 1400. The darker it is, the higher the resistance value will be. Therefore, a threshold of 1200 is set.
- If the sensor reaches a value higher than 1200, the LED will turn on (
lightUp). - If, on the other hand, it does not reach that value, the LED remains off (
turnDown).
In both cases, we add a small
delayso that the readings are not so fast.We finish with this code:
# delay la a0, time lw a0, 0(a0) addi sp, sp, -4 sw ra, 0(sp) jal ra, delay lw ra, 0(sp) addi sp, sp, 4 j loop loop: # read LDR addi sp, sp, -4 sw ra,0(sp) jal ra, analogRead #analogRead(lightSensorPin); lw ra,0(sp) addi sp, sp, 4 # Print value mv t1, a0 mv a1,a0 la a0,aux_msg addi sp, sp, -4 sw ra, 0(sp) jal ra, serial_printf lw ra, 0(sp) addi sp, sp, 4 # turn up led li t0, 1200 #Minumun value bgt t1, t0, lightUp blt t1, t0, turnDown j loop- If the sensor reaches a value higher than 1200, the LED will turn on (
And this is the result:

Example 5: [Advanced] Piano
This case is a little more complex than the rest, but very creative. It is recommended to have a breadboard with plenty of space.
Each musical note is a frequency that blows air into the buzzer membrane. Consult this page to learn how to calculate the other possible notes according to the desired tempo.
In this case, we are going to do it with interruptions. This example may cause a little more lag because there are continuous memory accesses and the “tone” function launches tasks.
Polling could be used.
Components::
- ESP32-C3-DevKitC-02 board
- 4 buttons
- A passive buzzer

Data: Declaration of pins and values of musical notes. We indicate in the data the pins we are going to use (avoiding "dangerous" pins such as
GPIO8andGPIO1) and the frequency values of the musical notes.Care must be taken with the position of these values, as we are going to use these memory positions as arrays.
In this example, we have positioned them as follows:
.data # GPIO buzzerPin: .word 5 button_C4: .word 7 button_D4: .word 10 button_E4: .word 3 button_G4: .word 4 # Notes note_C4: .word 262 note_D4: .word 294 note_E4: .word 330 note_G4: .word 392 #aux button_count: .word 4 time_delay: .word 10 anyPressed: .word -1 .align 4 button_handlers: .word handleButton0 .word handleButton1 .word handleButton2 .word handleButton3Another way to position them is like this:
.data # GPIO buzzerPin: .word 5 buttons: .word 7,10,3,4 # Notes notes: .word 262,294,330,392 # aux button_count: .word 4 anyPressed: .byte -1 .align 4 button_handlers: .word handleButton0 .word handleButton1 .word handleButton2 .word handleButton3Setup: Configure all pins In this case, we are going to complicate things a little, as there are a lot of pins to configure. First, we will configure pin 5 of the buzzer, which, unlike the buttons, will use an OUTPUT mode (since it emits a sound to the outside).
setup: # buzzer la a0, buzzerPin lw a0, 0(a0) li a1, 0x03 #OUTPUT addi sp, sp, -4 sw ra, 0(sp) jal ra, pinMode lw ra, 0(sp) addi sp, sp, 4Then, to configure the rest of the buttons, we created a loop that goes through all the selected buttons.
First, in setup, after initializing the buzzer, we initialize the variables in our loop:
la s0, button_C4 # Button list li s1, 0 # Position in the list la t3, button_count lw s2, 0(t3) # List lengthNext, we move on to the
loop_buttonsfunction, where we will:Check if we have already gone through the entire list
loop_buttons: bge s1, s2, end_loop_buttons # loop until all the buttons are positionedIf this is not the case, we scroll through the data to find the pin number we want.
# Take position of the button slli s3, s1, 2 add t1, s0, s3 #shift lw s4, 0(t1) #Button value mv a0,s4Once the position has been obtained, we configure the button with
INPUT_PULLUPmode, as they are buttons.li a1, 0x05 #INPUT_PULLUP addi sp, sp, -4 sw ra, 0(sp) jal ra, pinMode lw ra, 0(sp) addi sp, sp, 4As we indicated in the statement, we are going to use interrupts, so we have to assign them now knowing which button we are on (that is, we take advantage of the fact that the order of
button_handlersis the same as the order followed by the button array). For more information on how to assign interrupts, see example 2. We will indicate what the ISRs are like in the next point:# Attach Interrupts # Transform digitalPin into interrupt mv a0, s4 addi sp, sp, -4 sw ra,0(sp) jal ra, digitalPinToInterrupt #digitalPinToInterrupt(interruptpin); lw ra,0(sp) addi sp, sp, 4 # Search the correct pointer la t0, button_handlers add t1, t0, s3 lw a1, 0(t1) # Let the interrupt jump when button is pressed (on_low) li a2, 0x03 addi sp, sp, -4 sw ra,0(sp) jal ra, attachInterrupt #attachInterrupt(digitalPinToInterrupt(buttonPin[i]), handler[i], ON_LOW); lw ra,0(sp) addi sp, sp, 4- We add up a position and re-enter the loop
addi s1, s1, 1 # next button j loop_buttons - If we have finished going through the list, we return to main by executing a ret (or a
jr ra).end_loop_buttons: ret
Loop: This step can be done in two ways: with polling (i.e., constantly checking the status of all buttons) or with interrupts (pressing the button triggers an interrupt). The easiest way to do this exercise is to assign an interrupt to each button and, when pressed, have each one have its own ISR. Therefore, before starting the loop, we will create each of the necessary ISRs and assign them to the buttons. Refer to case 2 using interrupts for a better understanding.
In this case, we use a "flag" change since the
tone()function underneath launches "tasks" or "processes" underneath, which is not safe to do in an ISR.# ISR handleButton0: la t0, anyPressed lw t1, 0(t0) li t2, 0 # Assign button sw t2, 0(t0) jr ra handleButton1: la t0, anyPressed lw t1, 0(t0) li t2, 1 # Assign button sw t2, 0(t0) jr ra handleButton2: la t0, anyPressed lw t1, 0(t0) li t2, 2 # Assign button sw t2, 0(t0) jr ra handleButton3: la t0, anyPressed lw t1, 0(t0) li t2, 3 # Assign button sw t2, 0(t0) jr raThen, inside the loop, we check the status of the
anyPressedflag and add a smalldelayso that the watchdog does not trip.This loop checks that, if the flag is not at -1, the tone corresponding to that position sounds.
loop: la t0, anyPressed lw s0,0(t0) li t1, -1 bne s0, t1, startTune la t0, time_delay lw a0, 0(t0) addi sp, sp, -4 sw ra,0(sp) jal ra, delay #delay(200); lw ra,0(sp) addi sp, sp, 4 j loopstartTune: start playing the tone
In this case, once we have the position of the button that has been pressed, we search the memory array for the corresponding note value, similar to when we searched for the button.
To prevent the tone from having an indefinite duration, we have set a timeout of 200. Once the tone is played, it returns to the loop.
startTune:
# Clean value
la t0, anyPressed
li t1, -1
sw t1, 0(t0)
# Search value
la t0, note_C4
slli s1, s0, 2
add t1,s1,t0
lw s2, 0(t1) # Note value
# Play Tone
la t0, buzzerPin
lw a0, 0(t0)
mv a1,s2
li a2, 200
# tone(buzzerPin, notes[i], duration);
addi sp, sp, -4
sw ra,0(sp)
jal ra, tone
lw ra,0(sp)
addi sp, sp, 4
j loop
And there we have our piano:

