Skip to content

Instantly share code, notes, and snippets.

@trentgill
Created December 2, 2021 15:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trentgill/240a997d910c9a0e2c14131c9199d9fb to your computer and use it in GitHub Desktop.
Save trentgill/240a997d910c9a0e2c14131c9199d9fb to your computer and use it in GitHub Desktop.
some thoughts on grid support on crow
--- teletype-crow expansion
-- how to deal with grid leds?
-- sending the whole frame every tick is too much, even with a dirty flag
-- idea is to maintain the led buffer in a C array
-- grid.set(x,y,s) will directly call a C function which sets the nibble in question
-- thus the backing C array is only 64bytes (128 * 4bits)
-- when we call grid.update() we can dynamically decide how to send the updated frame
-- tools:
-- a) led_level(x,y,s) sends a single led. {address, cmd, (x<<4)|y, s} (could be optimized by combining cmd&y, requiring 8 i2c command slots)
-- b) led_row(x-off, y, s0|s1, s2|s3 ...) send a row of 8 leds {address, cmd, (x|y), 4packed} (again could optimize by combining cmd&x&y, requiring 16 i2c commands)
-- requirements:
-- when calling grid.set: (this must be optimized for speed, hence no checks, just saving metadata)
-- increment led-counter
-- save x|y|s into a 16-stage circular buffer
-- ? this could be conditional on whether the led-counter is <16 steps
-- ? i have a feeling the condition is more expensive than writing a half-word & mask-incrementing a u8
-- set x|y in active buffer to s
-- when calling grid.update:
-- if led-counter > 0
-- if led-counter <= 16 (minimal changes)
-- loop backwards over circular-buffer for led-counter steps
-- if state != old_state (check to ensure this led has actually changed)
-- led_set() -- maximum of 64bytes i2c (can optimize to 48bytes)
-- if led-counter > 16 (barely worse than led_map)
-- loop over each 8led row
-- if row (as u32) != old_row
-- led_row() -- max 16 * 7bytes = 112bytes (can optimize to 96bytes)
-- reset led-counter
-- flip buffer-bit (ie we now draw into the opposite buffer)
-- theoretically we could say: if the led-counter is > 64 we send a led_map command
-- this would reduce to only 66bytes, but would require overhaul of the i2c driver and only reduces the
-- traffic by a small amount.
-- plus, there is a benefit to the led_row approach as it breaks the message into multiple segments,
-- allowing for sharing of the bus in case there is some other ongoing messages occuring.
-- 96bytes at 400kHz ~== 2 milliseconds (520Hz, way higher than 60fps supported on grid!)
-- either crow or teletype (or both) will choke on *creating* the data to draw well before that
-- throughput becomes an issue.
-----
-- this all feels good. seems like the core limitation is going to be the crow script's ability
-- to express a grid ui in a tight way.
-- note we can provide table-syntax into the c-array if that helps?
-- grid.l[x][y] = 3 -- set grid location x,y to level 3
-- print( grid.l[x][y] ) -- print the level at location x,y
-- these table accesses could use metamethods but they'd be slow, so we can just provide some
-- light tables with closures for the x-dimension, so we only have __(new)index call which just
-- inquires directly to C.
@tehn
Copy link

tehn commented Dec 3, 2021

really basic additions:

  • minor optimization: perhaps maintain dirty bits for each row (set on led_set, cleared at led_update)
  • major: support led_all as it's very common
  • minor/maybe: include bit in cmd field for update/refresh, so an led_set could be configured to have an immediate refresh afterwards. (this really only saves having to initiate and send one more packet, but there's a sort of elegance to this that feels like an assembly op) ie

LSET x y z // just set LED
LSETR x y z // set LED and refresh

(i mean, in the end this is just saving a couple bytes, but perhaps the packet negotiation also has some overhead worth acknowledging)

furthermore, the breakthrough for me thinking about your proposal is to not regard the TT side as just being a dumb pipe which funnels messages directly to serial--- TT itself can maintain an LED state, so it's serial-refresh mechanism can stay the standard dirty-check + led_map (which is no big deal with usb peripheral space)

@trentgill
Copy link
Author

@tehn
thanks for the notes!
the set-and-refresh methods seem interesting and could save some overhead. indeed each message has about 2.5bytes of overhead, plus the switching time in the driver, so it's definitely non-zero. i'll do some benchmarking & logic-probing once we have a generally working version to see where optimization needs to be done.

i spent some time over the weekend getting dynamic leader/follower working on TT. the solution is actually not too messy, but i'm having to extend the libavr32 TWI code. thankfully the hardware interface is far simpler on AVR than STM!

this is an aside for this gist, but as i've been working through these issues, 'arbitration loss' is really a tiny slice of the error-space, and more importantly we just need to be wary of error handling when trying to drive a busy bus. i've already pushed a fix to this on the crow side, but i doubt TT has code to protect this, as it was never designed to work on a multi-leader bus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment