How to display text on a framebuffer? Easy once you know about PSF1 fonts. In this article the details of how to read and use a PSF1 font file is explained and a complete C program to display text on the framebuffer is given. 

 

 

Now available as a paperback or ebook from Amazon.

Applying C For The IoT With Linux

Contents

  1. C,IoT, POSIX & LINUX
  2. Kernel Mode, User Mode & SyscallProgram Execution
  3. Execution, Permissions & Systemd
  4. Signals & Exceptions
  5. Integer Arithmetic
  6. Fixed Point
  7. Floating Point
  8. File Descriptors
  9. The Pseudo-File System
  10. Graphics
  11. Sockets
  12. Threading
  13. Cores Atomics & Memory Management
  14. Interupts & Polling
  15. Assembler

Also see the companion book: Fundamental C

 

Framebuffer Text PSF 1

This example is included because increasingly IoT devices have access to VGA/HDMI graphics and increasingly often there is a need to display data or status messages using the framebuffer. Think of it as an alternative to adding a 7-segment or similar display. The only new feature to add to the basic framebuffer functions described in the previous sections is the use of a font file. There are many different types of font and font file formats, but for this simple application a bitmap font is the simplest to use. Linux makes use of PSF files for bitmapped console fonts. These are generally stored in /usr/share/consolefonts but this location varies. The selection of fonts you will find also varies. Add to this the fact that there are two versions of PSF format and you can see that things might not be so straightforward.

The first problem is we have to read a gzipped file. The simplest way to do this is to use the zlib library which is preinstalled on many Linux distributions. You need to add:

#include <zlib.h>

and you need to add the library file z to the linker. Once you have done this you can use the gzopen function exactly like the open function but in this case it will uncompress a gzipped file as you read from it using gzread. There is also a gzclose command and everything works exactly the same as the file descriptor functions, apart from the fact that the file is decompressed on the fly.

The PSF version 1 format is very simple and consists of a header followed by the font data. The header corresponds to the struct:

struct psf_header {
    uint8_t magic[2];
    uint8_t filemode;
    uint8_t fontheight;
};

For a version 1 format file the magic bytes are 0x36 and 0x04 and you should check that this is so before reading the rest of the file. The filemode byte tells you how many characters there are in the file and if there is any Unicode information:

0 : 256 characters, no unicode_data
1 : 512 characters, no unicode_data
2 : 256 characters, with unicode_data
3 : 512 characters, with unicode_data

In this example we can ignore any Unicode data. The font data follows the header and consists of fontheight bytes for each character in the font. These are the pixel rows of the character. So to get the font data into an array we would do something like:

gzFile font = gzopen(
                "/usr/share/consolefonts/Lat15-VGA8.psf.gz", "r");
gzread(font, &header, sizeof (header));
uint8_t chars[header.fontheight * 256];
gzread(font, chars, header.fontheight * 256);

The font file being used returns filemode = 2 and so it has 256 characters and unicode data that follows the font which we are ignoring. If you use a different font file, check the filemode and adjust the number of characters accordingly.

We can use the font data to find the 8xfontheight pixel data for any character ch using:

row = chars[ch * header.fontheight + j];

where j gives the row number from the top of the character.

To draw the character on the screen, we simply test each bit in each row in turn and draw a block if it is a 1:

 for (int j = 0; j < header.fontheight; j++) {
        row = chars[ch * header.fontheight + j];
        for (int i = 0; i < 8; i++) {
            if (row & 0x80) {
                setBlock(x1, y, BLOCKSIZE, c);
            }
            row = row << 1;
            x1 = x1 + BLOCKSIZE;
        }
        y = y + BLOCKSIZE;
        x1 = x;
    }

where setBlock is the function given in the previous section.

From this basic "draw a character" technique, we can easily create a function to do the same job and a function to draw all of the characters in a string.
The complete program is:

#define _POSIX_C_SOURCE  199309L
#include <stdio.h>
#include <stdlib.h>
#include <linux/fb.h>
#include <fcntl.h> 
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <inttypes.h>
#include <zlib.h>
#define BLOCKSIZE 10

struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;

uint8_t *fbp;
uint32_t block[BLOCKSIZE*BLOCKSIZE];

