GxEPD2_PP : Particle Display Library for SPI E-Paper Displays


#61

@BenteV Just to understand what you are doing because it is not entirely clear to me. Are you using something like PhotoShop to create and save your .BMP files? You can read these as .bmp files directly into and write to the EPD but this requires an understanding of the structure of BMP files and of the way the image on the EPD is scanned. BMP with monochrome (B/W) use 1 bit per pixel but the image is stored bottom to top in the BMP data area. There is packing on each line/row to the nearest multiple of 4 bytes. The image then needs to be scanned vertically from Left to Right or horizontally depending upon the orientation to create the char array. A slight further complication is the image needs to be padded to the nearest 8 bits i.e. a full byte. As @ZinggJM suggests, the easiest way to do this is download and use the image2lcd app to output files with the c array formatted correctly. This isn’t something you can do by hand but you can write a program to read .bmp files and format correctly.


#62

Thanks for the respons!

I was using a bitmaptoC program for converting my bitmaps to a C-array. And non of the arrays worked, thay al came out disorted. Now I’m using a processing program to convert bitmaps to C-array and it is working. All the other converting programs and online convert tools didn’t work for me!


#63

@BenteV,

if I look at your picture, it looks like you try to draw the second bitmap with a wrong width. That’s why I asked for more information, e.g. drawBitmap parameters.

I don’t know if there exists a library like SdFat for Particle, like for Arduino. If yes, it might be possible to port the GxEPD2_SD_Example from GxEPD2 to GxEPD2_PP, to draw BMP files from SD cards directly to e-paper displays. But I leave this exercise to particle users, as there seem to be quite few users of GxEPD2_PP.


#64

Where is this draw bitmap from SD file - in your library?

As an aside, have you setup the GDEH0213T72 - B/W 2.13” ?

I am struggling to adapt your wavetable and GXEPD2_213 for this display. Thanks


#65

No, I don’t have the GDEH0213T72. See also: https://github.com/ZinggJM/GxEPD2/blob/master/README.md

This list is missing in GxEPD2_PP still.


#66

Could you please outline how (which function) I should use the library to draw an image of size 24, 24 pixels from a c-array to an x,y position on a 420 BW e-paper display. So this is a partial update.

display.drawImage(gImage_cell_sym24h, 380, 278, 24, 24, false, false, false);

I have used this outside of the do{ } while construct?

To clear this to the background is there something I should use like this;
display.drawImage(GxEPD_WHITE, 380, 278, 24, 24, false, false, false);?


#67

I have completed the modification of the GDE0213B1 driver to supported the replacement GDEH0213B72 display (250 x 122 BW with fast partial update). Having completed the modifications to the WaveTables.h to add a new entry for the display and changed some of the command and data values in the GxEPD2_213.cpp functions - it works. However, the display is totally flipped along the horizontal - text and all. Would you be able to point me in the direction of what is likely causing this?

GxEDP2_213.cpp code included below:

GxEPD2_213::GxEPD2_213(int8_t cs, int8_t dc, int8_t rst, int8_t busy) :
  GxEPD2_EPD(cs, dc, rst, busy, HIGH, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate)
{
  _initial = true;
  _power_is_on = false;
  _using_partial_mode = false;
}
//no change required
void GxEPD2_213::init(uint32_t serial_diag_bitrate)
{
  GxEPD2_EPD::init(serial_diag_bitrate);
  _initial = true;
  _power_is_on = false;
  _using_partial_mode = false;
}
//no change required
void GxEPD2_213::clearScreen(uint8_t value)
{
  if (_initial)
  {
    _Init_Full();
    _setPartialRamArea(0, 0, WIDTH, HEIGHT);
    _writeCommand(0x24);
    for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++)
    {
      _writeData(value);
    }
    _Update_Full();
  }
  else
  {
    if (!_using_partial_mode) _Init_Part();
    _setPartialRamArea(0, 0, WIDTH, HEIGHT);
    _writeCommand(0x24);
    for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++)
    {
      _writeData(value);
    }
    _Update_Part();
  }
  if (!_using_partial_mode) _Init_Part();
  _setPartialRamArea(0, 0, WIDTH, HEIGHT);
  _writeCommand(0x24);
  for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++)
  {
    _writeData(value);
  }
  _Update_Part();
  _initial = false;
}
//no change required
void GxEPD2_213::writeScreenBuffer(uint8_t value)
{
  if (_initial) clearScreen(value);
  else _writeScreenBuffer(value);
}
//full screen update
//void EPD_SetRAMValue_BaseMap( const unsigned char * datas)
/*
    const unsigned char  *datas_flag;   
    datas_flag=datas;
    Epaper_Write_Command(0x24);   //Write Black and White image to RAM
    for(unsigned int i=0;i<FULL_SCREEN_BYTES;i++) {Epaper_Write_Data(datas[i]);}
    datas=datas_flag;
    Epaper_Write_Command(0x26);   //Write Black and White image to RAM (Red)?
    for(unsigned int i=0;i<FULL_SCREEN_BYTES;i++) {Epaper_Write_Data(datas[i]);}
    Epaper_Write_Command(0x22); 
    Epaper_Write_Data(0xC7);   
    Epaper_Write_Command(0x20); 
    Epaper_READBUSY();
*/
//no change required
void GxEPD2_213::_writeScreenBuffer(uint8_t value)
{
  if (!_using_partial_mode) _Init_Part();
  _setPartialRamArea(0, 0, WIDTH, HEIGHT);
  _writeCommand(0x24);
  for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++)
  {
    _writeData(value);
  }
}
//
void GxEPD2_213::writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
  delay(1); // yield() to avoid WDT on ESP8266 and ESP32
  int16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded
  x -= x % 8; // byte boundary
  w = wb * 8; // byte boundary
  int16_t x1 = x < 0 ? 0 : x; // limit
  int16_t y1 = y < 0 ? 0 : y; // limit
  int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
  int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
  int16_t dx = x1 - x;
  int16_t dy = y1 - y;
  w1 -= dx;
  h1 -= dy;
  if ((w1 <= 0) || (h1 <= 0)) return;
  if (!_using_partial_mode) _Init_Part();
  _setPartialRamArea(x1, y1, w1, h1);
  _writeCommand(0x24);
  for (int16_t i = 0; i < h1; i++)
  {
    for (int16_t j = 0; j < w1 / 8; j++)
    {
      uint8_t data;
      // use wb, h of bitmap for index!
      int16_t idx = mirror_y ? j + dx / 8 + (h - 1 - i) * wb : j + dx / 8 + i * wb;
      if (pgm)
      {
#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
        data = pgm_read_byte(&bitmap[idx]);
#else
        data = bitmap[idx];
#endif
      }
      else
      {
        data = bitmap[idx];
      }
      if (invert) data = ~data;
      else _writeData(data);
    }
  }
  delay(1); // yield() to avoid WDT on ESP8266 and ESP32
}
//no change required
void GxEPD2_213::writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
  if (black)
  {
    writeImage(black, x, y, w, h, invert, mirror_y, pgm);
  }
}
//no change required
void GxEPD2_213::writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
  if (data1)
  {
    writeImage(data1, x, y, w, h, invert, mirror_y, pgm);
  }
}
//no change required
void GxEPD2_213::drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
  writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm);
  refresh(x, y, w, h);
}
//no change required
void GxEPD2_213::drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
  writeImage(black, color, x, y, w, h, invert, mirror_y, pgm);
  refresh(x, y, w, h);
}
//no change required
void GxEPD2_213::drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
{
  writeNative(data1, data2, x, y, w, h, invert, mirror_y, pgm);
  refresh(x, y, w, h);
}
//no change required
void GxEPD2_213::refresh(bool partial_update_mode)
{
  if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT);
  else
  {
    if (_using_partial_mode) _Init_Full();
    _Update_Full();
  }
}
//no change required
void GxEPD2_213::refresh(int16_t x, int16_t y, int16_t w, int16_t h)
{
  x -= x % 8; // byte boundary
  w -= x % 8; // byte boundary
  int16_t x1 = x < 0 ? 0 : x; // limit
  int16_t y1 = y < 0 ? 0 : y; // limit
  int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
  int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
  w1 -= x1 - x;
  h1 -= y1 - y;
  if (!_using_partial_mode) _Init_Part();
  _setPartialRamArea(x1, y1, w1, h1);
  _Update_Part();
}
//no change required
void GxEPD2_213::powerOn(void)
{
  _PowerOn();
}
//no change required
void GxEPD2_213::powerOff(void)
{
  _PowerOff();
}
//display part of screen - quick update
//void EPD_Dis_Part(unsigned int x_start,unsigned int y_start,const unsigned char * datas,unsigned int PART_COLUMN,unsigned int PART_LINE)
/*
    unsigned int x_end,y_start1,y_start2,y_end1,y_end2;
    x_start=x_start/8;
    x_end=x_start+PART_LINE/8-1;
    y_start1=0;
    y_start2=y_start;
    if(y_start>=256)
    {
        y_start1=y_start2/256;
        y_start2=y_start2%256;
    }
    y_end1=0;
    y_end2=y_start+PART_COLUMN-1;
    if(y_end2>=256)
    {
        y_end1=y_end2/256;
        y_end2=y_end2%256;    
    }
    Epaper_Write_Command(0x44);       // set RAM x address start/end, in page 35
    Epaper_Write_Data(x_start);    // RAM x address start at 00h;
    Epaper_Write_Data(x_end);    // RAM x address end at 0fh(15+1)*8->128 
    Epaper_Write_Command(0x45);       // set RAM y address start/end, in page 35
    Epaper_Write_Data(y_start2);    // RAM y address start at 0127h;
    Epaper_Write_Data(y_start1);    // RAM y address start at 0127h;
    Epaper_Write_Data(y_end2);    // RAM y address end at 00h;
    Epaper_Write_Data(y_end1);    // ????=0 
    Epaper_Write_Command(0x4E);   // set RAM x address count to 0;
    Epaper_Write_Data(x_start); 
    Epaper_Write_Command(0x4F);   // set RAM y address count to 0X127;    
    Epaper_Write_Data(y_start2);
    Epaper_Write_Data(y_start1);
    Epaper_Write_Command(0x24);   //Write Black and White image to RAM
    for (unsigned int i=0;i<PART_COLUMN*PART_LINE/8;i++) {Epaper_Write_Data(datas[i]);} 
    Epaper_Write_Command(0x22); //part update
    Epaper_Write_Data(0x0C);   
    Epaper_Write_Command(0x20); 
    Epaper_READBUSY();    
} */
// see above
void GxEPD2_213::_setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
  _writeCommand(0x11); // set ram entry mode
  _writeData(0x01);    // x increase, y decrease : as in demo code
  _writeCommand(0x44);
  _writeData(x / 8);
  _writeData((x + w - 1) / 8);
  _writeCommand(0x45);
  _writeData((y + h - 1) % 256);
  _writeData((y + h - 1) / 256);
  _writeData(y % 256);
  _writeData(y / 256);
  _writeCommand(0x4e);
  _writeData(x / 8);
  _writeCommand(0x4f);
  _writeData((y + h - 1) % 256);
  _writeData((y + h - 1) / 256);
}
//no change required
void GxEPD2_213::_PowerOn()
{
  if (!_power_is_on)
  {
    _writeCommand(0x22);
    _writeData(0xC0);
    _writeCommand(0x20);
    _waitWhileBusy("_PowerOn", power_on_time);
  }
  _power_is_on = true;
}
//no change required
void GxEPD2_213::_PowerOff()
{
  _writeCommand(0x22);
  _writeData(0xc3);
  _writeCommand(0x20);
  _waitWhileBusy("_PowerOff", power_off_time);
  _power_is_on = false;
  _using_partial_mode = false;
}
//
void GxEPD2_213::_InitDisplay()
{
  _writeCommand(0x01); // Panel configuration, Gate selection
  _writeData((HEIGHT - 1) % 256);
  _writeData((HEIGHT - 1) / 256);
  _writeData(0x00);
  _writeCommand(0x0C); // softstart
  _writeData(0xd7);
  _writeData(0xd6);
  _writeData(0x9d);
  _writeCommand(0x2c); // VCOM setting
  _writeData(0x50);    // * different was 0xA8
  _writeCommand(0x3a); // DummyLine
  _writeData(0x1a);    // 4 dummy line per gate
  _writeCommand(0x3b); // Gatetime
  _writeData(0x08);    // 2us per line
  _setPartialRamArea(0, 0, WIDTH, HEIGHT);
}
/*****************************************************good display commands
//Initialise for full screen update
//void EPD_Full_Init(void)
/*
    pinResetFast(RES_Pin);     
    delay(100);             //was 1 now 100
    pinSetFast(RES_Pin);    //hard reset  
    delay(100);             //was 1 now 100

    Epaper_READBUSY();
    Epaper_Write_Command(0x12); // soft reset
    Epaper_READBUSY();

    Epaper_Write_Command(0x74); //set analog block control       
    Epaper_Write_Data(0x54);
    Epaper_Write_Command(0x7E); //set digital block control          
    Epaper_Write_Data(0x3B);

    Epaper_Write_Command(0x01); //Driver output control      
    Epaper_Write_Data(0xF9);
    Epaper_Write_Data(0x00);
    Epaper_Write_Data(0x00);

    Epaper_Write_Command(0x11); //data entry mode       
    Epaper_Write_Data(0x01);

    Epaper_Write_Command(0x44); //set Ram-X address start/end position   
    Epaper_Write_Data(0x00);
    Epaper_Write_Data(0x0F);    //0x0C-->(15+1)*8=128

    Epaper_Write_Command(0x45); //set Ram-Y address start/end position          
    Epaper_Write_Data(0xF9);   //0xF9-->(249+1)=250
    Epaper_Write_Data(0x00);
    Epaper_Write_Data(0x00);
    Epaper_Write_Data(0x00); 

    Epaper_Write_Command(0x3C); //BorderWavefrom
    Epaper_Write_Data(0x03);  

    Epaper_Write_Command(0x2C);     //VCOM Voltage
    Epaper_Write_Data(0x50);        //was 0x55 Good Display 0x70 changed

    Epaper_Write_Command(0x03); //      
    Epaper_Write_Data(0x15);

    Epaper_Write_Command(0x04); //      0x15,0x41,0xA8,0x32,0x30,0x0A

    Epaper_Write_Data(0x41);    
    Epaper_Write_Data(0xA8);    
    Epaper_Write_Data(0x32);    

    Epaper_Write_Command(0x3A);     //Dummy Line   
    Epaper_Write_Data(0x30);    
    Epaper_Write_Command(0x3B);     //Gate time 
    Epaper_Write_Data(0x0A);   

    EPD_select_LUT((unsigned char *)LUT_DATA); //LUT

    Epaper_Write_Command(0x4E);   // set RAM x address count to 0;
    Epaper_Write_Data(0x00);
    Epaper_Write_Command(0x4F);   // set RAM y address count to 0X127;    
    Epaper_Write_Data(0xF9);
    Epaper_Write_Data(0x00);
    Epaper_READBUSY();
*/
// initialise for full screen update
void GxEPD2_213::_Init_Full()
{
    _InitDisplay();
    _writeCommandDataPGM(GxGDEH0213B72_LUTDefault_full, sizeof(GxGDEH0213B72_LUTDefault_full));
    _writeCommand(0x37);    //added the following that were missing from original
    _writeData(0x00);  
    _writeData(0x00);  
    _writeData(0x00);  
    _writeData(0x00);  
    _writeData(0x40);  
    _writeData(0x00);  
    _writeData(0x00);
    _PowerOn();
    _using_partial_mode = false;
}
//initialise for partial update
//void EPD_Part_Init(void)
/*
    Epaper_Write_Command(0x2C);     //VCOM Voltage
    Epaper_Write_Data(0x50);        //try at a higher level
    Epaper_READBUSY();  
    EPD_select_LUT(( unsigned char *)LUT_DATA_part);
    Epaper_Write_Command(0x37); 
    Epaper_Write_Data(0x00);  
    Epaper_Write_Data(0x00);  
    Epaper_Write_Data(0x00);  
    Epaper_Write_Data(0x00);  
    Epaper_Write_Data(0x40);  
    Epaper_Write_Data(0x00);  
    Epaper_Write_Data(0x00);
    Epaper_Write_Command(0x22); 
    Epaper_Write_Data(0xC0);   
    Epaper_Write_Command(0x20); 
    Epaper_READBUSY();
*/
// initialise for part update OK
void GxEPD2_213::_Init_Part()
{
    _InitDisplay();
    _writeCommandDataPGM(GxGDEH0213B72_LUTDefault_part, sizeof(GxGDEH0213B72_LUTDefault_part));
    _writeCommand(0x37);    //added the following that were missing from original
    _writeData(0x00);  
    _writeData(0x00);  
    _writeData(0x00);  
    _writeData(0x00);  
    _writeData(0x40);  
    _writeData(0x00);  
    _writeData(0x00);
    _PowerOn();
    _using_partial_mode = true;
}
// update full screen OK
void GxEPD2_213::_Update_Full()
{
  _writeCommand(0x22);
  _writeData(0xC7);         //was 0xC4
  _writeCommand(0x20);
  _waitWhileBusy("_Update_Full", full_refresh_time);
  //_writeCommand(0xff);    //don't understand what this does
}
// update part of screen OK
void GxEPD2_213::_Update_Part()
{
  _writeCommand(0x22);
  _writeData(0xC0);         //was 0x04
  _writeCommand(0x20);
  _waitWhileBusy("_Update_Part", partial_refresh_time);
  //_writeCommand(0xff);    //don't understand what this does
}

#68

I have sent an e-mail to Good Display to ask if they are willing to sponsor me with a GDEH0213B72 panel, to support it in my libraries.

If I get a GDEH0213B72 panel, I will support it in my libraries.
Otherwise I am not willing to put any effort in this.


#69

They have been very responsive and supportive to me. Whilst you are awaiting a reply from Good Display - would you be able to answer my 2 questions:

  1. for the 4.2" BW display > To clear this to the background is there something I should use like this;

display.drawImage(GxEPD_WHITE, 380, 278, 24, 24, false, false, false); ?

Is there an obvious reason why the GDEH0213B72 display is showing as bottom to top rather than top to bottom?

Just looking for something I can narrow my search to fix it - like this is set by a command 0x11 for data entry sequence?


#70

To answer question 2 - I found that in GxEPD2_BW.h _reverse = true; in the constructor solves the issue. In the original code you had set this true if the panel was a GDE0213B1.

I will now move onto debugging the partial update mode as this does not appear to be working. Probably to do with the initialisation commands and data - I am seeing no output.


#71

I’m having a blast playing around with https://rop.nl/truetype2gfx/ - easy way to change the font to a custom front.

The only issue is that the resolution could be better on some of them. Is there a way to increase the resolution and get some sharper and more crisp characters?


#72

