
First, let me clarify a concept,bare programming, which refers to writing programs on bare metal. Bare metal, in the microcontroller field, refers to a microcontroller control system with hardware, so don’t get it twisted. Programming on bare metal is like cultivating a wasteland; every shovel that goes in will hit hard rocks. What’s the point of doing this? The pioneers seek the oasis of the coming year. And what do we, the so-called engineers cultivating bare metal, pursue? Of course, we pursue the completion of a task. We usually call ourselves highly educated individuals, so what should we think about during the pioneering process? Of course, we shouldn’t just think about how to complete the task; we should first think about what we are thinking. Is that convoluted? Yes, it is, and that’s how we arrive at thought.Thought is the commander of a simple person doing anything in a complex environment; it influences every detail of a pioneer’s life, including bare programming itself. When a person is wielding a hoe, sweating as they dig, can we know what they are thinking? Of course, it’s hard to say. If you dig yourself, you’ll know. But generally, it’s about the same; let’s just mention a few: Why is the sun so scorching? Why are there so many rocks? What’s the best thing to plant here? How many days until it’s done? This is too slow; it would be great to have a machine. Of course, this is just part of it; anyone can come up with many thoughts. So when we are pioneering on bare metal, what should we think about?Perhaps our general thoughts are:First, implement a simple function, then an important function. Today, I finally debugged this function, and tomorrow I can work on the next function. Why isn’t this the result I imagined? It’s really baffling! But wait a moment. If we take on a task,set up the test platform and start programming, thinking of completing one function after another, and then indulging in the joy of success, then programming like this is basically called thoughtless.Programming with thought cannot just pile up source code right away, as that will only lead to a bunch of rigid numbers awkwardly crammed together, regardless of whether they are redundant or contradictory.So before writing source code, we need to think about how to write it. Perhaps many people have thought about similar issues before writing, such as modularizing the task before organizing the program. But such thoughts are merely task-related and not the philosophy of bare programming. The philosophy of bare programming should influence the organization of source code during the process of organizing task modules and writing bare programs; it directly determines the quality of the source code.A data structure, a module formation, a microcontroller instruction, a hard instruction execution mechanism, a line driving method, an interrupt sequence, a jump delay, a code position, a logical organization, and the relationship between modules (running state vs. non-running state) are all components of the philosophy of bare programming. This may seem trivial,but bare programming is inherently like this; it differs from upper-level programs that have a powerful and complete operating system support. It is impossible to embed an operating system in a microcontroller; doing so would distort its purpose. So don’t let anyone jump out and say that a certain microcontroller has an operating system. Bare programming should be based on bare hardware; only useful functions should have code. The quality of bare programming may often not be felt in applications; perhaps you and others can achieve functionality, but good bare programming has good extensibility, maintainability, and the system has high stability and performance. And pursuing this high-quality technical realm requires good thought to guide it. Does it seem a bit confusing? Don’t say it’s confusing; I’m already confused just saying it.In summary, it’s about embedding an excellent soul into your source code, giving your source code a good thought.
(II) Specific Practices of Bare Programming
As mentioned earlier, bare programming requires thought, which may not be specific enough. Next, I will specifically discussthe specific practices of the philosophy of bare programming. A thoughtless bare program is like a human skeleton; it has a human shape but lacks humanity. The joints between the bones are connected by glue or strings, rigid and stiff. If we wrap the skeleton in flesh and add a soul, we will be amazed: Ah! This is a handsome guy, this is a beautiful girl! Because the skeleton has come to life. Bare programs are the same;if we say that this is enough according to traditional thinking, then bare programs are like skeletons, usually just a rough pile of functions, and will only be called garbage by future generations. And future generations may not be able to break out of this cycle, so future generations will also call this program garbage. Thus, it continues, passing down something that is always called garbage: thoughtless bare programs. I have been programming for many years and have thought about programming for many years. Continuous experience accumulation tells me:Writing good programs is not about how to complete the code, but how to organize the code. The object-oriented programming philosophy in upper-level machines is a very desirable thought. The object-oriented programming philosophy in upper-level machines has a very rich development package and powerful operating system support.How can we introduce such a philosophy into bare programming?Many people may think it’s impossible. In fact, nothing is impossible. No matter how complex the thought is, it ultimately boils down to assembly, to bare programming. Our microcontroller programs are a form of bare programming. The only difference is that when programming microcontrollers and microcomputers, we are at different heights on the development platform! Understanding this height may confuse many people because we rarely pay attention to it. So let me give another example to illustrate, even though it seems not very related to bare programming, the concept of height in this example is very clear. We know that the network transmission standard has seven layers:Application layer, Presentation layer, Session layer, Transport layer, Network layer, Link layer, Physical layer, what are so many layers for? Understanding the concept of such layering may be quite difficult, but understanding the thought behind such layering is much easier, and this is also the thought that hardware engineers should learn from to make our hardware design more standardized and forward-looking. The essence of this seven-layer thought isto refine a network transmission product, allowing different manufacturers to choose a suitable layer to develop their products; with different layers, their chosen development basis and content will differ. Higher-level developers inherit the achievements of lower-level developers, thus saving social resources and improving social productivity. I won’t elaborate on this guiding thought; you can understand it yourself. What I want to say is that the object-oriented programming philosophy on microcomputers is like the thought implemented at the application layer, while the object-oriented thought of bare programming is like the thought implemented at the link layer; it has no software development package, only physical architecture. However, the thought implemented at the application layer ultimately has to be translated to the physical architecture. Once you understand the above example, you will definitely understand thatthe object-oriented thought of bare programming can be realized, but the difficulty is much greater, and the understanding is much harder. But that’s okay; this is precisely the manifestation of software level. If you love technology, what’s there to fear? In fact, it won’t be too difficult; it’s just a slight change in the way things are done. Traditionally, we like to divide modules by functions, subdividing tasks. The object-oriented thought does not do this. The object-oriented thought first finds objects from a task, mixing in some modules to achieve functionality. This is the stark difference between the two styles. For example, if we want our microcontroller to output display information to a monitor, the traditional analysis method is to format the information, send the formatted data to the monitor for display, and it seems that this is enough. Different monitors use different sending programs or program segments, configuring different variables, those that can be shared are shared, and those that cannot be shared are separated.However, the object-oriented thought does not do this; instead, itfirst treats the monitor as an object, which has some functions and variable attributes. Different monitors use the same code identifiers in the object, such as function pointers (in C language), so that for any different monitor, the same code is used during the call. Perhaps someone will say that the traditional approach can also do this; why make it so convoluted? Actually, it’s not so; the benefits of using the correct thought have been mentioned many times before. If it’s still vague, go back and take another look. Having said so much theory, let’s talk about some specific practices. Taking KeilC as the compilation environment, let’s discuss some practices for organizing an object. First, identify the object, such as the monitor, which is a typical object. Next, analyze the basic characteristics that a living object should have, namely attributes and actions. The attributes of the monitor include: type code, brightness, contrast, video memory, etc., while actions include: initialization, content refresh and display, turning on and off, and various display effects like flashing, etc. This division is relatively easy to understand. Below, regarding the organization of code, attention should be paid to the independence and integrity of the object. First, place the monitor object in a separate document, and put the variables unique to the object and the object’s definition together. Distinguish between public and private variable definitions; for private variables, consider the arrangement of temporary and permanent variables. These arrangements strictly determine the lifespan of variables, which can save memory and avoid confusion. For example, if a function needs to use a variable, the function exits after being called, but there is a variable that only it uses and needs to retain the results generated by each call. How should this variable be defined? Many people would directly define it as a global variable, but a good practice is to define this variable as a local variable of that function but declare it as static. This way, this variable is transparent to other codes and cannot be modified by mistake, and the code is well categorized, making future maintenance easier. Using function pointers to unify the different handling methods of different types of monitors is also a good approach, allowing monitors with vastly different handling methods to use a unified object, but function pointers should be used cautiously. Alright, I’ve said a lot, and I’m getting dizzy. I’ll stop here. The essence of thought doesn’t have to have the same form; different people will have different understandings. I just hope to throw out some ideas for everyone’s programming career, and I feel a sense of accomplishment.
(III) Preparations
This article will reference an example. Before introducing the example, we need to do some preparatory work and then step by step approach the example. Taking the monitor control mentioned in the previous post as an example. The monitor is an object. Regardless of how complex the monitor is or how simple it is, it does not matter to the caller who needs to display information. In other words, for the subject that needs to use the monitor, they only care about displaying information, regardless of the myriad differences in monitors. As long as the monitor provides a certain function, it can be called. Of course, before calling, the data transmission rules of the monitor must be followed, but there is no need to consider the differences in transmission rules produced by different monitors. In other words, for the caller, they will never want multiple rules to complicate their calling code. Therefore, we first need to construct a relatively independent code segment, which is the monitor object. Below, we will take KeilC as the compilation environment for bare programming. As many people say, KeilC is not an OOP language; how can we do this? Precisely because we think KeilC cannot do it, I bring out this thought to discuss with everyone, making our programs more exciting and technically rich. The best way to form an independent code segment is to create a subdirectory under the main project directory, such as DISPLAY, and then create a document in the DISPLAY directory, such as DISPLAY.H, and include the DISPLAY.H document in an appropriate location. This way, an independent object-oriented code segment is initially formed. When maintaining the code in the future, you will never need to consider how the caller feels; just maintain this document, and you can ensure continuous updates to the monitor. Many people may say, what is this OOP? Don’t rush; this is just the beginning. The following is the specific process of organizing the code. For a monitor, we must have display requirements before we can customize it. If we cannot even propose usage requirements, we should not ask someone to make a monitor for us. So wemust first clarify what we want the monitor to do. Since it is a microcontroller-controlled monitor, we cannot imagine it like a microcomputer monitor, which has a large video memory that can display many pages and colors. If we think this way, we are making a blind analogy, indicating that we have not thoroughly researched the problem.For a microcontroller-controlled monitor, considering it can display a single character or a single line is basically sufficient..So we can define the following two object functions: dispShowAChar();// Display a characterdispShowALine();// Display a line of characters Since it is a bare system of a microcontroller, as software designers, we must be clear that the monitors we face often do not have CPUs. Therefore, we must understand thatthese two functions cannot occupy the CPU for long. Otherwise, our program will not be able to do anything and will only be a processing chip for the monitor. Therefore, these two functions must exit after running, and since display cannot be interrupted, there must be a code segment that remains active in the active code and does not affect other functions. Those who have done upper-level machine programs should be able to see that this code is a thread. We also use this concept in bare programming. Our monitor object needs a thread that is always active to interpret and execute the display function for the microcontroller system. Therefore, dispShowAChar() and dispShowALine() are not suitable for directly performing display work; their most appropriate job is to set the display content according to the specified format. This way, when we use them, we do not need to set complex code and nested calling relationships in these two functions, as that would waste a lot of code, and excessive calls would reduce the microcontroller’s running efficiency, increase hardware resource consumption, and in severe cases, may cause stack overflow without knowing why. Let’s also give this active thread a name: dispMainThread();// Execute display function according to specified requirements// Specified requirements include color information, flashing, scrolling, etc. As the program analysis continues, more concepts will emerge. The concept of multithreading mentioned here will be discussed later; multithreading in microcontrollers is also a complex and tedious processing issue, and it is too early to introduce it now. I just feel that I have said too much again, and I will continue in the next article.
(IV) Expanding Thoughts
For defining the capabilities of an object, we can generally start from the important aspects and then gradually expand, summarizing the other necessary capabilities into functions, thereby developing the object-oriented thought. In the previous text, we mentioned how the three functions came about, but we have not yet touched on the substance of the functions, so this post will explore the substantive planning and design of these three functions. With functional requirements in place, the primary task to implement them in bare programming isto design the data transmission method. Clearly, we must have a display area to store the content we want to display, as well as the display attributes of the content. We also need to plan how many characters or dots the display area will show. However, since we do not know in advance how much display capacity our display device will provide at once,we can define the memory of the display area, i.e., the video memory, to be larger so that any display that meets the design requirements can be satisfied. This approach is actually quite practical in bare programming because we rarely allocate dynamic space; once the program design is complete, all variable positions are determined. If it works, it works; if it doesn’t, the compilation will fail. Therefore, we can usually choose some newer microcontrollers with relatively rich memory resources. However, this approach also has a downside. For example, if we underestimate the pre-estimated size and the data space is insufficient, we will have to start over to change the size of the video memory, which will lead to corresponding changes in the entire display program. This is not the worst part; the worst part is when a new display requires changes in the data structure due to new functional requirements, we will be in trouble. The preliminary work may require significant changes, even requiring a complete rewrite and retuning. I absolutely hate such painful things. Therefore, we need to avoid such occurrences as much as possible.Here, the object-oriented thought is quite necessary. At this point, we need to introduce a new concept, which is the child of the object,sub-object. What we discussed earlier was actually just an abstract object without any specific appearance, merely a general plan of what capabilities all monitors must have. For each specific monitor, there is still no concrete design. Here, each specific monitor is a sub-object of the monitor object; they vary in form but must complete the specified functions. According to traditional OOP language theory, this creates an inheritance relationship, but in the philosophy of bare programming, I do not advocate introducing this concept because the inheritance in traditional OOP languages is purely a syntactical logical relationship, and the inheritance relationship is clear, while this thought in bare programming has no syntactical support, making the inheritance relationship very weak. It’s better to say it’s classification and generalization. But regardless of what kind of relationship it is, I don’t want to create a new term for such an obvious relationship, making it confusing for readers. Since we have introduced sub-objects, can we see what practical significance this approach has? Perhaps experienced senior programmers can see it. When we design the data for the parent object, we do not specify the specific data format and video memory size; instead, we let the sub-objects handle it themselves. Haha! Isn’t this approach very simple? It’s not my business, so I won’t care. Don’t say I’m being lazy; from the perspective of the parent object, this is the wisest approach because it cannot manage it, so it doesn’t. At this point, more questions may arise. If an object doesn’t manage anything, how can the caller use this object? You want to use it, but it doesn’t manage anything; how can that work? Don’t worry; the parent object does not manage specific matters; it still manages abstract matters; otherwise, it wouldn’t have a reason to exist. If you complain, it means you are thinking. Once you think, you can raise the questions, and the questions raised will be the basis for designing the parent object. Asking questions is much simpler than programming, such as: How many characters can the monitor display? Is it color or monochrome? Does the display mode support predetermined methods (like moving, flashing, etc.)? Is the working mode image or character? etc. Here, I would like to add that for the display mode, we are taking character display as an example. Since it is an object-oriented thought, I believe it will be easy to expand to image display mode. Once questions arise, we can continue to add code.
dispGetMaxCol();// Get the maximum number of columns in a row
dispGetMaxRow();// Get the total number of rows in the monitor
dispGetMaxColors();// Get the maximum number of colors in the monitor
dispSetShowMode();// Set the display mode; unsupported display modes automatically revert to normal display
dispSetWorkMode();// Set the working mode; if the mode is not available, return 0; if supported, return 1
For the definitions of these functions, each person can set them according to their habits. I just temporarily created this example, which may not be the best. My goal is to emphasize the thought. I also fear that the program will become too large; I would dislike writing a book that is too thick. It seems that after adding these functions, we do not see the specific form of the display data, and along with the previous functions, there is no clear statement. This feeling is quite correct; we indeed have not defined any video memory, but it seems that the functions have been defined, and we will use them this way in the future without fear; the program will definitely be completed.
(V) Data Transmission and Program Logic are Equally Important
Continuing the discussion from above, we mentioned that in order to use dispShowAChar(), dispShowALine(), and dispMainThread(), we introduced five new functions, the main goal of these new functions is to achieve data transmission between the caller and the callee. In program design, data transmission and program logic hold equal importance; the former often forms a protocol in the end, while the latter often manifests as various algorithms. In bare programming, our thought should mainly manifest as a soul, rather than pursuing syntactical perfection like C++. Therefore, for parameter transmission, we should not pursue syntactical perfection but rather use a flexible approach to transmission. In addition to functions being able to transmit data, directly calling values is also a very quick way. However, calls cannot be made arbitrarily; we should also learn the habits of C++ syntax and try to avoid letting some specialized variable names appear in program bodies unrelated to specialized variables. For example, in our design, we stipulate that our bare system supports a maximum of 65536 colors for the monitor, so we will use a 16-bit unsigned integer to store this indicator. To simplify future explanations, we will first define two data types: typedef unsigned int UINT;typedef unsigned char UCH; If we use functions to transmit this data, we can do it as follows: #define Monitor01_MaxColors 0xFFFF For the color calling function, we define it as follows: UINT dispGetMaxColors() {return Monitor01_MaxColors;} Clearly, if another monitor is a monochrome display, the color calling function only needs to change to the following form: #define Monitor02_MaxColors 0x0001UINT dispGetMaxColors() {return Monitor02_MaxColors;} Someone mentioned using arrays, which can solve many problems. That’s absolutely correct! The above example overlooks one issue: the same function name is supposed to do many different things, and we have not used switch() in the function body, which is clearly incorrect. To truly implement the transmission of the common attribute MaxColors for different monitors, we must add switch() to distinguish between different monitor types. Therefore, we need to introduce a new parent object attribute to indicate which child it is: UCH MonitorType = 0; // Monitor type, supporting up to 256 types of monitors And during initialization, we will initialize this attribute to 0 as the code for the default type of monitor. The following naming convention will be established to give the code a more standardized appearance: the interface functions of the parent object will start with a lowercase disp, variables will start with Monitor, macros will start with Monitor and contain at least one underscore, and macro functions will be composed of all uppercase letters. Therefore, without using arrays, the above code will change to the following form: #define Monitor_000#define Monitor_011#define Monitor_022UINT dispGetMaxColors() {// The following uses multiple exits, but this does not break anything; to save code, it can be completely usedswitch (MonitorType) {case Monitor_01: return Monitor01_MaxColors;case Monitor_02: return Monitor02_MaxColors;}return Monitor00_MaxColors; // Default returns the default monitor} This form is clearly too verbose; although it is very structured, we may still discard it when optimizing the program. Therefore, the use of arrays is mentioned here. Since it is an array, it should not belong to a specific sub-object but should be defined in the parent object. Although this approach may lead to difficult-to-understand new data being added to the parent object when adding new monitors, we can still tolerate this pain to save code. If we change to use arrays, the above code will change to the following form: #define Max_Monitor_Types 3#define Monitor00_MaxColors 1UINT MonitorMaxColorsArray[Max_Monitor_Types] = { Monitor00_MaxColors, // Default is monochromeMonitor01_MaxColors,Monitor02_MaxColors,}; The line marked with *** will be the sentence that needs to be modified continuously when expanding in the future. Therefore, the above function will be simplified as follows: UINT dispGetMaxColors() {return MonitorMaxColorsArray[MonitorType];} Some may even use macro functions to save runtime, as long as the calling rules are modified: #define DISPGETMAXCOLORS(c) c = MonitorMaxColorsArray[MonitorType]; Perhaps when we write the code in this way, every improvement will make us happy, as our code has been optimized again. But unfortunately, this kind of thoughtless optimization will bring us trouble in the not-so-distant future. I feel that my memory is not very good; perhaps I won’t remember things from a minute ago, and this kind of jumping around to modify things in the future will make me feel dizzy! Therefore, in engineering work, we need to keep the parent object and sub-objects as isolated as possible to reduce the amount of interrelated modifications. This is also an important significance of the object-oriented thought, which I will elaborate on in the next post.
(VI) Separation of Parent Object Interface Functions and Sub-Object Functions
In the previous text, we discussed some design ideas for dispGetMaxColors(). We have many good methods to implement it, but do we have better management methods to achieve it? This is the focus of looking at problems from a higher level, which is more important. This is also an important turning point from traditional thinking to object-oriented thinking. To transform this function into an object-oriented logical structure, we also need to do some preparatory work. First, let’s talk about the thought of parameter transmission.Try to minimize parameter transmission, this is an important measure to respect the hardware status of the C51 series 8-bit microcontroller. Remember, do not complain about the low level and limited resources of the C51; instead, we should have the enthusiasm to develop our bare programming object-oriented thought while respecting and loving the C51. In other words, no matter how rudimentary the system we face is, we have strategies to implement complex functions. From a developmental perspective, upgrading a product is not about blindly upgrading my CPU, as that will only lower the intelligence of product designers. Therefore, I believe the characteristic of the C51 should be simplicity, becoming simpler and simpler, rather than more and more complex. So I hope we can upgrade our thoughts as a direction for product development. Parameter transmission should minimize the use of pointers, floating-point types, etc., and focus on UCH and UINT, and the number of parameters should not be too many; ideally, the upper limit is two. If there are too many, use shared buffers or global variables. It’s best not to pass parameters. This function utilizes MonitorType to omit a parameter transmission. Second, we need to separate the interface functions of the parent object from the various possible implementations of the functions of the specific sub-objects. Here, we will need to use function pointers. Function pointers may not be commonly used, but they are not very complicated. First, let’s look at the form of our function: UINT dispGetMaxColors(void); To define a pointer type for this function, we only need to do the following definition: typedef UINT (*dGMC)(void); Then, for the dispGetMaxColors() function in the parent object, we only need to define a function pointer and provide an entry address for the corresponding function call of a sub-object when creating the parent object. Therefore, the entity of this function will only appear in the sub-object, while in the parent object, there will only be a variable definition: dGMC dispGetMaxColors; To assign it an initial value, we can also define a null pointer as a flag for unused judgment: #define NIL 0 Then, when initializing dispGetMaxColors, we only need to write the following statement: dispGetMaxColors = NIL; And the function call is also very simple, just like the actual function: if (dispGetMaxColors != NIL) vMaxColors = dispGetMaxColors(); If we add conventions, even the previous judgment statement can be completely omitted. Because the program space of our bare program is also the running space, there is no issue of code being loaded into and out of memory, so we do not need to consider the optimization of program memory. As long as we stipulate that objects must be created before use, the judgment statement will become meaningless. Moreover, even if we do not use it after creation, the function body will not be released because it is fixed in the program space; if you want to move it out, it cannot be achieved. Third, try to simplify the syntax used by the program to reduce misunderstandings that may arise from differences in the compiler.Some say that C51 is a subset of C; I believe this statement is unscientific. It can only be said that both inherit the basic spirit of C, but in essence, they target different objects, resulting in different outcomes. Therefore, I see some experts or interview questions that produce problems that leave me in awe. Perhaps I could spend a lifetime doing bare programming and still not be able to solve their problems, but I do not feel inferior to them. They have merely spent a lot of time studying their conventions on the compiler, while I do not want to spend time studying things that may change in the future. I hope to use a simpler approach to excel in my work. I only care about the speed of my project, the size of the code, the efficiency of operation, and the complexity of maintenance. Therefore, I also suggest that those who communicate with me can use a straightforward method to achieve our functions without overly considering whether my program can be used on an 8-bit microcontroller or a 16-bit microcontroller. Our system should be frequently upgraded, but we should not easily increase hardware costs. Of course, if you have the energy to study those conventions, that’s fine, as long as you are willing. Alright, the three points above, only the second point is the key I want to discuss; the other two are personal suggestions for finding some quick and practical methods based on specific situations. In fact, we can say that replacing the parent object with function pointers instead of entity functions is a thought; we have not used complex syntax but have separated objects from management, allowing different people to do different things, which is relatively easy. However, at the same time, we cannot let this intermediate link affect the efficiency of our program, so we can seek some methods ourselves without having to follow the rules. I may face some criticism for saying this, but I can tell everyone that computer language is a man-made science; there is no best, only better. We do not need to develop design thoughts strictly according to others’ ideas, so we should not take pride in some conventions; those things are only a matter of learning order, not a difference in level. What we should pay more attention to is that others have created tools, and we should use those tools to create the world! If you remain at the level of admiring tools, that is pointless! This post essentially only discusses a transformation and two suggestions; these are not specific programs but thoughts. I want to emphasize that it is not about format but about thought. In the next post, I will return to the organization of objects and discuss issues such as the hierarchical relationships of objects, object creation, object writing, etc. I also hope that someone can bring better methods back to the post, and we can learn from each other and improve together.
(VII)
The evolution of thoughts discussed earlier has been lengthy, and the object-oriented thought has reached a ripe stage. Below, I will first illustrate the hierarchical relationship of object-oriented thought in bare programming:
I believe this image is sufficient to clarify how we organize our display object disp in KeilC. Disp is an abstract object; it is merely a connection that completes the generalization of all sub-objects d000, d001, d002, up to d255, and provides a set of functional interfaces used by callers. These functional interfaces are precisely the function pointers mentioned in the previous post. The specific functional implementations and the requirements of different display objects for data structures can be left to the sub-object design engineers to decide. Clearly, everyone in the specific program design process of this scheme should focus their main energy on solving their own problems, thinking more about how to make things more beautiful, rather than being vague during code writing. The parent object designer must complete the overall scheme design, with more abstract thinking and less concrete workload, while the sub-object designer has the opposite task, needing to consider more specific designs without worrying about how others will use their things. Clearly, as the overall designer, one must strictly consider the data exchange relationships in between because we do not have an operating system, so the usage rules for available memory resources directly relate to the success or failure of our entire system. Chaotic usage often leads to system crashes, while relatively independent code is directly arranged by KeilC during compilation, and we do not need to consider their impact on the program. The size and location of the video memory in the example are key to the success or failure of our scheme. We all know that KeilC divides the memory of microcontrollers into four types: data, idata, pdata, and xdata. The size and characteristics of each type of memory determine the running effect of our code. We need to consider both the space required for information and the need for the microcontroller to meet our visual requirements. In this example, I believe we can choose xdata as the video memory. Why? Because I believe that as long as we handle it properly, our microcontroller can completely overcome the processing speed limitations, so we can prioritize meeting the information volume requirements, providing enough space to achieve the functions we want. There are many ways to speed up, such as choosing some high-performance new microcontrollers; the traditional ones are 12T, and now there are 6T and even 1T, which makes many instructions more efficient; appropriately increasing the crystal oscillator frequency; choosing more scientific algorithms; etc. Up to now, we can basically construct our objects. If you are interested, you can use #define to make some pseudo-code definitions to make our objects look more beautiful and closer to C++. However, I must say that we do not have strict class definitions here, so the definitions of classes and objects often have no boundaries.


Some screenshots of electronic books

【Complete Set of Hardware Learning Materials Collection】
