Shop OBEX P1 Docs P2 Docs Learn Events
P2 and SSD1306 OLED display driver [solved] — Parallax Forums

P2 and SSD1306 OLED display driver [solved]

ManAtWorkManAtWork Posts: 2,125
edited 2023-07-25 13:15 in Propeller 2

I think I saw that there is a driver or some demo software available for the P2 and the SSD1306 chip. But I can't find it anymore. The search engine only returns results for the P1.

Is this the only thing available? Or has anybody already written something for the P2?


  • Ah, I think I've found it, again:
    The serach function here in the forum is so terrible. :/ We really need a well maintained OBEX library.

  • Wow, it worked right away!

    I had to download the libraries:
    This is a really extensive piece of work! Thanks, Jesse! ( @avsa242) Lot's of useful stuff in there.
    It includes a lot of files I don't want to copy into my project directory so I moved it to C:/flexspin/library and added "-LC:/flexprop/library" to the tasks.json file of VSC (compileP2 ... "args": ...).
    It's a custom board with the crystal connected to the ethernet controller rather than the P2 directly. So I also had to change

        _xinfreq    = cfg._xinfreq ' instead of _xtlfreq
  • You're welcome! There's also a demo that shows the usage of more of the drawing primitives in the p2-spin-standard-library, in the demos/display subdir.
    It's definitely not super-fast as it just uses a generic SPI engine (or I2C, if that's the case), not custom-tailored for the display or anything, but it provides I think most of the usual drawing primitives. Of course if you have plot() you can draw anything :smiley:

    BTW it's great to see an application built around the P2. It makes me wish I had a CNC machine (just one of those cheap "super-safe" open-frame laser engravers) or a place to put one!


  • @avsa242 said:
    It's definitely not super-fast as it just uses a generic SPI engine (or I2C, if that's the case), not custom-tailored for the display or anything, but it provides I think most of the usual drawing primitives. Of course if you have plot() you can draw anything :smiley:

    Yes, I just looked at the code and it does everything pixel by pixel. Not very fast but the library code is very clean and straightforward. I don't care much about speed (at least for the display) because I use it only for status display and diagnosis. Costs only $3 and is much better than a row of discrete LEDs.

    BTW, to save the users from needing lenses I tried to use a bigger font and just replaced fnt: "font.5x8" by "font.8x16". But that doesn't work and only displays pixel garbage. Do I have to modify anything else or is this font not suitable for this type of display?

    BTW it's great to see an application built around the P2. It makes me wish I had a CNC machine (just one of those cheap "super-safe" open-frame laser engravers) or a place to put one!

    Laser engravers or 3D printers are quite affordable because the mechanics don't need to withstand high forces. The real metal cutting machines need to be much more rigid and heavier. But it's no wizardry. The easiest way is to convert an old manual mill with some stepper motors.

  • avsa242avsa242 Posts: 446
    edited 2023-07-22 15:22

    @ManAtWork said:
    BTW, to save the users from needing lenses I tried to use a bigger font and just replaced fnt: "font.5x8" by "font.8x16". But that doesn't work and only displays pixel garbage. Do I have to modify anything else or is this font not suitable for this type of display?

    That's something I've been meaning to address lately. When I first started writing that driver (and really any of the others), that was the only font I had at the time and so wrote the putchar() routine around that size, which is obviously not ideal. I don't think it'd be too hard to change it to accept up to 16 or 32 pixel-high sizes. I'm also not 100% sure what "format" that 8x16 font is in. I think I liberated that from Eric's P2 VGA text driver. I'm sure it's just a bitmap but I don't know how the glyphs are oriented compared to the 5x8 font.
    I can look into this today, as I'm working on graphics-related things anyway.

    EDIT: Sorry, that was Mark_T that provided that font. I was thinking of something else.

  • Don't spend too much time on it. I have a 8x12 font from another project which might fit better, anyway.

        { NOTE: the actual granularity of y coordinates is 8 pixels - they will be rounded
            to the nearest multiple of 8 by the driver (hardware limitation) }

    That shouldn't apply, I think. The PutChar() function does call plot(9 for every pixel so it should work with almost any size, even really odd ones.
    The 5x8 font is oriented in the Y direction per byte so I have to flip my 8x12 font over by exchanging X and Y in the PutChar() function, right?

  • Oh, that's in reference to coordinates for scrolling setup in the hardware-accelerated scrolling demo only. The font rendering is still limited, but not in the same way. It just uses some calculations that are hardcoded for 8 pixel-high fonts (like finding the offset in the font table - see putchar() in graphics.common.spin2h)

    I haven't found another 8x12 or 8x16 font to try yet, but in the meantime, I've expanded the text scaling capability with experimental independent x,y scaling. Try updating p2-standard-library and loading up SSD130X-Demo.spin2. Change the params in the call to font_scl() to scale up:

            disp.font_spacing(1, 0)
            disp.font_scl(1, 1)   ' (x, y) scaling; try for example (1, 2) for normal X-scaling but 2:1 Y-scaling
            disp.font_sz(fnt.WIDTH, fnt.HEIGHT)

    The text will have a bit of a "bold" look to it since it's just using filled boxes for scaled up pixels. See if it works for you.

  • As far as swapping the x,y coordinates for fonts oriented 90deg, I'm not sure...I haven't tried it :) It sounds like it'd work, but I'm not really sure...

  • avsa242avsa242 Posts: 446
    edited 2023-07-23 20:05

    Okay I just updated graphics.common.spin2h; update p2-spin-standard-library to use.

    • now works with other size fonts (have tried the original 5x8 font, a VGA 8x16 font, and some other 5x11 font - reference the .font files at
    • now works with 0deg rotated glyphs (like yours) or 90deg rotated glyphs (like the 5x8 font in the library). When building, define -Dputchar=putchar_0deg at build time, or -Dputchar=putchar_90deg for fonts like the 5x8 font.
    • now works with byte or bit-reversed order glyphs (bit or byte depending on the putchar variant used). When building, define -DFNT_GLYPH_REV for reverse order (applies to both putchar() variants)
    • added a TERMINAL symbol for use with char_attrs() to turn control code processing on or off during run-time (e.g., oled.char_attrs(oled.TERMINAL) to turn it on); with this bit set, a few of the chars like LF, CR will move the cursor, otherwise it'll just show the glyph. Note this behavior was essentially hardcoded on before all these changes, so any apps that were expecting it before would need the char_attrs() call added now. So things like oled.printf(@"line1\n\rline2") would need it turned on for the CR, LF to actually go to the next line.

    I'm not sure I'll keep this the way it's implemented right now forever. I'm not 100% crazy about making the putchar() variants switchable at build-time vs run-time, though I guess it really depends on the application and the font files whether more than one type would be needed. I didn't want to overload the existing putchar any more than it already is though. Will figure out a better way some time in the future :)

    For those fonts in that github repo I referenced above, or similar ones, you can use them in spin2 like:

        font file "fontfile.font"
    pub setup()
        disp.font_spacing(1, 0) ' (x,y) space between chars
        disp.font_scl(1, 1) ' (x, y) font scaling (only works with the putchar_90deg() variant, currently
        disp.font_sz(5, 8) ' (width, height) of font glyphs
        disp.font_addr(@font) ' address of font data
  • Just tried the rest of the fonts in that epto repository. They're all the same rotation, so all work with the following defined on the flexspin command line: -Dputchar=putchar_0deg

  • Thanks a lot, Jesse. It works. I just noticed that the keywords

        FIRSTCHAR   = 32
        LASTCHAR    = 127

    are defined but not supported. So I'd suggest to add this to the graphics.common.spin2h:

        long _fnt_cmin, _fnt_cmax
    PUB font_addr(addr, min=0, max=127): curr_addr
    ' Set address of font definition
        case addr
                _font_addr := addr
                _fnt_cmin:= min
                _fnt_cmax:= max
                return _font_addr
    PUB putchar_0deg(ch) ....
        { read a glyph in from the font table }
        if ch > _fnt_cmax OR (ch-= _fnt_cmin) < 0
        p := _font_addr + (ch * _font_height)
        repeat gl_row from 0 to last_glrow
  • Hmm, if I compile with "-g" I get an error message that stdio.h redefines putchar. Is there a way to declare that the OLED display is not the "standard terminal"? I fear I have to cut and paste the functions I use from your library to my own version and rename them. It seems to be "too clever" and re-uses as much code as possible for any imaginable scenario but that creates a conflict with serial debugging.

  • hmmm not sure what's going on there... I haven't used debugging features yet. That'd be an unfortunate side effect. 😕 Yes i include a set of common terminal i/o routines with every display or other similar driver so doing things like printf, str, etc works the same way.

    @ersmith does flexspin insert a putchar() into spin2 code when debugging is enabled? I name my elemental char method 'putchar' in all of my drivers that have terminal output.

  • If plain -g is used (as opposed to -gbrk) then Spin2 DEBUG() calls are turned into calls to the C printf() function. That may be pulling in putchar() indirectly. If you're on a P2, it's probably best to use -gbrk debugging, both to avoid this and to allow debugging of assembly language.

  • Never mind. I think I have to modify Jesse's library anyway. I'll need switching between different fonts at runtime. I'll use the biggger 8x12 font for the normal status display so it's clearly visible and a smaller 5x8 font for diagnosis so that as much information as possible fits on the small screen. I simply rename the putchar function.

    I think that there was a problem with -gbrk when Spin2 and C are used at the same time. I'll check it out.

  • Ok, I have both fonts selectable at run time and with debugging enabled.

    The 8x12 font looks a bit ugly as it is a downscaled version of the P1 ROM font and suffers from aliasing. I'll take care of that later if a customer pays me for implementing a logo...

  • avsa242avsa242 Posts: 446
    edited 2023-07-25 11:28

    What would you think about something like this added to the graphics library:

    pub set_font(ptr_fnt, ptr_setup)
        disp.font_sz(byte[ptr_setup][0], byte[ptr_setup][1])
        if ( byte[ptr_setup][2] == 0 )
        elseif ( byte[ptr_setup][2] == 90 )
        disp.set_font_char_range( byte[ptr_setup][3], byte[ptr_setup][4] )
        disp.font_spacing( byte[ptr_setup][5], byte[ptr_setup][6] )

    The font files would have a small setup block added to them to replace the constants in there now like:

        { font definition }
        width       byte 5
        height      byte 8
        rotation    byte 90
        firstchar   byte 0
        lastchar    byte 127
        hspace      byte 1
        vspace      byte 0
    PUB setup(): p
    ' Return pointer to 7-byte setup info block
        return @width

    So then in the application, you could switch between different fonts at runtime with something like:

                disp.pos_xy(0, 0)
                disp.set_font(fnt8x16.ptr(), fnt8x16.setup() )
                disp.printf(@"%d\n\r%dx%d ABCDabcd\n\r", getct(), fnt8x16.width, fnt8x16.height)
                disp.set_font(fnt5x8.ptr(), fnt5x8.setup() )
                disp.printf(@"%d\n\r%dx%d ABCDabcd\n\r", getct(), fnt5x8.width, fnt5x8.height)

    I haven't added it to the library yet but have tested the above and it works. :)

    A little more info: set_font_rotation_0deg() and set_font_rotation_90deg() actually make putchar a pointer to the putchar_0deg and putchar_90deg functions (so putchar is now a VAR long in the graphics lib)

  • ManAtWorkManAtWork Posts: 2,125
    edited 2023-07-25 11:34

    :) I just had about the same idea. I've implemented it in almost the same way but not that elegant...

    PS: I've also modified position() aka pos_xy() to take pixel coordinates instead of character columns/rows so that position is independent of the current font setting.

  • Hahah I'd forgotten I intended to do that at some point, too...
    So many projects, so little time!

  • Okay runtime-putchar switching (including an available BYOP - bring your own putchar; call set_putchar() with a pointer to your own putchar function), and pixel-level positioning are implemented now (only on P2 atm). The position code has to explicitly be switched on at build-time with -DFNT_POS_NOGRID - I didn't want to break existing code that assumes grid positioning (I have a lot of it).


  • Haha, I've just found a bug that is both funny and very evil. Although I'm sure it was coded with the best intentions it behaved like it came directly from hell. :D

    PUB address(addr): curr_addr
    ' Set framebuffer address
        case addr
                _ptr_drawbuffer := addr
                return _ptr_drawbuffer

    This works perfectly as long as it's executed on the P1 (where addresses are always below 32kB) or if your code size is small on the P2. Add some code and sooner or later it goes boom. addr becomes >32k and _ptr_drawbuffer is not set and remains ==0 resulting in memory being overwritten as soon as any display function is called.

    This took me several days to debug. I've commented out some lines of my code and the display immediately started working, again. No crashes and messed up results. So I blamed the functions I've commented out and searched for bugs but, of course, with no luck. Even rearranging function calls caused the bug to randomly appear and disappear because it influenced how the linker put the compiled code and also the buffer into different memory regions.

    I have great respect for Jesse's work. The library is written very cleanly with as much code shared and reused between different drivers and modules. But I also have some critizism here. IMHO, functions should serve a single purpose only, if possible. Overloading them for dual purpose has no real advantage. I think, two seperate functions set_address() and get_address() should be better and haven't caused unnecessary errors.

  • @ManAtWork
    Hah! I thought I'd written the spin2 version of that with enough headroom for the P2's 512k.
    Criticism taken and considered...this had crossed my mind in the past, I think, but I hadn't made a note of it anywhere to revisit, so forgot about it. I think I just wrote it like that originally out of habit, because so many of my drivers for devices operate in a read-modify-write fashion where I allowed a range of valid values for setting and anything invalid would just return the current setting instead (for most of the sensors or other devices that in order to modify one bit in a register without clobbering other unrelated bits in the same register). In this case however I agree there's no clear advantage.
    Sorry for all the grief this caused :# and thanks for the complements!


  • One more question: I'm looking for a way to display small pictures, e.g. symbols that are larger than text characters, say 32x32 pixels. There's a function bitmap() in graphics.common.spin2 but I don't know if it's suitable. As it uses the same offset for both source and destination bitmap I think it expects both to have the same size which is not true in my case. It also supports only X coordinates divisible by 8 as it only copies whole bytes.

    I could build a special symbol font that has only a few characters with larger size. The putch() functions draws them pixel by pixel so there are no restrictions about the coordinates. It's not very efficient but as I don't need moving sprites but only <1fps update rate I don't care much.

  • I think this should be possible. When I wrote bitmap() I think I was trying to get it to be faster, hence not re-using plot(), but this is probably another fundamental design flaw: it probably should use plot(), just so it behaves essentially the same way no matter the graphics driver (that's what I'd intended when writing this thing originally, anyway). I'll start with code from the putchar() methods since as you hinted, that's got pixel-level freedom, and is a bitmap drawer already.

  • Hmm, I looked at the code and it seems that putchar_0deg(ch) was not designed to work with fonts wider than 8 pixels. :|
    Given the complexity of handling all special cases (bytes per pixel format, reversed vs. normal bit order, 0 vs. 90° orientation) I think it's easier if I write my own copy_bitmap() function that directly copies the bytes into the display buffer.

  • Okay, there's an experimental bitmap_1bpp() added to just the spin2 library. You can try it from the latest at

    When testing this, I ran into a similar issue to the font orientation one I ran into when making those putchar() changes: the bitmap can be horizontal or vertical in its source form (bits within each bitmap image byte can represent different columns, or different rows of the image). I have a monochrome bitmap of the Propeller Beanie hat in the p2-spin-standard-library repo, and it wasn't rendering correctly with this new code (I borrowed it from Adafruit). I tried creating another bitmap and converting it to a byte array using LCD Assistant ( and found with Adafruit's code, it needs to be oriented in the 'Horizontal' byte orientation.
    After doing that, the bitmap seems to display correctly and I can move it to various parts of the screen.
    Try it and see if it works for what you're doing.


  • RaymanRayman Posts: 14,147
    edited 2023-11-18 02:51

    I also need some code for this. This looks like a good place to start!
    Wait... I thought this was for the Parallax OLED module... Seems similar, but Parallax one has SSD1331 ... Probably almost the same though, I imagine...

  • - might be quite a bit different at the low-level...I think I remember that chip being a bit of an oddball, with the accelerated primitives and faster command interface, but the driver is written with as much the same interface as the 1306 driver, so practically speaking, no different.

Sign In or Register to comment.