Having convinced myself to use a Sharp Memory Display for the Bramley, I put an order in at Pimoroni for the new 400x240 module. A few years ago, I'd bought a 96x96 version (photo above), and I decided to dig that out too so I could make an early start on the code.
Unlike the new display, the 'black' pixels on the older board actually have a mirror-like finish. It's striking, but depending on what it reflects it can be hard to read. Both displays use the same interface, though, so it will make good target practice until my new screen arrives.
I gathered my resources and prepared to write a Rust library.
- Datasheet for the 96x96 display
- Datasheet for the 400x240 display
- CircuitPython library by Adafruit
- C++ library by Adafruit
- C++ library by MakerDyne (MakerDyne manufactures a different breakout board, might be instructive)
Writing to the display
The Memory Display is driven using SPI, but unlike most SPI devices, it expects the chip select pin to be high when writing to the display, rather than low. And, unfortunately, I couldn't persuade the Raspberry Pi to use active high.
Eventually, I gave up and wired a generic GPIO pin as my chip select that I manually set high or low as required. Other libraries do the same, so perhaps it's a known limitation of the Pi. Anyway, with my workaround in place, I was ready to write some pixels.
Oh, the new screen arrived by the way! And good news - it works on both screens. There's just one problem: the refresh rate is too slow - especially on the large display - it can take 160ms to redraw the screen. Even if I did nothing but draw, that's only 6 frames per second!
If you want to know how painful 160ms of latency is, try it for yourself (alternative). If I'm going to use this screen, I'll need it to update faster.
I shaved a few milliseconds off by using chunked writes and toying with the SPI buffer size, but the real gain was from reading the datasheet. I had started with a "typical" 1MHz SPI clock, so by simply increasing the frequency to the "maximum" 2MHz, I could refresh the screen in 80ms. Much better.
After that first pass at improving the speed, I thought I'd benchmark against Adafruit's Python library to check I wasn't doing anything stupid. In the process, I noticed they were running the clock at 8MHz! So, with a little apprehension, I set my code to 8MHz too.
The screen updated in 37ms. But was it safe? That was a few weeks ago now, and since then - after 11 months at 8MHz - Adafruit have slowed their library down to the recommended 2MHz. Perhaps they'd run into problems?
I decided to ask Sharp for their opinion.
To my surprise, they responded. The memory display has a mean time between failures of 50KHrs at 25°C. And they explained that, at a higher speed, I'd increase the internal temperatures and lower its functional life. So it might be OK - depending on product variation and my control over the operating temperature - but they understandably wouldn't warrant values outside the spec.
So, at least to begin with, I'll stick to 2MHz and hope 80ms is fast enough. I wouldn't want to damage anyone elses screen if they ran my code - even if it works OK on mine. But, before I finish, this screen has one more trick up its sleeve.
Each screen update looks something like this:
COMMAND BYTE (PADDING) LINE ADDRESS 1 ROW OF PIXEL DATA 1 (PADDING) LINE ADDRESS 2 ROW OF PIXEL DATA 2 (PADDING) ... (PADDING)
Basically, a byte to tell the display we're writing to it, followed by each line's address and pixel data.
This is where the 'Memory' in 'Memory Display' comes in. Once updated, those lines display the same pixels continually while powered. So, in theory, I could skip any lines that remained the same between updates and send only those that changed.
So I modified my library to remember the pixels from each update and diff them against the next update. Now, if a row hasn't changed, I skip it.
It's a big improvement, and update times now vary between 0ms - 80ms, depending on how many rows were updated. Updating horizontal text, for example, would only modify a few rows, which is great because it gives low latency for typing.
The varying update times are a quirk, for sure - but I think this might be good enough.
My Rust library for the Sharp Memory Display (source code)
Next time, I'll look at reading input from the six buttons on the back of the device.