The First Program: Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
Die Seite wurde neu angelegt: „Our first program will initially do nothing; it will simply run an infinite loop. We use it as a foundational building block to create all further experiments. I will first describe how such a program is created, what peculiarities there are, how it is compiled, and then how it is executed. == Creating the Source Code == First, we open our text editor to write our first assembly program. <syntaxhighlight lang="asm"> /* The first program 13.10.2020 www.s…“
 
 
(6 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 52: Zeile 52:
!DESCRIPTION
!DESCRIPTION
|-
|-
|.text |Executable program code
|.text
This section contains read-only data and cannot be written to
|Executable program code
This section contains read-only data and cannot be written to
|-
|-
|.data |Data
|.data
Within this section, data can be read and written. The data is not executable
|Data
Within this section, data can be read and written. The data is not executable
|-
|-
|.rdata |Similar to .data, but the data cannot be written. This is used, for example, for constants.
|.rdata
|Similar to .data, but the data cannot be written. This is used, for example, for constants.
|-
|-
|.init |Generates code that is only executable at the start of the program.
|.init
|Generates code that is only executable at the start of the program.
|}
|}
== .globl ==
== .globl ==
In line 6, the label _start is defined as global. This makes the linker aware of this label. In our system, which we must program here, we need to define this label as global so the linker can place our code in memory at address 0x8000. For 32-bit bare-metal applications, the Raspberry Pi expects the program to be exactly there. This is due to the peculiarity of the Raspberry Pi. The Raspberry Pi initially doesn't start with the CPU, but with its graphics chip. A firmware is started there, the Raspberry Pi is initialized, and only then handed over to the CPU. During this time, this memory space is reserved, and the CPU is then assigned to look at memory location 0x8000 and execute the code there. For 64-bit bare-metal applications, the address is 0x80000.
In line 6, the label _start is defined as global. This makes the linker aware of this label. In our system, which we must program here, we need to define this label as global so the linker can place our code in memory at address 0x8000. For 32-bit bare-metal applications, the Raspberry Pi expects the program to be exactly there. This is due to the peculiarity of the Raspberry Pi. The Raspberry Pi initially doesn't start with the CPU, but with its graphics chip. A firmware is started there, the Raspberry Pi is initialized, and only then handed over to the CPU. During this time, this memory space is reserved, and the CPU is then assigned to look at memory location 0x8000 and execute the code there. For 64-bit bare-metal applications, the address is 0x80000.


The First Assembly Commands
== The First Assembly Commands ==


In this example, only two assembly instructions have been used so far, b and mov, highlighted in blue.
In this example, only two assembly instructions have been used so far, b and mov, highlighted in blue.


b (Branch)
=== b (Branch) ===


b stands for Branch and means that the program should branch.
b stands for Branch and means that the program should branch.
 
<syntaxhighlight lang="asm">
b main            @Branch to "main"
b main            @Branch to "main"
 
</syntaxhighlight>


The first Branch command (line 9) means it should jump to the label main, thereby starting our program.
The first Branch command (line 9) means it should jump to the label main, thereby starting our program.
 
<syntaxhighlight lang="asm">
MainLoop:          @Infinite loop
MainLoop:          @Infinite loop
   b MainLoop
   b MainLoop
 
</syntaxhighlight>


The second Branch command used here means it should jump to the label MainLoop. If you look at the code closely, you will see that a constant jump to the same code section occurs. This creates an infinite loop. We must do this here because otherwise, the program would do something undefined, possibly things we do not want.
The second Branch command used here means it should jump to the label MainLoop. If you look at the code closely, you will see that a constant jump to the same code section occurs. This creates an infinite loop. We must do this here because otherwise, the program would do something undefined, possibly things we do not want.


A Note on Labels
==== A Note on Labels ====


All labels that are jumped to must be concluded with : in the code. All alphanumeric characters plus a few special characters are allowed. Ideally, one should use meaningful labels so that the code remains readable later on.
All labels that are jumped to must be concluded with : in the code. All alphanumeric characters plus a few special characters are allowed. Ideally, one should use meaningful labels so that the code remains readable later on.


mov (Move)
=== mov (Move) ===
 
<syntaxhighlight lang="asm">
   mov sp,#0x8000  @Create a stack of 32768 bytes
   mov sp,#0x8000  @Create a stack of 32768 bytes
 
</syntaxhighlight>


The command mov means that information should be moved. In our case, we write the address 0x8000 into the stack pointer (sp) register. We need the stack later in our programs to store temporary data.
The command mov means that information should be moved. In our case, we write the address 0x8000 into the stack pointer (sp) register. We need the stack later in our programs to store temporary data.
Zeile 101: Zeile 106:
I will describe the mov command in more detail later on; for now, let's just see it in action.
I will describe the mov command in more detail later on; for now, let's just see it in action.


Number Formats
=== Number Formats ===


In the example mov, I used the "number" 0x8000. In the comment, the number 32768 is shown. These two numbers are identical. The prefix 0x is used to indicate a hexadecimal number. Without specifying a format, the compiler interprets the number as a decimal. Besides these two formats, there are a few others. Here’s a list:
In the example mov, I used the "number" 0x8000. In the comment, the number 32768 is shown. These two numbers are identical. The prefix 0x is used to indicate a hexadecimal number. Without specifying a format, the compiler interprets the number as a decimal. Besides these two formats, there are a few others. Here’s a list:


NUMBER TYPE BASE PREFIX ALLOWED CHARACTERS
{| class="wikitable"
Decimal 10 - 0-9
|+
Hexadecimal 16 0x or 0X 0-9, A-F
!NUMBER TYPE
Octal 8 O 0-7
!BASE
Binary 2 0b or 0B 0-1
!PREFIX
Floating Point 10 0f or 0F 0-9
!ALLOWED CHARACTERS
|-
|Decimal
|10
|
|0-9
|-
|Hexadecimal
|16
|0x oder 0X
|0-9, A-F
|-
|Octal
|8
|O
|0-7
|-
|Binary
|1
|0b oder 0B
|0-1
|-
|Floating Point
|10
|0f oder 0F
|0-9
|}
 
== Compiling ==
We have saved our source code in the directory “C:\msys64\home\xxx” on Windows. To compile it, we open “msys2” on Windows, which can be found via the Windows menu.
 
On Linux, we open the terminal.
 
Usually, the corresponding home directory is opened, regardless of whether we are using Windows or Linux.
 
If it is not there, you can simply switch to this directory using the command “cd ~”.
 
With “ls” you can display the contents.
 
Here is an example under Windows:
 
[[Datei:Comp1.png|rand|300x300px]]
 
To perform the compiling, three commands are needed:
<syntaxhighlight lang="shell">
arm-none-eabi-as erstes.s -o erstes.o
arm-none-eabi-ld --no-undefined erstes.o -o erstes.elf
arm-none-eabi-objcopy erstes.elf -O binary kernel7l.img
</syntaxhighlight>
 
[[Datei:Comp2.png|rand|400x400px]]
 
“arm-none-eabi-as” first generates an object file with the “-o” option. Since the assembler might not know everything yet, it initially generates this file.
 
The linker “arm-none-eabi-ld” generates the actual machine code and combines information that may not have been declared in the code itself.
 
Since the linker creates an “elf” file, but our Raspberry Pi in its initial state does not understand this and expects a different type of file, we need to convert this file. The command “arm-none-eabi-objcopy” takes care of this for us. The result is a “kernel7l.img” file.
 
[[Datei:Comp3.png|rand|400x400px]]
 
== Running the Program on the Raspberry Pi ==
=== Preparing the SD Card (Simple Method) ===
To run the file on the Raspberry Pi, we need to prepare a few things.
 
The simplest method is to use an already functioning SD card with “Raspbian” installed. When you look at this SD card in a file manager, there are two partitions. We are only interested in the boot partition. It is best to rename all the files that start with “kernel…”. These would interfere with running our program and might not start.
 
[[Datei:Sd1.png|rand|200x300px]]
 
Rename them so that you can later revert them if you decide you no longer want to continue… Here is an example:
 
[[Datei:Sd2.png|rand|150x300px]]
 
=== Preparing the SD Card (Preferred Method) ===
A second method that I prefer is to create a dedicated SD card for these experiments. Here’s how to do it.
 
First, format an SD card in “FAT32” format and name it “BOOT”. To make this card bootable, a few files are needed:
<syntaxhighlight lang="shell">
bcm2711-rpi-4-b.dtb
bootcode.bin
start.elf
start4.elf
</syntaxhighlight>
 
These can be downloaded either from https://github.com/raspberrypi/firmware/tree/master/boot or copied from a working SD card and then copied to the SD card.
 
=== Running the Program ===
Whichever version you’ve chosen, you can now copy your generated “kernel7i.img” file to the boot partition of the SD card. Insert the card into the Raspberry Pi and start it. In this example, nothing will happen because our code does nothing but run an infinite loop. And that is what it does.
 
If you have the HDMI cable connected, you can follow the boot process on the screen and see the colorful gradient at the end.


The translation has been completed and the content appears accurate. No changes have been noted that would necessitate a summary of modifications.
In the next chapter, we will let the Raspberry Pi perform the first visible action (turning on an LED) without needing to use any additional hardware.

Aktuelle Version vom 1. April 2025, 12:35 Uhr

Our first program will initially do nothing; it will simply run an infinite loop. We use it as a foundational building block to create all further experiments. I will first describe how such a program is created, what peculiarities there are, how it is compiled, and then how it is executed.

Creating the Source Code

First, we open our text editor to write our first assembly program.

/*
The first program
13.10.2020 www.satyria.de
*/
.section .init    @This shows the linker what to consider
.globl _start     @Generates a global label
_start:           @The label _start (entry address)

