Skip to content
Larry Bank edited this page Feb 17, 2025 · 12 revisions

Public API

The BBEPAPER class exposes the methods described below. One challenge to document each method is the behavior when working with a backing buffer (local copy of the graphics in RAM) versus working without one (aka Bufferless).

{
  public:
    BBEPAPER(int iPanel);
    void setAddrWindow(int x, int y, int w, int h);
#ifdef ARDUINO
    void initIO(int iDC, int iReset, int iBusy, int iCS = SS, int iMOSI = MOSI, int iSCLK = SCK, uint32_t u32Speed = 8000000);
#else
    void initIO(int iDC, int iReset, int iBusy, int iCS, int iSPIChannel, uint32_t u32Speed = 8000000);
#endif
    int writePlane(int iPlane = PLANE_DUPLICATE);
    void startWrite(int iPlane);
    void writeData(uint8_t *pData, int iLen);
    void writeCmd(uint8_t u8Cmd);
    int refresh(int iMode, bool bWait = true);
    void setBuffer(uint8_t *pBuffer);
    int allocBuffer(void);
    void * getBuffer(void);
    void freeBuffer(void);
    uint32_t capabilities();
    void setRotation(int iAngle);
    int getRotation(void);
    void fillScreen(int iColor, int iPlane = PLANE_DUPLICATE);
    void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
    void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
    void setTextWrap(bool bWrap);
    void setTextColor(int iFG, int iBG = -1);
    void setCursor(int x, int y);
    int loadBMP(const uint8_t *pBMP, int x, int y, int iFG, int iBG);
    int loadBMP3(const uint8_t *pBMP, int x, int y);
    int loadG5Image(const uint8_t *pG5, int x, int y, int iFG, int iBG, float fScale);
    void setFont(int iFont);
    void setFont(const void *pFont);
    void drawLine(int x1, int y1, int x2, int y2, int iColor);
    void drawPixel(int16_t x, int16_t y, uint8_t color);
    int16_t getCursorX(void);
    int16_t getCursorY(void);
    void getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
#ifdef ARDUINO
    void getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
#endif
    int dataTime();
    int opTime();
    int16_t height(void);
    int16_t width(void);
    void drawCircle(int32_t x, int32_t y, int32_t r, uint32_t color);
    void fillCircle(int32_t x, int32_t y, int32_t r, uint32_t color);
    void drawEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color);
    void fillEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color);
    void sleep(int bDeep);
    void wait(bool bQuick = false);
    void drawString(const char *pText, int x, int y);
    void setPlane(int iPlane);
    int getPlane(void);
    int getChip(void);
    void drawSprite(const uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iColor);    
#ifdef _LINUX_
    void print(const char *pString);
    void println(const char *pString);
    void print(int, int);
    void println(int, int);
    void print(const string &);
    void println(const string &);
    size_t write(uint8_t ucChar);
    void delayMicroseconds(int iTime);
#else
#ifndef __AVR__
    using Print::write;
    virtual size_t write(uint8_t);
#endif // __AVR__
#endif // _LINUX_

  private:
    BBEPDISP _bbep;
}; // class BBEPAPER

The class is a C++ wrapper of C code which does all of the actual work. The BBEPDISP structure is kept private, but is passed as a pointer to the 'worker' functions. This makes it easy to lift out the C code if you want to use it in a pure C project. Here are details of each method:
Instantiating the class requires passing it a valid e-paper panel type. See bb_epaper.h for a list of supported panels.
initIO()
There are two versions of the initIO() method - one for Arduino and the other for Linux/RPI. The library needs 4 GPIO lines (CS, D/C, RESET and BUSY), along with an SPI instance in order to control the e-paper panels.

writePlane(int iPlane)
This method writes your copy of the image plane(s) to the e-paper display memory (from your local framebuffer). With e-paper, sending the pixels to the display doesn't imply that they will be displayed. A call to refresh() is needed to show them. This method can send either one of the two planes, duplicate one, or send both. See the PLANE options for how. When working without a local framebuffer, this method will do nothing.

startWrite(int iPlane)
This method tells the e-paper controller to expect pixel data in the near future and when it arrives, to write it to the given plane. This method only accepts PLANE_0 and PLANE_1 as parameters. This method is for expert users who want more control of how data is formatted and sent to the display.

writeData(uint8_t *pData, int iLen)
This method writes raw pixel data to the memory of the e-paper panel. The startWrite() method tells the controller which plane is being written. This method is for expert users who want more control of how data is formatted and sent to the display.

writeCmd(uint8_t cmd)
This method writes a single byte command to the e-paper panel. This method is for expert users who want more control of how data is formatted and sent to the display.

refresh(int iMode, bool bWait = true)
This method causes the epaper display to refresh with the data in its memory. The mode can be one of three values: REFRESH_FULL, REFRESH_FAST and REFRESH_PARTIAL. Full refresh is where there is a series of white/black flashes of the whole display and can last several seconds. Fast refresh is a feature of some panels which reduces the number of cycles of flashing. Finally, partial refresh is faster and just updates the pixels which changed (only available on B/W panels). It is advisable to do a full refresh after a number of partial refreshes so that the colored particles don't accumulate a charge and become stuck. The final parameter allows you to optionally wait for the operation to complete (tests the BUSY signal line).

