Hardcore Microcontroller Programming and Philosophy

Hardcore Microcontroller Programming and Philosophy

1. What Is Bare Metal Programming?
First, let me clarify a concept: bare metal programming, a term I coined, refers to writing programs directly on the bare hardware. In the microcontroller field, bare metal refers to a microcontroller control system that operates without an operating system.
Programming on bare metal is like cultivating land in a barren field; every shovel hits hard rocks. What do we gain from this? The pioneers seek next year’s oasis. And what do we engineers, who cultivate bare metal, pursue? We certainly aim to complete a task.
We often call ourselves highly educated individuals, so what should we consider during this pioneering process? It shouldn’t just be about completing the task; we should first think about what we are thinking. It’s a bit convoluted, isn’t it? But that’s the point; this complexity leads to 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 metal programming itself.
When a person is hoeing the ground, sweating as they work, can we know what they are thinking? It’s hard to say. If you try it yourself, you’ll understand. But generally speaking, it’s similar: Why is the sun so harsh? Why are there so many rocks? What’s the best thing to plant? How many days until it’s done? This is too slow; how great would it be if there were 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? Our usual thought might be: first, implement a simple function, then an important function, finally debugging this function today so that tomorrow we can work on the next one. Why isn’t this result what I imagined? It’s truly perplexing! But wait a moment.
If we take a task, set up the test platform, and start coding while thinking of completing one function after another, then basking in the joy of success, that’s essentially programming without thought. Thoughtful programming cannot be just about piling up source code; otherwise, it will just lead to a bunch of rigid numbers awkwardly crammed together, regardless of whether they are redundant or contradictory. Thus, before writing source code, one must consider how to write it. Many people might think about modularizing the task before organizing the program. However, such thinking is merely task-related and does not represent the philosophy of bare metal programming. The philosophy of bare metal programming should guide the organization of source code during task modularization and bare program writing, directly affecting the quality of the source code.
A data structure, a module formation, a microcontroller instruction, a hard instruction execution mechanism, a pin driving method, an interrupt sequence, a timing delay, a code position, a logical organization, and the relationship between modules (running state and non-running state) are all components of the bare program philosophy.
This may seem trivial, but bare programs are inherently like this. They differ from upper-level programs, which have robust and complete operating system support. It’s impossible to embed an operating system in a microcontroller; doing so would distort its purpose. So, let’s not have anyone jump out and say that a certain microcontroller has an operating system. Bare programs should be built on bare hardware, where only useful functions have code. The quality of bare programs may often not be felt in applications; you may achieve functionality like others, but good bare programs have excellent extensibility and maintainability, with high stability and performance.
To pursue this high-quality technical realm, good thought must guide us. Does this seem a bit confusing? Don’t say it’s confusing; I’m already confused. In summary, it’s about embedding an excellent soul into your source code, giving it a noble thought.
2. How to Implement Bare Metal Programming
Previously mentioned, bare metal programming requires thought, but that may not be specific enough. Next, let’s discuss the specific practices of the philosophy of bare metal programming.
A bare program without thought is like a human skeleton; it has a shape but lacks humanity. The joints between the bones are merely glued or tied together, stiff and rigid. If we wrap the skeleton with 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.
Likewise, bare programs are the same. If we say that following traditional thinking is sufficient, then bare programs will resemble skeletons, typically just a rough assembly of functions, leading future generations to deem the program trash. And if future generations do the same, they may also call it trash, perpetuating the cycle of passing down something labeled as garbage: thoughtless bare programs.
After many years of programming and pondering, my accumulated experience tells me: writing good programs is not about how to complete the code but rather about how to organize the code. The object-oriented programming philosophy in upper-level machines is a highly commendable thought.
The object-oriented programming philosophy in upper-level machines is supported by a rich development package and a powerful operating system. How can we introduce such thoughts into bare programming? Many might think it’s impossible.
In fact, nothing is impossible. No matter how complex the thought, it ultimately boils down to assembly, to bare programs. Our microcontroller programs are a form of bare programs. The only difference is the height from which we stand during microcontroller programming versus microcomputer programming!
This height of understanding may confuse many, as we rarely pay attention to it. Here, I will give another example to clarify, even though it may not seem very relevant to bare programming, the concept of height in this example is quite clear.

We know that the network transmission standard consists of seven layers: application layer, presentation layer, session layer, transport layer, network layer, link layer, and physical layer. Why are there so many layers? Understanding this layered concept can be quite challenging, but once you grasp the thought behind it, it becomes much easier. This is also a philosophy that hardware engineers should learn from to make our hardware designs more standardized and forward-looking.
This seven-layer philosophy fundamentally breaks down a network transmission product, allowing different manufacturers to choose a suitable layer to develop their products. Different layers mean different foundational and developmental content. Higher-layer developers inherit the achievements of lower-layer developers, saving social resources and improving social productivity. I won’t elaborate further on this guiding thought; you can understand it yourself. What I want to say is that the object-oriented programming philosophy in microcomputers is like the thought realized at the application layer, while the object-oriented thought in bare programs is like that implemented at the link layer, where there is no software development package, only physical architecture. However, the thoughts realized at the application layer must ultimately translate to the physical architecture.
Once you understand the above example, you will certainly realize that the object-oriented thought of bare programs can indeed be realized, though it is much more difficult and requires deeper understanding. But that’s okay; this is precisely a reflection of software proficiency. If you love technology, why fear it? It’s actually not that difficult; it’s just a slight change in how we approach tasks.
Traditionally, we prefer to divide modules by function and subdivide tasks. Object-oriented thought does not do this. Instead, it first identifies objects within a task and mixes in 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 would be to format the information, send the formatted data to the monitor for display, and it seems that’s enough. Different monitors would use different sending programs or code segments, configuring different variables, combining what can be shared and separating what cannot.
However, the object-oriented thought does not work this way; it first treats the monitor as an object, which has certain functionalities and variable attributes. Different monitors use the same code identifiers in the object, such as function pointers (in C). Thus, for any different monitor, the same code is used during invocation. Some may say that the traditional approach can also achieve this; why complicate things? Actually, it’s not so. The benefits of using the correct thought have been stated many times before; if it’s still unclear, feel free to revisit.
Having discussed so much theory, let’s now talk about some specific practices. Using KeilC as the compilation environment, let’s discuss how to organize an object. First, we need to identify the object, such as the monitor, which is a typical object. Next, we analyze the basic characteristics that a living object should have, namely attributes and actions. The monitor’s attributes include: type code, brightness, contrast, and memory, while actions include: initialization, content refresh and display, power on and off, and various display effects like flickering.
This division is relatively easy to understand. Next, regarding code organization, we must pay attention to the independence and integrity of objects. First, isolate the monitor object in a separate document, placing the object-specific variables and definitions together, distinguishing 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, saving memory and avoiding confusion. For instance, if a function needs to use a variable, it should exit once the function is done, while a variable that is only used by that function but needs to retain results from each call should not be defined as a global variable. Instead, a good practice is to define it as a local variable but as static, making it transparent to other codes, preventing any accidental modifications, and ensuring good code classification for future maintenance. Using function pointers to unify the handling of different types of monitors is also an excellent approach, allowing various monitors with distinct handling methods to use a unified object, but function pointers should be used cautiously.
Alright, I’ve said enough to make my head spin. The essence of thought doesn’t need to have the same form; different people will have different understandings. I just hope to spark some inspiration for your programming careers, and I feel a sense of achievement in that.
3. Preparations
In this article, I will reference an example. Before introducing the example, we need to do some preparatory work and then gradually walk into the example. Taking the monitor control mentioned in previous posts as an example.
The monitor is an object. Regardless of how complex or simple its functions may be, it doesn’t matter to the caller needing to display information. In other words, for the entity needing to use the monitor, they only care about displaying information without worrying about the myriad differences in monitors. As long as the monitor provides a certain function, it can be called, but of course, the data transmission rules of the monitor must be adhered to, without considering the differences in transmission rules caused by different monitors. In other words, the caller will never want multiple rules to complicate their calling code.
Thus, we first need to construct a relatively independent code segment, which is the monitor object, using KeilC as the compilation environment for bare programs. As many say, KeilC is not an OOP language, so how do we do it? Precisely because we think KeilC cannot do it, I am bringing this thought forward for discussion, to make our programs more exciting and technically sophisticated.
The best way to form an independent code segment is to create a subdirectory in the main project directory, such as DISPLAY, and then create a document in the DISPLAY directory, like DISPLAY.H, and include the DISPLAY\DISPLAY.H document in an appropriate location. This way, an independent object-oriented code segment is initially formed, and when maintaining the code in the future, you will never need to consider the feelings of the caller; just maintain this document, ensuring continuous updates to the monitor.
Many might say, what’s the point of this being OOP? Don’t rush; this is just the beginning. The following will detail the specific process of organizing the code.
For a monitor, we must have display requirements before we customize it. If we cannot even articulate the usage requirements, we shouldn’t expect others to create a monitor for us. So, we first need to clarify what we want the monitor to do. Since it’s a microcontroller-controlled monitor, we cannot imagine it like a microcomputer monitor, which has a large memory that can display multiple pages and colors, transmitting data across the full screen. If we think that way, we are making a blind analogy, indicating a lack of thorough research into the issue. For a microcontroller-controlled monitor, considering it can display a single character or a single line is generally sufficient. Thus, we can define the following two object functions:
dispShowAChar(); // Display a character
dispShowALine(); // Display a line of characters
Since this is a bare system for microcontrollers, as software designers, we must clearly understand that the monitors we face often lack a CPU. Therefore, we must realize that these two functions cannot occupy the CPU for long; otherwise, our program will do nothing but display, effectively becoming a processing chip for the monitor. Thus, these two functions must exit after execution, and display cannot be interrupted. Therefore, we need a code segment that remains active in the running code without affecting other functions. Those who have written upper-level machine programs should see that this code is essentially a thread. We also use this concept in bare programming.
Our monitor object requires a continuously active thread to interpret and execute the display function for the microcontroller system. Therefore, dispShowAChar() and dispShowALine() are not suitable for directly performing display tasks; their most appropriate role is to set the display content according to specified formats. This way, when we use them, we do not need to set complex code and nested calling relationships within these two functions, as that would waste a lot of code and reduce the efficiency of the microcontroller, increasing hardware resource consumption, and in severe cases, causing stack overflow without understanding why. Let’s also name this active thread:
dispMainThread(); // Execute display function according to specified requirements
// Specified requirements include color information, flickering, scrolling, etc.
As we analyze the program further, more concepts will emerge. The concept of multithreading mentioned here will be discussed in future opportunities; multithreading in microcontrollers is a complex and tedious issue, and it’s still too early to introduce it now. I just feel that I’ve rambled on, and I will continue with the specifics in the next section.

