Tuesday, 5 February 2013

Coding Gold Dust: How to write an infinite loop in Python

One of the coding style issues that has come up more than often on the Raspberry Pi forum is: how to write an infinite loop in Python?

For obvious reasons, many RPi examples are about hardware or network interfacing, and often these examples are derived from C language code. Interfacing, due to it's event-driven nature (especially when reading something), warrants the use of an infinite loop - or well, an event-loop to be more precise.

In plain C, there is no boolean data type but instead anything 'non zero' evaluates to 'true'. Hence an infinite loop in C is typically (and rightfully so) implemented as a while loop like this:
while (1) {
    printf("Hello, world!\n");
}

It seems that this does work in Python as well:
while 1:
    print "Hello, world!"

By definition a while loop continues execution as long as the condition evaluates to true. So practically any expression that evaluates to true could be used:
while (1 == 1):
    ...
test = "true"
while (test == "true"):
    ...
while (1 < 2):
    ...
However, it should be obvious that these are neither beautiful, explicit, simple, readable nor obvious - which are what 'The Zen of Python' lists as 'guiding design principles' for Python, and could be taken as guiding principles for any Python code. In addition, especially the string comparison might incur a performance hit.

Many programming languages do define proper boolean types. For example Java has a data type called boolean and defines the values true and false for that type. Hence our loop in Java would be:
while (true) {
    System.out.println("Hello, world!");
}
Python too defines a class bool, which can have two values True and False. And because there is a boolean data type, it should make perfect sense to use that and (for consistency) only that to implement an infinite loop:
while True:
    print "Hello, world!"
I rest my case ;)

In addition to The Zen of Python, there is a Python Style Guide available - definitely worth a read.

P.S. Thanks for fellow RPI forum user alexeames for the idea of 'Gold Dust' :)

Tuesday, 22 January 2013

Low-level Graphics on Raspberry Pi (part three)

So, after memmap'ing the framebuffer (see part two), it appears as a contiguous section of RAM - an array of bytes. That is why we declared the 'frame buffer pointer' variable fbp as:
  char *fbp = 0;
and the memmap:
  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 0);
behaves about the same as if we allocated the buffer ourselves like:
#include <stdio.h>

int main(int argc, char* argv[])
{
    char *fbp = 0;
    char fb[100];

    fb[0] = 'a';

    fbp = fb; // similar to mmap ...

    printf("fb[0] = %c\n", fb[0]);
    printf("*fbp = %c\n", *fbp);
}
where the pointer variable fbp points to the first byte of the array fb. You might want to read more about C data-types and arrays vs pointers from your preferred source of C programming information...

Basically all RAM memory can be seen as an array of bytes - something like this:
The same could also be used to illustrate a particular array within the memory, where the array start byte would be the 'box zero' (fb[0] in the above example) and so on until the length of the allocated array at 'box n' (fb[99]).

As the framebuffer represents the two-dimensional display 'surface', we could illustrate the pixels as:
In case of a 8 bpp ('8 bits per pixel', 256 color, one byte per pixel) display mode, this would directly map to the framebuffer memory layout: so the "width'th" byte (fb[width]) in the array would represent the left-most pixel on the second (from the top) scanline and the 5th pixel on the 3rd row (highlighted in grey) would match the byte at '3 x width + 5' (fb[3 * width + 5]).

However, there are numerous other display modes generally used and in fact the default mode of RPi is 16 bpp (as seen in part one). This means that to be able to handle the framebuffer as 'just bytes', we need to switch to 8 bpp display mode.

This can be done from the command-line using the command:
fbset -depth 8
Now run the fbtest from the part one - it should output '..., 8 bpp'. If you try the fbtest2 from part two, you will most likely not get the same result as in 16 bpp mode - probably just a black screen (will have to verify this). To reset bit-depth back to original use '-depth 16' (or whatever fbtest said it was before).

So what's with the drawing now? The reason to the different result is that in 16 bpp mode, the bytes (byte-pairs) in the buffer describe the actual pixel color - in 8 bpp mode, the byte value is an index to a palette. In 16 bpp a value of 0 (in both bytes) would yield black - in 8 bit the value 0 yields the color stored in the palette at index 0 and could basically be anything in the RGB colorspace (incidently, the color 0 in the default Linux framebuffer palette seems to be black ...but it could be set to anything).

Speaking of the default palette: why not take a look what colors it contains? This example (building on the previous examples) changes the display mode to 8 bpp, draws vertical bars of varying colors (the 16 colors of the default palette), pauses for 5 seconds and restores the display settings:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>

// application entry point
int main(int argc, char* argv[])
{
  int fbfd = 0;
  struct fb_var_screeninfo orig_vinfo;
  struct fb_var_screeninfo vinfo;
  struct fb_fix_screeninfo finfo;
  long int screensize = 0;
  char *fbp = 0;


  // Open the file for reading and writing
  fbfd = open("/dev/fb0", O_RDWR);
  if (!fbfd) {
    printf("Error: cannot open framebuffer device.\n");
    return(1);
  }
  printf("The framebuffer device was opened successfully.\n");

  // Get variable screen information
  if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    printf("Error reading variable information.\n");
  }
  printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
         vinfo.bits_per_pixel );

  // Store for reset (copy vinfo to vinfo_orig)
  memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

  // Change variable info
  vinfo.bits_per_pixel = 8;
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
    printf("Error setting variable information.\n");
  }
  
  // Get fixed screen information
  if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    printf("Error reading fixed information.\n");
  }

  // map fb to user mem 
  screensize = finfo.smem_len;
  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 
                    0);

  if ((int)fbp == -1) {
    printf("Failed to mmap.\n");
  }
  else {
    // draw...
    int x, y;
    unsigned int pix_offset;

    for (y = 0; y < (vinfo.yres / 2); y++) {
      for (x = 0; x < vinfo.xres; x++) {

        // calculate the pixel's byte offset inside the buffer
        // see the image above in the blog...
        pix_offset = x + y * finfo.line_length;

        // now this is about the same as fbp[pix_offset] = value
        *((char*)(fbp + pix_offset)) = 16 * x / vinfo.xres;

      }
    }

    sleep(5);
  }

  // cleanup
  munmap(fbp, screensize);
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
    printf("Error re-setting variable information.\n");
  }
  close(fbfd);

  return 0;
  
}

Save as fbtest3.c, 'make fbtest3' and run as './fbtest3' - this should produce vertical color bars at the upper half of the screen:
Hmm, so far there has been very little Raspberry Pi specific stuff in this series - all the code should work on most (if not all) Linux systems as is ...maybe I should have thought more about the title ;)

[Continues in part four]