Skip to main content

Arduino in Assembly (First Post)

What is this?

Hello! This is my first post for the foxgirl laboratory blog. We'll be going over different projects as I experiment with projects and learn how they work! I'm doing this purely for the science (and engineering) as I want to learn more how different aspects of the universe work. For those interested in who I am, I'm a trans woman who writes software for fun. I would like to be able to write software which I get paid for, so if any companies are hiring for that position, let me know if you'd like to hire me.

First, we'll set some rules for how this blog will work. This is NOT a copy/paste tutorial. Chances are likely, the copy/pasted code and designs will not work, or at the very least, won't work as it should. I'll be writing this as I learn, so mistakes will be left in, with corrections added and noted later on (possibly even in different chapters). I'll be writing in a mixture of freeform and structured writing. Also, I'll be uploading files I believe I have permission to use (either from copyleft licensing or fair use laws under the United States jurisdiction). For files which I'm not sure I can upload, I'll link to them instead.

I plan on focusing more on the hardware side of computing in the near future. Currently, I have a 6502 microprocessor and other parts which are similar to the parts used by Ben Eater's Hello 6502 tutorial.  The parts I have can be seen at this Mouser project link. If you haven't ordered from Mouser before, do note, they primarily sell to manufacturing companies, so Mouser will ask you what your company does as part of the Know Your Customer guidelines put in place by the US Export Administration Regulations. I told them that I was wanting to know more about how a computer works on a hardware level, will be following along with Ben Eater's 6502 tutorials, and will be posting about them on this site you are reading right now. They'll ask after you place your first order, so don't wait until Thursday morning to make the order, else you'll be waiting for far longer than 2 days before they start processing the order. Also, make sure you don't use the products for anything illegal like making weapons of mass destruction or anything else which would violate human rights.

In the future, I'll be looking into photonic circuits. Currently, I have no idea how to get a photonic chip.

When writing this blog, I'll be assuming you, the reader, have at least some experience with higher level languages (such as C), GNU/Linux, the shell, git, reading documentation, binary in different formats such as hexadecimal, and basic mathematics such as algebra.

Arduino

For this first post, we're going to program an Atmega2560 Arduino to blink the internal LED on/off every second. When asking for help with getting proof of life on my Arduino after flashing it with assembly, I was sent a link to this Atmega328P kit.

When starting off with this project, you'll want both an Arduino (based) Atmega2560 and a USBasp ISP cable. The firmware on the USBasp ISP cable is out of date, so it may have trouble flashing bigger projects, and we'll be looking into how the customers in the reviews flashed the updated firmware to the ISP cable in the future.

The ISP cable is required to flash assembly directly to the Arduino as the binaries we are creating will not have the Optiboot bootloader which the Arduino IDE tacks onto sketches (Arduino's programs).  The Arduino IDE assumes the Optiboot bootloader is already installed on the Arduino when it uploads sketches. If it isn't, like it won't be after flashing the blink program, then you'll need to use the IDE to flash the bootloader back to it using the ISP cable.

The Atmega2560 runs at a clock cycle of 16 MHz or 16,000,000 cycles per second. CPUs operate on cycles and each instruction takes a specific number of cycles to execute. The number of cycles is documented per instruction on the ISA datasheet. The Instruction Set Architecture or ISA is a description of how the code should work. It doesn't always work that way, hence the prevalence of Illegal Instructions on older processors. You can read more about Illegal Instructions on this reverse engineering blog.

Arduino Blinker

In order to compile the blink program, you'll need an AVR compatible assembler such as AVRA. I installed my copy of AVRA from the Arch User Repository or AUR.  AVRA is based off of the official Microchip AVR Assembler. The documentation can be found on Microchip's website. The Atmega2560 is 8 bit. You can find all sorts of documentation for the Atmega2560 on microchip's website. For flashing the binaries to the Arduino and also reading the binaries back, you'll need avrdude. You can find documentation for avrdude in the git repo. avrdude is also what the Arduino IDE needs to flash the bootloader back when you are done with this project.

When compiling the program, I use the command below to output both the hex file avrdude flashes as well as a list file which compares the binary opcodes to the assembly instructions. The AVR microprocessor is based on the RISC architecture.

avra blink.asm -o blink.hex -l blink.list

When flashing the program to the Arduino Mega 2560, I use the following command and specify the device is the Atmega2560, as well as specify the programmer uses USBasp, and specify the location of final output from the assembler. The hex format allows flashing bytes only where they need to be flashed instead of having to pad out the file to get the bytes in the correct positions.

avrdude -p atmega2560 -c USBasp -U flash:w:blink.hex

I'll be using ports to write to the internal LED pin. The Internal LED is on pin D13 (digital 13) of the Arduino 2560. Pin D13 is on Port B as can be seen on the Arduino Mega 2560 pinout datasheet. There's also a pinout datasheet on the Atmega2560 microprocessor itself. The text, PB7, which is attached to the D13 section of the datasheet specifies that the pin is on bit 7 of port B. Such that bit 7 means the highest bit of a byte which is mapped from 7 to 0. This would look like binary 10000000 on if bit 7 was the only pin set to output. Also, I should note, the Atmega2560 is little endian. This means that when a number or other data takes more than one byte to represent it, the part that's read first will be the smallest portion of the number, What this means is, hex 0x01 0x00 would be the number 1, whereas 0x00 0x01 would be 256. One byte can go from 0 to 255, and then we tack on the 1 on a second byte to signify value 256. More information on how endianness works can be found here. There's also a hex converter which accounts for endianness by ScadaCore.

When using ports, we need to set all of the pins corresponding to that port at once, even if we don't plan on using them. When a bit is set to 1, it means output, and when set to 0, it means input. We set the pin modes by writing to data direction register B also known as DDRB. We can write to the pins by writing a value to port B or PORTB and we can read from the pins by reading from pin B or PINB.

We will be writing the below program in AVR assembly.

void setup() {
  // pinMode(LED_BUILTIN, OUTPUT);
  DDRB = 0b10000000;
}

void loop() {
  // digitalWrite(LED_BUILTIN, LOW);
  PORTB = 0b00000000;
  delay(1000);

  // digitalWrite(LED_BUILTIN, HIGH);
  PORTB = 0b10000000;
  delay(1000);
}

Before we start writing the assembly, we want to make sure we know how to connect the USBasp programmer to the Arduino. There's two ICSP pins on an Atmega2560. The one we want will be a grid of 2x3 pins immediately next to the Atmega2560 microprocessor. I've included a screenshot below to show the pins and its pinout. The screenshot came from the Arduino Mega 2560 pinout datasheet. The USBAsp uses the older terms for these pins, whereas the Arduino uses the newer terms. You'll want to connect the MISO pin on the USBAsp to the CIPO pin on the Arduino. You can see how the terms have changed on this Arduino ISP page. This connection will be useful for flashing both the Arduino bootloader when you are done with the project, as well as any custom binaries you write with no bootloader.

Atmega2560_ICSP0.png

When we first write our assembly file, we will want to specify a target processor. This will allow the compiler to know if an instruction is valid for our target as well as determine what opcodes to convert the assembly to and to verify if the code stays within the range of the address bus of that particular processor. A list of assembler directives will be able to help explain how each of the directives works. The directives themselves aren't assembly and won't be stored as opcodes themselves, only used for figuring out how to assemble the program. Since we are targeting the Atmega2560, we specify the target processor with the below directive.

.device Atmega2560

More will be written later...