[This article has been published in "MCU and Embedded System Application" magazine, please do not reprint]
The serial port communication module is a single-chip system or a common components in the current embedded system, which are widely used for system debugging and communication with the outside world. The general MCU has built-in hardware control modules, and users only need to write relatively simple control programs to use serial communication. Nevertheless, in practice, considering efficiency and convenience of use, it is necessary to carefully design serial communication software, so that other software modules communicating through serial port can be relatively simple and convenient, and try not to communicate with serial ports. Affect the performance of the entire system. Below we use the method used by common serial port software, and proposes the implementation method of serial port software modules based on serial buffer management policies called double buffer queues, and they should pay.
1 Common serial port processing method
The basement software of the serial port can be considered a serial driver. For the upper layer software, it should provide a relatively natural and simple usage. Taking the serial port as an example, the user can directly call a function to output a string or just like using the Printf function in the standard C on the general platform.
The on-the-top interface has been determined, and we discuss the problem of implementation. The implementation of serial driver usually has two:
Inquiry-based approach. Continuous detection of serial port hardware during transmission is empty, if yes, send one byte. If there is no data, it continues the above process.
The following is a Samsung's S3C44B0X MCU as an example, and a query code transmitted based on the query method is given.
Void uart_sendstr (char * pt)
{
Char * p;
P = Pt;
While (* p! = '/ 0')
{
While (! (RUTRSTAT & 0X2)); // Waiting until the serial buffer is empty
WRUTXH0 (* p ); // Send a byte
}
}
Interrupted method. In the above-based query-based approach, there is a significant disadvantage, that is, in the process of sending a string, the CPU can't do other things, and must wait for all characters to return. Take the above MCU as an example, its maximum frequency is 66MHz, and since the ARMV4 architecture is used, it can reach 0.9 instructions per cycle, and its serial port has a maximum baud rate of 115200 bps, which has a large number of instruction cycles were wasted, and It will seriously affect the real-time performance of the system when sending a long string. Therefore, in the actual system, it is generally more interrupted methods. After sending one byte, then do other processing, automatically enter the transmission interrupt after sending, and then sends the next byte. This approach increases the utilization of the CPU than the query method, and the CPU does not have to intervene when the serial port portion is transmitted and shifted, but the software portion of the serial port is also complicated, and the corresponding interrupt service program is needed. ISR) and the management of related software buffers. Since the interrupt is triggered by hardware, in order to find the data to be sent after the interrupt can be found, the most direct way is to set a global array and a pointer to the sending data, so that each interrupt will send a pointer to Bytes until it is finished.
2 Method based on double buffer queue
After the above interrupt mode, the entire processing flow is further considered, and the buffer is carefully designed for the interrupt service program and the upper program interaction.
Since the data transmitted and received by the serial port is relatively independent, it separates it separately, sets two buffers, one is the transmit buffer TXBUF, one is the receiving buffer RxBuf, and set two pointers respectively, one, one, respectively, one Record the interrupt service program to process the byte, another record that records the upper layer using the serial port service will process the byte. Take the serial port as an example, the two pointers are intXBUF, Outtxbuf. Outtxbuf points to the data that will be transmitted, and INTXBUF points to the upper program to place the data into the starting position of the buffer. This way we call it a method of using a double buffer queue. This method guarantees the order of data. When the buffer is large enough, the upper program can place all the data to be sent in a time in the send buffer, not one byte, and if multiple upper programs call the sending function will not cause confusion, because each All the data to be sent is placed at times. The principle is similar to the printer's management of the print task queue, and multiple users share a printer and issue their print tasks, but there will be no outputs of different tasks. The serial port is transmitted and received, which is a common background task. As long as the data to be transmitted in the buffer is transmitted, the interrupt intermittent transmission is transmitted, and the reception is also received, and the upper program is notified.
The following starts to analyze the specific implementation. The following is a statement about the buffer and related pointers:
#define TXBUFLEN 1000
#define rxbufflen 200
Char TXBUF [TXBUFLEN], RXBUF [RXBUFLEN];
CHAR * INTXBUF, * OUTTXBUF, * INRXBUF, * OUTRXBUF;
Int uarttxcount, uartrxcount;
After the serial port is initialized, the initialization of the buffer is performed, and the buffer after initialization and its pointer are shown in FIG.
Figure 1 Schematic diagram of the buffer and the corresponding pointer after initialization
The following is a related sample code sent by the serial port:
// The ISR transmitted by the interrupt uses a compilation implementation, and then calls UARTTX after performing field protection for subsequent processing.
// UART returns to restore the site.
Void UartTX (Void)
{
IF (outtxbuf == intXbuf) // txbuf
Return;
WruartBuf (* Outtxbuf); // Write the register to be sent to the serial port
Outtxbuf ; / / point to the data to be sent
IF (outtxbuf == (txbuf txbufflen) // buffer round
Outtxbuf = txbuf;
UARTTXCOUNT ; // Send count
}
/ / The upper program calls the interface of the serial port to send data, and send a string ending at the end of '/ 0'.
Void uart_printstr (char * pt)
{
CHAR * T, * P;
T = intXbuf;
P = Pt;
While (* p! = '/ 0') // Put the buffer one by one
{
T ;
IF (t == (TXBUF TXBUFLEN) // round
T = TXBUF;
IF (t == outtxbuf) // TXBUF full
Return;
* INTXBUF = * p ; // put into one data
INTXBUF = T; // intXBUF back shift}
UART_TXSTART (); // Start sending, followed by discussion
}
In order to facilitate the understanding of the buffer management, the TXBUF is given to the data and the full-time schematic. The plaid with shadows indicates that valid data.
Figure 2 is a schematic diagram of TXBUF
Figure 3 Schematic diagram of TXBUF
3 Method for sending an interrupt in the serial port first
The above method is specifically analyzed, but since the serial transmission interrupt is triggered when the serial port is triggered, then there is a problem, how to generate the first transmission interrupt? To solve this problem, there are two ways to combine specific CPUs.
For CPUs that can generate hardware interruptions, such as Intel 196 series, can be manually triggered by directly writing the INT PENDING register, and the effect is manually triggered before sending the first byte, the resulting effect and the real hardware interrupt. Take 196kc as an example, the specific method is to set a software FLAG, the initial value is 1, indicating that it is necessary to trigger. First put the data into the send buffer, before sending the first byte, set the FLAG to 0, indicating that it is no longer need to be manually triggered, and then triggered the transmission interrupt by the following code.
__INT_PEND1 | = 0x01;
In the process of interrupt delivery, if TxBuf is discovered (see the schematic code UARTTX above), the FLAG is reset to 1 before returnium, indicating that the transmission is completed, and the next transmission is sent to manually trigger. By this method of hardware characteristics and software control, data transmission can be achieved well.
For CPUs that cannot be software analog hardware interrupts, such as S3C44B0X, etc., can be implemented by first guiding the first byte transmission, and can also be transmitted throughout the transmission buffer. For one byte sent first, you need to carefully consider, first, the sent must be a valid byte, otherwise it will cause confusion of the recipient. In addition, if you write directly to the hardware buffer of the serial port, you may cause the transmitted data cross because other send tasks may be in progress. There are two ways to solve this situation:
First, in the UART_PRINTSTR, if it is not empty, if it is not empty, it indicates that the transmission has been started. In the transmission, it is not necessary to manually trigger the interrupt, you can directly put all the data to be sent into the buffer, wait for interrupt to send can. If so, the data outside the first byte can be placed in the buffer, and then the first byte is directly written to the hardware buffer, which in turn leads to an interrupt.
The second is to put the data to be sent into the buffer, then manually send the byte pointing by OuttxBuf, if the buffer of the serial port is not empty, indicating that the data is sent, and it will be given. The sample code is as follows:
Void UART_TXSTART (VOID)
{
IF (outtxbuf == intXbuf) // txbuf
Return;
If (rutrstat0 & 0x2) // If the hardware buffer is empty, send one byte
WRUTXH0 (* Outtxbuf);
Else
Retrun;
Outtxbuf ;
IF (Outtxbuf == (TXBUF TXBUFLEN))
Outtxbuf = txbuf;
Uarttxcount ;
}
Both methods are feasible. Since the second method does not need to determine if the buffer is empty, it is relatively simple, so the second method is used in the specific code. 4 conclusion
Regarding the size of the buffer, the above is a reference value, which can be considered in conjunction with the specific hardware, such as the size of the RAM, and the size of the data that needs to be transmitted. Simple estimation can also be performed before the setting, with the baud rate of 115200, and send up to 14400 bytes per second. It is then estimated that the maximum output amount of output data that may be generated across the system will be exceeded by the ability to exceed the serial port, and then the buffer size setting can be performed according to this value. The above method gains a good effect in an embedded OS-based system, performing serial output in each task, and has no data misalignment and buffer overflows, and gives other modules and Commissioning has brought great convenience. The method based on the dual buffer queue discussed herein is actually suitable for other peripherals other than the serial port, because it is more concerned about the interrupt buffer management policy, not the specific hardware operation. In addition, the code based on Intel 80196kc and Samsung S3C44B0X based on the above method is included. I hope that the methods and related code given here can provide some reference or revelation to other developers.
references
1. Wang Jian. Principle and Application Technology of MCS-96 Series. Wuhan: Huazhong University of Science and Technology Press, 2000.
2. S3C44B0X Datasheet
3. Yan Weimin, Wu Weimin. Data Structure (C Language Edition). Beijing: Tsinghua University Press, 2002.
4. Du Chunlei. ARM architecture and programming. Beijing: Tsinghua University Press, 2003.