fizban99 / microbit_ssd1306

Simple micropython library for the micro:bit to control the SSD1306 display
MIT License
54 stars 33 forks source link

show_bitmap frames per second? #11

Closed isparks closed 3 years ago

isparks commented 3 years ago

Great library, thank you.

I'm experimenting with using show_bitmap to flash between images to see how many frames/second you can expect with show_bitmap. I think I can get about 8 or 9 FPS on a micro-bit v1 before I start to see images drop out.

from ssd1306 import initialize
from ssd1306_bitmap import show_bitmap
import time

initialize()

while 1:
    show_bitmap("new_logo")
    time.sleep(1.0/8)
    show_bitmap("microbit_logo")
    time.sleep(1.0/8)

I didn't see a way to change the I2C frequency (changing it with i2c.initi(frequency=....) didn't seem to do anything.

9 FPS isn't terrible but am I missing any obvious way to do better with this method?

fizban99 commented 3 years ago

I guess the maximum frequency of 400KHz for I2C limits the real refresh rate to that amount more or less. With an SPI OLED device you might get better refresh rates...

isparks commented 3 years ago

Ok. I did a bit more experimentation. With these hacks to the show_bitmap I can display my 18 frames in various times based on setting the i2c frequency (now 18 frames in under 1 second)

from microbit import i2c
from ssd1306 import initialize, clear_oled, command, set_zoom, set_pos, ADDR
from ssd1306_text import add_text
import utime

def show_bitmap(filename):
    #set_pos()
    #command([0xae])  <-- don't clear screen, we're going to overwrite all pixels anyway!
    with open(filename, 'rb') as my_file:
        for i in range(0, 16):
            i2c.write(ADDR, b'\x40' + my_file.read(64))
    set_zoom(0) # required to invert image but if your image is not inverted you won't need this
    # command([0xaf])

i2c.init(freq=400000) # <-- set clock to max rate

initialize()

now = utime.ticks_ms()
for i in range(18):
    frame_num = str(i)
    while len(frame_num) < 2:
        frame_num = "0" + frame_num
    fname = "frame_" + frame_num + ".bin"
    show_bitmap(fname)

clear_oled()
end = utime.ticks_ms()
add_text(0, 2, str(utime.ticks_diff(end, now)))

With this I can get:

Frequency    Time (ms) for 18 frames written 
100000         2400      
200000         2400        (odd, does not change, tested over and over)
300000         1200
400000           990 

Omitting some of the calls in show_bitmap might leave things in a bad state for following show_bitmap calls after writing text using add_text() I find a call to set_pos() clears this problem for me.

Also, I might be imagining it but the old python speed-up trick to remove object lookups seems to make it 5-10ms faster overall:

w = i2c.write
....
        for i in range(0, 16):
            w(ADDR, b'\x40' + my_file.read(64))

One thing I don't understand (and this is my limited microcontroller knowledge) why this does not work:

        for i in range(0, 16):
            i2c.write(ADDR, b'\x40')
            i2c.write(ADDR, my_file.read(64))

In normal python I believe appending the my_file.read() operation to a byte would end up doing a new allocation. I thought I'd try with an array so we read into the array over and over rather than allocate new memory for 64 bytes on every loop:

w = i2c.write  
def show_bitmap(filename):
    b = bytearray(64)

    with open(filename, 'rb') as my_file:
        ri = my_file.readinto
        for i in range(0, 16):
            ri(b, 64)
            w(ADDR, b'\x40' + b)
    set_zoom(0) # required to invert image but if your image is not inverted you won't need this

And indeed, this combination seems to save 80-90ms across all 18 frames (4-5ms/show_bitmap). Shame we can't re-use the screen buffer already declared by updating bytes 1-65 of the screen buffer (0 already holds 0x40) and then writing screen[0:65] to the I2C. Wasn't obvious to me how to do it..

fizban99 commented 3 years ago

Nice! Yes I remember trying the two consecutive writes and not working for me either. This code was done quite a while ago and I am no longer working with microbits, but I am glad it inspired you to create something better!

fizban99 commented 3 years ago

Regarding reusing position 0, you might be able to accomplish it using memoryview. And maybe making the buffer global in order not to allocate it in every iteration. Finally, use "%02d" for the 0 padding of the frame number.

isparks commented 3 years ago

Thanks @fizban99 - I was looking for memoryview also but could not find it in the micro:bit version of micropython and putting buffer outside the function I considered but worried about "stealing" 64 bytes permanently from the user of the library.

Are you taking PR's or am I better to fork? I don't know enough about microcontrollers to actively maintain.

Very much appreciate your efforts to make it. Very excited the first time I wrote a pixel to the screen!

fizban99 commented 3 years ago

I think it is better to fork. As I said, I am no longer working with microbits, This was an old project I published in github for anyone to use, experiment and play with. This was also used in a school workshop I gave.

I found that micropython on the microbit was quite limited, mainly because of the reduced amount memory available for the actual code. I remember finding myself having to remove all the comments, use variables with short names and such in order not to get out of memory errors. So, although having generic libraries might seem like a good idea, in practice and because of the memory limitations of the microbit people might have to actually copy and paste only those pieces of code that they are interested in, in order to make their program work and even modify the copied code to fit their specific purpose...

I believe other microcontrollers with more memory benefit more from these type of libraries. The circuit playground seemed a good alternative, but I never got a chance to play with it.

isparks commented 3 years ago

Yes, totally agree. It reminds me a lot of the original BBC Micro - I remember having to shorten variable names to make my BBC BASIC programs fit the 32K. Thanks again.