Mastering Two Key Microcontroller Programming Concepts

Layered Thinking
The idea of layering is not something mysterious; in fact, many engineers working on projects are already using it themselves. I have seen many posts that do not mention this concept, yet layered structure is quite useful and once understood, it gives a sense of enlightenment. If I don’t understand how to drive an LCD, that’s easy; I can look at the datasheet, reference others’ programs, and quickly get it done. However, if I do not understand the principles of program design, it will lead to a lot of confusion during the project development process.
I have referred to various embedded books on the market, including MCS-51, AVR, ARM, etc., but I have not found any that introduce design principles. Even if there are, they are rare. Writing programs is not difficult, but writing them well and quickly requires some experience accumulation. Structured and modular program design is the basic requirement.
But how do we apply this abstract concept in engineering practice?It requires going through trials in the project process, summarizing some things, and abstracting them into theories, which is greatly beneficial for the accumulation of experience and the dissemination of technology.So I will humbly share some of my summaries.
Based on my personal experience, there are two design concepts that are very important.
One is the “time slice round-robin design concept,” which is very useful in solving multi-tasking problems in practice and can usually be used to judge whether a person is a microcontroller learner or an engineer. This must be mastered.(It will be introduced below). The second is the “layered shielding design concept,” which is the layered thinking.Below, I will use the keyboard scanning program as an introduction to what I want to discuss today.
Problem Statement
Microcontroller learning boards are generally designed simply; for example, the entire 4*4 keyboard matrix is assigned to port P1, with 8 control lines, just right. This makes the program easy to write. You just need to do something simple: KEY_DAT = P1;
The data from the port is read in.
Indeed, reality is not as good as it seems. In practical project applications, the pin multiplexing of microcontrollers is quite severe, which is very different from those so-called microcontroller learning boards.Another reason is that the general design process is “software cooperating with hardware.” Simply put, it means first determining the hardware schematic and wiring, and only then developing the software, because modifying hardware is relatively troublesome, while modifying software is easier. This is the traditional Chinese philosophy of yin and yang balance. Hardware design and software design are inherently at odds; one cannot have both. Making hardware design easier may cause significant trouble for software writing.
Conversely, if software design is made easier, hardware design will also be quite troublesome. If both hardware and software designs are made easy, there can only be two possibilities: either the design solution is very simple, or the designer has reached a very high level. We will not consider so many situations; we will look at the problem purely from the perspective of common practical applications.For the convenience of wiring, hardware often assigns IO ports to different ports. For example, as mentioned above, the 4*4 keyboard may have its 8 lines assigned to P0, P1, P2, and P3. In this case, the scanning keyboard program for the development board can go to hell. How do we scan the keys? I remember when I first learned, I had to write three very similar programs to scan each key one by one…Some may be unwilling to accept this, saying, “The things I spent a long time learning and were working well, how can you say they are useless?” Although it sounds a bit cruel, I still want to say, “Brother, accept reality; reality is harsh…”However, what sets humans apart from lower animals is our ability to create. When faced with difficulties, we think of ways to solve them, and thus we begin to contemplate…Finally, we introduce the concept of “mapping” we learned in middle school mathematics to solve the problem. The basic idea is to map keys from different ports to the same port.This way, the key scanning program is divided into three layers:
1) The lowest layer is the hardware layer, which completes port scanning, 20ms delay for debouncing, and maps port data to a KEY_DAT register, which serves as an interface to the upper driver layer.2) The middle layer is the driver layer, which only operates on the value of the KEY_DAT register. To put it simply, no matter how the hardware is wired at the bottom layer, in the driver layer, we do not need to care about that; we only need to care about the value of the KEY_DAT register. The indirect effect of this is that it “shields the differences of the underlying hardware,” so programs written for the driver layer can be used universally.The other function of the driver layer is to provide message interfaces for the upper layer. We use a message concept similar to Windows programs. Here, we can provide some key messages, such as: key press message, key release message, long press message, step message during long press, etc.3) The application layer. Here, we write key function programs according to different project requirements, which belong to the top layer of the program. It uses the message interface provided by the driver layer. The idea of writing programs at the application layer is that I do not care how the lower layers work; I only care about key messages. When key messages come, I execute functions; when there are no messages, I do nothing.Below is a simple common example to illustrate how we use this design concept.When adjusting the time on a stopwatch, it is required to hold down a certain key to continuously increase the time. This is very practical and widely used in home appliances.Before looking at the following content, everyone can think about whether this is difficult. I believe everyone will answer loudly, “Not difficult!!” However, I will ask again: “Is this troublesome?” I believe many will definitely say, “Very troublesome!!” This reminds me of the chaotic structure of the programs I wrote when I first learned microcontrollers. If you don’t believe it, you can try writing one with the 51; that way, you can better appreciate the superiority of the layered structure discussed in this article.
Project Requirements:
Two keys, assigned to P10 and P20, are the “increase” and “decrease” keys, requiring that when the key is pressed, it continuously increases or decreases the value.
Practice:
Assume the keys are pulled high; without pressing, it is high, and when pressed, it is low. Additionally, to highlight the problem, I have not included the debounce program here; it should be added in actual projects. The passing of parameters in C language functions is diverse; here, as an example, I used the simplest global variable to pass parameters. Of course, you can also use unsigned char ReadPort(void) to return a read key result, or even void ReadPort(unsigned char* pt) to use a pointer variable to pass the address and directly modify the variable. There are many methods; this depends on each person’s programming style.
1) Start writing the hardware layer program to complete the mapping
#define KEY_MIN  0X01
#define KEY_PLUS  0X01
unsigned char KeyDat;
void ReadPort(void) {
    if (P1 & KEY_PLUS == 0) {
        KeyDat |= 0x01;
    }
    if (P2 & KEY_MIN == 0) {
        KeyDat |= 0x02;
    }
}
C language should be easy to understand, right? If KEY_PLUS is pressed, P10 reads low, so P1 & KEY_PLUS results in 0 (xxxx xxx0 & 0000 0001), meeting the if condition, entering KeyDat |= 0x01 sets bit0 of KeyDat to one, meaning that KEY_PLUS is mapped to bit0 of KeyDat.KEY_MIN is similarly mapped to bit1 of KeyDat; if bit0 of KeyDat is 1, it indicates KEY_PLUS is pressed, and vice versa.There is no need to think of it as something mysterious; mapping is just that. If there are other keys, use the same method to map them all to KeyDat.2) Writing the driver layer program
If we think of KeyDat as port P1, then this is exactly the same as the standard scanning program on the learning board, right? Yes, this is the purpose of the underlying mapping.3) Writing the application layer program
According to messages, the hardware layer must be separated, but the requirements for the driver layer and application layer are not so strict. In fact, for some simple projects, there is no need to separate these two layers; we can flexibly respond according to practical applications.
In fact, writing programs this way is very convenient for porting; by appropriately modifying the ReadPort function of the hardware layer according to the board, the driver layer and application layer can often be used directly without modification, greatly improving development efficiency. Of course, this key program will have certain issues, especially when encountering mixed-use of normally closed keys and momentary keys. This is left for everyone to think about; after all, problems can always be solved, though some methods are better than others.
Time Slice Round-Robin Design Concept
Let me start with a small example to introduce today’s topic. Imagine a basic household appliance control board, which will certainly contain, to some extent: LED or seven-segment display, keys, and relay or thyristor outputs in these three parts. The seven-segment display requires dynamic scanning of 10ms to 20ms, and keys also need about 20ms of delay for debouncing. Have you realized that these times are actually running simultaneously?
Recall how our textbooks taught us to debounce keys? That’s right, a dead loop, absolutely a standstill dead loop, using instructions to time. This naturally raises a question: if the microcontroller is in a standstill dead loop, then what about other tasks? What about the dynamic scanning of the seven-segment display?
We can only wait until the key is scanned before proceeding, resulting in flickering of the seven-segment display due to the long scanning time. Shortening the debounce time for keys is not a solution. Imagine if we had many other tasks to perform simultaneously? One solution is the theme of today: the idea of time-slice scanning. Of course, this is not the only solution, but I have always used it and found it to be an excellent concept that can solve many practical problems. Boldly stating, the time-slice scanning concept is also one of the core ideas of microcontroller programming; whether you believe it is up to you to judge.
Implementation of Core Ideas
Actually, there are several stepsFirst, use RTC interrupts to time; I prefer a short RTC interrupt time of about 125us, which is needed to decode infrared remote control codes. RTC timing is quite accurate, so we should make the most of it.Second, place 3 (number can vary) timers in the RTC interrupt service routine (essentially just counters); my habit is to set them to 2ms, 5ms, and 500ms as benchmark times for the entire system to call. Therefore, they must be accurate; adjusting with an oscilloscope is sufficient, and it’s not difficult.Third, in the main program loop, include a dedicated subroutine for processing time. (Note: microcontrollers do not stop; they are constantly running loops, which is somewhat different from what we learned in school. I was asked about this in an interview…). All time processing is done in this time processing subroutine, which is very convenient. A microcontroller system needs to handle at least 10-20 different times and requires 10-20 timers, many of which need to work asynchronously. If each one is handled separately, it can be quite troublesome.Fourth, “programs run while waiting, not standing still to wait”—this sounds a bit abstract. An engineer I discussed this with at my company mentioned this issue, and I think it is also a relatively important concept in a time-slice system, so I called it that. Below, I will let the program speak; comments should be as detailed as possible, so you can understand the code directly from the comments.
(1) First, the interrupt service routine part:Interrupt every 125us;——————-Generate several benchmark times—————————
Mastering Two Key Microcontroller Programming Concepts
(1) The ref_2ms register continuously decrements by 1; it decrements 16 times per interrupt, so the elapsed time is 125us × 16 = 2ms, which is what we call a timer/counter. This way, we can use a system RTC interrupt to achieve many required timing intervals.(2) Set the 2ms timing end flag, which is used by the time processing program. This is a framework for a timer; the 5ms timing works exactly the same way.This program also uses a block framework, which is quite convenient, but is unrelated to today’s topic; I will write about it later when I feel like it. The above program is the timer in the interrupt service routine, with timings of 2ms, 5ms, and 500ms. The overflow is recorded by the flag_time flag, and the program can read this flag to know if the timing has been reached.
(2) Now let’s look at the unified time service subroutine
Mastering Two Key Microcontroller Programming Concepts
The above example uses the 20ms debounce timer for keys; once understood, you can see that we can completely mimic that timer and place many timers below, counting simultaneously. Each timer counts independently, and the first one to finish simply shuts itself down and sets the corresponding flag for other programs to call, without affecting the other timers! This way, we can place many timers here. Generally speaking, having ten to twenty is not an issue and can fully meet the timing needs of a microcontroller system.The structure of a single timer is quite simple: first, check whether the timing flag is allowed to enter the timing; then, a dedicated register increments or decrements by 1. After adding or subtracting the corresponding value, the timer shuts down and sets the corresponding flags needed.That’s about it; we can generate the timing we need. Isn’t this very convenient? Let’s look at what the microcontroller is doing during this time. Only when the interrupt timing reaches 5ms or 500ms does the overflow flag become effective, allowing entry into the above timing program; otherwise, it is doing other tasks. Moreover, when entering the above timer, it is clear that it is not stuck in a dead loop; it merely adds or subtracts a register and exits, which takes an extremely short time—about 5us to 20us—thus having no impact on the execution of the main program.
(3) Now let’s see how to call it specifically
We previously discussed the debounce timing processing issue for keys, and now we will use the method introduced above to see how to solve the problem. Key handling is also an important foundational knowledge, but it is not within the scope of this discussion, so we will only discuss how to solve the timing issue; we can continue discussing key issues next time, hoho~~~
Mastering Two Key Microcontroller Programming Concepts
It goes something like this: Determine when there is a key; if not, exit; if there is, start the debounce timing. On the second entry, directly control the judgment of whether the timing is sufficient by the flag bit.Again, we are waiting here, but this is the final point mentioned: we are running while waiting, not standing still to wait. Compared to a dead loop timer, during the time before the 20ms timing is reached, what is the microcontroller doing? In a dead loop, it is definitely waiting in place, doing nothing. But looking at the above program, it merely checks whether the timing is sufficient; the actual timing is done in the unified time subroutine. If the time is not yet reached, it exits and continues running other programs, until the timing arrives, the microcontroller determines that flag_delay and key_flow meet the conditions and begins the key processing program. During this period, the microcontroller is doing other things, and it only checks once in the main loop, so it has ample time to run other programs without wasting time on debouncing.
(4) Let’s take a look at my main program loop
Mastering Two Key Microcontroller Programming Concepts
This is the loop body I use; all functions are made into subroutine forms, making it convenient to attach as needed. This way, in a total loop body, the microcontroller continuously executes this loop. If the entire program adopts the time-slice scanning concept mentioned above, the time to loop back is quite short. Isn’t it somewhat similar to a computer’s approach? No matter how fast a computer is, it does not handle multiple tasks simultaneously; it processes one at a time and loops back very quickly, giving us the impression that it is processing multiple programs simultaneously. I think what I ultimately want to express is just this.
In my view, with this conceptual support, programming for microcontrollers becomes relatively easy; the remaining task is to focus on using programs to realize our ideas. Of course, this is just one feasible method; it does not mean this is the only way. If anyone has good ideas, please share them! Writing programs is an art; it is easy to write, but writing well and elegantly is quite challenging.
﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌

Disclaimer:This article is reprinted from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact us in a timely manner. Thank you!

Collection of Past Excellent Articles

Dry Goods | Gesture Recognition with Dual Cameras, Perfectly Restoring Human Motion Gestures.Click Here>>
After this article, don’t ask me how to make a smart car anymore.Click Here>>
Collective Intelligence, Multiple Robots Collaboratively Transporting!Click Here>>

Finally

If you find this article good,sharing it is also our motivation to continue updating.
5T Resource Giveaway!Including but not limited to: C/C++, Linux, Python, Java, PHP, Artificial Intelligence, PCB, FPGA, DSP, LabVIEW, Microcontrollers, etc.!
Reply “More Resources” in the public account to get it for free, looking forward to your attention~

Mastering Two Key Microcontroller Programming ConceptsLong press the QR code in the picture to follow

Leave a Comment