Assembler programming on the Raspberry Pi
Assembler on Pi

© Lead Image © elenabsl, 123RF.com
Talk to your Raspberry Pi in its native assembler language.
Assembler programs run directly on the computer's hardware, which means they can reach nearly the maximum achievable speed of execution. Because assembler program code is very low level, writing the code is more complicated, but it is still the best choice for some tasks, especially on a computer such as the Raspberry Pi with its limited resources. Before you can start creating programs, however, you need to plumb the depths of the CPU and peripheral architecture.
Machine Code
To begin, it makes sense to clarify some terms. The CPU only understands machine code – zeros and ones or, more precisely, voltage levels that represent zeros and ones. Each command in machine code has a human-readable abbreviation that is easy to remember. These abbreviations are known as mnemonics and act as assembler commands. Assembler code is specific to a CPU architecture, which means that code for a Raspberry Pi (ARM) will not run on a PC (x86).
Programming in assembler on the Raspberry Pi can be approached in two ways: First, you can create an image in which you package the code and then boot the small-board computer (SBC) from that image to run the program. In other words, you degrade the Raspberry Pi to a microcontroller. With this method, the Pi runs without an operating system. Although you have full access to everything, you don't even get a shell.
The second way is to run the assembler program on the Raspberry Pi itself, which gives you the luxury of an operating system with everything that entails; however, you are limited in terms of direct access to the hardware. The second method was used for the example in this article.
Setup
A Raspberry Pi 3 with the current Raspberry Pi OS Lite provides the basis for my experiments. I will use Raspberry Pi Imager [1] to prepare the SD card, after which, I can boot from the card and get started right away, because all the tools needed for coding in assembler are included in the image. That said, an additional action provides more convenience and flexibility (see the "Activating SSH" box).
Activating SSH
To remove the need for an additional monitor and keyboard, I recommend working on the Raspberry Pi over SSH. To get the service running correctly on first boot requires some minor intervention. To begin, create an empty /boot/ssh
file on the SD card; the SSH daemon will then launch automatically at boot time.
If needed, redirect the output of the X server over SSH from the Raspberry Pi to the desktop PC with the -X
option. This works best if you are also using Linux on the desktop computer. If your router supports local name resolution, use the
ssh -X pi@raspberrypi@local
command to open the connection. All graphical output from programs then end up on the desktop computer. If the local DNS does not work, use the IP address of the Raspberry Pi, which you can look up from the list of connected devices on the router.
No Hello World
When you start working with a new programming language, the traditional approach is create a "Hello World" program; however, it takes a fair amount of assembler code and some understanding of strategies to generate even this simple output. Therefore, the first small assembler program only outputs the return code on the console, which indicates the status of a program on exiting. Bash stores this value in the $?
variable, which you read with echo $?
.
A return code of 0 means that the previously executed command ran without error; a value greater than 0 indicates an error. Listing 1 shows the example program 42.s
, so named because the return code value 42 is the result. (Note that the title of this article is 42 in binary-coded decimal (BCD) encoding, which represents each decimal section from 0 to 9 as 4 bits (i.e., half a byte – or a nibble, if you prefer.)
Listing 1
42.s
.global main /* Entry point for the program */ main: mov r0, #42 /* Move value 42 to register r0 */ bx lr /* Return to calling program */
Assembler comprises relatively simple commands that do nothing more than move bytes back and forth, manipulate them, or react to a status bit, which makes it extremely important to document the code thoroughly and to use mnemonic identifiers where possible.
Labels are used in programming languages to mark points in the source code that serve as jump targets. The compiler exchanges the label for a physical memory address at build time, which clarifies the massive advantage of using labels: You do not need to calculate laboriously where in memory a particular command is located. Moreover, with each additional command you insert, all the addresses below it would move.
As in many other programming languages, you need to specify the starting point for a program in assembler. In Java and C the corresponding function is main()
; in assembler you define the global label main
. The label must precede the first line of code you want to execute, as shown in the assembler program in Listing 1.
The first line defines main
globally so that the linker can find it. That label is then used in the second line, immediately followed by the first command, the mov
command (short for move), which is used to move values (e.g., to store constant values in a register or to transfer the content of one register to another). To move values from registers into RAM or load them from RAM, you need the str
(store register) and ldr
(load register) commands instead. A register is a memory location on the CPU. An overview of the registers on the Raspberry Pi is shown in Table 1.
Table 1
ARM CPU Registers
Register | Mnemonic | Function |
---|---|---|
r0-r10 |
– |
General registers without a special function |
r11 |
fp |
Frame pointer register |
r12 |
ip |
General register without a special function |
r13 |
sp |
Stack pointer |
r14 |
lr |
Link register |
r15 |
pc |
Program counter |
The program status register acts as the CPU's internal control register. The states of the individual bits tell you what results specific CPU register operations return. The commands for conditional jumps react to these bits. One well-known control bit is the zero bit (bit 30), which indicates that the value of an arithmetic logic unit (ALU) of a CPU, wherein all calculations and comparisons are performed, is 0.
In the example in Listing 1, the mov
command stores a value of 42 in the CPU register r0
. When the program ends, the operating system reads the value of register r0
and stores it in shell variable $?
.
The bx
command in the last line causes the CPU to continue the program at a different memory address – in this case, the address found in register lr
. This register stores the address for program calls that the computer has to make after the program terminates.
The only question that now remains is how to generate an executable program from the assembler code. The workflows required to do this are very similar to the process of compiling C programs:
$ as -o 42.o 42.s $ gcc 42.o $ ./a.out $ echo $? 42
The first line creates an object file from the assembler source code, which is then bound in the next line to the operating system to obtain an executable file. Unless you specify otherwise, this file is named a.out
. You can execute the a.out
program as usual. The final command shows that the program returns the value 42.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.
News
-
elementary OS 7.1 Now Available for Download
The team behind elementary OS has released the latest version of its operating system with a focus on personalization, inclusivity, accessibility, and privacy.
-
The GNU Project Celebrates Its 40th Birthday
September 27 marks the 40th anniversary of the GNU Project, and it was celebrated with a hacker meeting in Biel/Bienne, Switzerland.
-
Linux Kernel Reducing Long-Term Support
LTS support for the Linux kernel is about to undergo some serious changes that will have a considerable impact on the future.
-
Fedora 39 Beta Now Available for Testing
For fans and users of Fedora Linux, the first beta of release 39 is now available, which is a minor upgrade but does include GNOME 45.
-
Fedora Linux 40 to Drop X11 for KDE Plasma
When Fedora 40 arrives in 2024, there will be a few big changes coming, especially for the KDE Plasma option.
-
Real-Time Ubuntu Available in AWS Marketplace
Anyone looking for a Linux distribution for real-time processing could do a whole lot worse than Real-Time Ubuntu.
-
KSMBD Finally Reaches a Stable State
For those who've been looking forward to the first release of KSMBD, after two years it's no longer considered experimental.
-
Nitrux 3.0.0 Has Been Released
The latest version of Nitrux brings plenty of innovation and fresh apps to the table.
-
Linux From Scratch 12.0 Now Available
If you're looking to roll your own Linux distribution, the latest version of Linux From Scratch is now available with plenty of updates.
-
Linux Kernel 6.5 Has Been Released
The newest Linux kernel, version 6.5, now includes initial support for two very exciting features.