Spin2/Interpreter Tricks for Cogless 96x64 OLED Driver (Now w/FlexProp Variant [No Tricks])
Terry Trapp (@ke4pjw) posted an OLED driver some time ago, but it wasn't until last weekend that I finally downloaded and played with the code. I had started on a similar driver for the same display a couple years ago, and even got i working a bit, but really didn't need it so I shelved that project.
I work for a laser tag company and our P1-based product is popular, but we really need to think about the next generation -- and for that we will use a P2. Once I get a few drivers sorted (SD access, SPIFF), the rest of the system will be work, but not terrible. Of course, we are definitely going to dump the 8x2 character LCD for an OLED. Hence my renewed interest.
I started by porting Terry's code to my style and looking for opportunities to make things leaner and more efficient. His version uses a cog to run the SPI code. I'm stingy with cogs (the P1 laser tag uses all 8), so I moved this code into an inline PASM function. Then I decided to move that code from a function into the Spin2 interpreter cog to use with call().
I spoke with Chip in the middle of the week and he showed me a better way to do what I wanted. In the end, I have a driver with most of the features Terry has (I don't care about scrolling), and I added some graphics primitives (translated from C drivers for the same device). One of the things I liked about Terry's driver is the ability to load a bitmap into the display. I did a bit of research on the bitmap format used and modified that routine so that it can use any size BMP that will fit into the display.
Now, the downside (for some) of my driver is that it only works with Propeller Tool and PNut. For those not running Windows or just preferring to use FlexProp, making a compatible version would be pretty easy (it was, see update below) -- but it would be slower in Spin2. Compiling with FlexProp would bring back the speed.
To me, this turned into an experiment with a purpose and shows off some neat features of the P2:
-- smart pins for SPI (both versions)
-- Spin2 interpreter has room to load custom user routines (_p2 version)
-- Custom PASM routines can be called from Spin2 (_p2 version)
-- Inline PASM can be embedded in a function (_p2 version)
-- Custom routines in user RAM of interpreter can also be called from inline PASM (_p2 version)
That last bit made me smile. Anything that could benefit from speed was converted to Inline PASM which called the custom code in the interpreter. I think this is neat and it made a demonstrable difference in the performance of the most functions. Character writing, for example, sped up about four fold.
Anyway, have fun and if you find an issue, please let me know here in the forums. I will be working with this for the next several months as we build a new laser tag product, so I'm sure I'll be updating frequently.
Thread featuring Terry's driver (written for FlexProp):
-- https://forums.parallax.com/discussion/169839/p2-running-the-parallax-96-x-64-color-oled-display-module-product-id-28087-ssd1331#latest
Update:
As an exercise I saved a copy of my driver with "_ez" appended (this is how I mark drivers as Spin2 only when others with same name exist) and back-ported everything to pure Spin2 (no inline PASM or interpreter tricks). I used this version and loaded up in FlexProp 5.9.12 and it works. This version will work with any tool (PNut, Propeller Tool, FlexProp) and may allow those who don't venture into PASM2 to make updates to existing functions. The attached archive has both drivers included.
Both driver versions are included in the archive.
Updates:
-- added support for 24-bit bitmaps
-- changed driver name to jm_ssd1331_p2.spin2
-- updated jm_ssd1331_ez.spin2 (for FlexProp) to match
-- implemented Ada's suggestion to use rgbsqz in bitmap24() (jm_ssd1331_p2 only)
-- changed bitmap16() to image16(); now supports bitmaps and raw data
-- changed image16() interface; raw images can be any size and any position
-- added {++lut} flag to _ez version for better FlexProp performance
Latest:
-- jm_ssd1331_p2__test - Archive [Date 2022.06.28 Time 07.28].zip
Comments
For those who want to create and used custom bitmaps, note that they must be saved in 16-bit (565), top-to-bottom format. You can use an online tool like this one:
-- https://online-converting.com/image/convert2bmp/#
For the conversion you'll need to specify:
-- Color: 16 (5:6:5, RGB Hi color)
-- Rows direction: Top-to-Bottom
-- Dithering: No (your call here)
The first two settings are critical so that the bitmaps are displayed correctly by the driver.
After a bit of research and some information from Terry, I wrote a little Python program that will do the conversion. You'll need to remove the .txt after the extension (forums won't allow Python attachments [why?]) and run with Python 3. All the the bitmaps in my demo were converted from 24-bit to 16-bit 5:6:5 with this Python program. They look great on the display.
Update:
-- changed to BITMAPV3INFOHEADER type header so that rgb565 bitmap displays properly in Windows (does not affect driver)
-- changed name to bmp24to16.py
-- added output type argument: -b for bitmap, -r for raw
Great post Jon! Between you and Terry, Parallax better stock up on these displays.
The up-shot of recreating a Spin2 only version is that prototyping new features is a bit easier. For the latest version I added support for 24-bit uncompressed bitmaps. I also ran timing tests to see how versions compared. These tests were with a full-size (96x64) bitmap on a P2 running at 200MHz
Would be thrilled if someone would verify my test results.
lmao
(don't have one of these oleds to actually test if that works. Might need a shl by 8 to align the colors?)
Also, more efficient version for if there wasn't a magic instruction that does it for you:
According to the PASM2 docs:
A bit of inline test code verifies that it works, and shaves important time off the 24-bit routine.
The longer version works, too.
I'm looking forward to having the P2 manual in my hands (I love physical books, hence Amazon loves me) to peruse all the instructions I haven't yet learned.
In Terry's original thread he explains how to process files with the FFMPEG utility to create raw frames; these frames have no header like a standard bitmap. To accommodate raw frames I changed
to
where itype is 0 for raw image data, !0 for bitmaps. Raw files must be 96x64. Bitmaps can be any size (up to 96x64) because width and height information is taken from the header.
I updated the demo with Terry's Propeller hat images and animated them (as he did in his original thread -- note that his code there only works in FlexProp). Frame rate is a function of P2 speed and driver/compiler used. With Propeller Tool using the PASM driver (_p2) at 200MHz, I could comfortably get 85 FPS with full-screen bitmaps. With FlexProp using the Spin2 driver (_ez) I was about to get 25 FPS (with room); again, using full size bitmaps.
Thanks for doing the FlexProp test, Jon. I can verify the bitmap24() result, at least.
You can get a nice bump to the FlexProp numbers by putting the spi_write() function into LUT or COG, e.g. by changing the definition of spi_write to:
(since the LUT declaration is in a comment this shouldn't hurt anything in PNut). This gets the FlexProp bitmap24() time down to 24.8 ms.
Thanks for the tip, Eric. Am I limited to doing that with just one function?
You can do as many as will fit.
I updated my Python image converter to output bitmap (-b) or raw (-b) files. See post 8089 for latest version.
I modified image16() -- again. Was:
Is now:
Instead of specifying the file type that information is conveyed via the w and h arguments. Use 0, 0 for w,h when displaying a bitmap. The code will get the dimensions from the bitmap header. If w,h are not 0, the data is assumed to be raw format and can now be any size and displayed at any place on the screen (stoplight animation demo updated to use raw images). Using raw format saves 70 bytes per image -- useful when small images are used as glyphs.
Per Eric's suggestion, the _ez version has the {++lut} flag on spi_write() to improve performance when using FlexProp
I guess if you really wanted to save on file size and already have a custom converter tool, you could add some simple QOI-style RLE/delta compression.
Fantastic work Jon! I know you spent hours on this and as always, it's quality code. Thank you for all your effort on this.
I have had some trouble getting it to work with PropTool 2.6.2. I did change the @"string" to String("string") and it compiles and works, but the data gets corrupted being sent to the display. At first I thought it was a problem with my Edge, so I swapped out the Edge card with a Rev C chip. Same problem. I then loaded my original code and ran it for several hours without issue. When I loaded this version, again it would cycle though partially and then corrupt the display within the first few cycles of the demo. I think this may be entirely due to me using the 2.6.2 PropTool. When the new PropTool gets out of beta, I circle back to it. I just haven't had the cycles to figure it out yet. I just wanted you to be aware that I had some trouble with it on PropTool 2.6.2.
Thanks, Terry.
I have tested the code using PT 2.7.0 with both the _p2 and _ez version of the driver, and under FlexProp 5.9.12 with the _ez version (which is faster using the {++lut} flag suggestion from Eric).
Please don't wait for 2.7.0 (which has the latest Spin2 compiler) to be out of beta -- those terms mean less to Parallax than to most of us, and it will be a long time before there is another update (Jeff is busy with P2 documentation). I haven't had any problems with 2.7.0 under Windows 10 Pro, and I really like using @"" instead of string("") .
I think you can install 2.7.0 without losing 2.6.x -- just rename your current Propeller Tool folder in Documents before you install 2.7.0. You can still run 2.6.2 by using the exe in that folder. You will want to copy your library from the 2.6.x folder to the 2.7.0 library folder. I did this when installing 2.7.0 but then deleted the 2.6 folder as it was no longer needed.
If you see something in my code that you think is a problem, do point it out. I think you know how careful I am about testing, but as I have demonstrated many times, I am human.