datacute / Tiny4kOLED

Library for an ATTiny85 to use an SSD1306 powered, double buffered, 128x32 pixel OLED, over I2C
MIT License
264 stars 39 forks source link

`fill` and `filltoEOL` don't account for vertical memory addressing mode #19

Open rsheldiii opened 5 years ago

rsheldiii commented 5 years ago

Hi there! thanks for making this library. I found a small issue with fill and probably a few other functions. In vertical addressing mode, according to the datasheet, when a segment is written the page address is incremented as opposed to the column address, opposite of horizontal and page addressing mode. In vertical addressing mode, the end of a line is 8 segments instead of 128, or 4 in our case due to double buffering.

What's interesting is despite commands 00-1F and B0-B7 being described as "for page addressing mode", setCursor seems to work just fine, though I am only using setCursor with an x value so far.

When using fill with vertical addressing mode, we use setCursor to get to the first segment, fill 128 segments (16 total lines of 8 segments per line, so transgressing into the buffer) and then setCursor back up to a column address of zero, resetting our progress. Whether or not the page address command is respected in vertical addressing mode, you clear at most ~17 total lines, since column address 0, page address 1 is only 8 segments into the memory instead of the normal 128.

I'm fine with clearing the entire screen and the buffer in my program, so I've replaced calls to clear() with 1024 data bytes of 0, taking advantage of the wrapping nature of the cursor in vertical addressing mode. It would be nice clear() and fill() worked out of the box in vertical addressing mode though!

datacute commented 5 years ago

Thanks for reporting. Incidentally, there's a revision 1.5 of the datasheet. Revision 1.2 added details of "Advanced Graphic Commands" i.e. fade out, blinking, and zoom in.

datacute commented 5 years ago

True, most of the text/fill/bitmap functions in the library assume that you've used the libraries default initialisation sequence (which includes Page addressing mode), and haven't changes the settings.

I added access to all the documented commands so that developers who wanted to experiment further were able to do so more easily.

Since you can supply your own init sequence, there's no easy way of determining which addressing mode is in use without adding quite a lot of code to the library. Even if a simple approach of only watching calls to setMemoryAddressingMode was used, switching the implementation of all the other methods to cope with the other two modes would use up quite a lot of space. (I haven't tried this to find out how much though.)

The main reason I started working on this library was to add double buffering on 128x32 displays. I toggle which half of RAM you write to (adjusting setCursor calls) and which half of RAM you display. I mention this because the fill and clear functions only work with the half the RAM that you're currently writing to. If you want to still use double buffering, and vertical addressing mode, you can probably still have a quick clear function by first calling setPageAddress (command 0x22) to wrap the writes to only be to half the pages of RAM. Then call startData(); clearData(512); endData();

I'm curious why you are using vertical addressing mode, and which screen you are using?

rsheldiii commented 5 years ago

I'm using a cheap 128x32 display off aliexpress: https://www.aliexpress.com/item/32712441521.html to display pong via an attiny: https://github.com/rsheldiii/Keebcard/ which is why I'm in vertical addressing mode. During operation I care about updating 4 vertical lines as fast as I conceivably can on a 1mhz chip, so switching to vertical mode allows me to skip a few unnecessary setCursor commands and gives me a nice speed bump. I'm using double buffering for most of the application, but there are times (like when the application starts or when the user pauses) that are not time-critical, where I could just clear the ram anyways. good point about 0x22 though, that's another one marked as "for page addressing mode only" in 1.1, I'll have to brush up on the new spec.

It's true that with a custom init sequence, tracking the state of the screen is difficult. I switch back to horizontal addressing for prints so adding some kind of compilation flag isn't ideal. You can already "trick" the cursor by directly sending page and column update commands, so I don't think assuming a default initialization and listening to setMemoryAddressingMode calls is that bad of an idea; if you want the added memoization functionality you gotta follow the rules. Maybe I'll take a stab at it sometime and see what kind of extra memory it creates.

Velodrom commented 4 years ago

I'm not sure if this is related to the same problem, but I've noticed that fill() and clear() (where clear is just another call to fill(0x0)) don't really cover the whole screen as one could expect. No modifications the initialization sequence.

Output after fill(0xFF): IMG_20191012_163958

Output after clear(): IMG_20191012_164052

datacute commented 4 years ago

@Velodrom that problem is solved by updating your TinyWireM version. Adafruit released a fix for that last year: https://github.com/adafruit/TinyWireM/releases/tag/1.0.1