Design considerations for single-chip microcomputer key scanning program
[Copy link]
New key scanning program
However, after browsing the Internet for a long time and reading a lot of source code, I did not find any trace of this key processing method, so I shared it with my colleagues. I firmly believe that this key processing method is convenient and efficient, and can be ported to any embedded processor because of the powerful portability of C language.
At the same time, some layered ideas are used here, which are also very useful in microcontrollers and are another focus of this article.
For experienced people, I suggest you look at the two expressions directly, and then think about them yourself and you will understand. You don’t need to listen to my self-praise later. I don’t mean to show off my skills in front of experts, hoho~~ But for novices, I suggest you read the whole article. Because this is the experience summarized from the actual project, which cannot be learned in school.
The following assumes that you know C language. Since it is purely described in C language, it has nothing to do with the processor platform. You can test the performance of this program on MCS-51, AVR, PIC, and even ARM platforms. Of course, I have used it in many projects myself and the effect is very good.
Well, as an engineer, we should stop talking nonsense and start. I will use AVR's MEGA8 as the platform for the following explanation. There is no other reason, because I only have AVR boards and no 51. You can also use 51, but the chip initialization part is different, and the register names are different.
Design of single chip computer key scanning program
Core algorithm:
unsigned char Trg;
unsigned char Cont;
void KeyRead( void )
{
unsigned char ReadData = PINB^0xff; // 1
Trg = ReadData & (ReadData ^ Cont); // 2
Cont = ReadData; // 3
}
Finished. Do you have an incredible feeling? Of course, it will be like that before you understand it. After you understand it, you will be amazed at the exquisiteness of this algorithm! !
Here is the program explanation:
Trg (triger) stands for trigger, and Cont (continue) stands for continuous pressing.
1: Read the port data of PORTB, invert it, and then save it in the ReadData temporary variable.
2: Algorithm 1 is used to calculate the trigger variable. A bitwise AND operation and an XOR operation. I think anyone who has learned C language should understand it. Trg is a global variable that can be directly referenced by other programs.
3: Algorithm 2, used to calculate continuous variables.
Seeing this, you may feel like "knowing the result but not the reason", right? The code is very simple, but how does it achieve our goal? Well, let's bypass the clouds and fog and see the blue sky.
The most commonly used button connection method is as follows: AVR has an internal pull-up function, but for the sake of illustration, I deliberately use an external pull-up resistor. Then, when the button is not pressed, the port data is 1, and if the button is pressed, the port data is 0. Let's take a look at how this algorithm works under several specific situations.
(1) When no button is pressed
The port is 0xff, ReadData reads the port and inverts it, which is obviously 0x00.
Trg = ReadData & (ReadData ^ Cont); (Initially, Cont is also 0) This is a very simple mathematical calculation. Since ReadData is 0, when it is "ANDed" with any number, the result is also 0.
Cont = ReadData; Save Cont is actually equal to ReadData, which is 0;
The result is:
ReadData = 0;
Trg = 0;
Cont = 0;
(2) The first time PB0 is pressed
The port data is 0xfe. ReadData reads the port and inverts it. Obviously, it is 0x01.
Trg = ReadData & (ReadData ^ Cont); Because this is the first press, Cont is the last value, which should be 0. Then the value of this formula is not difficult to calculate, that is, Trg = 0x01 & (0x01^0x00) = 0x01
Cont = ReadData = 0x01;
The result is:
ReadData = 0x01;
Trg = 0x01; Trg will only be 1 at this time, and 0 at other times
Cont = 0x01;
(3) PB0 is pressed and held (long press)
The port data is 0xfe, ReadData reads the port and inverts it to 0x01.
Trg = ReadData & (ReadData ^ Cont); Because this is a continuous press, Cont is the last value, which should be 0x01. Then the formula becomes Trg = 0x01 & (0x01^0x01) = 0x00
Cont = ReadData = 0x01;
The result is:
ReadData = 0x01;
Trg = 0x00;
Cont = 0x01;
Because the button is now long pressed, the MCU will continuously execute this function every certain period of time (about 20ms), so what will happen the next time it is executed?
ReadData = 0x01; this will not change because the button is not released
Trg = ReadData & (ReadData ^ Cont) = 0x01 & (0x01 ^ 0x01) = 0. As long as the key is not released, the Trg value will always be 0!!!
Cont = 0x01; As long as the key is not released, this value will always be 0x01!!
(4) When the button is released
The port data is 0xff, ReadData reads the port and inverts it to 0x00.
Trg = ReadData & (ReadData ^ Cont) = 0x00 & (0x00^0x01) = 0x00
Cont = ReadData = 0x00;
The result is:
ReadData = 0x00;
Trg = 0x00;
Cont = 0x00;
Obviously, this returns to the initial state, that is, the state where no button is pressed.
To sum up, do you understand? It is actually very simple. The answer is as follows:
Trg means trigger, that is, jump. As long as a key is pressed (the level jumps from 1 to 0), Trg will be set to 1 on the corresponding key position. If we use PB0, the value of Trg is 0x01. Similarly, if we press PB7, the value of Trg should be 0x80. This is easy to understand. In addition, the most critical point is that the value of Trg will only appear once each time it is pressed, and then it will be cleared immediately, without any manual intervention. Therefore, the key function handler will not be executed repeatedly, saving a lot of conditional judgments. This is the essence! ! Cont represents a long key. If PB0 is pressed and not released, the value of Cont is 0x01. Correspondingly, if PB7 is pressed and not released, the value of Cont should be 0x80, which is also easy to understand.
If you still don't understand, you can calculate the two expressions yourself, it shouldn't be difficult to understand.
Because of this support, key processing becomes very cool, let's look at the application:
Application 1: One-trigger key processing
Assume that PB0 is a buzzer button. Press it once and the buzzer will beep. This is very simple, but how did you do it before? Let's compare and see which one is more convenient?
#define KEY_BEEP 0x01
void KeyProc(void)
{
if (Trg & KEY_BEEP) // If KEY_BEEP is pressed
{
Beep(); //Execute buzzer processing function
}
}
How is it? Is it harmonious enough? Remember what I explained earlier about the essence of Trg? The essence is that it will only appear once. So when you press a key, the situation where Trg & KEY_BEEP is "true" will only appear once, so it is very convenient to handle, and the buzzer will not beep for no reason, hoho~~~
Or you may think this is easy to handle, no problem, let's continue.
Application 2: Long key press processing
There are often some requirements in the project, for example: if a button is pressed briefly, it will execute function A, if it is pressed for 2 seconds, it will execute function B, or if it is required to hold it for 3 seconds, count and add something, which is very practical. I don't know how you did it before? I admit that I was very depressed before.
But look at how we handle it here, maybe you will be surprised that the procedure can be so simple
Here is a simple example to illustrate the principle. PB0 is the mode button. Short press switches the mode. PB1 is add. Long press adds continuously (have you ever played with a digital watch? Yes, that's it!)
#define KEY_MODE 0x01 // Mode key
#define KEY_PLUS 0x02 // add
void KeyProc(void)
{
if (Trg & KEY_MODE) // If KEY_MODE is pressed, and it is useless to press this key frequently,
{//It will not execute a second time, you must release it first and then press it
Mode++; // Mode register plus 1, of course, this is just a demonstration, you can execute whatever you want
// Any code executed
}
if (Cont & KEY_PLUS) // If the "Plus" key is pressed and held
{
cnt_plus++; // Timing
if (cnt_plus > 100) // 20ms*100 = 2S if time is up
{
Func(); // The program you need to execute
}
}
}
I don't know how you feel about it. I think it's quite easy to complete the task. Of course, it's just a demonstration code.
Application 3: Mixed use of touch buttons and switch buttons
The touch-type buttons are probably the most commonly used, especially in single-chip microcomputers. The switch type is also very common, such as the lights at home, which will not be released once pressed unless they are turned off. There is nothing special about the processing principles of these two types of buttons, but have you ever thought about how these two types of buttons are processed in the same system? I remembered my previous processing, which separated two very similar processing procedures. Now it seems really stupid, but there is no other way, the structure determines the program. But now it is good, using the method introduced above, it can be easily done.
How does it work? You may also think that for a touch switch, we can handle single press and long press according to the above method. For a switch type, we only need to handle Cont. Why? It's very simple. Just treat it as a long key press. In this way, we find the common point and shield all the details. I won't give the program. It is completely the content of Application 2. I mention it here to illustrate the principle.
Well, this is the end of the useful key processing. Some friends may ask, why don't you talk about the delay debounce problem? Haha, you've seen through it. I really can't be lazy. Let's talk about this problem, and by the way, I will also talk about my own method of using the time slice wheel and how to debounce.
The method of delaying and eliminating jitter is very traditional, that is, the first time it is determined that a key is pressed, a certain delay (usually 20ms) is made before reading the port. If the data read twice is the same, it means that it is a real key press, not jitter, and the key processing program is entered.
Of course, don't tell me that you are going to an infinite loop like delay(20). If that's the case, I sincerely suggest that you put down everything you have and learn the time-sharing working principle of the operating system. Just know the idea roughly, and don't need to read the principle in detail, otherwise you will never escape the circle of "rookie". Of course, I am also a rookie. What I mean is that the real entry into microcontrollers starts with learning how to handle multitasking, which is also the biggest difference between school programs and company programs. Of course, this article is not specifically about this, so I won't embarrass myself.
My main program structure is as follows:
volaTIle unsigned char Intrcnt;
void InterruptHandle() // Interrupt service routine
{
Intrcnt++; // 1ms interrupt once, variable
}
void main(void)
{
SysInit();
while(1)//Execute a large loop every 20ms
{
KeyRead(); // Scan each subroutine
KeyProc();
Func1();
Funt2();
while(1)
{
if (Intrcnt>20) // Wait until 20ms is up
{
Intrcnt = "0";
break; // Return to the main loop
}
}
}
}
It seems that we have gone off topic, so let's get back to the question we just asked, which is how to debounce the key. We put the key reading program in the main loop, that is, we will execute the KeyRead() function every 20ms to get the new Trg and Cont values. OK, here is my debounce part: It's very simple
The basic structure is as above, which I like and have been using. Of course, to cooperate with this, each subroutine must not be executed for a long time, and it must not be in an infinite loop. Generally, the finite state machine method is used to achieve this. Please refer to other materials for details.
After understanding the basic principles, you can slowly think about how to use it. I think it is not difficult for smart engineers. For example, there are some processing,
How to determine whether a key has been released? It's very simple. If both Trg and Cont are 0, it has definitely been released.
|