I'm working on my friend's cosplay prop (a healing gun) from an FPS game called "The Finals". I have to write code to display the gun's healing/cooldown animation, synchronizing it to a normally-open pushbutton push i.e., the gun's trigger pull.
I've never worked with a TFT display before so I don't know how best to store the animation frames. I'm using a 2.8" Adafruit TFT 240x320px display, so there are 240*320 = 76,800 pixels, each pixel being 2-bytes, means a single frame would take up 153,600 bytes - more bytes than the arduino has in program memory (32,256 bytes)!
So storing each animation frame in an Arduino Uno's memory is not feasible. Here are some strategies I can come up with to do the animation but I was wondering if there are any drawbacks to each approach that would make them feasible:
Since the animation is simple, just write the algorithm to specify how each frame should be drawn. I'd have to implement boundary-fill algorithm to fill the + and x shapes. Luckily the algorithm isn't complicated.
Since each frame is simple (just a few colors), encode each pixel state as either 1 or 0 (e.g., a bool matrix; each frame would be 76,800 px / 8 = 9600 bytes) and store all frames inside a single file, inside a microSD (32/64/128GB). A 0 indicates the pixel should be black, 1 if it should be filled in with some specific color. An MCU would use byte-offset to address each frame (or portion of a frame, or multiple frames). The MCU could read an entire 9600-byte frame into its memory, run it by a function that will assign each bit/pixel a color (if the bit value is 1), and write out to the TFT screen.
Store each frame (153,600 bytes = 150KB) in the SD card, read from it in manageable chunks of 10KB, and paint the TFT.
One thing that might make (2) and (3) unfeasible is the SD card read/write speed. Assume we want 15-30 frames-per-sec animation.
Instead of SD cards, I use FRAM memory. It is inexpensive, operates at memory speed, and has no erase cycle requirements. FRAM allows read and write operations on a byte-by-byte basis, making it highly efficient.
These modules are available in both SPI and I2C formats, and a great advantage is that they are non-volatile, retaining data even when power is removed.
No, you would not. I think everything I saw in that video could be programmed with a little bit of cleverness.
That X for example first of all is half the problem it looks like just by symmetry.Successive rows are just lines of a certain length offset by an increasing amount. At whatever rate.
Twice two for loops. And so forth.
Do some tests dragging frames, I think you are better off just explicitly coding it. Some small number of backgrounds or key frames might make it easier.
Now of course someone will love it and ask for… the impossible.
I suspect you'll be unhappy with the frame rate with anything other than this.
Doing any sort of full frame transfer to a display is probably not feasible - the max data transfer rate of about 8mbps (bits), so a full screen update would take about 0.2s
Using an algorithmic approach, you should only have to send the pixels that actually change.
I'm playing around with the GFX library and find the primitive draw functions to take noticeable delay to complete, for the reasons westfw pointed out. Sending pixels that need to change for the animation is the only feasible solution if this is the bottleneck.
Drawing the + animation is simple so I won't get into it. The "X" cooldown animation is a bit more tricky because you have several considerations: (1) fill area is "X" (irregularly) shaped, (2) the growing gray region, (3) the shrinking red region flashes red->pink->gray->....
(2) "Successive rows are just lines of a certain length offset by an increasing amount". I see what you're hinting at - this is for drawing the boundary.
Filling inside the X boundary is a more tricky and where I think boundary fill might be useful. One possible challenge with this algo is that it operates on an input matrix, which means storing that matrix. I'm thinking about how I can save processing power by precomputing where all pixels that need to change are and storing only that information.
I did mean the lines going across the leg would be painted successively
Oh I see. Unfortunately, my friend is a stickler for details. If you take a look at the video, the "X" symbol isn't the usual "X" shape. It's a + symbol rotated 90 deg.
It's more complicated to describe the fill pattern as offsets but it's possible. First, define a vertical line down the middle of the X (call that line "XMID"). Then use the TFT::fillRect(x,y,rectWidth,lineWidth) function to fill the X one line at a time like so:
* indicates the location of the (x,y) arguments to fillRect.
The number of x's is the lineWidth e.g., xxxxx = a rectangle 5 pixels wide starting at some "*"
So... just grow it for a few cycles, shrink it for a few and so forth.
Like I said, programming this literally may tend to lotsa code that will be harder to get working, modify or enhance but that is the nature of aiming to get the highest speed to do one thing.
You can see this allatime down in the guts of some libraries. I am thinking of things like the contortions involved in sustaining the rate at which neopixels must be signaled. Pure hair.
As an added benefit to drawing a line-at-a-time, if your controller/library combination includes a "draw horizontal line" function, that's probably faster than drawing an arbitrary boundary line.