Skip to content

Protomatter: Integrate with CircuitPython #2706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Apr 14, 2020
Merged

Conversation

jepler
Copy link

@jepler jepler commented Mar 14, 2020

This pull request:

  • Adds displayio.FramebufferDisplay
  • Adds _protomatter.Protomatter
    It anticipates that a new module adafruit_protomatter will be used to combine the two into a displayio-using led matrix driver.

Current known problems:

  • Does not survive soft reset
  • Does not auto-refresh (these first two are related, enabling auto-refresh makes it crash at soft reset)
  • Rotation did not work
  • The console only gives 1 full row of text on the 64x32 panel, seems like it should be more (it's not really usable anyway)

The code I've been using to drive it is an adaptation of the "1D Wave Simulation" ulab demo: https://gist.github.com/jepler/5b11e292193f1fcb602900ff50e8944d

@jepler
Copy link
Author

jepler commented Mar 20, 2020

It's still not ready for inclusion, but I've heavily reworked this PR. See the initial comment for much more information, including a checklist of known problems. The one I'm worried about is the problems with auto_refresh / surviving soft reset, because the design requires this Python Protomatter class to work. This design may simply not be workable, so hopefully @tannewt has some ideas about how to restructure it so it can work. Rotation is "probably a shallow bug" but I haven't looked at it yet.

@jepler
Copy link
Author

jepler commented Mar 24, 2020

The 64x32 display isn't very useful for showing the console -- you get only 2 rows by 8 chars.
Making the display persist over soft resets is proving difficult. Instead, it resets itself at soft reset.

Because of the less than useful nature of the console, I wonder if the work could be accepted in this state; and later on we can come back and work on it when we have framebuffer lcds large enough for it to matter. @tannewt ?

@tannewt
Copy link
Member

tannewt commented Mar 24, 2020

@jepler What did you try when getting it to work across soft resets? Seems like you are close and having it will be useful when we support multiple panels in the future.

@jepler
Copy link
Author

jepler commented Mar 24, 2020

I'm aware that I need to preserve

  • all the involved pins (existing never-reset code should cover this, though)
  • the _protomatter.Protomatter object with its own internal allocations (these can't be moved without additions to protomatter's C API)
  • the 565-format bytearray that is used as the target for the FramebufferDisplay and converted to protomatter's internal format
  • the callback object which lets FramebufferDisplay notify the underlying framebuffer that it's been changed
    Because I identified the limitation in the protomatter C API, I wanted to not go too far down a road we may or may not want to travel.

The hardest parts of preserving across a reset are specific to Protomatter and don't seem to be things that apply for true hardware framebuffers.

@jepler
Copy link
Author

jepler commented Mar 24, 2020

I presently define _PM_allocator to call m_malloc. Here are its uses within protomatter:

core.c:    if((core->rgbPins = (uint8_t *)_PM_ALLOCATOR(rgbCount * sizeof(uint8_t)))) {
core.c:        if((core->addr = (_PM_pin *)_PM_ALLOCATOR(addrCount * sizeof(_PM_pin)))) {
core.c:    // _PM_ALLOCATOR() by definition always aligns to the longest type.
core.c:    if(!(core->screenData = (uint8_t *)_PM_ALLOCATOR(screenBytes + rgbMaskBytes))) {

@tannewt
Copy link
Member

tannewt commented Mar 24, 2020

Did you try moving the three protomatter allocations? If that works, we could add an api or simply go with hacking it.

@jepler
Copy link
Author

jepler commented Mar 28, 2020

This branch is going back into a heavily unstable state, I'm pushing some clearly not working code as a shortcut to transfer it between a couple of my computers.

@jepler
Copy link
Author

jepler commented Mar 29, 2020

actually the unstable version went in a branch that is not pull-requested yet. it's https://github.com/adafruit/circuitpython/compare/master..jepler:protomatter-displayio-v2

What's changed so far is:

  • I created an allocation/deallocation function which can use heap if available and supervisor allocation if needed
    • this involved exposing a new function in the supervisor allocator to check if a block was allocated by it, not by the heap
  • I placed a protomatter_protomatter_obj_t in primary_display_t, but do not use it yet
  • I am in the process of eliminating the need for the "userspace" class between FramebufferDisplay and Protomatter. This involves starting a framebuffer protocol in C.

Next steps:

  • Move the record of the used pins into protomatter_protomatter_obj_t, rather than into subordinate allocations. This means fixing the max number of RGB and address pins.
  • At reset, move the protomatter_obj into the fixed storage (if not there already), and then initialize a fresh Protomatter object using the supervisor allocator if necessary
    • this will cause a short period where the display is blanked
  • Similar to moving the pin info directly into the protomatter_obj, I may do the same thing in the Protomatter object to reduce allocations

I am going to choose a fixed limit of 30 RGB pins + 6 address pins, which fits in a multiple of 4 bytes. For the breakouts that adafruit sells, 6+4 is the actual number used.

This is more work than I anticipated initially, but I feel like the pieces are starting to come together and my understanding of displayio and the allocators is improving as an additional benefit.

@jepler jepler marked this pull request as ready for review March 31, 2020 22:26
@jepler
Copy link
Author

jepler commented Mar 31, 2020

I believe that generally speaking the code works at this point and it is ready for style review, though before it's done-done we need to get the protomatter changes merged upstream and add support for nRF on our end.

@jepler jepler requested a review from tannewt March 31, 2020 22:29
@jepler
Copy link
Author

jepler commented Mar 31, 2020

I should explain that the test changes are not really related, see micropython#4000 for background.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have those test changes integrated already. Do you think we just messed up the merge?

@tannewt
Copy link
Member

tannewt commented Apr 1, 2020

(I still have a bunch of review to do on this.)

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on! I've add a number of comments. I like the directions you are going by using a "framebuf as a bus". I think there is some work to do on the Framebuf stuff to slim it down and simplify it. I think having brightness on it makes sense but it should then delegate to the framebuffer provider to implement it. A 24bit TTL display will have a backlight pin but a protomatter display will need to adjust what it outputs.

@jepler
Copy link
Author

jepler commented Apr 2, 2020

As far as the backlight, it can delegate to the underlying framebuffer (a protocol function that is permitted to be NULL), or it can use a pin as now as a fallback. protomatter uses 0 to mean pause, nonzero to mean not paused; it doesn't scale RGB values to emulate intermediate brightness levels. I have a feeling that adding a fixed off time might give better results, so if that's important we should talk to @PaintYourDragon about it.

If we thought at least 2 framebuffers would do backlight via RGB pin as regular Displays do, then having the code in FramebufferDisplay will be a net reduction of copypaste backlight code.

@jepler jepler requested a review from tannewt April 3, 2020 16:07
@jepler
Copy link
Author

jepler commented Apr 3, 2020

@tannewt I've made changes for many of your review comments, and addressed some of the others inline. Please take another look when you have a chance.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as the backlight, it can delegate to the underlying framebuffer (a protocol function that is permitted to be NULL), or it can use a pin as now as a fallback. protomatter uses 0 to mean pause, nonzero to mean not paused; it doesn't scale RGB values to emulate intermediate brightness levels. I have a feeling that adding a fixed off time might give better results, so if that's important we should talk to @PaintYourDragon about it.

If we thought at least 2 framebuffers would do backlight via RGB pin as regular Displays do, then having the code in FramebufferDisplay will be a net reduction of copypaste backlight code.

I like the idea of FramebufDisplay having the brightness knobs (brightness and auto_brightness) but think the protocol should implement it and take the args on construction for the pin. We could have FramebufDisplay scale the RGB values like PixelBuf does but agree adding a fixed off time would give a better result. So, I'd removed backlight_pin for now.


//| .. method:: write(buf)
//|
//| Transmits the color data in the buffer to the pixels so that they are shown.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about calling this refresh() to match the other display types? I get that this would be needed if using protomatter without displayio.

protomatter_protomatter_swapbuffers(self_in);
}

STATIC void protomatter_protomatter_deinit_void(mp_obj_t self_in) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds similar to what common_hal is used for, C data structures rather than Python ones. Could you use the common_hal variants in the protocol?

These wrapper functions don't make much sense to me because the user of the protocol could just throw away the return too.

@jepler
Copy link
Author

jepler commented Apr 13, 2020

@tannewt I think I fixed all the issues you raised, except for this one. For some reason there's not a spot to reply inline, so ...

Protocols vs common_hal: Protocols take mp_obj_t as the first argument, while common_hal takes a type-specific object pointer. On reflection, I prefer getting the compile-time type checking that the protocol function pointer is of the right type, even if it means introducing an intermediate function to do the casting of the argument. (I think I did it with casts when I did audio sample protocols)

As always, I can change it if you feel strongly.

@jepler jepler requested a review from tannewt April 13, 2020 14:16
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm fine with the wrapper functions.

One comment on the API. I glanced at your Learn guide draft and had thoughts. Is there a library I should look at too?

Thanks!


//| :class:`~_protomatter.Protomatter` displays an in-memory framebuffer to an LED matrix.
//|
//| .. class:: Protomatter(width, bit_depth, rgb_pins, addr_pins, clock_pin, latch_pin, output_enable_pin, *, doublebuffer=True, framebuffer=None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just peeking at your guide for how I can test this. I think we want these all to be kwarg-only args because they are either numbers, pins or lists of pins. Reading the code doesn't help with what is what. The current positional arguments can be required and have no default.

I'd also take in the height even though it's implicit in the address pins. It can make the framebuffer smaller than the available address pins and can also validate enough address pins are given.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can make them kw-only.

I can make it so that height, if specified, is checked against the expected number. I'm not sure of the use case of creating a 16 line display on a 32 line panel, though.

however, the raspberry pi lib for driving these displays (I think that's the one) can drive multiple displays wired in "zigzag" fashion. In that case, the relation between the WxH of the screen is decoupled from the true width of the display. Protomatter C library doesn't support this (yet, not sure if planned).

@jepler
Copy link
Author

jepler commented Apr 14, 2020

There is a wrapper module, but it's not uploaded anywhere yet. It may just end up committed in the guide repo at this time, because I'm not sure there's time to get it into the library package on the timeline in which we want the guide to appear.

Looks like I can't get the current content at this computer I'm at, but it's close to this:

import displayio
import _protomatter

def protomatter_display(
    width,
    depth,
    rgb_pins,
    addr_pins,
    clock_pin,
    latch_pin,
    oe_pin,
    *,
    doublebuffer=True,
    framebuffer=None,
    rotation=0,
    backlight_pin=None,
    brightness=1.0,
    auto_brightness=False,
    auto_refresh=True,
    native_frames_per_second=60
):
    height = len(rgb_pins) // 3 * 2 ** len(addr_pins)
    pm = _protomatter.Protomatter(
        width,
        depth,
        rgb_pins,
        addr_pins,
        clock_pin,
        latch_pin,
        oe_pin,
        doublebuffer,
        framebuffer=framebuffer,
    )
    return displayio.FramebufferDisplay(
        pm,
        width=width,
        height=height,
        rotation=rotation,
        backlight_pin=backlight_pin,
        brightness=brightness,
        auto_brightness=auto_brightness,
        auto_refresh=auto_refresh,
        native_frames_per_second=native_frames_per_second,
    )

There's also a def default_display() but I am not sure that makes sense.

@jepler
Copy link
Author

jepler commented Apr 14, 2020

should rename "depth" to "bit_depth". The 4 value was just in the Arduino example, gives 4096 colors (16 effective levels per primary).

jepler and others added 26 commits April 14, 2020 18:24
POSIX specifies that text files end in a trailing newline
 - bump supervisor alloc count by 4 (we actually use 5)
 - move reconstruct to after gc heap is reset
 - destroy protomatter object entirely if not used by a FramebufferDisplay
 - ensure previous supervisor allocations are released
 - zero out pointers so GC can collect them
Co-Authored-By: Scott Shawcroft <[email protected]>
Just setting the timer handler to NO_INTERRUPT doesn't stop the
interrupt from occurring.
This may have been contributing to fragmentation of the supervisor
heap
Per @tannewt, this area "sees more churn", so it's probably the right
choice here
The numbered error from the underlying library is not helpful for
beginning users
 - rename oe_pin -> output_enable_pin
 - improve and reorganize docstrings
 - rename swapbuffers->refresh
 - rename "paused" -> "brightness", change semantics slightly
 - common_hal several functions
 - clarify why the common_hal routines can't be used directly in the
   protocol's function pointers
 - whitespace cleanups
 - remove prototypes for nonexistent functions
…eight checking

They're not readily distinguishable by type.

I also added the requested height optional parameter; this is checked
against the computed one.  It's not feasible to use this parameter to
artificailly reduce the number of used rows, because changes in the
underlying C protomatter library would be required.

Finally, I added a better error message when the number of RGB pins was
not what was expected.
.. change the in-rom palette to be in RGB565 order
@jepler
Copy link
Author

jepler commented Apr 14, 2020

@tannewt @ladyada Checks are green now, but since I had to "make translate" I need a fresh review before it can be merged.

Copy link
Member

@ladyada ladyada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

@jepler jepler merged commit 7ed1483 into adafruit:master Apr 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants