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.

Memory LCD extension board

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)

LS013B7DH03.cpp

Committer:
stevew817
Date:
2015-08-12
Revision:
11:0f8ae10b308d
Parent:
2:2f10f00fe56c

File content as of revision 11:0f8ae10b308d:

/***************************************************************************//**
 * @file LS013B7DH03.cpp
 * @brief Driver class for the Sharp LS013B7DH03 memory LCD on some kits.
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/

#include <mbed.h>
#include "LS013B7DH03.h"
#include "SPI.h"

/* LS013B7DH03 SPI commands */
#define LS013B7DH03_CMD_UPDATE     (0x01)
#define LS013B7DH03_CMD_ALL_CLEAR  (0x04)

/* Macro to switch endianness on char value */
#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))

namespace silabs {

LS013B7DH03::LS013B7DH03(mbed::SPI * spi, DigitalOut * CS, DigitalOut * ExtCom, const char *name) : BufferedDisplay(name) {
	//Save pointer to ChipSelect pin
	_CS = CS;
	_CS->write(0);

	//Save pointer to ExtCom pin
	_EXTCOM = ExtCom;
	_EXTCOM->write(0);

	//Save pointer to spi peripheral
	_spi = spi;
	_internalEventCallback.attach(this, &LS013B7DH03::_cbHandler);

	//Initialize
	_spi->set_dma_usage((DMAUsage)DMA_USAGE_NEVER);
	_refreshCount = 0;
	_lcdPolarity = 0;
	_state = IDLE;
	_completionCallbackPtr = NULL;
	_rowCount = 0;

	//Start toggling the EXTCOM pin
	//_displayToggler.attach(this, &LS013B7DH03::toggle, 0.008f);
}

/**
 * Call this function at 55 ~ 65 Hz to keep the display up-to-date.
 */
void LS013B7DH03::toggle() {
	_EXTCOM->write(!_EXTCOM->read());
	_refreshCount++;
}

/**
 * Function to get internal refresh counter
 */
uint32_t LS013B7DH03::getRefreshTicks() {
	return _refreshCount;
}

/**
 * Call this function to push all changes to the display
 */
int LS013B7DH03::update( cbptr_t callback ) {
	uint32_t rowCount = 0;
	bool update = false;

	// Check if something actually changed in the pixelbuffer
	for(rowCount = 0; rowCount < DISPLAY_HEIGHT/DISPLAY_BUFFER_TYPE_SIZE; rowCount++) {
		if(_dirtyRows[rowCount] != 0) update = true;
	}

	if(update == false) return LS013B7DH03_NO_ACTION;

	// Watch out to not mess up a transfer
	if(_state != IDLE) return LS013B7DH03_ERROR_BUSY;

	_completionCallbackPtr = callback;

	// Take control
	_state = WAIT_WRITE;
	_rowCount = 0;

	//Initialize the command vector
	_cmd[0] = (uint8_t)SWAP8(LS013B7DH03_CMD_UPDATE);
	_cmd[1] = SWAP8(1);

	// Activate LCD
	_CS->write(1);
	_csTimeout.attach(this, &LS013B7DH03::_cbHandlerTimeout, 0.01f);

	return LS013B7DH03_OK;
}

/**
 * Function to test display buffer
 */
int LS013B7DH03::showDemo() {
	for(uint32_t i = 0; i < DISPLAY_BUFFER_ELEMENTS; i+=2) {
		_pixelBuffer[i] = 0x00FFF000;
	}
	memset((void*)_dirtyRows, 0xFF, sizeof(_dirtyRows));

	return LS013B7DH03_OK;
}

/**
 * Call this function to immediately clear the display
 */
int LS013B7DH03::clearImmediate( cbptr_t callback ) {
	// Watch out to not mess up a transfer
	if(_state != IDLE) return LS013B7DH03_ERROR_BUSY;

	_state = WAIT_CLEAR;
	_completionCallbackPtr = callback;

	// Clear out the pixel buffer
	memset((void*)_pixelBuffer, White, sizeof(_pixelBuffer));
	memset((void*)_dirtyRows, 0, sizeof(_dirtyRows));

	_cmd[0] = (uint8_t)(SWAP8(LS013B7DH03_CMD_ALL_CLEAR | _lcdPolarity));
	_cmd[1] = 0;

	// Wait for the ChipSelect line
	_CS->write(1);
	_csTimeout.attach(this, &LS013B7DH03::_cbHandlerTimeout, 0.01f);

	return LS013B7DH03_OK;
}

void LS013B7DH03::_cbHandlerTimeout( void ) {
	this->_cbHandler(0);
}

void LS013B7DH03::_cbHandler( int event ) {
	if((_state == WAIT_WRITE) || (_state == WRITING))
	{
		_state = WRITING;
		while(_rowCount < DISPLAY_HEIGHT) {
			// Determine the next line to send
			if((_dirtyRows[_rowCount / DISPLAY_BUFFER_TYPE_SIZE] & (1 << (_rowCount % DISPLAY_BUFFER_TYPE_SIZE))) != 0) {

				// Row is dirty, send an update to the display
				_cmd[1] = (uint8_t)SWAP8(_rowCount + 1);
				memcpy((void*)&(_cmd[2]), (const void*)&(_pixelBuffer[_rowCount*(DISPLAY_WIDTH/DISPLAY_BUFFER_TYPE_SIZE)]), DISPLAY_WIDTH / DISPLAY_BUFFER_TYPE_SIZE * sizeof(DISPLAY_BUFFER_TYPE));

				if(_spi->transfer((uint8_t*)_cmd, (2 + (DISPLAY_WIDTH / DISPLAY_BUFFER_TYPE_SIZE * sizeof(DISPLAY_BUFFER_TYPE))) , (uint8_t*)NULL, 0, _internalEventCallback, SPI_EVENT_COMPLETE) != 0) {
					// SPI is busy, with another transaction. This means the data to the LCD has been corrupted, so fail here.
					_state = DONE;

					// Make sure the handler is called again
					_csTimeout.attach(this, &LS013B7DH03::_cbHandlerTimeout, 0.1f);
				}

				// Transaction is in progress, so update row state
				_dirtyRows[_rowCount / DISPLAY_BUFFER_TYPE_SIZE] &= ~(1 << (_rowCount % DISPLAY_BUFFER_TYPE_SIZE));
				_rowCount++;
				return;
			}

			// Row wasn't touched, so check the next row
			_rowCount++;
		}

		// Done sending!
		_cmd[1] = 0xFF;
		_state = TRANSFERS_DONE;
		if(_spi->transfer((uint8_t*)_cmd, 2, (uint8_t*)NULL, 0, _internalEventCallback, SPI_EVENT_COMPLETE) != 0) {
			// SPI is busy, with another transaction. This means the data to the LCD has been corrupted, so fail here.
			_state = DONE;

			// Make sure the handler is called again
			_csTimeout.attach(this, &LS013B7DH03::_cbHandlerTimeout, 0.1f);
		}
		return;
	}
	else if (_state == WAIT_CLEAR)
	{
		_state = TRANSFERS_DONE;
		if(_spi->transfer((uint8_t*)_cmd, 2, (uint8_t*)NULL, 0, _internalEventCallback, SPI_EVENT_COMPLETE) != 0) {
			// SPI is busy, with another transaction. This means the data to the LCD has been corrupted, so fail here.
			_state = DONE;

			// Make sure the handler is called again
			_csTimeout.attach(this, &LS013B7DH03::_cbHandlerTimeout, 0.1f);
		}
		return;
	}
	else if (_state == TRANSFERS_DONE)
	{
		_state = DONE;
		_csTimeout.attach(this, &LS013B7DH03::_cbHandlerTimeout, 0.01f);
		return;
	}
	else if (_state == DONE)
	{
		_CS->write(0);
		_state = IDLE;
		if(_completionCallbackPtr != 0) _completionCallbackPtr();
		return;
	}
}

} // namespace silabs