I welcome you to this part of my RoadTest review of Bridgetek BT817 evaluation board. In this part I will briefly describe one of my experiments which I have done for gaining experiences with BT817 chip and evaluation board. In this blog post I will describe how to draw basic elements, how to draw more complicated elements using stencil mask, and how to setup touch controller with support for generating interrupts. Except this article there are two other blog post reviewing BT817 EVE chip and ME817EV evaluation board. Another blog post describes operation of coprocessor which is more advanced approach of using BT817 in comparison with basic display list approach described in this article. Third blog post describe my second project which shows program displaying data from ZUSI 3 train simulator on display. Following links are links to other parts of my review. In this article I will connect BT817 to STM32 MCU and I will control display by firmware using this microcontroller but most content will not be dependent on STM32 platform and you can apply these information on any platform you want (including Arduino for example).
- Introduction, contents, summary, and score
- Review of Bridgetek BT817 Embedded Video Engine
- Review of Bridgetek ME817EV Evaluation Board
- Pacman using Bridgetek BT817 (this page)
- How does the BT817 coprocessor work?
- ZUSI 3 display (become available in the future)
Pacman
As part of my review, I created simple pacman game using STM32L552 MCU with screen drawn on display using reviewed evaluation board. Pacman is old simple game. You play with yellow player and you can control it’s direction on map. You must collect all dots from the map and prevent collision with autonomous ghosts. You can see my final implemented pacman on following video:
In following sections, I will briefly describe basic concepts of drawing elements to the display using most minimalistic way and some other features of BT817 chip like touch controller.
Initialize display communication
The first thing you need to do is setup communication between BT817 and your MCU/MPU (or other chip which you use for driving SPI bus). Bridgetek provide SDK which you can use. Provided SDK support is written in C and supports three platforms. You can potentially port it to your own platform. SDK supports running on computer (as a normal desktop program) and controlling BT817 over onboard FTDI FT4222 (FT4222 is originally USB-to-UART converter, but it can be reconfigured to USB-to-SPI). Second possible approach which is prepared inside SDK is using their own FT900 microcontrollers. Finally, third option to run SDK is with software emulated display instead of physical one. Even you decide to do not use provided SDK it is good reference for code examples, and it saved me a lot of time when troubleshooting my own library. There are also some different small SDKs and platform specific libraries for some platforms like PIC, STM32 or Arduino, but they are mostly outdated (but working), and they look more like project example rather than SDK. Because I like to know everything about chips, I decided to use chip in a very low-level approach and designed my own library (for STM32) from scratch.
Communication protocol is easy. It is standard SPI protocol which can be upgraded to QSPI for improving performance (you can increase bandwidth about 4 times when switching to QSPI). I attempted to try QSPI, but it does not work with my STM32 well. I have some notices about it in chapter Review of Development board. So, I used just plain SPI which works well. Device uses 22-bit address space (internally it most probably uses 32-bit address space, but 22-bit address space is accessible to you over SPI bus) and additional 2 bits identifies read or write operation. They also identify special host control commands. After you setup SPI communication you must do procedure specified in programming manual or you can find them in SDK sources. Procedure configures some system registers, clocking source, PLL settings for generating correct system clock and waiting until all internal engines boot properly. After that you need to configure display settings like resolution, refresh rate, offsets and some other display related things which mostly depends on parameters of display which you use. After that you can start writing to display list and triggering generating screen.
Most simple way to draw on display
Most simple way to draw basic objects like rectangles, circles or bitmaps on screen is using display list (DL). DL is accessible over SPI interface on specific addresses, and you can write them directly. Hardware accelerator then execute list when generating data transmitted to the display. Every command is encoded to the 32-bit number. DL capacity is 8 KiB so there is space for up to 2048 DL commands. You can consider DL commands as a BT817 assembler. DL commands are encoded in a familiar way. They are easy to encode and decode but in fact you do not need know them because SDK has macros for encoding them. Because encoding is so simple, I decided to do not copied macros from SDK to my app and encoded instructions manually in my pacman app. Most significant byte of 32-bit value is always operation code and then are parameters encoded starting from least significant bits.
Now I will show short DL example. Following commands can be used to clear whole screen to red color and draw blue ractange. First command set color which is used when clearing screen. Second command clears screen (it also clears stencil and tag buffers which I will describe later). Third command sets standard color used when drawing objects. Next command changes mode of drawing to rectangles. All following points (VERTEX2F commands) define corners of rectange. First vertex defines first point and second vertex define opposite corner of rectangle. Finally last command DISPLAY terminates commands. DISPLAY command is hint to graphics engine that previous command was last command in DL. After our DL is configured properly, we can let BT817 that it is complete, and it can start rendering it. This is referred as a swap.
App_WrDl_Buffer(phost, CLEAR_COLOR_RGB(255, 0, 0)); App_WrDl_Buffer(phost, CLEAR(1, 1, 1)); App_WrDl_Buffer(phost, COLOR_RGB(0, 0, 255)); App_WrDl_Buffer(phost, BEGIN(RECTS)); App_WrDl_Buffer(phost, VERTEX2F(160, 160)); App_WrDl_Buffer(phost, VERTEX2F(320, 320)); App_WrDl_Buffer(phost, DISPLAY()); GPU_DLSwap(phost, DLSWAP_FRAME);
After swapping DL, we can start filling new DL with new screen. Until we trigger next swap BT817 render DL as defined.
Methods of writing to Display List
In previous paragraphs I mentioned that you can write directly to the DL which is accessible directly over SPI at address 0x300000 but this is not only way how to write to DL. It is the simplest way, but it is not ideal when you use coprocessor (I will describe coprocessor in next article). Writing DL directly and using coprocessor at once is problematic because coprocessor also touch the DL and you must pay attention and prevent overwriting commands generated by coprocessor. More advanced way is writing DL commands to coprocessor FIFO like any other coprocessor commands. In this way you do not write DL commands to address block at 0x300000, but you write them to the command FIFO at address 0x308000 and coprocessor copy them to DL when it finds them in command FIFO. In this example I just use DL and do not use coprocessor at all, so I decided to use basic way (direct writing) in my pacman demo. In the later demos and projects, I will use more advanced way and I will write DL commands to command FIFO instead.
Drawing more complex objects using stencil buffer
Since we know how DL commands works and how to write them, we can switch to some more complex aspects. Drawing primitive objects like rectangles and circles was already mentioned but what if you want to draw something more advanced? For example, imagine drawing pacman. One concept allowing drawing more advanced objects is stencil buffer. While name refer to buffer it most probably is not internally implemented as a buffer, but this is not important for us. Stencil buffer is buffer which for every pixel store one value ranging from 0 to 255. Value is invisible and unless you configure following properties it does not affect graphical output at all. By default, whole buffer is cleared to zero. You can configure two things: stencil function and stencil operation (in fact you can configure third thing – stencil mask, but for simplicity I will omit it. It is important when you want to use stencil buffer in more advanced way). Stencil operation defines what happen with pixel which you have drawn. For example, you can set stencil operation to increment and when you draw rectangle then all values in stencil buffer which are located at the drawn area will change its value from 0 to 1. Pixels which are not covered by drawn rectangle remain value 0 in stencil buffer. Stencil function is configurable function with reference value. Possible functions for example are EQUAL, NOT EQUAL, GREATHER THAN, EQUAL OR LESS THEN, ALWAYS, NEVER and so on. Stencil function do not affect stencil buffer but affect graphical output. When drawing object, every pixel and it’s stencil buffer value is compared using configured function and if condition match, then pixel is drawn. Otherwise it is not drawn. Using this approach, you can draw only some part of expected primitive.
I will show how to use stencil buffer on following demonstration which come from my demonstration project. Note that because I am using my own library function sending DL commands to device looks differently to the previous example above, but technically it works in the same way. Second difference is that I encoded commands manually (without macros from SDK), so you will see encoded them as a hexadecimal number and corresponding user-friendly name will be always written at the end of line as part of comment. When using Bridgetek SDK you can write just user-friendly name in your code.
Example: Drawing pacman
As the first step I will execute command that clears stencil buffer. If you remember to previous code example, there were command CLEAR(1,1,1). This command has three (boolean) parameters. First parameter defines that graphics buffer should be clear, second parameter define if stencil buffer should be cleared and third parameter define if tag buffer should be cleared (I will describe tag buffer later). In this case I want to clear just a stencil buffer and do not affect any other buffer, so I will use CLEAR(0,1,0) command. Following command clear whole stencil buffer.
BT817_WriteDl(0x26000002); // CLEAR(0,1,0)
This command drawn nothing but updated stencil buffer. For illustration I will show value of stencil buffer at specified parts of image using following image which means that whole area has value 0 in stencil buffer:
Now I will configure and use stencil buffer. I will draw part of pacman which won’t be drawn. I will draw it using transparent colour and the only purpose of this drawing is to modify stencil buffer. At beginning I will configure stencil operation to increment stencil buffer value when object is drawn and set alpha channel (transparency) to 0 which means that used colour is fully transparent.
BT817_WriteDl(0x0C000000 | (3 << 3) | 3); // STENCIL_OP(INCR, INCR) BT817_WriteDl(0x10000000); // COLOR_A(0)
Now I start drawing triangle of pacman which should not be covered by yellow color. BT817 cannot draw triangle directly but can draw edge stripe which is in this case sufficient. I will show later how to draw polygon. Edge strips is drawing primitive available in four variants (up, down, left and right). Edge strips draw every pixel which is after (or before or above or below depending on variant used) line specified by two points. Let’s suppose you pass following two points A and B to EDGE_STRIP_R (R means right):
Edge strip (right) will fill following are by colour:
For example, EDGE_STRIP_A (A means above) will flood following area when used with same points:
Order of specifying points (A first or B first) does not matter.
So, when drawing pacman I will use this drawing primitive to define which would not be drawn (it will be drawn by transparent colour as configured above).
BT817_WriteDl(0x1F000005); // BEGIN(EDGE_STRIP_R) BT817_WriteDl(0x40000000 | ((cellCenterX * 16) << 15) | ((cellCenterY) * 16)); // VERTEX2F() BT817_WriteDl(0x40000000 | (((cellCenterX + (int)(cos(deg * 3.14 / 180) * cellSize)) * 16) << 15) | ((cellCenterY + (int)(sin(deg * 3.14 / 180) * cellSize)) * 16)); // VERTEX2F()
You may think about multiplying coordinates by 16. It is because BT817 support antialiasing and subpixels. You can for example draw half pixel and BT817 will update bitmap by grey color instead of black in the area of sibling pixels resulting into feeling that line is drawn in the middle of two pixels.
Commands above drawn nothing but update stencil buffer in following way:
Drawn area (which is invisible due to nature of transparent colour) has different (incremented) value in stencil buffer and we will utilize it now. Now I will change A channel of colours back to fully visible colours and configure drawing colour to yellow (R=255, G=255, B=0).
BT817_WriteDl(0x100000FF); // COLOR_A(255) BT817_WriteDl(0x04FFFF00); // COLOR_RGB(0xFF,0xFF,0)
After that I will change mode from drawing edge strips to drawing circles (point in BT817 terminology) and change radius of drawn circles:
BT817_WriteDl(0x1F000002); // BEGIN(POINTS) BT817_WriteDl(0x0D000000 | (cellSize / 2 - 3) * 16); // POINT_SIZE(16 * ...)
After that I will reconfigure stencil function. As described above stencil function defines test function and affects which pixels are drawn and which are omitted. I change function to draw only pixels that have value 0 in stencil buffer. (parameter value is second argument while third argument is stencil mask).
BT817_WriteDl(0x0A0500FF); // STENCIL_FUNC(EQUAL, 0, 255)
And now I can draw point:
BT817_WriteDl(0x40000000 | ((cellCenterX * 16) << 15) | (cellCenterY * 16)); // VERTEX2F()
Command above draw following:
Previous command also affected stencil buffer. Because we did not change stencil operation, it incremented stencil buffer value of every pixel covered by circle (including pixels which was not drawn). Stencil buffer now looks as follows:
In fact, we do not need stencil buffer anymore. Last step of drawing pacman is drawing small black eye. I change stencil function from EQUAL with parameter 0 to ALWAYS which draw object no matter of stencil buffer value. Of course, I need to reconfigure colour (from yellow to black) and point size. I do not need to change drawing object (BEGIN command) because I am drawing the same object as previous.
BT817_WriteDl(0x0A0700FF); // STENCIL_FUNC(ALWAYS, 0, 255) BT817_WriteDl(0x04000000); // COLOR_RGB(0,0,0) BT817_WriteDl(0x0D000000 | 32); // POINT_SIZE(2px) BT817_WriteDl(0x40000000 | (((cellCenterX + (int)(cos(deg * 3.14 / 180) * cellSize / 3)) * 16) << 15) | ((cellCenterY + (int)(sin(deg * 3.14 / 180) * cellSize / 3)) * 16)); // VERTEX2F()
After executing commands above we get pacman. Logic in my code is of course more complicated because It must distinguish pacman direction, but it is very similar to code presented above.
Example: Drawing polygon
As I mentioned in previous section, BT817 has no support for drawing polygon but stencil buffer can be used for drawing this kind of objects (and much more!). I will describe how to draw following polygon:
Drawing polygon is based on incrementing stencil buffer and utilizing EDGE_STRIPE drawing primitive while color is transparent. At end is object drawn by refilling whole screen with correct stencil function configured and non-transparent color used. I will suppose that you have stencil buffer cleared to value 0. This is not mandatory step, but it became much more challenging when you for some reason need to have stencil buffer filled by non-zero values.
After configuring stencil operation to increment you just draw EDGE strip with points specified as a point of polygons. Order usually does not matter. Except special cases you can use whatever EDGE strip variant you want. I will show right variant and later I will demonstrate that it works event with any other variant.
So, at beginning use some line of polygon and pass its vertexes to EDGE STRIP (RIGHT) drawing command. It will affect stencil buffer in following way.
Now choose another line and draw it in the same way. It will affect stencil buffer in following way.
After that draw another line using EDGE_STRIP command. It will affect stencil buffer in following way.
After that draw another line using EDGE_STRIP command. It will affect stencil buffer in following way.
After that draw last line using EDGE_STRIP command. It will affect stencil buffer in following way.
Now we draw all lines using edge strip command and last remaining step is reconfigure stencil function to EQUAL with parameter 1 and draw rectangle from point (0, 0) to (1024, 600) where 1024, 600 is resolution of display. Because stencil function is set to EQUAL 1 this means that command wont flood whole display by a colour but it just flood area of required polygon.
As I promised now, I will show that algorithm works with any EDGE STRIP command. I will draw the same polygon using EDGE STRP BELOW command instead of RIGHT. Process is the same. Pass points of some line (order of drawing lines does not matter) to DRAW STRIP (BELOW) command and it will affect stencil buffer in the following way:
Then some other line. It will affect stencil buffer in following way.
Then some other line. Things get complicated a little, but some areas will collapse later. It will affect stencil buffer in following way.
Then some other line. It will affect stencil buffer in following way.
And finally, last line. It will affect stencil buffer in following way.
Okay, you may thing that it now looks more complicated because we have there more numbers than in previous example but in fact it is still simple. After turning stencil buffer to this state, you just need to fill areas where stencil buffer has value 1 and 3. Depending on complexity of drawn object you can get even higher number and then you will need to fill all pixels when stencil buffer has odd number. If you do not understand why this ahppended, you see following example. Imagine drawing following polygon:
After drawing them using EDGE STRIP BELLOW you get following stencil buffer:
For painting this polygon, you need to fill display 4 times. First time with stencil function EQUAL 1, then EQUAL 3, then EQUAL 5 and finally EQUAL 7. You probably see that this is not efficient. This is perfect example for last stencil feature, and this is stencil mask. Stencil mask allow you to ignore some bits when working with stencil buffer values. You probably remember command when I set EQUAL function with value 0 and mask 255. It looked as follows:
BT817_WriteDl(0x0A0500FF); // STENCIL_FUNC(EQUAL, 0, 255)
Mask 255 means that all eight bits are checked. But for now, we need only if number is odd or even and this we can do by masking out 7 most significant bits and checking just one least significant bit.
BT817_WriteDl(0x0A050101); // STENCIL_FUNC(EQUAL, 1, 1)
After setting stencil function in this way we can render polygon just using one rectangle fill.
Touch controller
Another feature of BT817 chip which I have utilized is touch controller. Touch controller is unit which controls I2C interface to chip which physically checks for touches and executes appropriate commands to this chip for achieving continues retrieval of touches. After receiving touches, you can read details about them using standard (Q)SPI interface which you use for any other communication with BT817. Details about touches you then read from BT817 registers instead reading them from touch controller natively over I2C bus. It simplifies complicated implementation of this I2C protocol and managing registers and configuration of this external chip. In diagram it looks as follows:
Initialization is very easy and consists of only 3 steps. First step is resetting BT817 touch engine. Next step consists of configuring 6 calibration values which are used to recalculate touched pixel location from physical coordinates received from touch controller. For retrieving these values, you can use coprocessor routine, but I did it in much easier way and obtained them from demo application using debugger. Obtained values I hardwired to my STM32 project. Of course, for production use it is better to implement calibration routine correctly but for this simple demo hardwired values works well (on my machine ) . Final third step of configuring touch engine is enabling extended mode which allows use to support multiple simultaneous touches. BT817 Touch controller support up to 5 touches. Focaltech FT5426 supports 10 simultaneous touches at once. After this you can do optional step and configure generating interrupts. This I have done, so I checked for touches only when external interrupt on my STM32 happens.
Last feature of touch controller is support for tags. Last buffer which I have not described yet is tag buffer. It has similar behaviour like stencil buffer but does not offer function and operation feature. Instead of this you can configure them for settings static value when drawing some primitive. You for example set tag buffer value to 1 when drawing first rectangle and then you can set tag buffer value to 2 when drawing another primitive. Later when touch happen BT817 touch engine will provide tag value of elements being touched (1 or 2 depending on what element was touched or 0 when some other point was touched). In fact, when you use tag buffer you do not need to parse coordinates of touch, but you can use tag buffer value to determine what control have been touched. It can save you lot of time because for example checking touches inside circle is pretty complicate when it is done manually.
What’s next?
This is all from this project description and tutorial. I described some fundamental aspects of using BT817 and shown one demonstration project which I have done for gaining experiences with BT817 graphics engine. In next article I will describe how coprocessor works. If you are interested in reading about my opinions on RoadTested product, you can read my articles Review of Bridgetek BT817 Embedded Video Engine and Review of Bridgetek ME817EV Evaluation Board.