Difference between revisions of "Projets:Perso:2013:RaspEink"

From Electrolab
Jump to: navigation, search
(Driver: Modifying Demo Code to Display Custom BMP)
m (Step 4: Use it)
 
(21 intermediate revisions by one user not shown)
Line 1: Line 1:
 
== Introduction ==
 
== Introduction ==
The aim of the RaspEink project is to drive a small e-Paper display with a Raspberry Pi. While such a display could serve many purposes (status reports, weather station etc.) it will be used in this project to complete an existing home automation system, namely to display current temperature in all rooms and the status of the heating system.
+
 
 +
The aim of the RaspEink project is to drive a small e-Paper display with a Raspberry Pi. Such a display can be used for many purposes, for example to display home automation data as described here: [[Projets:Perso:2013:RaspEink FHEM]]
  
 
== Hardware ==
 
== Hardware ==
  
* Raspberry Pi Model B
+
* Raspberry Pi Model B or Raspberry Pi 2 Model B
 
* [http://www.embeddedartists.com/products/displays/lcd_27_epaper.php 2.7 inch E-Paper display from Embedded Artists]
 
* [http://www.embeddedartists.com/products/displays/lcd_27_epaper.php 2.7 inch E-Paper display from Embedded Artists]
  
Line 10: Line 11:
  
 
* Raspian linux from official source
 
* Raspian linux from official source
* [http://fhem.de/fhem.html FHEM home automation] software
 
* Perl, required to run FHEM
 
* Python for extraction of data from logfile and generation of a bitmap
 
 
* Modified version of the demo driver to send bitmap to display.
 
* Modified version of the demo driver to send bitmap to display.
  
 +
== Step 1: Set up the Hardware and test it ==
  
Software diagram:
+
Follow the excellent guide "Epaper_RaspberryPi.pdf" from Embedded Artists which can be downloaded from their website:
  
[[File:RaspEink_diagram.jpg]]
+
http://www.embeddedartists.com/products/displays/lcd_27_epaper.php
  
The central piece of software is the Python program (piper.py). It processes the logfiles that are created by FHEM and extracts all required information (temperatures, valve positions, humidity and burner status). It then creates a bitmap from this information and launches the driver. The driver, written in C, loads the BMP and sends it to the display through SPI.
+
The demo driver used at the time of writing is called '''epaper_pi_130307.tar.gz'''
Initially, the Python program was supposed to do both the image generation and hardware interfacing but the SPI implementation in Python turned out to be too buggy.
+
  
 +
The document describes how to connect the display to the Raspberry, download, compile and run the demo software and driver. The demo software simply loops between two static logos. Once this works, we are ready to...
  
== Hardware Setup and Test ==
+
== Step 2: Modify the driver to accept external bitmaps ==
  
The display is wired exactly as described in the excellent guide "Epaper_RaspberryPi.pdf" from Embedded Artists which can be downloaded from their website:
+
With the demo software working, it is time to modify the source code to make it load a BMP instead of the static logos. Please note that the modifications described below are only for educational purposes. Try them at your own risk. I assume no responsibility for any damage done by using these modifications nor do I guarantee that the modifications work.
http://www.embeddedartists.com/products/displays/lcd_27_epaper.php
+
  
The document also describes how to download, compile and run the demo software and driver. The demo software simply loops between two static logos.
+
Download this archive which contains a modified ''main.c'' and a test bitmap: [http://pozor.ch/RaspEink.tar.gz RaspEink.tar.gz] and extract it to the ''ePaper'' folder created in Step 1.
  
 +
== Step 3: Compile the modified source code ==
  
== Driver: Modifying Demo Code to Display Custom BMP ==
+
With the main.c file replaced, we have to compile again as described in the PDF guide from Step 1. Make sure you are in the same directory as main.c and type:
  
With the demo software working, it is time to modify the source code to make it load a BMP instead of the static logos. Please note that the modifications described below are only for educational purposes. Try them at your own risk. I assume no responsibility for any damage done by using these modifications nor do I guarantee that the modifications work.
+
<pre>
 +
make
 +
</pre>
  
The only modifications to the code were done in '''main.c'''.
+
== Step 4: Use it ==
  
First, the following variables and local function were introduced:
+
Test the new driver by running it without any parameter:
  
 
<pre>
 
<pre>
/******************************************************************************
+
sudo ./epaper
* Local Functions
+
</pre>
*****************************************************************************/
+
  
 +
It should display ''lorem ipsum'' text on the display, with a thin black border around it:
 +
 +
[[File:Testimage.png]]
 +
 +
You can now pass your own BMP as a parameter.
 +
 +
'''The BMP needs to have a width of 264 pixel, a height of 176 pixels and it needs to be monochrome (1 bit per pixel)'''
 +
 +
<pre>
 +
sudo ./epaper yourimage.bmp
 +
</pre>
 +
 +
[[File:RaspEink output.png]]
 +
 +
== How does it work? ==
 +
 +
Here is a quick explanation on what happens in ''main.c'':
 +
First, the following variables are declared:
 +
 +
<pre>
 
uint8_t filebuffer[176][36] = {};
 
uint8_t filebuffer[176][36] = {};
 
uint8_t buffer[176][33] = {};
 
uint8_t buffer[176][33] = {};
 +
</pre>
  
void readfile(FILE *f) {
+
''filebuffer'' will hold the BMP after it is loaded. ''buffer'' will hold the final image.
int x,y;
+
   
+
    fseek(f, 62, SEEK_SET);
+
    fread(filebuffer, 1, 176*36, f);
+
    fclose(f);
+
  
for(x=0; x<=176; ++x) {
+
<pre>
for(y=0; y<=33; ++y) {
+
int readfile(FILE *f) {
buffer[x][y] = ~filebuffer[175-x][y];
+
int x,y, width, height, bpp, offset;
}
+
int valid_picture = 1;
}
+
uint8_t header[54];
  
}
+
fread(&header, 1, 54, f);
 +
 +
width = header[18] | header[19] << 8 | header[20] << 16 | header[21] << 24;
 +
height =  header[22] | header[23] << 8| header[24] << 16 | header[25] << 24;
 +
bpp =  header[28]| header[29] << 8;
 +
offset = header[10] | header[11] << 8 | header[12] << 16 | header[13] << 24;
 
</pre>
 
</pre>
  
''filebuffer'' will hold the BMP after it is loaded. ''buffer'' will hold the final image. With ''fseek'' we go to a specific postion in the file, more precisely to the start of the actual pixel data. This postion (62 in our case) depends on the version of BMP used. There are several versions and which one is used largely depends on the image manipulation software. The Wikipedia article about the [http://en.wikipedia.org/wiki/BMP_file_format BMP file format] gives a nice overview.
+
The ''readfile'' function analyzes and reads the bitmap file passed to it. First we read the BMP header to find out the parameters of the BMP. There are several BMP versions and which one is used largely depends on the image manipulation software. The Wikipedia article about the [http://en.wikipedia.org/wiki/BMP_file_format BMP file format] gives a nice overview.
  
For the purpose of this software we are using monochrome BMP (i.e. with 1 bit per pixel). A test bitmap can be downloaded [http://pozor.ch/testimage.bmp here]. Such BMP can be created in GIMP for example by reducing the number of indexed colors to 2. After saving the bitmap, it is a good idea to open it in a Hex editor and verify the offset of the pixel data. This value is stored in position 0x0Ah. If it is not 62 (0x3Eh), you need to adjust this value above. If you don't, the image might show a shift.
+
We then extract the ''width'', ''height'' and bit depth (''bpp'') using left-shifts since the integer values are stored in little endian format.
  
The software currently does not check whether the BMP format is OK.
+
The ''offset'' is very important. It tells us where in the file the actual pixel data starts. This ''offset'' again depends on the specific BMP version used.
  
The ''for'' loops then simply flip and invert the pixel data.
+
<pre>
 +
printf("Width: %d\n", width);
 +
if (width != 264) {
 +
printf("ERROR: Wrong width of BMP. Needs to be 264 pixels.\n");
 +
valid_picture = 0;
 +
}
 +
printf("Height: %d\n", height);
 +
if (height != 176) {
 +
printf("ERROR: Wrong height of BMP. Needs to be 176 pixels.\n");
 +
valid_picture = 0;
 +
}
 +
printf("Bits per pixel: %d\n", bpp);
 +
if (bpp != 1) {
 +
printf("ERROR: Wrong bit depth of BMP. Needs to be monochrome (1 bit per pixel).\n");
 +
valid_picture = 0;
 +
}
 +
        printf("Data offset of pixel array: %d\n", offset);
 +
</pre>
  
The main method can then be replaced completely by this one:
+
This block of code test that the dimensions and bit depth are correct.
 +
 
 +
<pre>
 +
      if ( valid_picture == 1) {
 +
        fseek(f, offset, SEEK_SET);
 +
        fread(filebuffer, 1, 176*36, f);
 +
        fclose(f);
 +
 
 +
        for(x=0; x<=176; ++x) {
 +
        for(y=0; y<=33; ++y) {
 +
        buffer[x][y] = ~filebuffer[175-x][y];
 +
        }
 +
        }
 +
        return 1;
 +
        }
 +
        else return 0;
 +
</pre>
 +
 
 +
If the picture is valid, we can start reading it into memory.
 +
 
 +
With ''fseek'' we go to the specific postion in the file where the pixel data starts (''offset'').
 +
 
 +
The ''for'' loops then simply flip and invert the pixel data and save it into the final ''buffer''.
  
 
<pre>
 
<pre>
/******************************************************************************
 
* Main method
 
*****************************************************************************/
 
 
int main(int argc, char *argv[])
 
int main(int argc, char *argv[])
 
{
 
{
  
 
   uint8_t* pOldImg;
 
   uint8_t* pOldImg;
 +
 
   char* path = "";
 
   char* path = "";
 
   FILE *f = NULL;
 
   FILE *f = NULL;
Line 103: Line 161:
 
 
 
   }
 
   }
 +
</pre>
  
 
+
In the ''main'' function we check if an argument (your own BMP file) was passed at the start of the program (''argc == 2'') or not (''argc == 1''). If no argument is passed, we load the included test image. Otherwise we try to load the BMP indicated by the user.
 +
 
 +
<pre>
 
   bsp_init();
 
   bsp_init();
 
    
 
    
   readfile(f);
+
   if (readfile(f) == 1) {
  
  memset((uint8_t*)&PreloadImage[0][0], 0xff, 176*33);
+
  printf("Sending Image...\n");
  pOldImg = (uint8_t*)&PreloadImage[0][0];
+
  epd_DisplayImg(DISPLAY_IN_USE, (uint8_t*)&buffer[0][0], (uint8_t*)&buffer[0][0]);
  
 
+
  printf("Done.\n");
    printf("Sending Image...\n");
+
  return 1;
    epd_DisplayImg(DISPLAY_IN_USE, (uint8_t*)&buffer[0][0], pOldImg);
+
  }
    pOldImg = (uint8_t*)&buffer[0][0];
+
  else {
 
+
    printf("No valid picture. Aborting.\n");
 
+
    return 0;
  printf("Done.\n");
+
  }
  return 1;
+
}
+
 
</pre>
 
</pre>
  
The functions above simply load the file after doing some basic error checks. If no file is mentioned as a parameter or the file is not found, a test bitmap is loaded (the bitmap can be downloaded [http://pozor.ch/testimage.bmp here]).
+
Finally, we initialize the display using the function supplied by the driver from Embedded Artists. We then read the BMP and if successful, start sending it to the display.
 
+
If all compiles well, you can use the software now to load your own bitmap:
+
  
<pre>sudo ./epaper yourbitmap.bmp</pre>
+
The ''epd_DisplayImg'' usually needs both the new image and the previous image as arguments. This is to reduce ghosting effects, i.e. the driver will first display the inverse of the previous frame to erase the picture before sending the new frame. Since our driver does not know what the previous frame was, we simply send the new frame twice.
  
== piper.py: Generating BMP ==
+
That's it!

Latest revision as of 22:28, 26 March 2015

Introduction

The aim of the RaspEink project is to drive a small e-Paper display with a Raspberry Pi. Such a display can be used for many purposes, for example to display home automation data as described here: Projets:Perso:2013:RaspEink FHEM

Hardware

Software Overview

  • Raspian linux from official source
  • Modified version of the demo driver to send bitmap to display.

Step 1: Set up the Hardware and test it

Follow the excellent guide "Epaper_RaspberryPi.pdf" from Embedded Artists which can be downloaded from their website:

http://www.embeddedartists.com/products/displays/lcd_27_epaper.php

The demo driver used at the time of writing is called epaper_pi_130307.tar.gz

The document describes how to connect the display to the Raspberry, download, compile and run the demo software and driver. The demo software simply loops between two static logos. Once this works, we are ready to...

Step 2: Modify the driver to accept external bitmaps

With the demo software working, it is time to modify the source code to make it load a BMP instead of the static logos. Please note that the modifications described below are only for educational purposes. Try them at your own risk. I assume no responsibility for any damage done by using these modifications nor do I guarantee that the modifications work.

Download this archive which contains a modified main.c and a test bitmap: RaspEink.tar.gz and extract it to the ePaper folder created in Step 1.

Step 3: Compile the modified source code

With the main.c file replaced, we have to compile again as described in the PDF guide from Step 1. Make sure you are in the same directory as main.c and type:

make

Step 4: Use it

Test the new driver by running it without any parameter:

sudo ./epaper

It should display lorem ipsum text on the display, with a thin black border around it:

Testimage.png

You can now pass your own BMP as a parameter.

The BMP needs to have a width of 264 pixel, a height of 176 pixels and it needs to be monochrome (1 bit per pixel)

sudo ./epaper yourimage.bmp

RaspEink output.png

How does it work?

Here is a quick explanation on what happens in main.c: First, the following variables are declared:

uint8_t filebuffer[176][36] = {};
uint8_t buffer[176][33] = {};

filebuffer will hold the BMP after it is loaded. buffer will hold the final image.

int readfile(FILE *f) {
	int x,y, width, height, bpp, offset;
	int valid_picture = 1;
	uint8_t header[54];

	fread(&header, 1, 54, f);
	
	width = header[18] | header[19] << 8 | header[20] << 16 | header[21] << 24;
	height =  header[22] | header[23] << 8| header[24] << 16 | header[25] << 24;
	bpp =  header[28]| header[29] << 8;
	offset = header[10] | header[11] << 8 | header[12] << 16 | header[13] << 24;

The readfile function analyzes and reads the bitmap file passed to it. First we read the BMP header to find out the parameters of the BMP. There are several BMP versions and which one is used largely depends on the image manipulation software. The Wikipedia article about the BMP file format gives a nice overview.

We then extract the width, height and bit depth (bpp) using left-shifts since the integer values are stored in little endian format.

The offset is very important. It tells us where in the file the actual pixel data starts. This offset again depends on the specific BMP version used.

	printf("Width: %d\n", width);
	if (width != 264) {
		printf("ERROR: Wrong width of BMP. Needs to be 264 pixels.\n");
		valid_picture = 0;
	}
	printf("Height: %d\n", height);
	if (height != 176) {
		printf("ERROR: Wrong height of BMP. Needs to be 176 pixels.\n");
		valid_picture = 0;
	}
	printf("Bits per pixel: %d\n", bpp);
	if (bpp != 1) {
		printf("ERROR: Wrong bit depth of BMP. Needs to be monochrome (1 bit per pixel).\n");
		valid_picture = 0;
	}
        printf("Data offset of pixel array: %d\n", offset);

This block of code test that the dimensions and bit depth are correct.

       if ( valid_picture == 1) {
        	fseek(f, offset, SEEK_SET);
        	fread(filebuffer, 1, 176*36, f);
        	fclose(f);

        	for(x=0; x<=176; ++x) {
        		for(y=0; y<=33; ++y) {
        			buffer[x][y] = ~filebuffer[175-x][y];
        		}	
        	}
        return 1;
        }
        else return 0;

If the picture is valid, we can start reading it into memory.

With fseek we go to the specific postion in the file where the pixel data starts (offset).

The for loops then simply flip and invert the pixel data and save it into the final buffer.

int main(int argc, char *argv[])
{

  uint8_t* pOldImg;

  char* path = "";
  FILE *f = NULL;
  
  if(argc == 1) {
	path = "testimage.bmp";
	printf("No file specified, loading testimage.bmp\n");
	f = fopen(path, "rb");
  }
  
  
  if(argc == 2) {
	path = argv[1];
	printf("Loading %s\n", path);
	f = fopen(path, "rb");
	if(f == NULL) {
	  printf("File not found, loading testimage.bmp\n");
	  path = "testimage.bmp";
	  f = fopen(path, "rb");	
	}
	
  }

In the main function we check if an argument (your own BMP file) was passed at the start of the program (argc == 2) or not (argc == 1). If no argument is passed, we load the included test image. Otherwise we try to load the BMP indicated by the user.

  bsp_init();
  
  if (readfile(f) == 1) {

	  printf("Sending Image...\n");
	  epd_DisplayImg(DISPLAY_IN_USE, (uint8_t*)&buffer[0][0], (uint8_t*)&buffer[0][0]);

	  printf("Done.\n");
	  return 1;
  }
  else {
  	  printf("No valid picture. Aborting.\n");
  	  return 0;
  }

Finally, we initialize the display using the function supplied by the driver from Embedded Artists. We then read the BMP and if successful, start sending it to the display.

The epd_DisplayImg usually needs both the new image and the previous image as arguments. This is to reduce ghosting effects, i.e. the driver will first display the inverse of the previous frame to erase the picture before sending the new frame. Since our driver does not know what the previous frame was, we simply send the new frame twice.

That's it!