struct color {
    uint32_t r;
    uint32_t g;
    uint32_t b;
    uint32_t a;
};

uint32_t setRawPixel(uint32_t x, uint32_t y, uint32_t pixel) {
    uint32_t location = x * (vinfo.bits_per_pixel / 8) + y * finfo.line_length;
    *((uint32_t*) (fbp + location)) = pixel;
}

void setPixel(uint32_t x, uint32_t y, struct color c) {
    uint32_t pixel = (c.r << vinfo.red.offset) | (c.g << vinfo.green.offset) | (c.b << vinfo.blue.offset) | (c.a << vinfo.transp .offset);
    setRawPixel(x, y, pixel);
}

void setBlock(uint32_t x, uint32_t y, uint32_t L, struct color c) {
    for (int i = 0; i < L; i++) {
        for (int j = 0; j < L; j++) {
            setPixel(x + i, y + j, c);
        }
    }
}

struct psf_header {
    uint8_t magic[2];
    uint8_t filemode;
    uint8_t fontheight;
};
struct psf_header header;

void displayChar(char ch, int x, int y, struct color c, uint8_t chars[]) {
    uint8_t row;
    int x1 = x;
    for (int j = 0; j < header.fontheight; j++) {
        row = chars[ch * header.fontheight + j];
        for (int i = 0; i < 8; i++) {
            if (row & 0x80) {
                setBlock(x1, y, BLOCKSIZE, c);
            }
            row = row << 1;
            x1 = x1 + BLOCKSIZE;
        }
        y = y + BLOCKSIZE;
        x1 = x;
    }
}

void displayString(char s[], int x, int y, struct color c, uint8_t chars[]) {
    int k = 0;
    while (s[k]) {
        displayChar(s[k], x, y, c, chars);
        x = x + BLOCKSIZE * 9;
        k++;
    }
}

int main(int argc, char** argv) {
    
    int fd = open("/dev/fb0", O_RDWR);
    ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
    ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
    
    vinfo.grayscale = 0;
    vinfo.bits_per_pixel = 32;
    ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo);
    ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);

    fbp = mmap(0, vinfo.yres * finfo.line_length, PROT_READ |
                                 PROT_WRITE, MAP_SHARED, fd, 0);

    gzFile font = gzopen(
             "/usr/share/consolefonts/Lat15-VGA8.psf.gz", "r");

    gzread(font, &header, sizeof (header));
    
    uint8_t chars[header.fontheight * 256];
    gzread(font, chars, header.fontheight * 256);

    struct color c = {0xFF, 0x00, 0x00, 0xFF};
    int x = 50;
    int y = 400;    
    displayString("Hello World!", x, y, c, chars);

    return (EXIT_SUCCESS);
}

If you try this out you will find the message overwrites everything currently on the screen. If you want to restore the screen you need to save its original state before writing over it. You can also change the size of the font by changing BLOCKSIZE. As the font is defined in 8-bit rows, making it much bigger makes it look very blocky.

To do better you either need a vector font, such as Truetype, or you need a custom bitmap font at the correct resolution. You can improve the vertical resolution using alternative PSF 1 files, but the horizontal resolution is always 8 bits.

An easy alternative is to use a PSF 2 font which is the subject of the next section.

 

Summary of Chapter That This Article Is Taken From

  • Selecting a graphics system for Linux is difficult because there is so much choice and many different levels of operation.
  • The framebuffer gives you direct access to the graphics buffer.
  • You can use it to write directly to the screen and it doesn't take account of windows or any other part of the GUI.
  • It is possible to use font files to write text to the framebuffer.
  • The standard window system is X11 and it has a client-server architecture with the program that does the drawing as the client, and the program that does the rendering on a device as the server.
  • X11 can be used via the Xlib library.
  • X11 can also handle user input and for this you have to implement an event handling loop.
  • The GTK Framework is a complete GUI system with windows, buttons and events.
  • The structure of almost any GUI framework makes your program asynchronous and this can be confusing at first.
  • You can use GTK via function calls, but it is much easier to use the Glade drag-and-drop editor.

 

 

 

 

 

comments powered by Disqus