FBDirectOnConsoleLCD
Does the Frame Buffer Device Work?
Well... this is a pretty simple question to answer... Yes, the frame buffer works, otherwise we wouldn't get the GumStix logo on the screen after booting. But I figured it might be prudent to spend a little time verifying that it works the way I think it's supposed to work.
Getting Frame Buffer Info
Drawing Stuff on the Screen By Writing to /dev/fb0
Here's some code I wrote to exercise the frame buffer device. You can see the results on episode 2 of the hbmobile vlog.
When you want to draw stuff on the screen, you take the following steps:
- Include the appropriate headers
- Open the "/dev/fb0" device file
- Use the
ioctl
syscall to get information about the device - Use
mmap
to map the frame buffer into your process' memory space - Poke pixels into screen memory
You can download a tar file with this code (which is released under a BSD style license) and a Makefile to help compile it. File:Fbtest.tgz
Include Headers, Create a main(), Yadda, Yadda, Yadda
So one of the things you can see that I do is I made an "App" data structure that's supposed to store the state of the application. You don't have to do this, I just thought it made things a little easier to deal with.
The most important part of the following code fragment is that it lists the headers you're supposed to use.
/* Macro Definitions */
/* File Inclusions */
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <linux/fb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <asm/param.h>
#include <string.h>
#include <sys/time.h>
/* Typedefs, Structs, Unions, Enums, Etc. */
typedef enum {
FBT_E_NOERR = 0,
FBT_E_USAGE,
FBT_E_NULL,
FBT_E_MALLOC,
FBT_E_FILE,
FBT_E_SCREENO,
FBT_E_SCREENM
} FBTErr;
typedef struct {
char *device;
int screen_fd;
unsigned char *screen_ptr;
unsigned int size;
int width;
int height;
int bpp;
int left_margin;
int right_margin;
int top_margin;
int bottom_margin;
} App;
/* Static Function Prototypes */
static FBTErr fbt_init( App **app, int argc, char *argv[] );
static FBTErr fbt_clear( App *app );
static FBTErr fbt_cleanup( App *app );
static FBTErr fbt_rainbow( App *app, unsigned int offset );
static FBTErr fbt_marching_rainbow( App *app );
/* Global Variable Declarations */
/* Function Definitions */
int main( int argc, char *argv[] ) {
FBTErr err = FBT_E_NOERR;
App *app = NULL;
do {
/* initialize the app structure */
if( ( FBT_E_NOERR != ( err = fbt_init( &app, argc, argv ) ) ) ) {
break;
}
/* clear the screen */
if( ( FBT_E_NOERR != ( err = fbt_clear( app ) ) ) ) {
break;
}
/* paint a rainbow */
if( ( FBT_E_NOERR != ( err = fbt_marching_rainbow( app ) ) ) ) {
break;
}
} while( 0 );
if( ( FBT_E_FILE == err ) || ( FBT_E_SCREENO == err ) ||
( FBT_E_SCREENM == err ) ) {
perror( NULL );
}
fbt_cleanup( app );
return( err & 0xFF );
}
Initializing Things
For my money, this is where the most interesting stuff relating to frame buffer access occurs.. and it's not really that interesting. It's just boilerplate code.
/* fbt_init() - initialize the app structure
** if *app is null, the function will allocate a new app structure for you.
** otherwise it assumes you've done it yourself. If app is null... well, that's
** an error.
*/
FBTErr fbt_init( App **app, int argc, char *argv[] ) {
FBTErr err = FBT_E_NOERR;
unsigned int unalloc = 0;
struct fb_var_screeninfo screeninfo;
unsigned int bpp;
do {
if( NULL == app ) {
err = FBT_E_NULL;
break;
}
if( NULL == *app ) {
*app = (App *) malloc( sizeof( App ) );
unalloc = 1;
if( NULL == *app ) {
unalloc = 0;
err = FBT_E_MALLOC;
break;
}
}
memset( *app, 0, sizeof( App ) );
(*app)->screen_fd = -1;
(*app)->device = "/dev/fb0";
printf( "opening %s\n", (*app)->device );
if( ( (*app)->screen_fd = open( (*app)->device, O_RDWR ) ) < 0 ) {
err = FBT_E_FILE;
break;
}
printf( "device %s is file descriptor %d\n", (*app)->device, (*app)->screen_fd );
if( ( ioctl( (*app)->screen_fd, FBIOGET_VSCREENINFO, &screeninfo ) ) > 0 ) {
err = FBT_E_SCREENO;
break;
}
if( screeninfo.bits_per_pixel > 24 ) {
bpp = 32;
} else if( screeninfo.bits_per_pixel > 16 ) {
bpp = 24;
} else if( screeninfo.bits_per_pixel > 8 ) {
bpp = 16;
} else {
bpp = 8;
}
(*app)->width = screeninfo.xres_virtual;
(*app)->height = screeninfo.yres_virtual;
(*app)->bpp = screeninfo.bits_per_pixel;
(*app)->size = (*app)->height * (*app)->width * ( bpp / 8 );
(*app)->left_margin = screeninfo.left_margin;
(*app)->right_margin = screeninfo.right_margin;
(*app)->top_margin = screeninfo.upper_margin;
(*app)->bottom_margin = screeninfo.lower_margin;
printf( "got device info: %d x %d (%d bits per pixels)\nmargin is (%d,%d,%d,%d)\n",
(*app)->width, (*app)->height, (*app)->bpp, (*app)->left_margin,
(*app)->right_margin, (*app)->top_margin, (*app)->bottom_margin );
(*app)->screen_ptr = (unsigned char *) mmap( 0, (*app)->size,
( PROT_READ | PROT_WRITE ), MAP_SHARED, (*app)->screen_fd, 0);
if( MAP_FAILED == (*app)->screen_ptr ) {
err = FBT_E_SCREENM;
break;
}
printf( "screen pointer is %d bytes at %08X\n", (*app)->size, (*app)->screen_ptr );
unalloc = 0;
} while( 0 );
if( 0 != unalloc ) {
free( *app );
*app = NULL;
}
return( err );
}
Clearing the Screen
This code demonstrates simple access to the screen. We put a 0xFF in every byte in the frame buffer. It's pretty straight forward...
static FBTErr fbt_clear( App *app ) {
FBTErr err = FBT_E_NOERR;
unsigned int i, size;
do {
if( NULL == app ) {
err = FBT_E_NULL;
break;
}
for( i = 0; i < app->size; i++ ) {
app->screen_ptr[i] = 0xFF;
}
} while( 0 );
return( err );
}
Drawing a Rainbow
This code shows how I draw a rainbow pattern.
static FBTErr fbt_rainbow( App *app, unsigned int offset ) {
unsigned int i,c,x;
for( i = 0; i < app->size; i++ ) {
switch( i % 3 ) {
case 0:
c = ( offset + i ) % 0x40000;
x = c;
break;
case 1:
x = c >> 9;
break;
case 2:
x = c >> 18;
}
app->screen_ptr[i] = (unsigned char) ( x & 0xFF );
}
}
static FBTErr fbt_marching_rainbow( App *app ) {
unsigned int i;
fd_set foo;
struct timeval time;
FD_ZERO( &foo );
memset( &time, 0, sizeof( struct timeval ) );
time.tv_sec = 0;
time.tv_usec = 150000;
for( i = 0; i < 130560; i++ ) {
fbt_rainbow( app, i * 3 );
select( 0, &foo, &foo, &foo, &time );
}
return( FBT_E_NOERR );
}
Cleaning Up
Before we quit, we unmap the screen and close the device file. I think that Linux is smart enough to do this automagically when your process quits, but hey, it's always polite to explicitly tell the system your intentions.
static FBTErr fbt_cleanup( App *app ) {
FBTErr err = FBT_E_NOERR;
do {
if( NULL == app ) {
err = FBT_E_NULL;
break;
}
if( ( NULL != app->screen_ptr ) && ( MAP_FAILED != app->screen_ptr ) ) {
munmap( app->screen_ptr, app->size );
}
if( app->screen_fd >= 0 ) {
close( app->screen_fd );
}
} while( 0 );
return( err );
}