After contacting a variety of MCUs, contacting complex design requirements, running through the operating system, etc., when we return to the bare-metal development of the microcontroller, we will unwittingly consider the architecture of the entire program design; a good The program architecture is a watershed for experienced engineers and a beginner.
The following is my summary of the microcontroller program framework and some common parts of development: any time-critical requirements are our enemy, we only need to increase the hardware cost to eliminate it when necessary; for example, you need 8 digital tubes To show that we must use the MCU to work well with dynamic scanning without relevant hardware support; dynamic scanning will more or less prevent the MCU from handling other things.
In the case where the MCU is heavily burdened, I will choose to use a similar max8279 peripheral ic to solve this problem;
Fortunately, there are many things that are not time-critical: for example, keyboard scanning, the rate at which people tap the keyboard is limited, we don't need to scan the keyboard in real time, or even scan every few tens of ms; This tens of ms interval, our MCU can also do a lot of things; although the single-chip computer is running bare metal, but often the actual needs determine the attitude we must run out of the operating system - multitasking program;
For example, a common situation has 4 tasks:
1 keyboard scanning; 2 led digital tube display; 3 serial port data needs to be accepted and processed; 4 serial port needs to send data;
How to construct this MCU program will be our focus; in the era of reading, I will put the keyboard scanning query in the main loop, while the serial port receives the data interrupt, and the corresponding frame format is formed in the interrupt service function. The corresponding flag bit is used to process the data in the loop of the main function, and the serial port sending data and the display of the led are also placed in the main loop; thus the whole program uses the communication mode of the flag variable, and cooperates with each other in the main loop and the background. Executed during the interruption;
However, it is necessary to point out that it is not appropriate: the time slice of each task may be too long, which will result in poor real-time performance of the program. If you add more than one task in this way, making a loop too long, it is possible that the keyboard scan will be very insensitive. So to build a good general programming model, we must find a way to eliminate the time-consuming part of each task and decompose each task again; let's talk about the specific measures for each task:
1 keyboard scan
Keyboard scanning is a common function of single-chip microcomputers. The following points indicate the common obstacles in the keyboard scanning program that seriously hinder the real-time performance of the system. It is well known that the waveform after a key is pressed is like this (assuming low effective): after a key is pressed, The signal on the data line appears to be jittered for a period of time, then low, and then when the button is released, the signal jitters for a period of time and then goes high. Of course, in the process of the data line being low or high, some narrow interference signals may appear.
Unsigned char kbscan(void){unsigned char sccode,recode;P2=0xf8; if ((P2&0xf8)!=0xf8) { delay(100); //delay 20ms to debounce ---------this is too time consuming , very bad if((P2&0xf8)!=0xf8) { sccode=0xfe; while((sccode&0x08)!=0) { P2=sccode; if ((P2&0xf8)!=0xf8) break; sccode=(sccode<<1 )|0x01; } recode=(P2&0xf8)|0x0f; return(sccode&recode); } } return (KEY_NONE);}
Keyboard scanning requires software debounce, which is not controversial. However, the function uses software delay to debounce (ms level delay), which is a big taboo to maintain the real-time performance of the system;
There is also a code to determine the release of the button:
While( kbscan() != KEY_NONE); //Infinite loop waiting
This is very bad. If you press and hold the keyboard, this will cause other tasks in the whole system to be executed. This will be a serious bug.
Someone will handle this like this:
While(kbsan() != KEY_NONE ){ Delay(10); If(Num++ > 10) Break;}
That is, if the keyboard is pressed all the time, it will be treated as a valid key. Although this does not cause other tasks of the whole system to be inoperable, it also greatly reduces the real-time performance of the system because he uses the delay function; we use two effective methods to solve this problem: 1 In simple cases, we still use the above kbscan () function to scan, just delay the software used to debounce, debounce and judge the release of the button with a function to deal with it, it does not require software delay, Instead, use the timer's timing (usually with normal timing); the code is as follows
Void ClearKeyFlag(void){ KeyDebounceFlg = 0; KeyReleaseFlg = 0;}void ScanKey(void){
++KeyDebounceCnt;//Debounce timing (this timing can also be handled in the background timer timing function)
KeyCode = kbscan(); if (KeyCode != KEY_NONE) { if (KeyDebounceFlg)// Enter the debounce state flag { if (KeyDebounceCnt > DEBOUNCE_TIME) / / is greater than the debounce specified time { if (KeyCode == KeyOldCode ) / / button still exists, return key value
{ KeyDebounceFlg = 0; KeyReleaseFlg = 1; // release flag return; //Here exit with keycode } ClearKeyFlag(); //KeyCode != KeyOldCode, just dithering} }else{ if (KeyReleaseFlg == 0) { KeyOldCode = KeyCode; KeyDebounceClg = 1; KeyDebounceCnt = 0; }else{ if (KeyCode != KeyOldCode) ClearKeyFlag(); } } }else{ ClearKeyFlag();//Clear the flag without a button} KeyCode = KEY_NONE;
In the case of complicated button conditions, such as long buttons, key combinations, and keys, and other complex functions, we tend to use the state machine to achieve keyboard scanning;
//avr MCU 4*3 scan state machine implements char read_keyboard_FUN2() { static char key_state = 0, key_value, key_line, key_time; char key_return = No_key,i; switch (key_state) { case 0: // initial state, Perform a 3*4 keyboard scan key_line = 0b00001000; for (i=1; i<=4; i++) // scan the keyboard { PORTD = ~key_line; // output line level PORTD = ~key_line; // must send 2 times! ! ! (Note 1) key_value = Key_mask & PIND; // Read column level if (key_value == Key_mask) key_line <<= 1; // No button, continue to scan else { key_state++; // There is a button to stop scanning break; / Turn the debounce confirmation state} } break; case 1: // This state to determine if the button is caused by jitter if (key_value == (Key_mask & PIND)) // Read the column level again, { key_state++; // turn Enter wait button release state key_time=0; } else key_state--; // Two column levels return to state 0, (debounce processing) break; case 2: // Wait for button release state PORTD = 0b00000111; // Line all output low PORTD = 0b00000111; // Repeat once if ( (Key_mask & PIND) == Key_mask) { key_state=0; // Column lines are all high return status 0 key_return= (key_line | key_value) ;//Get the key value} else if(++key_time>=100)//If not released for a long time { key_time = 0; key_state = 3; / / enter the key state key_return = (key_line | key_value); } break; case 3:// for the key, every 50ms get a key value, windows xp system is to do this PORTD = 0b00000111; // Line line all output low PORTD = 0b00000111; // Repeat send if ( (Key_mask & PIND) == Key_mask) key_state=0; // Column lines are all high return status 0 Else if(++key_time>=5) //The key for each combo every 50MS { key_time=0; key_return= (key_line | key_value); } break; } return key_return;
The above four states are used. The general keyboard scan only uses the first three states, and the latter state is designed to increase the "connect key" function. Connect key—that is, if you press a key, it responds quickly to the key value multiple times until it is released. The keyboard scan function can be executed once every 10ms in the main loop; we set the time limit to 10ms, of course, the requirements are not strict.
2. Digital tube display
In general, the eight-in-one digital tube we use uses dynamic scanning to complete the display; it is very gratifying that the human eye can't find it when it flashes above 50hz. Therefore, we have ample time to dynamically scan the digital tube. Here we set the time limit to 4ms (250HZ), use the timer to be 2ms, scan the display in the timer interrupt program, only one of them is displayed at a time; of course, the time limit can also be lengthened, the more recommended method is Put the display function into the main loop, and set the corresponding flag bit in the timer interrupt;
// Timer 0 compare match interrupt service, 4ms timing
Interrupt [TIM0_COMP] void timer0_comp_isr(void)
{
Display(); // Call LED scan display
........................
}
Void display(void) // 8-bit LED digital tube dynamic scanning function
{
PORTC = 0xff; // It is necessary to turn off the segment selection here, otherwise the digital tube will produce a smear.
PORTA = led_7[dis_buff[posit]];
PORTC = position[posit];
If (++posit >=8 )
Positive = 0;
}
3 serial port receiving data frame
When the serial port is received with interrupt mode, this is understandable. But if you try to complete the reception of one frame of data in the interrupt service routine, it will be a big problem. Always remember that the shorter the interrupt service function, the better, otherwise it will affect the real-time performance of this program. A data frame usually consists of several bytes. We need to judge whether a frame is completed and whether the verification is correct. In this process, we can't use software delay, and we can't wait in an infinite loop; so we only put the data in a buffer queue in the serial port receive interrupt function.
As for the composition of the frame, and the work of checking the frame, we solve it in the main loop, and we only process one data in each loop. The processing interval of each byte of data is more flexible because we have already cached it in the queue.
/*=========================================================================================== Time event description: put every 10ms in the big loop output: none input: none================================= ==========*/void UARTimeEvent(void){ if (TxTimer != 0)//The time to wait for the transmission to decrement--TxTimer; if (++RxTimer > RX_FRAME_RESET) // RxCnt = 0 // If the timeout is accepted (ie, an incomplete frame or a frame is received), the received incomplete frame is overwritten}/*====================== ===================== Function: Serial Receive Interrupt Description: Receive a data, store it in cache output: none input: none========= ===================================================================================================================================== Status = UCSRA; data = UDR; if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0){ RxBuf[RxBufWrIdx] = data; if (++RxBufWrIdx == RX_BUFFER_SIZE) //Receive data in buffer RxBufWrIdx = 0; if (++RxBufCnt == RX_BUFFER_SIZE){ RxBufCnt = 0; //RxBuff erOvf=1; } }}/*================================================== = Function: Serial port receiving data frame Description: When non-zero output, receive one frame of data in large loop to execute output: ==0: no data frame!=0: data frame command word input: none==== ================================================================================= INT8U ChkRxFrame(void){ INT8U dat; INT8U Cnt; INT8U sum; INT8U ret; ret = RX_NULL; if (RxBufCnt != 0){ RxTimer = 0; // Clear the count time, UARTimeEvent() does abandon the entire frame data for the receive timeout //Display( Cnt = RxCnt; dat = RxBuf[RxBufRdIdx]; // Get Char if (++RxBufRdIdx == RX_BUFFER_SIZE) RxBufRdIdx = 0; Cli(); --RxBufCnt; Sei(); FrameBuf[cnt++] = dat; if (cnt >= FRAME_LEN) // Make up a frame { sum = 0; for (cnt = 0;cnt < (FRAME_LEN - 1);cnt++) sum+= FrameBuf[cnt]; if (sum == dat) ret = FrameBuf[ 0]; cnt = 0; } RxCnt = cnt; } return ret;}
The above code ChkRxFrame() can be placed in the serial port receiving data processing function RxProcess() and then executed in the main loop. The above uses a timing variable RxTimer, which is a subtle solution to the abandon frame processing of the receive frame timeout. It does not use any wait, and the main loop only receives one byte of data at a time, which is very short. We started to frame the entire system: we chose a system that is not commonly used by TIMER to generate the system benchmark beats required by the system. Here we choose 4ms; in meg8 we have the following code:
// Timer 0 overflow interrupt service routineinterrupt [TIM0_OVF] void timer0_ovf_isr(void){ // Reinitialize Timer 0 value TCNT0=0x83; // Place your code here if ((++Time1ms & 0x03) == 0) TimeIntFlg = 1; }
Then we design a TimeEvent() function to call some functions that need to be called cyclically at the specified frequency. For example, we will feed the dog and the digital tube dynamic scan display every 4ms. Every 1s, we will call the LED flashing program. We perform a keyboard scanning program every 20ms;
Void TimeEvent (void){ if (TimeIntFlg){ TimeIntFlg = 0; ClearWatchDog(); display(); // In the 4ms event, call the LED scan display, and feed the dog if (++Time4ms > 5){ Time4ms = 0 TimeEvent20ms();//In the 20ms event, we process the keyboard scan read_keyboard_FUN2() if (++Time100ms > 10){ Time100ms = 0; TimeEvent1Hz();// In the 1s event, we make the work indicator flash} } UARTimeEvent();//Serial data reception event, processed in 4ms event}}
Obviously the whole idea has been very clear, and the loop events that the CPU needs to handle can be easily added to the function according to its requirements for time. But we have requirements for this event:
The execution speed is fast, short, and there is no long delay waiting. The execution time of all events must be less than 4ms of the system's reference time slice (the system reference beat can be increased as needed). So our keyboard scanner, digital tube display program, serial port receiving program are as I showed earlier. If you have to use a long delay (such as the delay used in the analog IIc timing)
We have designed such a delay function:
Void RunTime250Hz (INT8U delay)//The unit of this delay function is 4ms (system reference beat) { while (delay){ if (TimeIntFlg){ --delay; TimeEvent(); } TxProcess(); RxProcess(); }
We need to delay the time = delay * system remembers the beat 4ms, this function ensures that while the delay, our other events (keyboard scan, led display, etc.) have not been delayed; well, our main function Main() will be very short: V
Oid main (voie){Init_all();while (1) { TimeEvent(); //Processing for loop events RxProcess(); //Serial processing of received data TxProcess();// Serial port send data processing}}
Overall, our system has become a nearly omnipotent template. According to the cpu of your choice, you can select a timer and add your own event function. It is very flexible and convenient, and the general MCU can be competent. The template can be fixed.
The whole system takes the global mark as the main line, and the system is not scattered. The system is relatively small, but it sacrifices a Timer. It is very suitable in the single-chip microcomputer with lack of resources. I once saw a template of a user's "micro-computer utility system". Taking 51 as an example, the overall idea is similar to this one, but he writes more compact and very appreciative; but personally feel that the code overhead is larger, and the same is used. However, because the system uses the global flag as the driving event, the comparison feels messy. The global is best to make comments, but it should pay attention to some invisible function recursion. Don't recurs too deeply. (Some microcontrollers do not support it.) ).
Mazzucchelli Glasses,Eye Frame Glasses,Black Sunglasses,Vintage Sunglasses
Danyang Hengshi Optical Glasses Co., Ltd. , https://www.hengshi-optical.com