b main            @Branch to "main"

.section .text    @Instruction to the linker that code is coming

main:             @Create the label "main"
   mov sp,#0x8000 @Create a stack of 32768 bytes

MainLoop:         @Infinite loop 
   b MainLoop

erstes.s

Save this file in the default installation directory on Windows as follows: C:\msys64\home\xxx, where "xxx" usually refers to your name. On Linux, it can be saved in the "Home" directory. Name it erstes.s. The suffix .s designates the file as assembly source code.

First, I will describe the program and the commands used, and in the end, we will compile it.

Comments

In this example, I have highlighted the comments in orange so that they can be better recognized. In the following examples, these will be shown in olive.

There are two types of comments. Comments that span multiple lines start with /* and end with */. This can be seen in lines 1-4 in the example.

In the following lines, @ is used to create a line comment. The comment starts from the @ character and ends at the end of the line.

Sections

Sections in assembly are needed to tell the linker how to handle the following lines.

Our first .section command (line 5) generates an initiation of the program. This code is executed first but should not be called again afterwards. In this section, we jump to our main program.

In line 11, we use the .text section. This information tells the linker that it is executable code. This part is also write-protected.

Here is a selection of possible .section commands:

SECTION NAME DESCRIPTION
.text Executable program code

This section contains read-only data and cannot be written to

.data Data

Within this section, data can be read and written. The data is not executable

.rdata Similar to .data, but the data cannot be written. This is used, for example, for constants.
.init Generates code that is only executable at the start of the program.

.globl

In line 6, the label _start is defined as global. This makes the linker aware of this label. In our system, which we must program here, we need to define this label as global so the linker can place our code in memory at address 0x8000. For 32-bit bare-metal applications, the Raspberry Pi expects the program to be exactly there. This is due to the peculiarity of the Raspberry Pi. The Raspberry Pi initially doesn't start with the CPU, but with its graphics chip. A firmware is started there, the Raspberry Pi is initialized, and only then handed over to the CPU. During this time, this memory space is reserved, and the CPU is then assigned to look at memory location 0x8000 and execute the code there. For 64-bit bare-metal applications, the address is 0x80000.

The First Assembly Commands

In this example, only two assembly instructions have been used so far, b and mov, highlighted in blue.

b (Branch)

b stands for Branch and means that the program should branch.

b main             @Branch to "main"

The first Branch command (line 9) means it should jump to the label main, thereby starting our program.

MainLoop:          @Infinite loop
   b MainLoop

The second Branch command used here means it should jump to the label MainLoop. If you look at the code closely, you will see that a constant jump to the same code section occurs. This creates an infinite loop. We must do this here because otherwise, the program would do something undefined, possibly things we do not want.

