Display driver for Sharp's range of SPI-driven memory LCD's, leveraging the power of asynchronous transfers. Currently supports LS013B7DH03, but easily extendable to other models as well.
Dependents: memLCD-Demo memLCD-Demo memLCD-Demo MemLCD-Temperature-Humidity-Demo ... more
Information
All examples in this repo are considered EXPERIMENTAL QUALITY, meaning this code has been created as one-off proof-of-concept and is suitable as a demonstration for experimental purposes only. This code will not be regularly maintained by Silicon Labs and there is no guarantee that these projects will work across all environments, SDK versions and hardware.
Caution
This library builds upon the asynchronous SPI interface provided by mbed, but not all platforms currently support this. So, make sure your platform is capable of asynchronous SPI (all Silicon Labs platforms are), otherwise you will get compiler errors!
Usage
The library is purposefully quite simple to use. To set it up, you initialize an SPI object and the required I/O pins, and call the library constructor with those. Make sure the display is powered on, enabled, and the inversion mode is set to external (EXTMODE is high).
setup
#define SCK PD2 #define MOSI PD0 DigitalOut CS(PD3); DigitalOut EXTCOM(PC4); SPI displaySPI(MOSI, NC, SCK); silabs::LS013B7DH03 display(&displaySPI, &CS, &EXTCOM);
You should also swap out the pin names for the relevant names on your platform.
After setup, you usually want to clear any static information left on the screen. To do that, you would call clearImmediate, and optionally provide a callback. The callback will get called when the clearing operation is complete and the display has been cleared. In this example, refreshCallback sets a global boolean 'refreshed' to true when called.
clearing the display
refreshed = false; int result = display.clearImmediate(refreshCallback); if(result == LS013B7DH03_OK) { while(refreshed == false) sleep(); } else { printf("Display error: %d", result); }
Of course, instead of sleeping while the display is clearing, you could also just continue with the program, and check back whether the callback happened or not.
Then comes the fun part, actually writing stuff on the display! Since the display only supports one-way communication (i.e. you cannot read from it), it uses an internal frame buffer in RAM. So, to actually display something on the LCD, two steps need to happen: writing to the pixel buffer, and at the very end writing the pixel buffer to the LCD.
Pixelbuffer operations
The MemoryLCD library builds upon Simon Ford's TextDisplay library, which means you can use all of that functionality. Even printf is supported!
most important display operations
// Set one pixel at x=10, y=40 to the color White display.pixel(10,40,White); // print "I love mbed" at the character position x=4, y=5 // i.e. starting on row 5, 4 characters from the left border display.locate(4,5); display.printf("I love mbed!"); // show a bitmap at a given location // Constraints: bitmap is 8-bit, MSB first, 1 bit per pixel. Width, height and starting coordinates must be divisible by 8. // This example: show a bitmap which is 128 pixels wide and high, and start at location 0,0. display.showBMP(&mbed_logo, 128, 128, 0, 0); //Draw a line (really a 1px wide rectangle) from (4,10) until (4,15) display.fill(4, 10, 1, 5, White);
Updating the LCD
Updating the LCD (i.e. moving the framebuffer to the display) happens much the same way as clearing it: asynchronously. To start the update, you call update() on the display, optionally providing a callback to be called after the whole update has happened. In the example below, refreshCallback sets a global boolean 'refreshed' to true when called. Signature: void refreshCallback(void);
updating the display
refreshed = false; int result = display.update(refreshCallback); if(result == LS013B7DH03_OK) { while(refreshed == false) sleep(); } else { printf("Display error: %d", result); }
Full example
example program
#include "LS013B7DH03.h" #include "mbed_logo.h" /******************** Define I/O *****************************/ DigitalOut myled(LED1); #define SCK PD2 #define MOSI PD0 DigitalOut CS(PD3); DigitalOut EXTCOM(PC4); DigitalOut EXTMODE(PD4); DigitalOut DISP(PD5); SPI displaySPI(MOSI, NC, SCK); silabs::LS013B7DH03 display(&displaySPI, &CS, &EXTCOM); /******************** Define Timers *****************************/ LowPowerTicker timeKeeping; /***************** Define global variables **********************/ #define INIT_SECONDS 17600 volatile uint32_t prevSeconds = INIT_SECONDS, seconds = INIT_SECONDS; volatile bool refreshed = false; /***************** Define callback handlers *********************/ void secondsCallback(void); void refreshCallback(void); void secondsCallback(void) { seconds++; } /** * Callback for refresh completion */ void refreshCallback(void) { refreshed = true; } /*************************** MAIN *******************************/ int main() { // Enable the LCD EXTMODE = 1; DISP = 1; // Start generating the 1Hz call for keeping time timeKeeping.attach(&secondsCallback, 1.0f); // Reset the LCD to a blank state. (All white) refreshed = false; display.clearImmediate(refreshCallback); while(refreshed == false) sleep(); printf("Initialization done! \n"); // Apply mbed logo bitmap to the pixel buffer display.showBMP((uint8_t*)mbed_enabled_logo, 128, 128, 0, 0); display.printf("I like MBED!"); // Push update to the display refreshed = false; display.update(refreshCallback); // Sleep while doing the transmit while(refreshed == false) sleep(); // Go into clock mode while(1) { sleep(); // In clock mode, only update once per second if(prevSeconds != seconds) { display.locate(4,15); display.printf("%02d:%02d:%02d", (seconds / 1200) % 24, (seconds / 60) % 60, seconds % 60); if(refreshed == true) { prevSeconds = seconds; refreshed = false; display.update(refreshCallback); } } } }
Datasheet
Datasheet for the LCD (hosted by Mouser)
BufferedDisplay.cpp@10:231fa7861d1f, 2015-08-12 (annotated)
- Committer:
- stevew817
- Date:
- Wed Aug 12 13:32:01 2015 +0000
- Revision:
- 10:231fa7861d1f
- Parent:
- 9:2441ef131ab8
Use masking instead of modulo to speed up operation of pixel()
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
Steven Cooreman |
0:a0faa86660d4 | 1 | /***************************************************************************//** |
Steven Cooreman |
0:a0faa86660d4 | 2 | * @file BufferedDisplay.cpp |
Steven Cooreman |
0:a0faa86660d4 | 3 | * @brief Buffered version of GraphicsDisplay |
Steven Cooreman |
0:a0faa86660d4 | 4 | ******************************************************************************* |
Steven Cooreman |
0:a0faa86660d4 | 5 | * @section License |
Steven Cooreman |
0:a0faa86660d4 | 6 | * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b> |
Steven Cooreman |
0:a0faa86660d4 | 7 | ******************************************************************************* |
Steven Cooreman |
0:a0faa86660d4 | 8 | * |
Steven Cooreman |
0:a0faa86660d4 | 9 | * Permission is granted to anyone to use this software for any purpose, |
Steven Cooreman |
0:a0faa86660d4 | 10 | * including commercial applications, and to alter it and redistribute it |
Steven Cooreman |
0:a0faa86660d4 | 11 | * freely, subject to the following restrictions: |
Steven Cooreman |
0:a0faa86660d4 | 12 | * |
Steven Cooreman |
0:a0faa86660d4 | 13 | * 1. The origin of this software must not be misrepresented; you must not |
Steven Cooreman |
0:a0faa86660d4 | 14 | * claim that you wrote the original software. |
Steven Cooreman |
0:a0faa86660d4 | 15 | * 2. Altered source versions must be plainly marked as such, and must not be |
Steven Cooreman |
0:a0faa86660d4 | 16 | * misrepresented as being the original software. |
Steven Cooreman |
0:a0faa86660d4 | 17 | * 3. This notice may not be removed or altered from any source distribution. |
Steven Cooreman |
0:a0faa86660d4 | 18 | * |
Steven Cooreman |
0:a0faa86660d4 | 19 | * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no |
Steven Cooreman |
0:a0faa86660d4 | 20 | * obligation to support this Software. Silicon Labs is providing the |
Steven Cooreman |
0:a0faa86660d4 | 21 | * Software "AS IS", with no express or implied warranties of any kind, |
Steven Cooreman |
0:a0faa86660d4 | 22 | * including, but not limited to, any implied warranties of merchantability |
Steven Cooreman |
0:a0faa86660d4 | 23 | * or fitness for any particular purpose or warranties against infringement |
Steven Cooreman |
0:a0faa86660d4 | 24 | * of any proprietary rights of a third party. |
Steven Cooreman |
0:a0faa86660d4 | 25 | * |
Steven Cooreman |
0:a0faa86660d4 | 26 | * Silicon Labs will not be liable for any consequential, incidental, or |
Steven Cooreman |
0:a0faa86660d4 | 27 | * special damages, or any other relief, or for any claim by any third party, |
Steven Cooreman |
0:a0faa86660d4 | 28 | * arising from your use of this Software. |
Steven Cooreman |
0:a0faa86660d4 | 29 | * |
Steven Cooreman |
0:a0faa86660d4 | 30 | ******************************************************************************/ |
Steven Cooreman |
0:a0faa86660d4 | 31 | |
Steven Cooreman |
0:a0faa86660d4 | 32 | #include "BufferedDisplay.h" |
Steven Cooreman |
0:a0faa86660d4 | 33 | |
Steven Cooreman |
0:a0faa86660d4 | 34 | #define SWAP8(a) ((((a) & 0x80) >> 7) | (((a) & 0x40) >> 5) | (((a) & 0x20) >> 3) | (((a) & 0x10) >> 1) | (((a) & 0x08) << 1) | (((a) & 0x04) << 3) | (((a) & 0x02) << 5) | (((a) & 0x01) << 7)) |
Steven Cooreman |
0:a0faa86660d4 | 35 | |
Steven Cooreman |
0:a0faa86660d4 | 36 | namespace silabs { |
Steven Cooreman |
0:a0faa86660d4 | 37 | |
Steven Cooreman |
0:a0faa86660d4 | 38 | BufferedDisplay::BufferedDisplay(const char *name) : GraphicsDisplay(name) { |
Steven Cooreman |
0:a0faa86660d4 | 39 | memset((uint8_t*)_pixelBuffer, White, sizeof(_pixelBuffer)); // init full frame buffer |
Steven Cooreman |
0:a0faa86660d4 | 40 | memset((uint8_t*)_dirtyRows, 0xFF, sizeof(_dirtyRows)); // init dirty status |
Steven Cooreman |
0:a0faa86660d4 | 41 | } |
Steven Cooreman |
0:a0faa86660d4 | 42 | |
Steven Cooreman |
0:a0faa86660d4 | 43 | /** |
Steven Cooreman |
0:a0faa86660d4 | 44 | * Override of GraphicsDisplay's pixel() |
Steven Cooreman |
0:a0faa86660d4 | 45 | */ |
Steven Cooreman |
0:a0faa86660d4 | 46 | |
Steven Cooreman |
0:a0faa86660d4 | 47 | void BufferedDisplay::pixel(int x, int y, int colour) { |
stevew817 | 7:6cf0aa7bc0fc | 48 | /* Apply constraint to x and y */ |
stevew817 | 7:6cf0aa7bc0fc | 49 | if(x < 0 || y < 0) return; |
stevew817 | 9:2441ef131ab8 | 50 | if(x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) return; |
stevew817 | 7:6cf0aa7bc0fc | 51 | |
stevew817 | 7:6cf0aa7bc0fc | 52 | /***************************************************************************************************************** |
stevew817 | 7:6cf0aa7bc0fc | 53 | * The display expects LSB input, while the SPI is configured for 8bit MSB transfers. Therefore, we should |
stevew817 | 7:6cf0aa7bc0fc | 54 | * construct the framebuffer accordingly, so that an MSB transmission will put pixel 0 first on the wire. |
stevew817 | 7:6cf0aa7bc0fc | 55 | * |
stevew817 | 7:6cf0aa7bc0fc | 56 | * So the actual pixel layout in framebuffer (for 128x128) is as follows: |
stevew817 | 7:6cf0aa7bc0fc | 57 | * { //Framebuffer |
stevew817 | 7:6cf0aa7bc0fc | 58 | * { //Line 0 |
stevew817 | 7:6cf0aa7bc0fc | 59 | * {p0, p1, p2, p3, p4, p5, p6, p7}, //Line 0 byte 0 (byte 0) |
stevew817 | 7:6cf0aa7bc0fc | 60 | * {p8, p9,p10,p11,p12,p13,p14,p15}, //Line 0 byte 1 (byte 1) |
stevew817 | 7:6cf0aa7bc0fc | 61 | * ... |
stevew817 | 7:6cf0aa7bc0fc | 62 | * {p120,p121,p122,p123,p124,p125,p126,p127} //Line 0 byte 15 (byte 15) |
stevew817 | 7:6cf0aa7bc0fc | 63 | * }, |
stevew817 | 7:6cf0aa7bc0fc | 64 | * { //Line 1 |
stevew817 | 7:6cf0aa7bc0fc | 65 | * {p128,p129,p130,p131,p132,p133,p134,p135}, //Line 1 byte 0 (byte 16) |
stevew817 | 7:6cf0aa7bc0fc | 66 | * ... |
stevew817 | 7:6cf0aa7bc0fc | 67 | * }, |
stevew817 | 7:6cf0aa7bc0fc | 68 | * ... |
stevew817 | 7:6cf0aa7bc0fc | 69 | * { //Line 127 |
stevew817 | 7:6cf0aa7bc0fc | 70 | * {...}, //Line 127 byte 0 (byte 2032) |
stevew817 | 7:6cf0aa7bc0fc | 71 | * ... |
stevew817 | 7:6cf0aa7bc0fc | 72 | * {...} //Line 127 byte 15 (byte 2047) = 128*128 bits |
stevew817 | 7:6cf0aa7bc0fc | 73 | * } |
stevew817 | 7:6cf0aa7bc0fc | 74 | * } |
stevew817 | 7:6cf0aa7bc0fc | 75 | * |
stevew817 | 7:6cf0aa7bc0fc | 76 | * This means that to calculate the actual bit position in the framebuffer byte, we need to swap the bit |
stevew817 | 7:6cf0aa7bc0fc | 77 | * order of the lower three bits. So pixel 7 becomes bit offset 0, 6 -> 1, 5 -> 2, 4->3, 3->4, 2->5, 1->6 and 0->7. |
stevew817 | 7:6cf0aa7bc0fc | 78 | *****************************************************************************************************************/ |
Steven Cooreman |
0:a0faa86660d4 | 79 | uint8_t swapx = 7 - ((unsigned int)x & 0x07); |
Steven Cooreman |
0:a0faa86660d4 | 80 | x = ((unsigned int)x & 0xFFFFFFF8) | swapx; |
Steven Cooreman |
0:a0faa86660d4 | 81 | |
stevew817 | 7:6cf0aa7bc0fc | 82 | /* Since we are dealing with 1-bit pixels, we can avoid having to do bitshift and comparison operations twice. |
stevew817 | 7:6cf0aa7bc0fc | 83 | * Basically, do the comparison with the requested state and current state, and if it changed, do an XOR on the framebuffer pixel and set the line to dirty. |
stevew817 | 7:6cf0aa7bc0fc | 84 | */ |
stevew817 | 10:231fa7861d1f | 85 | bool change = ((_pixelBuffer[((y * DISPLAY_WIDTH) + x) / DISPLAY_BUFFER_TYPE_SIZE] & (1 << (x & DISPLAY_BUFFER_TYPE_MASK))) != ((colour & 0x01) << (x & DISPLAY_BUFFER_TYPE_MASK))); |
Steven Cooreman |
0:a0faa86660d4 | 86 | if(change) { |
stevew817 | 10:231fa7861d1f | 87 | /* Pixel's value and requested value are different, so since it's binary, we can simply do an XOR */ |
stevew817 | 10:231fa7861d1f | 88 | _pixelBuffer[((y * DISPLAY_WIDTH) + x) / DISPLAY_BUFFER_TYPE_SIZE] ^= (1 << (x & DISPLAY_BUFFER_TYPE_MASK)); |
Steven Cooreman |
0:a0faa86660d4 | 89 | |
stevew817 | 7:6cf0aa7bc0fc | 90 | /* notify dirty status of this line */ |
stevew817 | 10:231fa7861d1f | 91 | _dirtyRows[y / DISPLAY_BUFFER_TYPE_SIZE] |= (1 << (y & DISPLAY_BUFFER_TYPE_MASK)); |
Steven Cooreman |
0:a0faa86660d4 | 92 | } |
Steven Cooreman |
0:a0faa86660d4 | 93 | } |
Steven Cooreman |
0:a0faa86660d4 | 94 | |
Steven Cooreman |
0:a0faa86660d4 | 95 | int BufferedDisplay::width() { |
Steven Cooreman |
0:a0faa86660d4 | 96 | return DISPLAY_WIDTH; |
Steven Cooreman |
0:a0faa86660d4 | 97 | } |
Steven Cooreman |
0:a0faa86660d4 | 98 | int BufferedDisplay::height() { |
Steven Cooreman |
0:a0faa86660d4 | 99 | return DISPLAY_HEIGHT; |
Steven Cooreman |
0:a0faa86660d4 | 100 | } |
Steven Cooreman |
0:a0faa86660d4 | 101 | |
Steven Cooreman |
0:a0faa86660d4 | 102 | /** |
Steven Cooreman |
0:a0faa86660d4 | 103 | * Function to move bitmap into frame buffer |
Steven Cooreman |
0:a0faa86660d4 | 104 | * arguments: |
Steven Cooreman |
0:a0faa86660d4 | 105 | * * bitmap: pointer to uint8 array containing horizontal pixel data |
stevew817 | 6:fe04073fe90c | 106 | * * bmpWidth: width of the bitmap in pixels (must be multiple of 8) |
stevew817 | 6:fe04073fe90c | 107 | * * bmpHeight: height of the bitmap in pixels |
stevew817 | 6:fe04073fe90c | 108 | * * startX: starting position to apply bitmap in horizontal direction (0 = leftmost) (must be multiple of 8) |
stevew817 | 6:fe04073fe90c | 109 | * * startY: starting position to apply bitmap in vertical direction (0 = topmost) |
Steven Cooreman |
0:a0faa86660d4 | 110 | */ |
Steven Cooreman |
0:a0faa86660d4 | 111 | void BufferedDisplay::showBMP(const uint8_t* bitmap, const uint32_t bmpWidth, const uint32_t bmpHeight, const uint32_t startX, const uint32_t startY) { |
stevew817 | 8:39206d1e11f7 | 112 | uint32_t bmpLine = 0, y = startY, bytesPerLine = ((bmpWidth >= (DISPLAY_WIDTH - startX)) ? (DISPLAY_WIDTH - startX) : bmpWidth) / 8; |
Steven Cooreman |
0:a0faa86660d4 | 113 | |
Steven Cooreman |
0:a0faa86660d4 | 114 | /* Apply constraints */ |
Steven Cooreman |
0:a0faa86660d4 | 115 | if((bmpWidth & 0x07) != 0) return; |
Steven Cooreman |
0:a0faa86660d4 | 116 | if((startX & 0x07) != 0) return; |
stevew817 | 6:fe04073fe90c | 117 | if(startX >= DISPLAY_WIDTH) return; |
stevew817 | 6:fe04073fe90c | 118 | |
stevew817 | 6:fe04073fe90c | 119 | //Superflouous due to for-loop check |
stevew817 | 6:fe04073fe90c | 120 | //if((startY >= DISPLAY_HEIGHT) return; |
Steven Cooreman |
0:a0faa86660d4 | 121 | |
stevew817 | 7:6cf0aa7bc0fc | 122 | /* Copy over bytes to the framebuffer, do not write outside framebuffer boundary */ |
stevew817 | 6:fe04073fe90c | 123 | for(; y < DISPLAY_HEIGHT; y++) { |
stevew817 | 7:6cf0aa7bc0fc | 124 | /* Check that we are not writing more than the BMP height */ |
stevew817 | 8:39206d1e11f7 | 125 | if(bmpLine >= bmpHeight) break; |
stevew817 | 7:6cf0aa7bc0fc | 126 | |
stevew817 | 8:39206d1e11f7 | 127 | /* Copy over one line (bmpLine) from the BMP file to the corresponding line (y) in the pixel buffer */ |
stevew817 | 6:fe04073fe90c | 128 | memcpy( (void*) &(((uint8_t*)_pixelBuffer)[((y * DISPLAY_WIDTH) + startX) / 8]), |
stevew817 | 8:39206d1e11f7 | 129 | (const void*) &(bitmap[bmpLine * (bmpWidth / 8)]), |
stevew817 | 6:fe04073fe90c | 130 | bytesPerLine); |
Steven Cooreman |
0:a0faa86660d4 | 131 | |
stevew817 | 7:6cf0aa7bc0fc | 132 | /* Set dirty status for the line we just overwrote */ |
stevew817 | 6:fe04073fe90c | 133 | _dirtyRows[y / DISPLAY_BUFFER_TYPE_SIZE] |= (1 << (y % DISPLAY_BUFFER_TYPE_SIZE)); |
stevew817 | 8:39206d1e11f7 | 134 | bmpLine++; |
Steven Cooreman |
0:a0faa86660d4 | 135 | } |
Steven Cooreman |
0:a0faa86660d4 | 136 | |
Steven Cooreman |
0:a0faa86660d4 | 137 | return; |
Steven Cooreman |
0:a0faa86660d4 | 138 | } |
Steven Cooreman |
0:a0faa86660d4 | 139 | } |