Explore the world of computer graphics
How to Write a DISPLAY DRIVER from Start to Finish!
Estimated read time: 1:20
Summary
In this video, hoff._world takes us through the fascinating journey of creating a simple display driver from scratch, demystifying various computer graphics concepts like frame buffers, bit depth, rasterization, and more. The tutorial covers the essential steps needed to write a display driver for an embedded system, explaining how computer instructions translate visual elements like smiley faces onto the screen, and introduces basic graphic handling with frame buffers and vector graphics. Alongside code demonstration, the video also discusses refresh rate, frame rate, bitmap images, and fonts.
Highlights
- Learn how to write a display driver from scratch and understand computer graphics concepts! 🚀
- Frame buffers and bit depths are key components in rendering graphics on displays. 🎨
- Discover how to code for different color depths, including 24-bit color representation. 🌈
- Explore bitmap and vector graphics differences and learn how to integrate them in programs. 🖼️
- Basics of drawing text on displays through bitmap font generation and rendering. 📝
- Interfacing via SPI and understanding display hardware are crucial for driver success. 📡
- Adapting to device-specific traits is vital for successful embedded display driver development. 💡
Key Takeaways
- Understanding the basics of computer graphics terminology is crucial for writing display drivers. 🖥️
- Frame buffers are essential for representing images in memory and vary by color depth. 🎨
- Bit depth determines how many bits represent each pixel's color, influencing the display quality. 🌈
- Building a display driver involves coding with considerations for frame updates, resolution, and efficient bit operations. 👨💻
- Bitmaps and vector graphics offer different storage and rendering benefits in display applications. 🖼️
- Handling fonts in embedded systems requires manipulating bitmap representations of text. 📜
- SPI communication is critical for interfacing with display hardware, necessitating hardware-specific coding. 📡
- Device-specific characteristics influence the display driver development, especially for embedded systems. 💡
Overview
This engaging tutorial by hoff._world delves into the captivating realm of computer graphics, demystifying technical terms like bitmaps, vectors, and frame buffers. The video offers a hands-on approach to crafting a simple display driver for embedded systems, culminating in the visualization of smiley faces on screen. It emphasizes understanding bit depth and color representation, which are crucial for rendering images effectively.
Throughout the video, viewers explore the coding intricacies involved in writing display drivers, from setting up frame buffers to optimizing bit operations for efficient rendering. The presentation embraces a playful, educational tone, making complex concepts accessible, while offering practical demonstrations of coding techniques vital for creating a functioning display driver.
Additionally, the tutorial highlights the significance of strategic hardware communication, touching on SPI protocols and device-specific considerations crucial for interfacing with display panels. By integrating bitmap and vector graphics, as well as custom font rendering, viewers gain a thorough insight into building dynamic graphic interfaces pertinent to modern computing spaces.
Chapters
- 00:00 - 01:30: Introduction and Overview of Display Drivers The chapter introduces key concepts in computer graphics, including buffers, vectors, bitmaps, bit depth, fonts, and rasterization. It emphasizes the fascinating and complex nature of computer graphics and poses a fundamental question about the process of rendering images on screens. The chapter also previews the practical task of creating a simple display driver for an embedded system to demonstrate these graphics concepts.
- 01:30 - 05:00: Frame Buffers and Bit Depth The chapter introduces the fundamental concepts of frame buffers and bit depth in computer graphics. It uses a playful approach to explain these baseline concepts, acknowledging their complexity. To illustrate these ideas, a simple display driver is written to demonstrate how graphics are rendered on a screen. The discussion is centered on how to instruct a computer to render a smiley face at a specific position on a display, exploring the representation of such graphics elements.
- 05:00 - 10:00: Rendering with Frame Buffers This chapter discusses the concept of frame buffers in computer graphics. It begins by explaining that a frame buffer is the in-memory representation of an output image on a display. The chapter sets the stage for diving into the code related to frame buffers. An example of rendering a smiley face is used to illustrate how the image is stored in memory. The chapter emphasizes the need to understand this foundational concept before moving on to more advanced topics.
- 10:00 - 15:00: Software Rendering and Graphics Libraries The chapter discusses concepts related to software rendering and how graphics libraries interact with GPUs. It begins by explaining the role of a GPU in executing instructions to construct an in-memory representation of a visual output, important for generating images on a screen.
- 15:00 - 25:00: Bitmaps and Refresh Rates This chapter covers the concept of bit depth in graphics, explaining how the number of bits impacts color representation in digital images. It describes a one-bit frame buffer, where each pixel on the screen can only be represented by a single bit, resulting in a monochromatic image. This is likened to settings seen in Nvidia or AMD control panels, where users can adjust the bit depth to vary the color display. The explanation is tied to practical examples like seeing images flash on screens, elucidating how fewer bits result in limited color representation.
- 25:00 - 30:00: Vector Graphics The chapter on 'Vector Graphics' begins by discussing monor frame buffers, providing examples like blue and white embedded displays. It explains how such displays use binary representation, with '1' representing a white tile and '0' representing a blue or black tile, depending on the display. The chapter aims to educate on how to write a driver for these types of e-ink displays, stressing the meaning of 'mono' in this context as simply referring to a single color versus background, not specific colors like black and white or blue and white.
- 30:00 - 39:00: Fonts The chapter titled 'Fonts' explains the concept of color representation in computer displays. It begins by describing monor frame buffers that use a single bit of color to depict an image on a screen, emphasizing the flexibility of color choices irrespective of the base colors. The discussion progresses to the topic of drawing more colors on the screen, introducing color representations such as 24-bit (rgb888) and 16-bit (rgb565). These numerical notations express the depth of color a display can support, crucial for rendering vibrant and diverse visuals.
- 39:00 - 51:00: Sending Data to the Display The chapter 'Sending Data to the Display' involves a technical discussion on how data is sent to a display. It begins with an explanation of a 24-bit example and delves into the representation of data in memory in relation to bit depth. The focus is on a 'monor frame buffer', where each pixel on the display is represented by one bit, providing a foundational understanding of how displays handle data at a basic level.
- 51:00 - 57:00: Practical Implementation on Hardware The chapter titled 'Practical Implementation on Hardware' discusses the use of arrays filled with bytes in the C programming language. It highlights how, although space can be saved by representing everything with a single bit, using a byte per pixel simplifies calculations. This is demonstrated using a hypothetical 34x16 ultra-high-resolution display, with each pixel represented by a byte.
- 57:00 - 57:30: Conclusion In the Conclusion chapter, the author discusses the possibilities of rendering graphics, specifically a smiley face, on a screen. The discussion explores the use of data structures, starting with a one-dimensional array and the possibility of employing more complex structures such as multi-dimensional arrays. They explain the concept of indexing, beginning from a start position at zero, and suggest the process can continue along the array. The description ends abruptly, indicating perhaps a continuation beyond the provided text about rendering the smiley face.
How to Write a DISPLAY DRIVER from Start to Finish! Transcription
- 00:00 - 00:30 frin buffers vectors bit Maps bit depth fonts rasterization all these terms relate to computer Graphics you've probably heard at least some of them around on the internet somewhere they are seriously cool computer Graphics is such a mystifying and interesting field how do things actually get drawn on screens for people to visualize we're going to write our very own simple display driver for this embedded systems display right here I'll pop it up on screen to illustrate all of these Concepts because computer graphics
- 00:30 - 01:00 such a cool field these are Baseline Concepts and they scale up pretty complexly so complexly is that even a word anyway we're going to write a very simple display driver to get you in the headspace of all of these Concepts illustrate them and hopefully get you interested in computer Graphics what you see before you is a simple smiley face rendered on the display how did we even get that far how did we tell the computer we want to put a smiley face in that exact position on the display how do we even represent the smile face in
- 01:00 - 01:30 computer language in the first place we're going to get into all of that with first an example on frame buffers which answers our first question don't worry we'll dive into the code straight after this I know the Whiteboard and the graphics are a little bit boring but bear with me okay so a frame buffer is simply the inmemory representation of the output image on the display so let's say I want to render this smiley face here in memory I actually have to store that so what my computer will do either the CPU depending on the system you
- 01:30 - 02:00 might have a GPU in there what it will do is execute the instructions necessary to construct an inmemory representation of your output image so this is where the concept of bit depth comes in so what is a frame buffer the most simple frame buffer you can have is simply a one-dimensional array that's it like down here for instance we have this one-dimensional array which will simply be every bite is a pixel and it is the width times the height now think about this this is the concept of bit depth
- 02:00 - 02:30 bit depth and you've probably seen this in your you know Nvidia or AMD control settings panel is how many bits are able to represent every color so think about that right if we have a onebit frame buffer that means that every pixel on the screen has one single bit available to describe the color essentially that's a one or a zero this is known as a mono frame buffer so if you've ever seen a flash these images up on screen
- 02:30 - 03:00 everybody's probably seen the blue and white embedded displays that would be an example of a a monor frame buffer right you might have a one represent a white tile and a zero represent a blue tile and there's also this particular display that we're going to be writing the driver for this used say monor frame buffer it's an e in display where a one might represent a white tile and a zero might represent a black tile so mono is not necessarily blue and white or black black and white mono really just refers
- 03:00 - 03:30 to you have one bit available and those colors could be anything I could have a red base and a green you know set and that would still count as a monor frame buffer because I'm still using one bit of color to describe the image on the display now if you want to draw more color on the screen all right you can talk about 24bit you can talk about 16 bit there are all of these color representations that you can have and they are all designated so rgb888 for instance is 24-bit rgb565 is 16bit
- 03:30 - 04:00 etc etc and if you think about what that means I'll scroll down to a 24-bit example here essentially you can still have the same representation in memory but you are multiplying this by your bit depth so we'll we'll dive into what that actually means but I want you to First consider a monor frame buffer so with this monor Frame buffer what we have is every single Pixel on this display can be represented by one bit so even though
- 04:00 - 04:30 down here you know I have this array filled with bytes because in C that is sort of the minimum that you can specify in terms of arrays and such you would still to save space only represent everything by a single bit but for the sake of Simplicity we are going to say that every pixel in this display and by the way this is a 34x 16 display I know ultra high resolution we're going to say that every pixel is represented by a bite just to keep our calculation simple
- 04:30 - 05:00 so let's say I want to render this smiley face here on the screen I know it's a fantastic drawing um let's think about that for a minute so our start pixel if we think about this in terms of an array okay we're going to use a one-dimensional array the question is can you use more complex data structures like multiple Dimension arrays the answer is yes you can we have this start position here is zero this is our initial index which would be zero and we can go all the way along until we get to here and maybe you can't here because my
- 05:00 - 05:30 beautiful face is in the way but this is a 33 over here so this is index 33 and so that means that we have we start at index zero we're at index 33 right now and because we have a one-dimensional array when we wrap all the way back here and start at index 34 like this new line starts at index 34 so if I want to access the pixel 73 on our 2D coordinate system that would represent the start of this smiley face here I could use the formula x + width *
- 05:30 - 06:00 y to get the index of that of the index of the bit if we're using a normal monor frame buffer where every bit represents a pixel or for our simplified example 109 as the index for our array where we want to flip that so let's just say for the sake of argument that this right here was index 109 what we'd actually go ahead and do is flip this to a one so we draw it as you know 1 0 0 1 Etc
- 06:00 - 06:30 we're going to write some code to do some very Baseline monor frame buffer operations now keep in mind this code is being written to be illustrative and not efficient there are optimizations that you can do to the code we're going to be writing today the first thing that you want to go ahead and do is make yourself a C file and a h file like I've done here I'm calling mine baby driver it's a little little film you might have heard of um and the first thing that we're going to want to do is Define the frame buffer object itself so that includes the width and the height of this display which you can get from the data sheet or
- 06:30 - 07:00 however else you're finding information about your physical display so how many pixels can it actually represent and that is going to form the size of our frame buffer something I want to make clear before we begin is that the graphics Library we're making implements software rendering what that means is that Graphics operations are done using the CPU rather than the GPU I think this is still valuable for newcomers and beginners because if you've never done any graphics programming before the flow of operations will make complete sense to you and you can use that to learn Concepts and then you can apply them to
- 07:00 - 07:30 gpus where it makes a little bit less sense plus if you're into embedded systems at all a lot of the rendering is still done in software using microcontrollers directly even really Advanced Graphics here's the data sheet for our display as you can see we've got a display format here of 122x 250 I'm going to generalize this to 250X 128 so it's nicely divisible by 8 for us and I'd recommend you do the same if you're working with monor frame buffers because we'll be representing this in bits instead of bites and here here's our frame buffer size definition so of
- 07:30 - 08:00 course it's just going to be the frame buffer width which we've defined here is 250 height 128 and this times this divid by 8 because remember we're going to be working with bits so every bit in our mono frame buffer is representing one pixel on the display let's define our frame buffer so it's going to be a u8t one-dimensional array I'm going to call this BD frame buffer it's going to be bdfb size and we'll initialize it all to zero and of course I have to include standard in so this is going to be the frame buffer object in memory that we
- 08:00 - 08:30 actually do operations on and set pixels and unset pixels we're going to write the very fundamental operation for our frame buffer which is to set or unset a pixel it's going to start off like this we're going to call this void BD set pixel so you can see here we've got an x a y and a state so remember we've got a monor frame buffer so it'll be set which will do is true or unset which is false if you guys forget about bits for a second and imagine this 1D array as an array of bytes like we were doing in the theory over view section then this
- 08:30 - 09:00 position here actually lands us on the bite that we want to flip so if you think about it that's the width * y so you know we're going y down in terms of our rows and the rows are of this width plus the X so we go down in the y direction and then plus however many X that we want to go to and so this will give us the bytes now what we actually have to do is go ahead and divide this by eight because if you think about it what we've done is actually overshot by eight times because there are eight bits in a bite so if we want to find the
- 09:00 - 09:30 correct bit to index then we'll land on this position here when we divide by eight a cheeky optimization you can do is to change that divide by 8 to this bit shift by three because this will give you the exact same effect as a divide by 8 if you consider the bit maths and on some embedded platforms they don't actually have Hardware division so division operators with the traditional division will be very slow now the compiler may or may not do this for you but it's fun to see it written out because it's interesting and yeah hard division you want to have it
- 09:30 - 10:00 otherwise your division is going to be very slow now that we've landed on the correct bite we actually have to set the correct bit because we've just landed on the one that contains the bit with that we're looking for but we haven't actually set that yet so if you think about it this is just going to be a very simple bit mask now here is where we actually do that depending on the state so we've got if State and here we're going to or equals this so we're going to be setting that to a one and down here we're going to be and equaling the KN of that so that'll be clearing that bit or setting it to a zero now you might be wondering Hof I understand bit
- 10:00 - 10:30 masking but what is up with this math over here what what's going on there um so we'll explain that very briefly 0x80 is actually the representation of 128 which in bits is 1 followed by 7 zeros so that is actually the mask that we're going to want to move what we're going to be doing is Shifting the position of that one to highlight the bit that we want to set and we do that by shifting this by x anded with ZX7 so I'll put uh this zx80 up on screen what this ZX7
- 10:30 - 11:00 actually is is 11 one one in binary and what that's actually doing is capping this x value for the shift such that our shift is not going to go out of range and trust me this math just works um if you think about it and I'll put this stuff up on screen you'll see how this works we are shifting this by this Exposition which gets capped at uh 7 we can also do an equivalent get pixel function like this because there are many situations where you would want to know the value of a pixel in your frame
- 11:00 - 11:30 buffer whether it's set or not and we can do this in functionally the exact same way we calculate the position here and in but instead of setting it with the bit mask we're actually just going to return the value of the bit mask so if it ends with our mask we'll get a value greater than zero or we'll get zero if it does not and that's kind of how our mono frame buffer is working and we can apply this to our 24bit one so let's say down here we're looking to store 24-bit color so that means we have 8 bit available to us to describe the
- 11:30 - 12:00 pixels and you might have realized 8bit color is actually what traditional heximal web based colors use like if you've seen these HTML codes and you'll see 255 255 255 you know this is actually an 8bit color representation here this uh web based heod decimal code and 24-bit color is the most common on Modern monitors and such like this so we could describe a 24-bit frame buffer like this we could still use our simple
- 12:00 - 12:30 1D array could you use a more complex data structure yes for the sake of Simplicity we will not be doing that so we have this one dimensional array and the size in bits keep in mind this is the size in bits is now going to be width time height time bit depth so that would be for our display here we have a 34 so this would be 34 * 16 * 24 and you can add that to this formula here to find the bit that you want to address so if we still wanted to access this position here at 73 to land on the
- 12:30 - 13:00 sequence of bits that we need to change cuz remember in here we've gone from having one BTE representing an individual pixel to now what we would actually have is a group of bytes that represent one pixel so over here we would have like a red over here we'd have a green and here we'd have a blue so you can see now that instead of just going from a monor frame buffer where we have one bite or one bit
- 13:00 - 13:30 to describe our uh color we can now use a group of bytes in this case for RGB 888 we have one bite that describes each rgnb so our formula is now going to have this bit depth at the end so if we want to index 73 we can plug 7 in as X plus uh our width which is 34 this should be a 4 34 * Y which is 3times our bit depth
- 13:30 - 14:00 which is 24 now consider that we're looking for the index so you know we've got these indexes here so divide that by 8 and we get 327 as the index in our you know grouped array that we start at so you know for the sake of argument let's say this was index I think it was 327 so this is index 327 for that particular pixel this is going to be the r this is going to be the G and this is going to be the B so that is a very
- 14:00 - 14:30 simple overview of the concept of a frame buffer how you might store one in memory and bit depth while we're on the subject of frames and frame buffers I want to talk about refresh rate and frame rate now these are very popular terms with Gamers of course because these two things as a sort of coupled pair if you like determine how smooth or fluid any animations Graphics video playback look to the person experiencing them now refresh rate you can think of
- 14:30 - 15:00 it as the hard cap and this is measured in Hertz in terms of frame buffers this is the number of times per second that we can push a frame buffer update to the display so if I have a refresh rate of 60 HZ which is a very common desktop computer display refresh rate that means I can push a frame buffer update to that display 60 times per second and it will actually properly update on the display by have 144 HZ and I'm running 144 HZ at uh my house that means I can push a frame buffer update 144 times per second now on this e in
- 15:00 - 15:30 display that we're going to be riding the driver for I was able to achieve a refresh rate of 1 Herz which sounds crazy great right crazy crazy crazy but for an iink display 1 Herz is not actually so bad and I was able to do that without very much effort now frame rate is sort of like the rendering cap so if you think about your Hardware compute power whether that be on the CPU or the GPU we talk about rendering we talk about preparing or rasterizing the frame buffer all of the sorts of words
- 15:30 - 16:00 essentially as we mentioned before the frame buffer is the image representation or the inmemory representation of the image that's displayed on the screen the complete image and to do that you have to run a whole bunch of calculations like you are rendering vectors to the screen you might be doing 3D rotations and things like that that if you're doing a much more complex graphics driver you have to deal with and if your compute renderer falls behind your refresh rate you know like you've got a Time operation going tick tick tick tick tick we're pushing a new update that's when you get lag screen tearing things
- 16:00 - 16:30 like that so you want to make sure that your Hardware is more performant if you're running an embedded system you want to make sure that your CPU and I think some STM microprocessors have 3D accelerators or 2D accelerators anyway um but you want to make sure that your Hardware is up to scratch and your algorithms are optimized those are the two big things bits bits bits I hope you guys like bits we're going to talk about bit Maps you've heard the term bit map everybody has heard the term bit map what is a bit map a bit map is referred to to with images Okay so let's say we
- 16:30 - 17:00 have this image and this is a fantastic website I'm going to link this in the description this is a great website so a bit map if I were to represent my image this is a bit map okay what we were just looking at before if I take this uh little guy outside of this display and I just have it as like you know let's say I bound it here I bound it here I bound it here and I save this as an image that's a bit map right there a bit map is very simply an array or otherwise
- 17:00 - 17:30 storage of bits such that you know this one's a zero this one's a zero this one's a zero this one's a zero this one's a one that's what a bit map is a bit map is simply the inmemory representation of your image it's almost like a mini frame buffer in of itself so let's get back to this website where I'm going to show you this in a real image so this is really great you can upload an image of your choice and you can essentially choose all of these things about it and you can turn the power on to whatever you want so these were some
- 17:30 - 18:00 of the ones that I was talking about before we have one bit line art which is mono our monor frame buffer we have uh 8bit where's our 24-bit yeah so three bytes per pixel this is our 24-bit RGB there's also RGB a if you guys also know that you could essentially add an alpha channel for transparency and such like this but yeah so if we just go ahead and hit convert on this and there are some other things you can change as well so you can make it static const unsign you can change this to a different data
- 18:00 - 18:30 format a progam uh if you're just storing this in a normal C program like progams for embedded and stuff like that um anyway this is our representation of the image this is a 704x 74 image and it gives you a preview of what it'll look like on the display in your format and what we're actually going to do is render this to our little display that I told you when running a driver for very shortly so essentially yeah if you want to render a bit map think about this
- 18:30 - 19:00 this is a massive massive thing to know if you want to render a bit map all you need to do is directly copy it into the frame buffer because if you think about it this is essentially already in the perfect frame buffer output form we simply have to copy it to the frame buffer and then send that off to the display driver in order to actually render that on the screen so if I change this to 24-bit RGB you can see we've got Ox FFF and this is what I was talking about before so each of these will be
- 19:00 - 19:30 able to represent eight pixels because we've got eight bits on our monor frame buffer if I go ahead and hit uh convert on this you can see here we now change to heximal codes where each of these lines here represents one pixel because we've got you know all of these hex codes and we've got eight bits per color I resized that image so it will actually fit on our screen and I copied the bitmap representation from that website into this kuromi doc file file and I also have this header file and all this
- 19:30 - 20:00 header file does is Define the size and let us use this array in our other programs so what we're going to do now is write a function that takes a bit map with an x a y and a size and just copies it into our frame buffer and of course you can stack bit maps on top of each other and that's how you would create a more complex scene if you like we're going to write a function to copy this bit map into the frame buff and now remember this is not the most efficient code this is the most illustrative code so we're going to start off by taking in an X and Y this is going to be our start
- 20:00 - 20:30 position we're going to take the width of the bit map in bits the height of the bit map in bits or pixels and the bit map image itself now the reason we're doing this all in bits or in pixels is so that to the person using this API it's the most transparent right like we want to be able to have them access it in X and Y coordinates and in pixels rather than having to worry about the individual bytes or such how we're representing our frame buffer so it's just a kind of abstraction and because we have these get and set pixels we're
- 20:30 - 21:00 perfectly able to do that the basic idea is that we're going to Loop through every single bit in this bit map and set the equivalent position in the frame buffer now to do that we're going to need this mask in order to actually check the individual bit of our bit of our bit map we're going to need the total size of this image that we're going to Loop through this is in bytes um so we're going to be looping through this array in bytes and then looping through each individual bit of individual bite we're going to need the original X and Y positions that that we start at because we're going to be
- 21:00 - 21:30 modifying these As offsets and we're going to be looping through this entire array in bytes because again bytes that's the most minimal that c can represent for our indexing this is how we actually set each individual pixel or individual bit in our frame buff according to the mask so we're going through and looping through every bit because remember We're looping through every bite now we've got to Loop through every bit this check mask is just going to go one one one one one so that we can check the set value of each bit over
- 21:30 - 22:00 here in our bit map so then all we do here is set the X and Y position to the state of this bit ma bit mask because remember if this turns up true or like positive so if there's a match between the one position and one of the bits in here this will evaluate to greater than one which is true which is the you know the Boolean state for this or otherwise that will equivalate to zero which is going to be false so this will get set to a zero in our frame buffer so that's
- 22:00 - 22:30 essentially what we do and then we go x++ because of course we're going one bit at a time now keep in mind we're not done yet because what you might notice is because we're dealing with onedimensional arrays here what this will actually do is set this in landscape mode forever and we never break to a new line even though we might have reached our height requirement so what we're going to have to do is add a check to increase our y position which I've gone ahead and done here so here we check if x minus the original X cuz
- 22:30 - 23:00 remember we could start we don't necessarily start at xal 0 we might be starting at like xal 50 or something like that so if the incremented X so this will be like 51 52 53 blah blah blah blah blah if x minus the original X is equal to the width so that means we have essentially gone width ways the entire width of our image then we're going to increase Y and we're going to reset x to the original X position CU remember like if we're setting X back to zero and we've started our image at 50 we're going to draw the first line at 50
- 23:00 - 23:30 and then the rest at zero and we don't want to do that we want to reset to the correct Exposition so that's why we're doing this and also if our y position is equal to the height then we're just going to return because that means we' finished the height of our image great now I can show you guys a fun demo so what I've done here is I've just included all of the things and we're going to draw the bit map and then I'm going to print out the frame buffer that we've created so you can see just using standard IO for now so what do is we just draw the bit map here I'm drawing
- 23:30 - 24:00 it at 50 um then we Loop through the height and the width and all I'm doing is just getting the pixel at that position and printing out what it is so let's go ahead and check this out if we go here and I do the whole make thing boom we print out our frame buffer and you can see we're printing this little image out here at 50 so now what I can do is if I want to modify this like let's put this at 20 I can go ahead and do that and you can see we're drawing it at 20 so we are actually updating I can go ahead and draw two of them so if I
- 24:00 - 24:30 want to draw another one at you know like 60 or something like that I can go ahead and do that and so you know it's actually gone and overlapped it because I haven't put them 100 spacing apart but if I do this one at 120 it should be spacing them apart like that and my terminal is going off the screen unfortunately because um you know I don't have enough room to show the whole frame buffer but that is what we're doing so you can see there where literally and I can maybe draw another one at a different y positions so if I
- 24:30 - 25:00 position them in between here 60 at y like 40 or something uh so we seg folded just there and the reason for that was because I forgot to add this check here to not go out of bounce so sorry guys my bad I forgot the add of bounce check but we are no longer seg falting in fact what I can show you is this which is the you know overlapped and cut off third image that we're pasing here and just like four here is our fully rendered scene on
- 25:00 - 25:30 the display we've got the three overlapping images here just like our virtual frame buffer except this time we've pushed it to the actual display pretty cool huh everybody loves vector graphics these days you go to the web you download an image it's sometimes an SVG they're great for icons they're great for scaling what is a vector image and why is it different from a bit map image well think about a bitmap image in your image file or in your memory you have stored pixel inform information for every single Pixel and you can get
- 25:30 - 26:00 compression formats like JPEG that do compression on these and try and reduce the file size but if you consider the simple bit map that we were talking about before you have a represent representation of every pixel in the file or the computer's memory and that actually does take quite a bit of storage now they're fast to render because all you have to do is pretty much direct copy them to the frame buffer but they take up storage and sometimes you don't want that especially for more simple Graphics now we're here in inkscape this is a very popular vector graphics editor I have no idea
- 26:00 - 26:30 how to use it I've just drawn this little smiley face here but vector graphics the key thing that makes them different is that instead of storing pixel information they store instructions on how the computer can actually draw this in real time so if I were to save this smiley face instead of saying oh we've got a pixel here pixel here pixel here it would actually save it as instructions on how to draw this is a KDE SVG file it's actually the one for the file manager dolphin which let me see if I can get up a very very
- 26:30 - 27:00 small example of maybe you've seen this if you're a KD user but essentially yeah the instructions on how to draw this are a little bit complicated and on an embedded system good luck drawing this as a vector um so yeah use your vectors according to the system that you're scaling for if you think about the most simple Vector graphic that you could draw it's probably a rectangle or a box so here we've got the the instructions to tell the computer how to draw this
- 27:00 - 27:30 because again that's what a vector graphic is we start with X and Y so our start X and start y we've got a width and we've got a height so this is literally just one of the most simple vector graphics we loop from x i = x i less than X Plus width so we start at our X and we just go along the width and we do the exact same thing for the Y going down we just have a simple bounce check here remember the previous section and we set the pixel if that bounce check is correct so I've added this into our test code that I can show you so here we draw the bit map right and then
- 27:30 - 28:00 down here we draw our rectangle at x40 y 105 with a width of 50 and a height of 20 so if I switch on over to that you can see here this is what I drew right so if I show you it running again this is like this now keep in mind um the this is a monor space font but the height isn't exactly accurate so if I draw a square um like if I change this to 20 it's not actually going to look like a square on here because the height is different but this is actually 20 pixels down and 20 pixels across so if
- 28:00 - 28:30 the pixels were a perfect square like they will be on the display this is a perfect square well trust me on that one trust me on that one so yeah we I mean we can add another one to show you guys right so you know if I draw this at zero instead then you can see we're just drawing two of them and you can see here that we're now starting to be able to construct our frames so in our scene you know if I want to have this bit map down here and this rectangle down here and you know I have another one over there and blah blah blah this is how we construct our scene or our frame that we
- 28:30 - 29:00 want to Output to the user and if we have animations all we do is we update these little things here on our frame buffer according to you know what tick we're up to also in our exploration on vector graphics I've done the world's worst triangle algorithm so here we can draw a right diagonal line so that's a diagonal line going right and up so if you think about that you start off at the Y position you go y+ height and essentially you add an offset so every
- 29:00 - 29:30 time you go across by the width you add an offset to the next line and so if we start here originally and we go like that the next line we're going to start here and go like that depending on your offset so that's how you draw a diagonal line you essentially draw a straight line adding an offset every time and I did the same thing for a left one where we take away the offset every time so our triangle algorithm is you draw the right diagonal you draw the left diagonal and then you draw a rectangle as the base world's worst triangle algorithm and I've got it in here in our
- 29:30 - 30:00 little scene so if I swap over to our frame buffer you can see I've drawn a little triangle in there and we can modify some of the parameters about this triangle so let's change the uh side lengths to 10 um let's change the position to like 40 and you can see we're drawing our triangle I can draw our actually I might not draw the 41 cuz that'll overlap but I can draw our rectangle I can draw our triangle I can draw our bit map app so this is vector graphics right we're
- 30:00 - 30:30 giving the computer instructions on how to draw our scene and here is our vector graphics scene drawn on the display so we've got a bit map over there on the left and then our two Vector triangles and our Vector Square so essentially once they're in the frame buffer vector graphics rendering works exactly the same way as normal bit Maps they just go and get transmitted to the display just drawing the vector graphics versus drawing bit Maps is where you get that big difference we're going to talk about fonts we've learned about bit Maps
- 30:30 - 31:00 vectors how do we draw text and fonts on the screen so if I want to draw like the quick brown fox jumped over the lazy dog or something like that how do I actually draw that on the screen like when we're using this it's pretty standard to draw text on you know normal computer programs like we're going to have print F that we can use but what if I want to make a print F equivalent that goes directly to the screen so this is where we need to talk about fonts now there are in the embedded world at least there are vector fonts and bitb fonts now in the real world we use true type and OTF
- 31:00 - 31:30 open type fonts which are not exactly vector and not exactly bit map they're sort of a hybrid between the two I'm not going to pretend to really understand the inid of open type and true type fonts but they are something in between we're going to deal with the more simple Vector pure Vector versus pure bitmap fonts now here we have a bitmap font generator this is another excellent website that I will link to you guys where we can really see how bitmap fonts work we know about bitmap images I want you to think about this a bitmap font is
- 31:30 - 32:00 nothing but a collection of bitmap images that represent the entire alphabet so a will be a bit map B will be a bit map c will be a bit map and so all you have to do in your code is if you can map each individual character in the string so you're looping through the string all you have to do is link that to the appropriate bit map and draw that bit map using the exact same logic as we've already done before so have a look at this website right we can do we can
- 32:00 - 32:30 select a font down here which is really nice and it will generate the bit map code for us which is you know this thing here and so what we have to do is go through and we Index this array over here we Index this array at where the character we want to draw is represented and that way we can draw the text on the screen exactly like this there are different families that you can choose so you know you can go ahead and choose a different text family if you're wanting a different font style and you can even I mean you can make your own font Styles if you like and convert them
- 32:30 - 33:00 to bit Maps there are like you know bitmap like image to bitmap generators and you can go through if you've got a font that you want to convert like aerial or something like that we're going to use this website to generate ourselves a bitmap font so a couple things I want to clear you guys into you go over here to presets go ahead and select generic C 8x8 and then go to the output and select cray flat and go and select yourself a font so I actually want to use IB M CGA 16x 16 so it'll
- 33:00 - 33:30 tell you the pixel size the width and the spacing and that is depending on your screen size 16 ties font is what I want to use for today and you can change the width of the output and the height of the output and you can see what your bitmap font is going to look like in real time right here if you still get out of range errors when you've put in the correct width and height you can add in an offset and there we go we've got our flat array of bitmap font so this generates a bit map image for every character in the alphabet all well you can select the range here but anyway
- 33:30 - 34:00 we're going to use this website to generate ourselves a bitmap font so a couple things you want to do go to preset select generic C 8x8 and then come over to output and select C array flat that's what I prefer to work with you can choose nested if you prefer but our calculations will use a flat array then choose yourself a font I actually want to go with the IBM CGA family and I'm going for a 16x16 font size choose that depending on the resolution and size of your display for our case today 16x 16 is I want so I go ahead and change the width to 16 and the height to
- 34:00 - 34:30 16 and if it's still cut off a little bit I'm going to add a tiny little y offset so this is a flat array of a bit map of every single character of the alphabet so what I want to go ahead and do now is hit MSB first this is a question of endianness so endianness is important for bits and bytes and how btes represented in different computer systems today I'm not going to really talk about it because that's not the focus of this video but it's an interesting concept and I might do a follow-up later but anyway this is a representation of every character in the
- 34:30 - 35:00 alphabet in bitmap form so go ahead and copy this into a C file and we'll get writing this algorithm now to actually write the text drawing function remember what I was telling you before it's literally we literally these are all bit Maps so we can use this same draw bit map function we just have to map the character of the text to the correct uh bit map to start us off with this prototype we've got the start X and Y the size of our font in pixels so that would be 16 for us and the text that we want to draw just start off with we've got the font that we want to go for so
- 35:00 - 35:30 often times if you're doing a graphics Library you'll have multiple fonts and multiple different sizes and they're all these flat arrays so what you can do is just Swap this pointer depending on for example the size parameter that's passed in and uh choose a different font from there or otherwise do it so this is just a pointer to the font that we want to use this is a pointer to the character that we want to use and we'll be using some pointer arithmetic in order to get that we want to keep the starting X and the starting y like the running X and the Running Y because what we're going to be doing is text wrapping so you know
- 35:30 - 36:00 if we get to the end of the display we want to wrap the text over at the beginning so we need to keep track of a running X and A Running Y and the target is going to be used for our pointer arithmetic to get the character that we want so we'll write out a switch statement for the size which we've just done like this right so in this case if we wanted a different font size like you know if we had a uh case 24 with a different one we could maybe do like uh font equals CGA 24 if I had one so so you can just use this switch statement um sort of prototype to change the font
- 36:00 - 36:30 that you want to use or Target then we just want to Loop through every character in our text which we start off pretty standardly like this we go from I to the length of the text and now we do the mass that we need to Target the right pointer so what is this size time size / 8 if you think about that we're just taking the full like size in memory of our character which would be 16 pixels so 16 squ because it's a square and then we divide that by eight because we're doing this in bits so this is the size of our characters in memory and
- 36:30 - 37:00 what we actually do is multiply that by the character that we're at minus a space why do we do that I'm going to put an asky chart up on the screen so the asky space bar is the first starting character in our aski alphabet that we have text sorry bit maps in order to represent so if we take the value of our character which will be after the space bar and we minus the space bar away what we actually are left with is how many
- 37:00 - 37:30 spaces in memory like how many characters in memory of this big flat 1D array we need to increase by or increase our you know pointer by in order to land on the Target that we want which is the character of this text which we can write as simply draw car is font plus Target so that's our pointer arithmetic there hopefully that makes sense to you all we're doing is finding out how many characters in memory we need to skip in order to land on the Target that we want
- 37:30 - 38:00 from here we can literally just call this draw bit map function cuz that's what we're doing right we're literally just drawing a bit map now that we found out which character we want to do and the width and the height are obviously going to be our font size which is usually you know 16 and now because we're going character by character we have to increment running X by the size so that'll move us along in our frame buffer to the next point where we're going to draw this character by the width of our character we're not done yet though because we still have to do some boundary checks so for example what
- 38:00 - 38:30 do we do if we overrun our width of the display so we're drawing our characters and we get to the final the edge of the display well what we're going to do is reset running X to X so if you think about that if we go to a new line we're going to start back from where our text started and we're going to increment Running Y by size so down here after we finish drawing a character that's where running x is incremented by the size but if we hit this uh the end of the display and we want to to a new line that's where we increment Running Y by size so
- 38:30 - 39:00 what we're doing is just jumping back to that original position one line down and I'm also going to add a boundary check for the Y because if we've reached the end of our display we just don't want to render anything and that's all there is to it so over here on our test program I'm going to draw the text https trent. surf big things happening watch this space csgo surf is so back anyway if I go over to our uh little thing over here and I run this you can see in our virtual frame buffer we've printed out the text h https trent. Surf and I can of course modify the position of this so
- 39:00 - 39:30 you know if I want to kick this back up to y = 40 um I can go ahead and do that and bring it back down we rerun this it goes up here so yeah let's see this render on a real display here's text from our library rendering on the real display now you might have noticed that the color is off compared to our background well if you've thought about this at all inverting the color is actually really trivial all you have to do is whenever you see a zero write a one instead and whenever you see a one right a z instead so you just invert it if you want to invert the color so the
- 39:30 - 40:00 color not matching here is pretty trivial to fix the big thing is that we have the text drawing in the first place we have our in-memory representation of our frame buffer we can draw vectors bit Maps fonts how do we get that thing onto a real display so it's not just all in memory this is where it gets a little bit device specific so in these little embedded systems displays they're actually made up of two main components well a couple main components but on this display here you'll have a controller chip that actually is the thing that handles Communications so
- 40:00 - 40:30 when my microcontroller wants to render a frame buffer update or do whatever it wants to it will be talking to the display controller and the display controller will then apply the physical characteristics the physical electrical characteristics required in order to update the pixels and get the pixels on the display to change so you have two components that are typically mixed and matched that you need to consider when you're running a driver for these which is number one is the controller so what is the control protocol that you actually have to speak what's the language you have to speak to this thing to get it to do what you want and
- 40:30 - 41:00 particularly with these ear in displays the physical characteristics of the panel which is the second component you've got the controller and the panel that's really important as well now this particular display communicates using SPI now if you're not familiar with SBI I did a full video where I do the most common embedded systems protocols which are isqu C uart Andi if you're not familiar with SBI check that out SBI is the communication bus that we're going to be using to speak the protocol on this thing which will understand from manufacturer provided code and the data
- 41:00 - 41:30 sheet we have here the two files of interest on the left hand side is the manufacturer provided code you can see we've got a 122x 250 display using this IC with four line serial on 3.3 Vols which is exactly the panel that we're targeting and on the right hand side we have the display controller data sheet now what we want to do is sort of match the operations that are happening in this file over here with the manufacturer provided code to what is happening in the data sheet because here here's the thing guys this code sucks this manufacturer provider code it's
- 41:30 - 42:00 horrible look at this look at how poorly this is formatted it's doing all sorts of very weird things that I don't want anywhere near my embedded code bases so what I want to do and look at this they haven't even bothered to do hash defines for the commands and the data that they're sending so what I want to do is like like look at this who wants to use this code so what we're going to do is sort of use this and this data sheet to write our own communication Library to talk to the display that hopefully is done a little bit like what are they
- 42:00 - 42:30 what what is this here how is that going to how is that going to work into any embedded codebase ever yeah so what it looks like they're doing over here by the way is they have these full frame buffer images right so they've got this full frame buffer representation here and they just go ahead and transmit the whole thing and that's what they do so there's no Graphics library in here like what we've built this is literally just we've got a pre-made frame buffer and we're going to transmit it to the display which actually is fine because we've already got our own Graphics Library what we want to do is take the
- 42:30 - 43:00 communications part of this which is you know this section here right with we've got the reset we've got the init we've got uh the clean display we've got pick display and we just want to make our own that's written a bit nicer and then we can sort of tie that together with our Graphics library and we've got a full display driver for this device now the way that you approach a task like this is we're going to start at the start and at the start we call epd reset and epd init epd in it sounds a bit more
- 43:00 - 43:30 interesting so we're going to start with that as you can see here it's got a couple flags that it sets up so that might be useful for us later we're kind of just going to do a nicer logic P we're going to see if we can reorganize this logic and make it a bit nicer and also I want to do definitions for all of these commands cuz like you know this doesn't mean anything to me if I'm looking at it so what we can do is cross reference that with the data sheet so we can see right command 0x00 what does it actually do well let's find out we scroll down on the data sheet for the control chip for a little bit we'll eventually land on this command table so
- 43:30 - 44:00 what we'll actually find out is that the command which is all zeros 000000 is this panel setting command here and if you scroll down a little bit further it'll actually give you information on all of the things that this lets us do so as you can see here uh if CD by the way for this display this is one of the quirks of this controller if you set the CD line to zero you are sending it a command and if you set it to one you are sending it data so that's the kind of Distinction
- 44:00 - 44:30 that it makes that's another gpio line that you would set as opposed to you know chip select and and everything like that so this is the command line here so for 00000000 that is this um panel setting so what you would then go ahead and do if I go to our thing over here you do exactly what I've done so I'm just going to Hash Define BD command PSR as 0x so that way we can now go over to um you know this in it function or wherever if we write our own inet function instead
- 44:30 - 45:00 of saying you know transmit command 0x0 I can then say transmit command com PSR which makes a lot more sense I've gone ahead and done just that all I did you know this looks scary like oh what are all these hash defines literally it's just me going through this command table you can see here power setting pwr is 00 blah blah blah 1 so you know that's what I've set it as here zx01 and this is how you approach something like this guys you just it's GR work you just got to do it out we go through now what's the next
- 45:00 - 45:30 command we do power off that sounds useful maybe I do want to send a power off command well that's 00000000 1 0 so that's going to be command oxo2 so that when we're actually using it in our display driver it makes a lot more sense it's more normal than saying you know if I've got a command that I want to turn it off if I just say transmit command oxo2 that means nothing to me when I'm reading it and it means nothing to anybody else using the library so this is just a way to make it a lot more readable and a more usable than whatever they're doing here we can do the exact
- 45:30 - 46:00 same thing for the data pass to all of these commands so you can see here as an example we write a command ox1 and then we write data so what does all of this actually mean well the data sheet is going to tell us that so for this power setting here you can see CD flips from 0 to 1 which means we're no longer sending a command we're sending data and you can see here that the first bite is going to be this Ven and vgn so what does that actually mean that is the source power selection and the gate power selection ction now what they've gone ahead and done here is set this to ox3 so if you
- 46:00 - 46:30 think about what that's going to mean it means we have a one and a one so these are both set to one which means that internal DC to DC is going to be for both the source and the gate so that's what that means so what I can do now is write in my hash Define and give this a more meaningful name and I'll show you what I've done I've done that for all of these uh command datas as well I'm not going to go through that on video because I don't want to waste your time but that's the idea I went through and I lined this up with what the data sheet was saying and I gave it a more
- 46:30 - 47:00 meaningful name just like I did with the commands before we can write our own init function we have to give the library a way to actually send data over SBI because now that we're on the comms layer that's what we're going to be doing most of the time so you can see here they've done a right CMD thing here and what they're basically doing is bit bashing SPI so they're putting Source clock High they're putting the chip select low they're sending data and then they're putting the chip select back high so they're sort of bit bashing SPI these days we have Hardware abstraction liaries that will do this for us and we can send data one bite at a time you'll
- 47:00 - 47:30 notice what they're doing here is actually sending data one bit at a time we don't really want to do that so what I'm going to do is write an abstraction that essentially uses function pointers as a way to give the user a way to register their own SPI send function and I'll show you what that means it means we end up with these extra lines here so if we go from top to bottom we have three dummy functions that do literally nothing at all so what they do is this is a dummy transmit so a dummy SPI transmit that takes in a bite and does nothing uh dummy CD or DC to change the
- 47:30 - 48:00 state from command to data as you saw in the data sheet and a dummy chip select pin so what we can then do is register these function pointers so BD SPI TX and that is what our library is actually going to use every time it needs to transmit a bite over SPI so right now it's registered to this dummy function that does nothing but what the user can actually do is to generalize this for every Hardware platform imaginable the user can actually write their own SPI transmit function using their Hardware
- 48:00 - 48:30 abstraction libraries provided to them by their chip manufacturer whether that's Nordic or STM or at Mega or whatever and they can then register that using this function here using a function pointer and then the library will be using the correct SPI transmit for their Hardware platform same thing goes for these these are just gpio pins right so it's going to change depending on your Hardware platform so you can just register your own and the library will just use that internally here's our very own init function the difference is night and day this is a lot more descriptive and you can easily map this
- 48:30 - 49:00 to the data sheet now I did do a transmit command and a transmit data all that does is do our set CD to True which means it's setting the line high or set CD false so it's setting the line low um for transmitting our Command or data respectively and then it uses this SP spit TX we talked about but yeah this is our new init function it's the exact same logic as this one over here but just translated nicely with our hash defines am I going to pretend to know what half of these do the the answer is no all I know is that they work and I haven't bothered to read into it too
- 49:00 - 49:30 much I know what some of them mean but the other ones I just haven't bothered to look into and you don't have to either that's the beauty of this thing if you got manufacturer provider code you don't necessarily have to understand all of it now we want to know how to render the frame buffer to the screen so this is the thing of Interest here right we in the main we can init the display how do we render a frame buffer and that's done here with this piore display _ GC that takes in this image as a parameter which is actually the size of a full frame buffer so this function right here what does it do it's got this border flag here some logic and then it
- 49:30 - 50:00 transmits some cryptic commands but don't worry we mapped them so we'll know what they do um but this is going to be something that we're going to have to keep track of in an internal state so instead of having these big globals I might keep a state struck somewhere or something like that and then we do this so we write this command ox13 and then it looks like we Loop through the entire frame buer because that's gate time Source that's you know going to be our size divid by 8 so that's the size of our frame buffer and pior here is what we're transmitting as part of this uh
- 50:00 - 50:30 thing and that's going to be transmitting every bite in our frame buffer and then after that this formatting is horrible we called L GC and display refresh so what are those do we're going to investigate that here's our mystery function and it is so cryptic it's unimaginable here's another thing we're going to have to keep track of in a global State variable there's some Flags here that are getting set all over the place we're sending a bunch of random commands and then We're looping through and uh this is yeah lot stands for lookup table if you didn't know um I'm we just using
- 50:30 - 51:00 sing and then it Loops through and sends all of these random things so what are these well these are actually arrays that are kept up here at the top so these Lots what they actually represent is a waveform I think to the best of my knowledge I'm not going to pretend to be an expert on eaper displays but to the best of my knowledge this is an electrical waveform that gets sent through the pixels of the display that actually causes them to physically electronically move and update and this is why we have to send that to the controller because the controller could be controlling a number of different
- 51:00 - 51:30 panels and it's up to the panel manufacturer who is in this case the one who's provided our code to provide us with the lookup tables that are appropriate for that particular panel so that's what we have to send to the controller the lookup table for the electric waveform that it has to generate to update our display for this particular panel and so that's what we're going to be transmitting so we're going to have to copy all of these lookup tables into our driver because that's what the manufacturer provid if the manufacturer doesn't provide Local app tables good luck buddy I think
- 51:30 - 52:00 you're cooked um so we're going to have to do that and then we'll look at replicating this logic wherever it was here yeah um I think we can make this a lot nicer and uh yeah we'll we'll move this to a global State there's a GC waveform which is a full display refresh so I think with e papers you can do a full refresh or a partial refresh the partial refresh here is Du um and du from how I've been using it it updates a lot faster but you could potentially get ghosting so that's the thing you got to
- 52:00 - 52:30 worry about so there's du updates which are faster about like two times faster or three times faster to update but you could get ghosting or there's uh GC which is a full refresh so these logic essentially look exactly the same but it just looks like the lookup tables are different so what I might do is like swap a pointer depending on what refresh you want to do I'll think about it I mean I've already thought about it I'm pretending to think about it I've already written the code obviously so I know how I would do do it um I'm not going to go through and explain this
- 52:30 - 53:00 while I'm writing it I'm just going to show you the finished product and I'm going to explain it there cuz I think that's going to be a lot better here's the mining that I did off camera over here we've got an enum for the lookup table type cuz remember we've got two we've got a full refresh one and a partial refresh one I've got an internal State thing that keeps track of you know these flags and stuff that this function is setting that were previously just Global so that's just a way to for me to organize them a little bit better and over here is where I've actually done the L transmit now essentially what I've done is I've taken this logic here and
- 53:00 - 53:30 I've implemented it using State variables and you know I swap the pointers here for the lookup table depending on the type so you know we can say if we're using this type we're going to do this one just so we don't duplicate this across two different functions and of course the commands actually have L see you know as the command instead of like ox 23 or whatever it is so hopefully that should be a lot more readable but yeah this is essentially just a logic clone of this one that I've formatted a bit nicer I've
- 53:30 - 54:00 done proper hash defines I've done a global State variable and things like that and so now what we can do is look at actually transmitting our frame buffer which was the thing that we were looking at before right so we've got the pick uh display whatever function which was down here so you can see here you know we just do WR CMD for the Border uh that's if we want to do the Border um we've got transfer new data or whatever which is dtm2 that's the same command and then we transmit the frame buffer
- 54:00 - 54:30 and then we call this and I've actually included a parameter for if you want to do a full refresh or a partial refresh and it will do the the correct L by the way and then we just call a refresh and yeah so that's pretty much it we're ready to put this on a real display that is super exciting isn't it as's one last quo with this display I need to mention to you guys before we render this on real hardware and that's that the controller is expecting a frame buffer in portrait orientation whereas we're addressing it in landscape mode so what we have to do instead is when we do our
- 54:30 - 55:00 set pixel and get pixels we just have to transpose them so this is the previous logic in landscape wise operation so you can see here we're doing the width time y + x which makes sense and here we're doing X shifted but we have to change that to height * x + y and then y shift just so we transpose that from landscape mode into portrait mode when we're addressing this not sure how many of you guys have worked with st microcontrollers before but the dev board that I'm down on has one so I'm setting this project up in cuber Max we're only using SPI 1 pretty much so
- 55:00 - 55:30 I've got that set up where I transmit only Master we've got 8 Bits as our data frame size and we're sending at 8 megabits per second because the controller only supports 20 or less in terms of gpio we need one for chip select one for command SL dat signaling one for display reset and one that we're taking in as a busy pin because you know sometimes the display can be busy and we want to know about that but that's pretty much it it's a pretty simple project we're pretty much just using SPI we're here in the main. C file that Cube generated and here's some of my own code
- 55:30 - 56:00 so I've done the set CD and spit TX and a reset display function that just toggles the gpio PIN for a little bit that is appropriate for my Hardware platform because I'm using STM so I'm going to be using how and you can see down here we can actually do this register thing so set set CD and we can give it the function pointer to this custom one that we've specified and the same thing for spit TX so all we're doing this is our user code everything else was generated by STM Cube we power on the display init the display and then we're going to fill the frame
- 56:00 - 56:30 buffer with white draw the bit map of our smiley face draw trent. Surf and then draw some vectors and here we just render the frame buffer again and again and again every 2 seconds so technically this would be a uh one FPS every two seconds uh so that would be I don't know one divid by two or something like that 0.5 frames per second uh pretty high we're we're pushing pixels here guys so yeah let's render this on the display ignore how messy this setup is it was thrown
- 56:30 - 57:00 together very haphazardly for this video but what I want you to focus on is this display here and this STM Dev board now when I flash this code hopefully we're going to get our frame buffer so let's try that power on and after 2 seconds we should render our first frame boom there we have it the smiley trent. surf uh I can you see the little triangle there over the r that's the triangle uh I don't think the square and the other triang angle are showing because they're drawing in white when I've set the background color to white but yeah you
- 57:00 - 57:30 can see there we're actually rendering that frame buffer to the display which is really sick and that's how I did you know these other two here set them up and a great thing about eaper displays is that they actually retain their image when they're powered off so I can fully cut the power to this thing and it will retain the image on the screen pretty cool huh that's it from me guys this was an absolute Mammoth of a video to put together and I really hope you enjoyed it cheers