Embedded Linux development—UART serial communication driver hardware programming

0. Basic preparation of serial port

In embedded systems, many devices can be controlled through serial ports, such as WiFi, Bluetooth, RFID, etc., so using serial ports in embedded devices is particularly important.

First, a few concepts are briefly clarified.

RS232, RS485 and TTL refer to electrical level standards. Generally speaking, TTL uses 0V to represent low level and +5V to represent high level. RS232 uses negative logic levels, that is, +3V ~ +15V represents low level, and -3V ~ -15V represents high level. RS485 and RS232 are both communication interfaces based on serial ports, and the operations of data transmission and reception are the same. RS485 uses differential signal negative logic, + 2V ~ + 6V represents low level, and – 6V ~ -2V represents high level.

The above three protocols (RS232, RS485 and TTL) are electrical characteristics and specify the physical layer interface requirements. The construction of physical layer standards is like the construction of various highways. With highways, we can run various cars on them and formulate different driving rules for various cars. This is the communication protocol, UART (Universal Asynchronous Receiver/Transmitter, Commonly referred to as UART) is one of the communication protocols. Its full name is Universal Asynchronous Receiver-Transmitter. It standardizes the most common asynchronous transmission communication mode in serial communication. It will be explained in detail below.

The commonly mentioned serial port and COM port refer to the physical interface. They usually have two styles of 9-pin and 4-pin standards. The four pins in the 4-pin serial port standard are: VCC, TX, RX and GND, where TX and RX are the transmitter (Transmit) and the receiver (Receive) respectively. Generally speaking, when two serial ports send and receive information to each other, their two pins need to be interconnected, that is, the TX of port A is connected to the RX of port B, and conversely, the RX of port A is connected to the TX of port B.

In the D-type 9-pin interface on the left, generally speaking, the four pins actually used are VCC, TX, RX and GND.

Let’s take a closer look at what’s going on with the serial port.

A serial port usually refers to a serial interface, as opposed to a parallel interface. For example, if there is an 8-bit data sent from A to B, the difference between parallel and serial is as follows.

As you can see, serial transmission only requires one cable, while parallel transmission requires 8 wires. Their respective characteristics are as follows:

There are few serial transmission cables, so the signal attenuation is slow, the electromagnetic compatibility is good, and the transmission distance is long; there are many parallel transmission cables, so the signal attenuation is fast, the electromagnetic compatibility is poor, and the transmission distance is short.

At first glance, the serial transmission speed seems to be 1/N of the parallel transmission, but in fact due to the physical limitations of the electromagnetic interference of parallel cables, the highest frequency of parallel transmission (such as tens of MHz) is much lower than that of serial transmission. The highest frequency (up to tens of GHz), so the speed of serial transmission is no slower than parallel.

The serial transmission hardware interface is simple, takes up little space, and is low in cost; the parallel transmission hardware interface is complex, takes up a large space, and is slightly higher in cost.

Serial transmission has fewer cables, so the normal transmission of data requires various specific algorithms, and the software design is complex; in parallel transmission, each data bit is independent, and the algorithm and software design are relatively simple.

Simply put, serial transmission has more advantages than parallel transmission. In fact, as the main frequencies of various devices become higher and higher, serial communication is becoming more and more popular in the field of modern computers.

So how does this one-line serial communication transmit data? There are mainly two methods below.

1. Synchronous transmission

The core of the so-called synchronous transmission is to use a unified clock to control the sender and receiver, and then specify a synchronization start character, and at the same time specify the number of digits (usually 5-8 bits) contained in each character to be transmitted. .

Since synchronous transmission must require the clocks of both the sending and receiving parties to be consistent, or even require the use of strictly the same clock source, which is an unattainable requirement in most cases, so although synchronous transmission is faster than asynchronous transmission, asynchronous transmission is more widely used. transmission.

2. Asynchronous transmission

