Sharp Memory LCD Displays = Ultra Low Power

I tried powering the Photon from a 3.7v 18650 cell and now the LCD prints each screen without errors. :thinking:

So that leads me to beleive that my USB power source is causing the image transfer issues somehow since the battery resolves the issue.

That’s progress.

Now can I get this working on the Argon when it’s running off battery power? Let’s find out real quick :grinning: :spark:

This is what I when running the same code on the Argon that used to work just fine.

Looks like the transfer to the LCD is not right anymore for some reason?

The only thing that changed is the Firmware the Argon is running.

@RWB, the library likely needs to be rewritten to use SPISettings() and possibly with lock on SPI to ensure exclusivity during transactions. I’ll try to get to it this week though my wife is on holidays so it is not my first priority :wink:

2 Likes

Thanks for taking a look, it’s driving me crazy :grinning:

I have $50 for you soon as you get the library fixed up to work on the Argon.

Is there anyway to store bitmap images on the extra memory chip on the Argon?

I still have that code you created a while back that downloads bitmaps over HTTP and stores them on an SD card. Ideally, I would need that code to work on the Argon if possible so $100 if you have time to make sure that code works on the Argon and a 2.7 inch LCD.

@RWB, no money required. Instead give to a local charity. As for using the external flash, that is still not available and I have no idea if/when it will be so.

3 Likes

@RWB, quick update. I am able to run the display with DeviceOS 2.0.0-rc.1 on a Photon without problems but NOT on the Argon. I’ll need to pull out my logic analyzer to see what the problem is but so far, I am perplexed as to why it isn’t working.

1 Like

Same here.

Photon works but Argon almost works, you can see image that’s supposed to be there but it’s scrambled for some reason. I assume it’s timing or something.

I loaded firmware 1.3.1 on the Argon and it does the same thing. Firmware 1.3.0 gives me instant Red SOS Panic.

1 Like

@RWB, I can see the screen clear on startup but nothing else, ie. no snow.

@RWB, the easiest way to maintain the VCOM on the Sharpmem display is using a low power 555 timer. Otherwise, you likely have to wake Argon every second to toggle the pin. I don’t believe a PWM output will continue working in STOP sleep (which maintains pin levels vs tri-stating them).

Weird isn’t it :thinking:

I tried firmware from 1.2.1 and up and I can’t get the screen to display properly on any of the firmware versions.

Are you stuck or still playing with it?

I have found some low power timer chips for the 1Hz pulse with 1uA current consumption but wanted to ask if the NRF had any similar type of feature built in by chance.

External chip for Vcom it is then.

1 Like

@RWB, I found the issue with the SPI on the Argon. I’ll be documenting it shortly but I have other things to do currently. It has to do with switching SPI modes “on the fly”, which in this case is MSBFIRST to LSBFIRST. More later.

1 Like

@RWB, @rickkas7, it seems there is an issue with the SPI firmware on Gen3 devices where the CLK clock line will “glitch” when the SPI configuration is changed “on-the-fly”. I recall this being an issue which was fixed in Gen2 devices. The library being used is the Adafruit_SharpMem code I had adapted to work with my Adafruit_mfGFX library. The code is here:

The SharpMem display requires commands be sent in MSBFIRST format while display data to be sent in LSBFIRST format (!!). As such, the Adafruit_SharpMem code has an additional SPI byte send function to send a byte in LSBFIRST order and then switch back to MSBFIRST format which is the default. The tested code moved the SPI.setBitOrder() function calls from various locations to the sendByteLSB() function as follows:

void Adafruit_SharpMem::sendbyteLSB(uint8_t data) 
	SPI.setBitOrder(LSBFIRST);
	SPI.transfer(data);
	SPI.setBitOrder(MSBFIRST);
}

The test code displays a set of calculated graphs on the display. Capturing the SPI data using a DSLogic LA, the first action on the display is to clear it by sending 0xFF for all display bytes.

On a Photon compiled to DeviceOS 2.0.9-rc.1, the code runs flawlessly. The captured SPI data shows the expected normal SPI pin activity (2 command bytes then data). Note the “clean” CLK pin being active only when data is to be sent.

On the Argon, the same code and DeviceOS (only CS pin is changed), the CLK shows pre-transfer activity, likely when SPI.setBitOrder(LSBFIRST) is called:

The unwanted CLK pulse causes a false bit to be clocked into the SPI receive register of the display. The outcome is what looks like “noise” on the display, as data is now corrupted.

In order to test if the SPI.setBitOrder() call is the problem, I added code to flip the bit order (MSBFIRST to LSBFIRST) and thus remove the need for changing the SPI mode. This was done with a nibble lookup table coupled with bit shift operations. The new sendByteLSB() function is as follows:

void Adafruit_SharpMem::sendbyteLSB(uint8_t data) 
{
	// Table to reverse the top and bottom nibble then swap them.
	static const unsigned char lookup[16] = {
       0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
       0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };

	SPI.transfer((lookup[data&0b1111] << 4) | lookup[data>>4]);
}

With this rewritten function, the SPI CLK line no longer glitches and the code displays the expected test graphs as expected.

I will need to let the Particle folks know about this SPI bus glitching if @rickkas7 can’t comment.

4 Likes

Excellent news! :grinning:

I’ll give this a shot and report back.

Did you include these changes in your library you linked to in your last post or are you waiting to hear back from @rickkas7 ?

@RWB, I did not include it as I was not sure which code you were using. The code needs to be converted to a library anyway so I’ll work on that when I get a chance.

Got it.

If it just requires finding and replacing certain SPI transaction code swaps I can give it a go, just need some guidance before diving in.

Thanks for finding out what the issue is.

@RWB, if you post your code (here or via PM), I can help you with that.

@peekay123 Thanks for helping :beers:

Here is the test code I’m using in the images I posted.

It just scrolls through like 5 different full size images on the 2.7 inch screen along with a clock screen where the font sizes are to large and the info at the bottom overlap each other.

It’s your library, I’m not sure if it’s the latest version of your library or not since the first version worked fine and I just stuck with it.

Here is the code folder I’m using in Visual Studio.

https://drive.google.com/drive/folders/1bLUXjQq5eWJCJbgwxrgjf5IVI1PxJklh?usp=sharing

@RWB, you can replace the contents of the Adafruit_SharpMem.cpp file with this code. Let me know how it works.

/*********************************************************************
This is an Arduino library for our Monochrome SHARP Memory Displays

- Adapted to Particle devices by Paul Kourany (peekay123), 2015
- Updated June 2020 to avoid hardware SPI bus glitch when changing bit order

Pick one up today in the adafruit shop!
------> http://www.adafruit.com/products/1393

These displays use SPI to communicate, 3 pins are required to  
interface

Adafruit invests time and resources providing this open source code, 
please support Adafruit and open-source hardware by purchasing 
products from Adafruit!

Written by Limor Fried/Ladyada  for Adafruit Industries.  
BSD license, check license.txt for more information
All text above, and the splash screen must be included in any redistribution
*********************************************************************/

#include "Adafruit_SharpMem.h"

/**************************************************************************
	Sharp Memory Display Connector
	-----------------------------------------------------------------------
	Pin   Function        Notes
	===   ==============  ===============================
	1   VIN             3.3-5.0V (into LDO supply)
	2   3V3             3.3V out
	3   GND
	4   SCLK            Serial Clock
	5   MOSI            Serial Data Input
	6   CS              Serial Chip Select
	9   EXTMODE         COM Inversion Select (Low = SW clock/serial)
	7   EXTCOMIN        External COM Inversion Signal
	8   DISP            Display On(High)/Off(Low)

**************************************************************************/

#define SHARPMEM_BIT_WRITECMD   (0x80)
#define SHARPMEM_BIT_VCOM       (0x40)
#define SHARPMEM_BIT_CLEAR      (0x20)
#define TOGGLE_VCOM             do { _sharpmem_vcom = _sharpmem_vcom ? 0x00 : SHARPMEM_BIT_VCOM; } while(0);

byte sharpmem_buffer[(SHARPMEM_LCDWIDTH * SHARPMEM_LCDHEIGHT) / 8];

/* ************* */
/* CONSTRUCTORS  */
/* ************* */
Adafruit_SharpMem::Adafruit_SharpMem(uint8_t clk, uint8_t mosi, uint8_t ss) :
Adafruit_GFX(SHARPMEM_LCDWIDTH, SHARPMEM_LCDHEIGHT) {
	_clk = clk;		// not used since software SPI support removed
	_mosi = mosi;	// not used since software SPI support removed
	_ss = ss;

	// Set the vcom bit to a defined state
	_sharpmem_vcom = SHARPMEM_BIT_VCOM;

}

void Adafruit_SharpMem::begin() {
	setRotation(2);
}

void Adafruit_SharpMem::init() {

	// Configure SPI for display.
	SPI.setDataMode(SPI_MODE0);
	SPI.setBitOrder(MSBFIRST);
	SPI.setClockSpeed(4500000);
	SPI.begin(_ss);
}

/* *************** */
/* PRIVATE METHODS */
/* *************** */


