Why bother with EVE's circular RAM_CMD buffer?


New member
I've spent about two weeks coming up to speed and getting my EVE display to actually display anything. I made this progress by progressively adding complexity: get MCU powered, get its clock right, get SPI working with my o-scope, figure out how to control the backlight via EVE, and finally into graphics. I did this by following the FT81X Programmers Guide and the Crystalfontz Arduino demo, even though my target MCU is not Arduino. (I thought I'd include all that so any other total newbies such as myself might have a rough roadmap to follow.)

My question is: Why bother with the circular nature of the RAM_CMD buffer? It seems laborious to track my location so I don't overflow the buffer address. What I'm doing instead is at the beginning of each display list writing both REG_CMD_READ and REG_CMD_WRITE to 0. It seems to me the only benefit of a circular buffer is so that it can be written to and read from simultaneously, with the read chasing the write. But best I can tell the read only happens when I write the final written byte address into REG_CMD_WRITE register at the end of my list.

(Or maybe in writing out this question I have answered it myself: I can do that end REG_CMD_WRITE command to get the reading to start, and then immediately continue on writing into RAM_CMD from my current location. This would speed things up such that I am reading and writing simultaneously.)
Looking for additional LCD resources? Check out our LCD blog for the latest developments in LCD technology.


New member
Well, no, I didn't answer it myself that first post. Here's the full skinny:

1. Starting with a fresh power-up, the co-processor has 4096 bytes of RAM_CMD that you can use. The REG_CMD_READ and REG_CMD_WRITE pointers are both zero.

2. Write your first display list to RAM_CMD starting at address 0x308000 and incrementing by the required four bytes. Note that when you write things like letters that are only one byte, you need to backfill with 0xff until you've completed the four byte requirement. And yes, you'll need to keep track of where you are in the RAM_CMD address list using the MCU.

3. Note that your display list should always start with CMD_DLSTART() then CLEAR(1,1,1) followed by the actual items to be displayed.

4. Note that your display list should always end with DISPLAY() and CMD_SWAP() and then Step 5.

5. The very last thing to do to get the co-processor to jump into action is to set the REG_CMD_WRITE register with the address of the last byte you wrote. As explained in Section 5.1.1 of the FT81x Programming Guide (a must have!) writing to this register triggers the co-processor to work thru the display list you just uploaded. It works thru it by "sending" it to the RAM_DL of the inactive display list. The very last thing in your display list was that CMD_SWAP() command, which tells the RAM_DL to swap into the graphics engine and get displayed. (It's called swapping because there are two RAM_DL areas: the one you're building and the one the graphics engine is using. CMD_SWAP() swaps the two.)

6. At this point the REG_CMD_READ and REG_CMD_WRITE are again equal and at some address location in the middle of the RAM_CMD buffer. The datasheet tells you to continue on from that point, and you should. This means you need to track that address the whole time and make sure to roll it over to zero when it hits 4096 above 0x308000. I did this by doing a modulo on the AddressOffset, as I'm calling it:
AddressOffset = (AddressOffset + 4) % 4096; //Increment address by four bytes, wrapping to zero at 4096
I use the line above at the end of each of my 32-bit SPI writes to keep track of my current address. The AddressOffset is a value 0-4096 that I OR with 0x308000 to set my write address.

7. So why doesn't just resetting both REG_CMD_READ and REG_CMD_WRITE to zero work? I was doing this as part of my Note 3 steps above, but when I got into "animation" where I'm moving things around with fast screen redraws, I was getting artifacts and unsmooth motion. The reason for this (in my esteemed guestimation) is that either I'm not actually writing REG_CMD_READ when I think I am, or when I write REG_CMD_WRITE = 0 I trigger the read routine which cycles thru the RAM_CMD, all the way from zero to zero. That was basically giving me "scraps" screens of parts of what I had previously written into RAM_CMD. Or just garbage screens in between my good screens.

Neither the datasheet nor programmers guide are written in a way that allows one to follow a step-by-step to get things running.



So why doesn't just resetting both REG_CMD_READ and REG_CMD_WRITE to zero work?
-If you change it to the circular buffer, does the animation work as expected? I'm also not sure what's going on inside the chip so I can't explain why setting it to zero won't work, unfortunately. But, I do think your theory of it cycling through the old data is a pretty solid theory.

Neither the datasheet nor programmers guide are written in a way that allows one to follow a step-by-step to get things running.
-Thank you for the feedback. I'll take this to the eng team and we will discuss expanding our documentation.


I just had another thought, it's also possible that you're writing too fast and setting REG_CMD_READ to 0 before the co-processor has updated it. Also, here's a link to their forum if you'd like to try browsing there: http://www.brtcommunity.com/index.php

I looked through it a bit and didn't see anything addressing your exact scenario but you might have better luck. It seems most people are reading REG_CMD_READ and comparing it to REG_CMD_WRITE.
Last edited:

CF Tech

Ya, I think you would need to make sure that the processor is completely caught up before you started writing again. You could overwrite your "early" data before the processor finishes reading it.

As an experiment, I tried modifying the coordinates of an object inside the display list to animate its position without writing the entire list. As I remember I did get it to work, but it was clumsy enough that I did not pursue it further.

. . . doing a modulo on the AddressOffset, as I'm calling it:
AddressOffset = (AddressOffset + 4) % 4096; //Increment address by four bytes, wrapping to zero at 4096
. . .
Depending on your processor and compiler, typically an "and" operation will be much faster than a modulo, which typically involves a divide and was traditionally one of the slowest operations a processor could do:

AddressOffset = (AddressOffset + 4) & 0x0FFF; //Increment address by four bytes, limit it 0 to 4095 using bitwise and operation

But with modern fast processors, those old-school optimizations might not mean much anymore.

In any case, for our sample Arduino code, the bulk of the time is spent waiting for the next frame time to come around. Using SPI to ship out the 4096 (max) bytes is quite quick in comparison.

. . . Neither the datasheet nor programmers guide are written in a way that allows one to follow a step-by-step to get things running.
That was our hope of the example code . . . to offer an un-obfuscated insight into what was actually happening.

As far as using the buffer as linear rather than circular, I do not see why it could not be made to work. You would need to discover and work out all the fiddly details though. Using the existing circular code that is known good seems a more prudent approach.


New member
1. As far as checking where the read pointer is in relation to the write buffer, to avoid overfilling the ring buffer, I'm currently writing so slowly to the buffer that there's no way I could do that. In other words, the buffer can empty waaaay faster than I'm filling it. (I think my SPI clock is in the 18kHz range right now, but have disconnected my oscope and can't check.) Maybe, when I turn up my processor and SPI clock, the race gets closer, but even then I'm only dealing with the buffer every 15-20ms, which I'm assuming is much longer than necessary for the coprocessor engine will take to empty a full buffer. A potential problem for another day.

2. The Crystalfontz sample code was super helpful, couldn't have gotten here without it. I'm not a software engineer, so figuring out the syntax was quite the challenge and will continue to be. I'd never even heard of modulo before, but the Googs pointed me toward % which made reading my rookie syntax easier for my mediocre mechanical engineer brain! If speed turns out to be a problem then I'll go the & route. Or maybe I'll switch to the & route immediately for some software learning action.