Graphics in C (PI5): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
 
Zeile 155: Zeile 155:
| style="width: 33%; text-align:center;" | [[English|< Home >]]
| style="width: 33%; text-align:center;" | [[English|< Home >]]
| style="width: 33%; text-align:right;" | [[Chars in C (PI5)e|Next (Chars in C (PI5)) >]]
| style="width: 33%; text-align:right;" | [[Chars in C (PI5)e|Next (Chars in C (PI5)) >]]
|}
{|
|-
| style="width: 33%; text-align:left;" | [[Error Handling in C (PI5)|< Back]] || style="width: 33%" |[[English|^ Home]] || style="width: 33%; text-align:right;" | [[Chars in C (PI5)e|Next >]]
|}
|}

Aktuelle Version vom 23. August 2024, 12:02 Uhr

Preliminary Note

On the Raspberry Pi 5, a lot has changed in terms of graphics, making it more difficult for BareMetal developers to program graphics correctly. There was already a discussion on GitHub: Raspberry Pi Firmware Issue #1904.

I do not like this development or decision!

Nevertheless, I managed to implement a display on the Raspberry Pi 5 that is functional. Unlike previous models, the screen is specified in the `config.txt` file and is already present when the first boot occurs.

We use the entry "framebuffer_depth=32" in the "config.txt" file. This entry creates a framebuffer with a depth of 32-bit and a resolution of 1920x1080 pixels.

On the Raspberry Pi, multiple processors are responsible for different tasks. There is also a processor (GPU) that manages graphics. Interestingly, this processor is also responsible for the initial start of the Raspberry Pi. After the initial initialization, it hands control over to the ARM processor.

Since each processor works independently, we need to find a way for these processors to communicate with each other. For this purpose, a kind of mailbox was set up, where we send a "letter" to tell the graphics processor what we want from it. In response, the graphics processor uses the same mailbox. As usual in computer science, the structure of the "letter" is defined. We have to adhere to these guidelines.

Structure of a Mailbox Message

A TAG structure always begins with a 32-bit number indicating the length of the entire structure. This is followed by a 32-bit buffer to possibly store a response that is transmitted during a call. Then come the individual TAGs. Each TAG has a basic structure: first, the name of the TAG is transmitted, followed by the number of values in bytes (32 bits, equivalent to 4), and again a buffer for the response. Then follow the values for the respective TAG. The entire structure ends with a NULL TAG.

  • Mailbox Length
  • Mailbox Request Code
    • TAG1 Name
    • TAG1 Length
    • TAG1 Buffer
      • TAG1 Value1
      • TAG1 Value2
      • ...
    • TAG2 Name
    • ...
    • TAGx NULL (End TAG)

You can read more information on GitHub. It is important that each individual TAG must be aligned to 32-bit.

Unlike previous models, it is easier to get the memory address of the graphics on the Raspberry Pi 5. Unfortunately, it is no longer possible to configure a desired display, such as the screen resolution. We have to use what is possible and request the address of the graphics memory with PROPTAG_ALLOCATE_BUFFER.

Our structure for the mailbox call is now as follows:

u32 pScreen[8] ALIGN(16) = {
    8*4, // 8 entries * 4 bytes
    CODE_REQUEST,
    PROPTAG_ALLOCATE_BUFFER,
    8,
    4,
    0,  // m_nBufferPtr
    0,
    PROPTAG_END
};

We will pass this structure to the mailbox function that we will write, and additionally, we will pass the mailbox channel. For communication (ARM to VC), channel 8 is used, which we previously defined with BCM_MAILBOX_PROP_OUT.

u32 graphicsAddress = 0;
u32 DrawColor = 0xFFFFFFFF;

#define ADDRESS_MASK 0x3FFFFFFF

void Init_Screen(void) 
{
    uintptr pScreenAddress = (uintptr)pScreen;
    while (TRUE) {
        BcmMailBox_Write(BCM_MAILBOX_PROP_OUT, (u32)pScreenAddress);
        if (pScreen[5] != 0)
        {
            break;
        }
    }
    // Adjust the address and save it
    graphicsAddress = pScreen[5] & ADDRESS_MASK;
}

In our tag list, we get a response from the graphics chip under PROPTAG_ALLOCATE_BUFFER indicating where the graphics memory is located. We can query the position in the tag with pScreen[5].

If the memory address still has a zero value, we know that communication has either not yet occurred or there was an error. We call the mailbox until we get a response.

The address we get back is the address as seen by the graphics chip. Since we are working on the ARM processor, we need to adjust the address to our address space and save the address in graphicsAddress. This is done with graphicsAddress = pScreen[5] & ADDRESS_MASK.

The Mailbox

Currently, we are keeping it very simple and leaving it to the calling program to evaluate the result.

void BcmMailBox_Write(u32 nChannel, u32 nData)
{
    write32(MAILBOX1_WRITE, nChannel | nData);
}

Note

This is a very simple implementation of the mailbox function. Later, we will extend it, as we should first check whether the mailbox is ready to process data. This is not always possible, and we would have to wait until the data in the mailbox is processed.

DrawPixel Function

Now we have the graphics address, but so far we cannot display anything. To do this, we create a DrawPixel function.

The function is passed the coordinates as integers. First, we check if the pixel fits on the screen at all. If not, we skip this function.

If the pixel fits, we need to calculate the position in memory: Each line can hold 1920 pixels (SCREEN_X). We multiply y by the number of pixels per line and add x to it. Since we have 32 bits (4 bytes) per pixel, we multiply the result by 4. This gives us the memory area our pixel would have if the graphics address were zero. We simply add the graphics address.

Now we have calculated the position where we can store our pixel. Since our system has a depth of 32 bits, representing the color, we simply need to store our color value here.

The Color

The color we use here is called ARGB. Each letter stands for a specific behavior:

  • A: Alpha (transparency)
  • R: Red component
  • G: Green component
  • B: Blue component

Each value can range from 0 to 255. The higher the value, the more intense the color component or coverage. For simplicity, we will always choose a color coverage of 100% and use the hexadecimal system:

Examples of colors:

  • 0xffff0000: Red
  • 0xff00ff00: Green
  • 0xffffff00: Yellow

Before we use the DrawPixel function, we store the color value as a variable DrawColor in memory. We read this value and copy it to the previously calculated position.

Here is the code for the DrawPixel function:

void DrawPixel(u32 x, u32 y)
{
    if ((x < SCREEN_X) && (y < SCREEN_Y))
    {
        write32(graphicsAddress+(((SCREEN_X*y)+x)*4), DrawColor);
    }
}

DrawPixel Function in Action

Now we use our new function in our kernel:

int main (void)
{
    LED_off();
    Init_Screen();
    u32 i;
    for (i=100; i<=500; i++)
    {
        DrawPixel(i, i);
    }
    LED_Error(2);
}

We create a loop that counts from 100 to 500. We pass this value to the DrawPixel function as x and y. This creates a line that runs diagonally from the top left to the bottom right. If this is successfully executed, we intentionally go into error code 2 to see that the Raspberry Pi has processed everything.

You can download the source code as a ZIP file from the following link: https://www.satyria.de/arm/sources/C/graphics.zip


< Back (Error Handling in C (PI5)) < Home > Next (Chars in C (PI5)) >