As the name suggests, the essence of asynchronous transmission does not require the synchronization of the clocks of the sending and receiving parties, which means that they can each have their own clocks (no synchronization is always required, but the frequency must be the same, otherwise there will be no communication at all. Like chickens and ducks, that is, the baud rate must be consistent), which greatly reduces the complexity of project implementation. Therefore, it is important to pay attention to asynchronous transmission.

Since it is asynchronous transmission, that is, the sender can send data at any time without any prior coordination with the receiver, then the receiver will naturally have a question: When will your data come? The general approach is as follows: specify an idle potential state (such as high level), then specify a start bit (such as a low-level clock interval), and then follow the normal data bits (of course the length can also be specified, such as 8-bits), then an optional parity bit (used to verify whether the bits are flipped due to the influence of the electromagnetic environment during data transmission), and then a stop bit (such as a high-level clock interval). —Timing diagram

1. Overview of Linux serial port

The configuration of serial port parameters generally includes baud rate, number of start bits, number of data bits, number of stop bits and flow control mode. Here you can configure it to baud rate 115200, start bit 1b, data bit 8b, stop bit 1b and no flow control mode.

The setting of the serial port is mainly to set the values of each member of the struct termios structure.

#include<termios.h> -----/usr/include is in the same directory as stdio.h

typedef unsigned char cc_t;
typedef unsigned int speed_t;
typedef unsigned int tcflag_t;