4. Expanding Thoughts

Regarding the definition of an object’s capabilities, we can generally start with the important ones and then gradually expand, inductively summarizing other necessary capabilities into functions, thus further developing the object-oriented philosophy. In the previous text, we mentioned how the three functions came about, but we have yet to discuss the substance of these functions. This post will explore the practical planning and design of these three functions.
Once we have the functional requirements, we must implement them. In bare programming, the primary task of implementation is to 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 that content. We also need to plan how many characters or dots the display area should show. However, since we do not know how much display capacity our display device will provide at once, we can define the memory of the display area, that is, the video memory, to be larger, ensuring that any monitor meeting the design requirements can be satisfied. This practice is still quite practical in bare programming because we rarely request dynamic space. Once the program is designed, all variable positions are determined; it either works or fails to compile. Therefore, we can usually choose newer microcontrollers with richer memory resources.
However, this approach has a downside. For example, when our initial estimate is insufficient, leading to inadequate data space, we must start from scratch to modify the size of this video memory, resulting in corresponding changes to the entire display program. That’s not even the worst part; the worst is when a new monitor requires a change in data structure due to new functional demands, and we would be in a bind. The earlier work may require significant changes, even necessitating a complete rewrite and retuning, which I find extremely painful.
Therefore, we should strive to avoid such occurrences. At this point, the object-oriented philosophy becomes quite necessary. Here, we introduce a new concept: the child object of an object. What we discussed earlier was merely an abstract object, lacking any specific form, and only broadly planned what capabilities every monitor must possess. For each specific monitor, there is no concrete design yet. Here, each specific monitor is a child object of the monitor object. They vary in form but must all fulfill the prescribed functions. According to traditional OOP language theory, this creates an inheritance relationship; however, in bare program philosophy, I do not advocate introducing this concept because traditional OOP language inheritance is purely a syntactical logical relationship, with clear inheritance relationships, while this thought in bare programming lacks any syntax support, making the inheritance relationship very weak. It’s better described as classification and generalization. Regardless of the relationship, I prefer not to create a new term for such an obvious relationship, which might confuse the reader.
Since we have introduced child objects, can we see what practical significance this approach has? Perhaps experienced senior programmers can see it. When designing data for the parent object, we do not specify the specific data format and video memory size but rather leave it all to the child objects to manage. The parent object doesn’t concern itself with anything. Haha! Isn’t this approach simple? It’s not my concern, so I won’t worry. Don’t say I’m being lazy; from the perspective of the parent object, this is the wisest approach because if it can’t manage it, it shouldn’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 doesn’t manage specific tasks but does manage abstract tasks; otherwise, it wouldn’t have reason to exist. If you complain, it shows you are thinking. If you’ve thought about it, raise the questions, for they will serve as the basis for designing the parent object. Asking questions is much simpler than programming. For instance: How many columns can the monitor display in a row? Is the color multi-colored or monochrome? Does the display mode support predetermined methods (such as moving, flickering, etc.)? Is the working mode graphical or character-based? Here, I’ll add that for display modes, we’ll take character display as an example; since it’s object-oriented thought, expanding to graphical display modes should be easy.
Once the questions are raised, we can proceed 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; automatically switch to normal display if unsupported
dispSetWorkMode(); // Set the working mode; return 0 if unsupported, return 1 if supported
For the definitions of these functions, individuals can set them according to their preferences. I just temporarily created this example; it may not be the best. My goal is to emphasize the thought. I also fear making the program too large; it would be too thick to publish a book.
It seems that after adding these functions, we still haven’t seen the specific form of the display data. Together with the previous functions, there is no explicit statement. This feeling is indeed correct; we have not defined the video memory in any way, but it seems that the functionalities have been defined, and indeed they have been, and we will use them this way without fear; the program will definitely be completed.
5. Data Transmission and Program Logic Are Equally Important
Continuing from the previous discussion, we mentioned that to use dispShowAChar(), dispShowALine(), and dispMainThread(), we introduced five new functions. The primary goal of these new functions is to achieve data transmission between the caller and the callee.
In program design, data transmission holds an equally important position as program logic. The former often forms a protocol in the end, while the latter typically manifests in various algorithms.
In bare programming, our thought should primarily be expressed as a soul, rather than pursuing syntactical perfection like C++. Therefore, for parameter transmission, we should not pursue syntactical perfection but rather adopt a flexible approach. In addition to functions being able to transmit data, direct value calls are also a quick method, but calls should not be made carelessly; we should also learn from C++ syntax habits, trying not to let specialized variable names appear in program bodies unrelated to them. For example, if our design stipulates that this bare system supports a maximum of 65536 colors for the monitor, we would use a 16-bit unsigned integer to store this indicator. To simplify future explanations, we first define two data types:
typedef unsigned int UINT;
typedef unsigned char UCH;
If we transmit this data using functions, we can do so as follows:
#define Monitor01_MaxColors 0xFFFF
For the color calling function, we define it as follows:
UINT dispGetMaxColors() {
return Monitor01_MaxColors;
}
It’s evident that if another monitor is a monochrome display, the color calling function can simply be changed to:
#define Monitor02_MaxColors 0x0001
UINT dispGetMaxColors() {
return Monitor02_MaxColors;
}
Previously, someone mentioned using arrays, which can solve many problems. That’s absolutely correct! The above example overlooks one issue: using the same function name to perform many different functions without employing switch() is evidently incorrect. To truly implement the shared property MaxColors for different monitors, we must add switch() to distinguish between different monitor types. Therefore, we need to introduce a new property for the parent object to denote its child number:
UCH MonitorType = 0; // Monitor type, supporting up to 256 types of monitors
We initialize this property to 0 during initialization as the default type monitor code. The following naming convention will be established to give the code a more standardized appearance: Parent object interface functions start with lowercase disp, variables with Monitor, macro data starts with Monitor and contains at least one underscore, and macro functions are composed of uppercase letters. Without using an array, the above code will transform into the following form:
#define Monitor_000
#define Monitor_011
#define Monitor_022
UINT dispGetMaxColors() { // The following uses multiple exits, but this doesn’t disrupt anything; to save code, it can be entirely used
switch (MonitorType) {
case Monitor_01: return Monitor01_MaxColors;
case Monitor_02: return Monitor02_MaxColors;
}
return Monitor00_MaxColors; // Default returns the default monitor
}
Clearly, this form is too verbose. Although it is very structured, it might be discarded during program optimization. Therefore, the use of arrays has been mentioned. Since it’s an array, it should not belong to any specific child object but should be defined in the parent object. Even though doing so may involve adding new data that is difficult to understand when adding new monitors in the parent object, we can endure such pain to save code. If we switch to using arrays, the above code will change to:
#define Max_Monitor_Types 3
#define Monitor00_MaxColors 1
UINT MonitorMaxColorsArray[Max_Monitor_Types] = { Monitor00_MaxColors, // Default is monochrome
Monitor01_MaxColors,
Monitor02_MaxColors,
};
In this case, the line marked with *** will be the sentence that needs to be modified continuously during future expansions. Therefore, the above function will simplify to:
UINT dispGetMaxColors() {
return MonitorMaxColorsArray[MonitorType];
}
Some may even use macro functions to save runtime; all it takes is to modify the calling rules:
#define DISPGETMAXCOLORS(c) c = MonitorMaxColorsArray[MonitorType];
When we write the code like the above, every improvement brings us joy; our code has been optimized again. However, unfortunately, this thoughtless optimization will bring us trouble in the not-too-distant future. I feel my memory is poor; I might forget things from a minute ago. This kind of chaotic modification during future expansions will leave me dizzy!
Thus, in engineering work, we need to keep the parent object and child objects as isolated as possible to reduce the amount of interrelated modifications. This is also a significant meaning of the object-oriented thought. I will elaborate on this modification in the next post.
6. Separation of Parent Object Interface Functions and Child Object Functionalities
In the previous text, we discussed some design thoughts regarding dispGetMaxColors(). We have many good methods to implement it, but do we have better management methods to achieve it? That is the focus of looking at problems from a higher level and is more important. This is also a crucial turning point from traditional thinking to object-oriented thinking.
To transform this function into an object-oriented logical structure, we must first do some preparatory work.
The first is the thought of parameter transmission. We should minimize parameter transmission as a crucial measure respecting the hardware status of the C51 series 8-bit microcontroller. Remember, don’t complain about the low level and limited resources of the C51; instead, we should develop our bare program object-oriented thoughts with respect and love for the C51. This means that regardless of how rudimentary the system we face is, we have strategies to implement complex functionalities. Furthermore, from a developmental perspective, upgrading a product does not mean blindly upgrading my CPU, as that would only lower the intelligence of product designers. Therefore, I believe the essence of the C51 should be to become simpler and simpler, not more complex. Thus, I hope we can consider upgrading our thoughts as a direction for product development. When passing parameters, we should reduce pointers, floating-point types, and so on, primarily using UCH and UINT, and keep the number of parameters to a minimum, ideally no more than two. If there are too many, we should use shared buffers or global variables. It’s best to avoid passing parameters.
This function utilizes MonitorType to omit a parameter transmission.
The second is that we need to separate the interface functions of the parent object from the various possible functionalities of the specific child objects. Here we need to use function pointers. Function pointers may not be commonly used, but they are not very complex. 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:
typedef UINT (*dGMC)(void);
For the dispGetMaxColors() function in the parent object, we only need to define a function pointer and provide an entry address for calling the corresponding functionality of the child object when creating the parent object. Therefore, the entity of this function will only appear in the child object, while in the parent object, there will only be a variable definition:
dGMC dispGetMaxColors;
To assign an initial value, we can also define a null pointer as an unused flag:
#define NIL 0
When initializing dispGetMaxColors, we only need to write the following statement:
dispGetMaxColors = NIL;
Function calls are also straightforward, just like the actual function:
if (dispGetMaxColors != NIL) vMaxColors = dispGetMaxColors();
If we add a convention, we can completely omit the previous judgment statement. Since the program space of our bare program is also the running space, there is no issue of loading and unloading code from memory, so we do not need to consider program memory optimization issues. We just need to stipulate that objects must be created before use, making judgment statements meaningless. Even if we create them and no longer use them, the function body will not be released because it is fixed in the program space; you cannot move it out even if you want to.
Third, we should simplify the syntax used in the program to reduce misunderstandings that may arise from compiler differences. Some say C51 is a subset of C; I find this statement unscientific. It can only be said that both inherit the basic spirit of C but yield different results due to being targeted at different objects. Thus, I see some experts or interview questions presenting problems that leave me in awe; perhaps I could spend a lifetime writing bare programs and still not solve their problems. However, I do not feel inferior to them. They simply spent a lot of time researching their conventions on the compiler, while I do not wish to spend time studying things that may change in the future. I hope to use a simpler approach to excel in my tasks. I only care about the speed of my project, the size of the code, the efficiency of execution, and the complexity of maintenance. Therefore, I also suggest that those who communicate with me use a straightforward method to achieve our functionalities without worrying too much about whether my program can run on an 8-bit microcontroller or a 16-bit one. Our systems should undergo frequent upgrades, 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, of the three points above, only the second is the key point I wanted to convey; the other two can be tailored by individuals based on their specific circumstances to find some quick and practical methods. In fact, to replace the entity function of the parent object with a function pointer is a thought process; we haven’t used complex syntax, but rather separated the objects from a management perspective, allowing different people to handle different tasks more easily. However, we should also ensure that this intermediary step does not impact our program’s efficiency, so we can certainly seek our own methods without being overly rigid. I say this, and it may attract some criticism, but I can tell you that computer languages are an artificial science; there’s no such thing as the best—only better. We don’t need to follow others’ thoughts to develop design philosophies; instead, we should focus on using the tools created by others to create our own world! If you remain at the level of merely appreciating tools, that’s a wasted effort!
In essence, I have only discussed one transformation and provided two suggestions. These are not specific programs but rather thoughts. I want to emphasize that it’s not about format but about thought. In the next post, I will return to the organization of the object itself, discussing issues such as the hierarchy of objects, object creation, and object writing. I also hope that someone can contribute better methods to this post so that we can learn from each other and improve together.
The evolution of thoughts presented earlier has been discussed for quite some time, and the object-oriented philosophy is now ripe and ready to be expressed. Below, I will illustrate the hierarchical relationship of object-oriented thought in bare program design:
Hardcore Microcontroller Programming and Philosophy
I believe this image clearly explains how we organize our display object disp in KeilC. disp is an abstract object; it merely serves as a connection, completing the generalization of all child objects from d000 to d255 and providing a set of functional interfaces for the callers. These functional interfaces are the function pointers mentioned in the previous text. The specific functional implementations and the different display objects’ data structure requirements can be determined by the child object design engineers themselves.
Clearly, everyone involved in the specific program design process of this scheme should focus their main energy on solving their own problems, thinking more about how to execute tasks beautifully, rather than muddling through during code writing. The parent object designer must complete the overall scheme design, with more abstract thinking and less concrete workload, while the child object designer needs to do the opposite, focusing on concrete designs without worrying about how others will use their creations.
It’s evident that as the overall designer, one must carefully consider the data exchange relationships in between, as we lack an operating system. Therefore, the rules for utilizing available memory resources directly impact the success or failure of our entire system. Chaotic usage often leads to system crashes. Relatively independent code is arranged directly 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 critical to the success of our scheme. We know that KeilC divides microcontroller memory into four types: data, idata, pdata, and xdata. The size and characteristics of each type of memory determine the running effects of our code. We must consider the space needed for information along with the sufficient processing capabilities of the microcontroller to meet our visual requirements. In this example, I believe we can choose xdata for video memory. Why? Because I feel that as long as we handle it properly, our microcontroller can overcome processing speed deficiencies, thus prioritizing the need for information volume and providing ample space to implement our desired functionalities.
There are many ways to speed up, such as choosing newer microcontrollers with superior performance; traditionally, these were 12T, but now there are 6T and even 1T, which makes many instructions more efficient; appropriately increasing the crystal oscillator frequency; choosing more scientific algorithms; and so on.
At this point, we can basically construct our object. If you are interested, you can use #define to make some pseudo-code definitions, making our object look more aesthetically pleasing and closer to C++. However, I must emphasize that we do not have strict class definitions here, so the definitions of classes and objects often lack boundaries.
Hardcore Microcontroller Programming and PhilosophyHardcore Microcontroller Programming and Philosophy

Disclaimer: This article is a network reprint, and the copyright belongs to the original author. If the videos, images, or texts used in this article involve copyright issues, please leave a message at the end of the article, and we will handle it promptly! The content of this article reflects the original author’s views and does not represent this public account’s endorsement of their views or responsibility for their authenticity.

Hardcore Microcontroller Programming and PhilosophyHardcore Microcontroller Programming and Philosophy

Leave a Comment