void setBuffer(uint8_t *pBuffer); int allocBuffer(void); void * getBuffer(void); void freeBuffer(void);

How to...work with e-paper

As a display type, e-paper is unique. It presents some serious challenges (slow updates), and some amazing benefits (high contrast, wide viewing angles and no energy needed to retain the image). Because of it's uniqueness, the software which drives it also needs to be unique. Here are the basic steps needed to work with e-paper:

  • Wake up the e-paper controller from deep sleep (toggle RESET signal)
  • Send a sequence of commands to initialize the controller (specific to the panel it's controlling)
  • Transmit the pixels to the internal framebuffer of the controller (from your local copy or directly generated)
  • Tell the controller the type of update to do (optionally send a lookup table)
  • Tell the controller to initiate the update
  • Wait for the controller to finish the update (monitor the BUSY signal)
  • Tell the e-paper controller to sleep

How do we accomplish this with the bb-epaper library?
Here is a minimal "hello world" program which does all of the above:
 BBEPAPER bbep(EPD42_400x300); // instantiate the class with the panel type
 bbep.initIO(PIN_DC, PIN_RST, PIN_BUSY, PIN_CS, MOSI, SCK, 4000000); // tell the library how the panel is connected
 // The bb_epaper library functions automatically wake up the panel if it was in a sleeping state
 bbep.fillScreen(BBEP_WHITE); // fill the display memory with white (no local copy of graphics)
 bbep.setFont(FONT_16x16); // use the internal 16x16 font
 bbep.setTextColor(BBEP_BLACK);
 bbep.setCursor(0,0); // start at the top left of the display (native orientation)
 bbep.println("Hello World"); // use the println() method to draw the text directly into the e-paper's framebuffer memory
 bbep.refresh(REFRESH_FULL, true); // do a full refresh and wait for BUSY to go idle
 bbep.sleep(DEEP_SLEEP); // put the panel into deep sleep mode (framebuffer RAM is retained)

Internal Memory Format

The RAM framebuffer (on the MCU side) consists of one or two planes of 1-bit per pixel image memory. Two planes will be allocated when working with both B/W and 3/4 color e-paper displays. The reason B/W displays need 2 memory buffers is to support partial updates. The memory is laid out in the same way as the e-paper's internal framebuffer: each row of pixels has horizontal bytes with the MSB on the left. In other words, pixel 0,0 is bit 7 of byte 0, while pixel 1,0 is bit 6 of byte 0. The upper left corner of the native orientation of the display is 0,0. E-paper displays such as the 2.9" 128x296 are native portrait orientation (tall). You can normally tell where row 0 of the display is based on the chip position (usually covered with a white rubber protector).

Full vs. Fast vs. Partial Updates

E-paper displays have a lot of complex physics happening under the covers. Each pixel of a Black/White display is composed of a collection of tiny black and white pigment granules floating in tiny oil beads. The granules have a positive or negative charge and are moved by placing them in an electric field. A large positive and negative voltage (15 to 36V) is applied to a conductor near each pixel to push the granules that have the same charge, or pull the granules that have the opposite charge. The process of changing the pixel color takes time and multiple passes. The mobility of the granules in the oil is also changed depending on the temperature and if they are in motion or at rest. Each pulse of the electric field pushes the granules on their path, but doesn't push them fully. They must be given multiple pushes to get to their extreme positions of maximum white or black. The 'full refresh' mode uses multiple passes of sending all of the pixels to their extreme white and black positions to increase their mobility in the oil, then the final step sets them to the color that you've chosen. The 'fast' version of this is available as a built-in feature on some displays. It's just an alternate LUT (look up table) of timings to control the charges which reduces the number of full color swings. Finally, the 'partial' refresh is where the 'old' pixels are compared to the 'new' pixels and electric charges are only applied to the pixels which changed color. They are given a shorter push in the direction of the new color without the full black/white flash. This results in updates that are much faster and less visually jarring. The down side is that pixel colors are not able to be changed to fully black or fully white and you will see visible 'ghosting' (faint remnants of old pixel colors). It is recommended to do full updates after 10 or so partial updates to get the colors back to full contrast and avoid the possibility of leaving residual charges on the granules (need more info about this). The normal way to use the partial update feature is the have a 1-bit memory plane representing the old pixels and a 1-bit memory plane representing the new pixels. On the SSD16xx (Solomon) e-paper controllers, you just need to provide the new pixels and it does the rest. The UC8xxx (UltraChip) controllers seem to need you to explicitly write the old and new pixels to the two memory planes each time you do a partial update. For the partial update example in this library, I explicitly write the old and new pixels before each update to ensure that it works correctly on every supported display. The average times for these different types of refreshes on black and white e-paper displays is usually on the order of 2.5 seconds for full refresh, 1.5 seconds for fast refresh and 400 milliseconds for partial refresh.

Clone this wiki locally