struct termios //Serial port structure ------
{
    tcflag_t c_iflag; /* input mode flags */ /* input mode flags */
    tcflag_t c_oflag; /* output mode flags */ /* output mode flags */
    tcflag_t c_cflag; /* control mode flags */ /* Control mode flags --stop bit, data bit, check bit, start bit*/
    tcflag_t c_lflag; /* local mode flags */ /* local mode flags */
    cc_t c_line; /* line discipline */ /* line discipline */
    cc_t c_cc[NCCS]; /* control characters */ /* Control characteristics */
    speed_t c_ispeed; /* input speed */ /* input speed */
    speed_t c_ospeed; /* output speed */ /* output speed */
    #define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
    #define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};

The most basic settings of the serial port include baud rate setting, parity bit and stop bit setting.

The most important thing in this structure is c_cflag. By assigning a value to it, the user can set the baud rate, character size, data bits, stop bits, parity bits, hard and soft flow control, etc.

Here, the c_cflag member cannot be initialized directly, but it must be used through “AND” and “OR” operations using some of its options.

c_iflag: Input mode flag, controls the terminal input mode. The specific parameters are as follows.

c_iflag parameter table

Key value description

IGNBRK Ignore BREAK key input

BRKINT If IGNBRK is set, the input of the BREAK key will be ignored. If BRKINT is set, a SIGINT interrupt will be generated.

IGNPAR ignore parity errors

PARMRK identifies parity error

INPCK allows input parity

ISTRIP removes the 8th bit of a character

INLCR converts the input NL (line feed) into CR (carriage return)

IGNCR ignores entered carriage returns

ICRNL converts the input carriage return into a line feed (if IGNCR is not set)

IUCLC Convert input uppercase characters to lowercase characters (non-POSIX)

IXON allows control of the XON/XOFF flow during input

IXANY Entering any character will restart stopped output

IXOFF allows control of the XON/XOFF flow during input

IMAXBEL starts ringing when the input queue is full. Linux uses this parameter but assumes that it is always set.

c_oflag: Output mode flag, which controls the terminal output mode. The specific parameters are as follows.

c_oflag parameter

Key value description

OPOST processed output

OLCUC Converts input lowercase characters to uppercase characters (non-POSIX)

ONLCR converts the input NL (line feed) into CR (carriage return) and NL (line feed)

OCRNL converts the input CR (carriage return) into NL (line feed)

ONOCR does not output the carriage return character in the first line

ONLRET does not output the carriage return character

OFILL sends padding characters to delay terminal output

OFDEL uses ASCII code DEL as the filling character. If this parameter is not set, the filling character will be NUL (/0’) (non-POSIX)

NLDLY Line feed output delay, which can be NL0 (no delay) or NL1 (delay 0.1s)

CRDLY carriage return delay, the value range is: CR0, CR1, CR2 and CR3

TABDLY horizontal tab output delay, the value range is: TAB0, TAB1, TAB2 and TAB3

BSDLY space output delay, can be BS0 or BS1

VTDLY vertical tab output delay, can be VT0 or VT1

FFDLY page change delay, can be FF0 or FF1

c_cflag: Control mode flag, specifying terminal hardware control information. The specific parameters are as follows.

c_oflag parameter

Key value description

CBAUD baud rate (4 + 1 bit) (non-POSIX)

CBAUDEX additional baud rate (1 bit) (non-POSIX)

CSIZE character length, the value range is CS5, CS6, CS7 or CS8

CSTOPB sets two stop bits

CREAD using receiver

PARENB uses parity checking

PARODD uses parity checking for input and even parity for output

HUPCL hangs when shutting down device

CLOCAL ignore modem line status

CRTSCTS uses RTS/CTS flow control

c_lflag: local mode flag, controls the terminal editing function, the specific parameters are as follows.

c_lflag parameter

Key value description

ISIG When INTR, QUIT, SUSP or DSUSP is input, the corresponding signal is generated

ICANON uses the standard input mode ~ICANON inputs directly without pressing Enter

XCASE When ICANON and XCASE are set at the same time, the terminal uses only uppercase letters. If only XCASE is set, input characters will be converted to lowercase characters unless the characters use escape characters (non-POSIX, and Linux does not support this parameter)

ECHO displays input characters

ECHOE If ICANON is set at the same time, ERASE will delete the entered characters and WERASE will delete the entered words

ECHOK If ICANON is also set, KILL will delete the current line

ECHONL If ICANON is set at the same time, line breaks will still be displayed even if ECHO is not set.

ECHOPRT If ECHO and ICANON are set at the same time, printed characters will be removed (non-POSIX)

TOSTOP sends the SIGTTOU signal to the background output

tcgetattr()

1. Prototype

int tcgetattr(int fd, struct termois *termios_p);

2. Function

Get the initial value of the terminal medium (fd) and assign its value to temios_p; the function can be called from the background process; however, the terminal attributes may be changed by subsequent foreground processes.

tcsetattr()

1. Prototype

int tcsetattr(int fd, int actions, const struct termios *termios_p);

2. Function

To set terminal-related parameters (unless low-level support is required but cannot be met), use the termios structure referenced by termios_p.

optional_actions (the second parameter of the tcsetattr function) specifies when the change will take effect:

TCSANOW: Change happens immediately

TCSADRAIN: Changes take effect after all output written to fd has been transmitted. This function should be used when modifying parameters that affect the output. (Change the value when the current output is completed)

TCSAFLUSH: Changes take effect after all output written to the object referenced by fd has been transmitted, and all input accepted but not read in is discarded before the change occurs (same as TCSADRAIN, but all current values are discarded).

The most basic settings of the serial port include baud rate setting, parity bit and stop bit setting. The most important thing in this structure is c_cflag. By assigning a value to it, the user can set the baud rate, character size, data bits, stop bits, parity bits, hard and soft flow control, etc. Here, c_cflag cannot be directly assigned Member initialization, and to use some of the options through “AND” and “OR” operations.

CBAUD

Bit mask for baud rate

B0

0 baud rate (discard DTR)

B1800

1800 baud rate

B2400

2400 baud rate

B4800

4800 baud rate

B9600

9600 baud rate

B19200

19200 baud rate

B38400

38400 baud rate

B57600

57600 baud rate

B115200

115200 baud rate

EXTA

external clock rate

EXTB

external clock rate

CSIZE

bit mask of data bits

CS5

5 data bits

CS6

6 data bits

CS7

7 data bits

CS8

8 data bits

CSTOPB

2 stop bits (if not set, 1 stop bit)

CREAD

Receive enable

PARENB PARODD

Parity bit enable uses odd parity instead of even parity

HUPCL

Hang on last close (abandon DTR)

CLOCAL

Local connection (does not change port owner)

CRTSCTS

Hardware flow control

2. Serial port configuration process

1. Save original serial port configuration

First of all, for the sake of safety and the convenience of debugging the program in the future, you can save the original serial port configuration first. Here you can use the function tcgetattr(fd, & amp;old_cfg). This function gets the configuration parameters of the terminal pointed to by fd and saves them in the termios structure variable old_cfg. This function can also test whether the configuration is correct, whether the serial port is available, etc. If the call is successful, the function returns a value of 0. If the call fails, the function returns a value of -1. Its usage is as follows:

if(tcgetattr(fd, & amp;old_cfg) != 0)
{
    perror("tcgetattr");
    return -1;
}

2. Activation options

CLOCAL and CREAD are used for local connection and acceptance enablement respectively. Therefore, these two options must first be activated through bit masks.