I have published Version 1.1.0.

  • corresponds to GxEPD2 Version 1.1.6
  • added support for GDEH0213B72 2.13" b/w, replacement for GDE0213B1

I have tested compile, upload and execution with Particle Desktop IDE.
Testing with Particle WEB-IDE was not possible, as selection of the library hung on Loading…
I have no time to find out why the Particle environment doesn’t work for me.

The e-paper panel GDEH0213B72 is the first free sample I received from Good Display.
I had asked for a free sample, as the GDE0213B1 is no longer available.

I thank Good Display and Waveshare for making affordable e-paper displays available for the Arduino community.

Jean-Marc Zingg


E-Ink display showing today's quote
#73

I finally completed one of my goals with the e-ink display

Thank you so much @ZinggJM :hugs::dizzy::coffee::clinking_glasses::rainbow::tada::confetti_ball:

(another goal is to print some photos via the internet - but that don’t seem to possible)


#74

Printing photos via (from) the internet would require 1. an http: site with 2. photos in .bmp format since conversion of .bmp to display on the e-ink displays is possible - just a lot of processing.

Nice project.


#75

Just seen this post - I am glad Good Display gave you a free sample to develop against.

Something must have gone wrong with the library. When I tried to open it (from Web IDE), I just see Loading… other libraries are OK.