/**************************************************************************/
/*
	@brief  Sends a single byte in MSBFIRST (default) format.
*/
/**************************************************************************/
void Adafruit_SharpMem::sendbyte(uint8_t data) 
{
	SPI.transfer(data);
}


/**************************************************************************/
/*
	@brief  Sends a single byte in LSBFIRST format (by reversing bit order)
*/
/**************************************************************************/
void Adafruit_SharpMem::sendbyteLSB(uint8_t data) 
{
	// Table to reverse the top and bottom nibble then swap them (MSBFIRST to LSBFIRST conversion)
	static const unsigned char lookup[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };

	SPI.transfer((lookup[data&0b1111] << 4) | lookup[data>>4]);
}

/* ************** */
/* PUBLIC METHODS */
/* ************** */

/**************************************************************************/
/*! 
	@brief Draws a single pixel in image buffer

	@param[in]  x
				The x position (0 based)
	@param[in]  y
				The y position (0 based)
*/
/**************************************************************************/
void Adafruit_SharpMem::drawPixel(int16_t x, int16_t y, uint16_t color) 
{
	if ((x >= SHARPMEM_LCDWIDTH) || (y >= SHARPMEM_LCDHEIGHT))
	return;

	if (color)
		sharpmem_buffer[(y*SHARPMEM_LCDWIDTH + x) /8] |= (1 << x % 8);
	else
		sharpmem_buffer[(y*SHARPMEM_LCDWIDTH + x) /8] &= ~(1 << x % 8);
}

/**************************************************************************/
/*! 
	@brief Gets the value (1 or 0) of the specified pixel from the buffer

	@param[in]  x
				The x position (0 based)
	@param[in]  y
				The y position (0 based)

	@return     1 if the pixel is enabled, 0 if disabled
*/
/**************************************************************************/
uint8_t Adafruit_SharpMem::getPixel(uint16_t x, uint16_t y)
{
	if ((x >=SHARPMEM_LCDWIDTH) || (y >=SHARPMEM_LCDHEIGHT)) return 0;
	return sharpmem_buffer[(y*SHARPMEM_LCDWIDTH + x) /8] & (1 << x % 8) ? 1 : 0;
}

/**************************************************************************/
/*! 
	@brief Clears the screen
*/
/**************************************************************************/
void Adafruit_SharpMem::clearDisplay() 
{
	memset(sharpmem_buffer, 0xff, (SHARPMEM_LCDWIDTH * SHARPMEM_LCDHEIGHT) / 8);
	// Send the clear screen command rather than doing a HW refresh (quicker)
	pinSetFast(_ss);
	delayMicroseconds(6);		//SS needs to be high 6us before data - Spark is FAST!
	sendbyte(_sharpmem_vcom | SHARPMEM_BIT_CLEAR);
	sendbyteLSB(0x00);
	TOGGLE_VCOM;
	delayMicroseconds(2);		//SS needs to be low 2us after data before going high - Spark is FAST!
	pinResetFast(_ss);
}

/**************************************************************************/
/*
	@brief Renders the contents of the pixel buffer on the LCD
*/
/**************************************************************************/
void Adafruit_SharpMem::refresh(void) 
{
	uint16_t i, totalbytes, currentline, oldline;  
	totalbytes = (SHARPMEM_LCDWIDTH * SHARPMEM_LCDHEIGHT) / 8;

	// Send the write command
	pinSetFast(_ss);
	delayMicroseconds(6);		//SS needs to be high 6us before data - Spark is FAST!
	sendbyte(SHARPMEM_BIT_WRITECMD | _sharpmem_vcom);
	TOGGLE_VCOM;

	// NOTE: this code could be modified to send whole display lines at a time
	// instead of single bytes (see https://github.com/pkourany/SharpMemFRAM)

	// Send the address for line 1
	oldline = currentline = 1;
	sendbyteLSB(currentline);

	// Send image buffer
	for (i=0; i<totalbytes; i++)
	{
		sendbyteLSB(sharpmem_buffer[i]);
		currentline = ((i+1)/(SHARPMEM_LCDWIDTH/8)) + 1;
		if(currentline != oldline)
		{
			// Send end of line and address bytes
			sendbyteLSB(0x00);
			if (currentline <= SHARPMEM_LCDHEIGHT)
			{
				sendbyteLSB(currentline);
			}
			oldline = currentline;
		}
	}

	// Send another trailing 8 bits for the last line
	sendbyteLSB(0x00);
	delayMicroseconds(2);		//SS needs to be low 2us after data before going high - Spark is FAST!
	pinResetFast(_ss);
}
1 Like

Thanks! I’ll report back shortly :+1:

1 Like