 newtio.c_cflag |= CLOCAL | CREAD;

Calling the cfmakeraw() function can set the terminal to the original mode. In the following examples, the original mode is used for serial port data communication.

cfmakeraw( & amp;new_cfg);

3. Set baud rate

There is a special function for setting the baud rate, and the user cannot directly operate it through the bit mask. The main functions for setting the baud rate are: cfsetispeed() and cfsetospeed(). The use of these two functions is very simple, as follows:

cfsetispeed( & amp;new_cfg, B115200);
cfsetospeed( & amp;new_cfg, B115200);

Generally, users need to set the input and output baud rates of the terminal to be the same. These functions return 0 on success and -1 on failure.

4. Set character size

Unlike setting the baud rate, there is no ready-made function for setting the character size, and a bit mask is required. Generally, first remove the bit mask in the data bits, and then set it again as required. As follows:

new_cfg.c_cflag & amp;= ~CSIZE; /* Clear data bit settings with data bit mask */
new_cfg.c_cflag |= CS8;

5. Set parity bit

Setting the parity bit requires the use of two members in termios: c_cflag and c_iflag. First, activate the parity bit enable flag PARENB in c_cflag and whether to perform even parity. At the same time, activate the parity check enable (INPCK) for input data in c_iflag. There is no check digit, the code is as follows:

new_cfg.c_cflag & amp;= ~PARENB;

If odd parity is enabled, the code is as follows:

new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;

When even parity is enabled, the code is as follows:

new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag & amp;= ~PARODD; /* Clear the even parity flag and configure odd parity */
new_cfg.c_iflag |= INPCK;

6. Set stop bit

Setting the stop bit is accomplished by activating CSTOPB in c_cflag. If there is one stop bit, CSTOPB is cleared; if there are two stop bits, CSTOPB is activated. The following are the codes when the stop bit is one and two bits respectively:

new_cfg.c_cflag & amp;= ~CSTOPB; /* Set the stop bit to one bit */
new_cfg.c_cflag |= CSTOPB; /* Set the stop bit to two bits */

7. Set minimum characters and wait time

Under normal circumstances, you can set it to block and wait until the data is read before returning.

termios_new.c_cc[VTIME] = 0;
termios_new.c_cc[VMIN] = 4;

8. Clear serial port buffer

Since the serial port needs to be properly processed after the serial port is reset, the current serial port device can be adjusted at this time.

Use tcdrain(), tcflow(), tcflush() and other functions declared in to process the current serial port buffer

data, their format is shown below.

int tcdrain(int fd); /* Block the program until all data in the output buffer is sent*/

int tcflow(int fd, int action); /* Used to pause or restart output */int tcflush(int fd, int queue_selector); /* Used to clear the input/output buffer*/

In this example, the tcflush() function is used for data that has not yet been transmitted in the buffer, or received

However, the processing method of data that has not yet been read depends on the value of queue_selector. Its possible values are as follows.

TCIFLUSH: Clear data received but not read.

TCOFLUSH: Clear the output data that has not been successfully transmitted.

TCIOFLUSH: Includes the first two functions, which are to clear unprocessed input and output data.

As in this example, the first method is used:

tcflush(fd, TCIFLUSH);

9. Activate configuration

After completing all serial port configurations, activate the previous configuration and make the configuration take effect. The function used here is tcsetattr(),

Its function prototype is:

tcsetattr(int fd, int optional_actions, const struct termios*termios_p);

The parameter termios_p is a new configuration variable of type termios.

The possible values of the parameter optional_actions are as follows:

TCSANOW: Configuration modifications take effect immediately.

TCSADRAIN: Configuration modifications take effect after all output written to fd has been transmitted.

TCSAFLUSH: All accepted but unread input will be discarded before the modification takes effect.

This function returns 0 if the call is successful and -1 if it fails. The code is as follows:

if ((tcsetattr(fd, TCSANOW, & amp;new_cfg)) != 0)
{
    perror("tcsetattr");
    return -1;
}

10. Add\
to the data sent by the serial port to end

Serial port programming process:

  1. Open the serial port—- open
  2. Initialize the serial port or configure the serial port — Custom function
  3. Access serial port—- read/write
  4. Close the serial port—- close

3. Example code

#include <stdio.h>
#include<termios.h> //Introduce the serial port file under Linux
#include <string.h>

/*

    The data format of one frame of the UART interface is 1 start bit, 8 data bits, no parity bit, 1 stop bit, baud rate: 9600
*/
int init_linux_uart(int fd)
{
//1. Define the serial port structure
        struct termios old_cfg,new_cfg;
//2. Get the property configuration of the current serial port
//extern int tcgetattr (int __fd, struct termios *__termios_p) __THROW;
        if(tcgetattr(fd, & amp;old_cfg) != 0)
        {
             perror("tcgetattr");
             return -1;
        }
//3.Set the communication mode of the serial port
//extern void cfmakeraw (struct termios *__termios_p)
//Clear the new configuration properties to zero
        bzero( & amp;new_cfg, sizeof(new_cfg));
        new_cfg = old_cfg;
        cfmakeraw(&new_cfg);
    
 //4. Configure serial port baud rate, data bits, stop bits, parity bits
        cfsetispeed(& amp;new_cfg, B9600);
        cfsetospeed(&new_cfg, B9600);
 //Activation option: CLOCAL CREAD
    new_cfg.c_cflag |= CLOCAL | CREAD;
 //Set data bits: 8 bits, need to use mask setting
    new_cfg.c_cflag & amp;= ~CSIZE; /* Clear data bit settings with data bit mask */
    new_cfg.c_cflag |= CS8;
 //Set no verification
    new_cfg.c_cflag & amp;= ~PARENB;
    //Set stop bit
    new_cfg.c_cflag & amp;= ~CSTOPB;
    
    //Clear the buffer
    new_cfg.c_cc[VTIME] = 0;
        new_cfg.c_cc[VMIN] = 4;
        tcflush(fd,TCIFLUSH); // Clear fd
    //5 Set the configured serial port to the system
        if ((tcsetattr(fd, TCSANOW, & amp;new_cfg)) != 0)
        {
            perror("tcsetattr");
            return -1;
        }
        return 0;
}


int main(void)
{
    
    
        //1.Open the serial port --- open
        //2. Configure the serial port --- configure according to the actual application of the serial port --- customize a function to implement
        //3. Read or write serial port --- read write
        //4. Close the serial port --- close
}

4. Module application instructions

After the above-mentioned serial port initialization configuration of the Linux development board is completed, as long as it is a module device that uses the serial port for communication, no matter what the module is, you only need to perform the corresponding data sending and receiving processing according to the device’s command list to complete the preliminary configuration of the module device. drive.

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. CS entry skill treeLinux introductionFirst introduction to Linux 38113 people are learning the system