#76

Thank you all for the feedback, it is a pleasure to learn that this library is useful and used on the Particle platform.

Please note that I also have published a version of GxEPD2 for Particle, there is now a common source on GitHub, only the example(s) are different. The example for Particle is under extras/Particle on GitHub. This version also does not load in the Particle WEB-IDE.

@Norfeldt
GxEPD2 has an example GxEPD2_WiFi_Example for loading bitmaps from the WEB. It uses the classes WiFiClient and WiFiClientSecure. It should be quite easily portable to the Particle environment, but I don’t have the time for it.

@armor
The failure to load in the WEB-IDE is known and reported in my post above. You need to use the Desktop-IDE instead. Most likely the library has too many files for the WEB-IDE now; I didn’t have the time to investigate.

Whoever wants to investigate the WEB-IDE loading issue can compare the versions on GitHub, and/or load a previous version as private library. I learnt that you can simply upload a private library, for test or as replacement for a published library.

For me it is every time an abnormal amount of effort to test and verify a version for the Particle environment. So the published versions for Particle will usually lag behind the Arduino Version.

It would be desirable to have a paged organisation of forum topics, as in the Arduino Forum, to avoid the unpleasant scrolling to the end of a lengthy topic!


#77

I find the here used Discourse approach superior to the paging approach.
By means of the “scrollbar” on the right side of the thread, you can quickly navigate to the desired post without having to scroll over and load the intermediate ones.
image
Just grab the thumb (e.g. 77/77) and drag to where you want and release it at the post you desire. I find that to be quicker and more intuitive than the paged approach used by the Arduino forum.
You even get immediate feedback what post number and what posting date you would find yourself when you let go of the handle.
This all-in-one approach also makes searching in one topic simpler (IMO).


#78

@ScruffR, thank you, I hadn’t discovered that “thumb”, it works well, and I can get fast to the end.

I admit that I am prejudiced in favor of the Arduino Environment, as I use it daily, whereas I rarely use the Particle Environment.


#79

I understand. Thank you again for making it possible to use ink-display with particle devices.


#80

Hosting .bmp files wouldn’t be a problem - it’s how to transfer it to the device (currently I use web-hooks and REST calls - so only strings of certain length allowed)