A Note on Labels

All labels that are jumped to must be concluded with : in the code. All alphanumeric characters plus a few special characters are allowed. Ideally, one should use meaningful labels so that the code remains readable later on.

mov (Move)

   mov sp,#0x8000  @Create a stack of 32768 bytes

The command mov means that information should be moved. In our case, we write the address 0x8000 into the stack pointer (sp) register. We need the stack later in our programs to store temporary data.

A stack operates in a way that the last number placed on it is the first one taken off (LIFO). The stack pointer (sp) negates its position, meaning the first number is stored at memory location 0x8000, and the next at 0x7FFF.

In our case, we reserve memory from 0 to 0x8000 for the stack.

I will describe the mov command in more detail later on; for now, let's just see it in action.

Number Formats

In the example mov, I used the "number" 0x8000. In the comment, the number 32768 is shown. These two numbers are identical. The prefix 0x is used to indicate a hexadecimal number. Without specifying a format, the compiler interprets the number as a decimal. Besides these two formats, there are a few others. Here’s a list:

NUMBER TYPE BASE PREFIX ALLOWED CHARACTERS
Decimal 10 0-9
Hexadecimal 16 0x oder 0X 0-9, A-F
Octal 8 O 0-7
Binary 1 0b oder 0B 0-1
Floating Point 10 0f oder 0F 0-9

Compiling

We have saved our source code in the directory “C:\msys64\home\xxx” on Windows. To compile it, we open “msys2” on Windows, which can be found via the Windows menu.

On Linux, we open the terminal.

Usually, the corresponding home directory is opened, regardless of whether we are using Windows or Linux.

If it is not there, you can simply switch to this directory using the command “cd ~”.

With “ls” you can display the contents.

Here is an example under Windows:

To perform the compiling, three commands are needed:

arm-none-eabi-as erstes.s -o erstes.o
arm-none-eabi-ld --no-undefined erstes.o -o erstes.elf
arm-none-eabi-objcopy erstes.elf -O binary kernel7l.img

“arm-none-eabi-as” first generates an object file with the “-o” option. Since the assembler might not know everything yet, it initially generates this file.

The linker “arm-none-eabi-ld” generates the actual machine code and combines information that may not have been declared in the code itself.

Since the linker creates an “elf” file, but our Raspberry Pi in its initial state does not understand this and expects a different type of file, we need to convert this file. The command “arm-none-eabi-objcopy” takes care of this for us. The result is a “kernel7l.img” file.

Running the Program on the Raspberry Pi

Preparing the SD Card (Simple Method)

To run the file on the Raspberry Pi, we need to prepare a few things.

The simplest method is to use an already functioning SD card with “Raspbian” installed. When you look at this SD card in a file manager, there are two partitions. We are only interested in the boot partition. It is best to rename all the files that start with “kernel…”. These would interfere with running our program and might not start.

Rename them so that you can later revert them if you decide you no longer want to continue… Here is an example:

Preparing the SD Card (Preferred Method)

A second method that I prefer is to create a dedicated SD card for these experiments. Here’s how to do it.

First, format an SD card in “FAT32” format and name it “BOOT”. To make this card bootable, a few files are needed:

bcm2711-rpi-4-b.dtb
bootcode.bin
start.elf
start4.elf

These can be downloaded either from https://github.com/raspberrypi/firmware/tree/master/boot or copied from a working SD card and then copied to the SD card.

Running the Program

Whichever version you’ve chosen, you can now copy your generated “kernel7i.img” file to the boot partition of the SD card. Insert the card into the Raspberry Pi and start it. In this example, nothing will happen because our code does nothing but run an infinite loop. And that is what it does.

If you have the HDMI cable connected, you can follow the boot process on the screen and see the colorful gradient at the end.

In the next chapter, we will let the Raspberry Pi perform the first visible action (turning on an LED) without needing to use any additional hardware.