Linux environment application programming (9): serial port

One: Overview of the serial port

         The operation of UART can be divided into the following parts: data transmission, data reception, interrupt generation, baud rate setting, Loopback mode, infrared mode and hard and soft flow control mode. In Linux, all device files are generally located under "/dev". The device names corresponding to serial port 1 and serial port 2 are "/dev/ttyS0" and "/dev/ttyS1" in turn, and USB-to-serial devices The names are usually "/dev/ttyUSB0" and "/dev/ttyUSB1". You can check the file under "/dev" to confirm. The operation method of the device under Linux is the same as the operation method of the file. The serial port can be read and written using simple read() and write() functions. The only difference is that other parameters of the serial port need to be configured separately.

Two: serial port settings

The setting of the serial port is mainly to set the value of each member of the struct termios structure:

#include<termios.h>
struct termios
{
    unsigned short c_iflag; /* 输入模式标志 */
    unsigned short c_oflag; /* 输出模式标志 */
    unsigned short c_cflag; /* 控制模式标志*/
    unsigned short c_lflag; /* 本地模式标志 */
    unsigned char c_line; /* 线路规程 */
    unsigned char c_cc[NCC]; /* 控制特性 */
    speed_t c_ispeed; /* 输入速度 */
    speed_t c_ospeed; /* 输出速度 */
};

        termios is a standard interface defined in the POSIX specification, which means terminal equipment (including virtual terminals, serial ports, etc.). The port is a terminal device, which is generally configured and controlled through a terminal programming interface. Before explaining the serial port-related programming in detail, first understand the terminal-related knowledge. The terminal has 3 working modes, namely canonical mode, non-canonical mode and raw mode.

        By setting the ICANNON flag in the c_lflag of the termios structure, you can define whether the terminal works in canonical mode (set the ICANNON flag) or non-canonical mode (clear the ICANNON flag). The default is the canonical mode. In canonical mode, all input is processed on a line basis. Before the user enters a line ending character (carriage return, EOF, etc.), the system calls the read() function and cannot read any characters entered by the user. Line ending characters (carriage return, etc.) other than EOF will be read into the buffer by the read() function just like ordinary characters. In the standard mode, line editing is possible, and at most one line of data can be read by calling the read() function. If the number of data bytes requested to be read in the read() function is less than the number of bytes that can be read in the current row, the read() function will only read the requested number of bytes, and the remaining bytes will be used next time Be read again. In the non-standard mode, all input is instantaneously effective, and the user does not need to input the line ending character separately, and line editing is not allowed. In non-standard mode, the setting of the parameters MIN (c_cc[VMIN]) and TIME (c_cc[VTIME]) determines the calling method of the read() function. The setting can have 4 different situations

 MIN = 0 and TIME = 0: The read() function returns immediately. If there is readable data, read the data and return the number of bytes read, otherwise the read fails and returns 0.
 MIN> 0 and TIME = 0: The read() function will be blocked until MIN bytes of data can be read.
 MIN = 0 and TIME> 0: As long as there is data to read or TIME one-tenth of a second has elapsed, the read() function returns immediately, and the return value is the number of bytes read. If it times out and no data is read, the read() function returns 0.
 MIN> 0 and TIME> 0: The read() function returns when there are MIN bytes to read or the time interval between two input characters exceeds TIME tenths of a second. Because the system does not start the timer until the first character is input, in this case, the read() function returns after reading at least one byte.

       Strictly speaking, the original mode is a special non-standard mode. In the original mode, all input data is processed in bytes. In this mode, the terminal cannot be echoed, and all specific terminal input/output control processing is not available. The terminal can be set to the original mode by calling the cfmakeraw() function. The specific implementation is:

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8

        The most basic settings in 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 values ​​to it, users can set the baud rate, character size, data bits, stop bits, parity bits, and hard and soft flow control. Here, the c_cflag member cannot be initialized directly, but some of the options should be used through "AND" and "OR" operations.

Constant names supported by c_cflag:

Bit mask of CBAUD baud rate
B0 0 Baud rate (DTR abandoned)
……
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 Data bit mask
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 parity bit enable PARODD
use odd parity instead of even parity
HUPCL when finally closed Hang up (abandon DTR)
CLOCAL local connection (do not change the port owner)
CRTSCTS hardware flow control

The input mode flag c_iflag is used to control character input processing at the receiving end of the port.

Constant names supported by c_iflag:

INPCK parity check enable
IGNPAR Ignore parity
errors.PARMRK parity error mask
ISTRIP Cut off the 8th bit
IXON Start output software flow control
IXOFF Start input software flow control
IXANY Input any character to restart output (default is input The output restarts only after the start character)
IGNBRK Ignore the input termination condition
BRKINT When the input termination condition is detected, send a SIGINT signal
INLCR to convert the received NL (line feed) to CR (carriage return)
IGNCR Ignore the received CR (carriage return) Fu)
and both ICRNL received CR (carriage return) to NL (new line)
the received uppercase to lowercase character mapping IUCLC
ring when the input queue is full when IMAXBEL

The output mode flag c_oflag is used to process the characters sent out by the control port.

Constant names supported by c_oflag:

OPOST enables the output processing function. If this flag is not set, other flags
will be ignored. OLCUC will convert uppercase characters in the output to lowercase characters.
ONLCR will convert the newline character ('\n') in the output into a carriage return ('\ r')
ONOCR If the current column number is 0, then no carriage return is output.
OCRNL converts the carriage return ('\r') in the output to a line feed ('\n') ONLRET does not output the carriage return
OFILL and sends padding Character to provide delay
OFDEL. If this flag is set, it means that the filling character is DEL character, otherwise it is NUL character
NLDLY Line feed delay mask
CRDLY Carriage return delay mask
TABDLY Tab delay mask
BSDLY Horizontal backspace delay mask
VTDLY Vertical backspace delay mask
FFLDY page feed delay mask                                                                                                                              

c_lflag is used to control the local data processing and working mode of the control terminal.

Constant names supported by c_lflag:

If ISIG receives a signal character (INTR, QUIT, etc.), it will generate the corresponding signal
ICANON. Enable normal mode
ECHO. Enable local echo function.
ECHOE. If ICANON is set, backspace operation is allowed.
ECHOK. If ICANON is set, the KILL character will delete the current Line
ECHONL If ICANON is set, line feed is allowed to be
echoed. ECHOCTL If ECHO is set, control characters (tab, line break, etc.) will be displayed as "^X", where the ASCII code of X is equal to the ASCII code for the corresponding control character Add 0x40. For example: the backspace character (0x08) will be displayed as "^H" (the ASCII code
of'H ' is 0x48) ECHOPRT If ICANON and IECHO are set, the deleted characters (backspace, etc.) and deleted characters will be displayed as
ECHOKE If ICANON is set, it is allowed to echo the KILL character
NOFLSH set in ECHOE and ECHOPRT. Under normal circumstances, when the INTR, QUIT and SUSP control characters are received, the input and output queues will be cleared. If this flag is set, all queues will not be emptied.
TOSTOP If a background process tries to write to its controlling terminal, the system sends a SIGTTOU signal to the process group of the background process. This signal usually terminates the execution of the process
IEXTEN enables the input processing function

c_cc defines special control characteristics.

Constant names supported by c_cc:

VINTR interrupt control character, corresponding key is CTRL+C
VQUIT exit operator, corresponding key is CRTL+Z
VERASE delete operator, corresponding key is Backspace (BS)
VKILL delete line character, corresponding key is CTRL+U
VEOF file end character, The corresponding key is CTRL+D
VEOL additional line ending character, the corresponding key is Carriage return (CR)
VEOL2 The second line ending character, the corresponding key is Line feed (LF)
VMIN specifies the minimum number of characters to
read VTIME specifies each read Timeout between characters

1. Save the 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,&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 return value is 0, if the call fails, the function return value is -1.

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

2. Activation options

CLOCAL and CREAD are used for local connection and accepting enable respectively. Therefore, the two options must be activated first by means of bit mask.

newtio.c_cflag |= CLOCAL | CREAD;

The terminal can be set to the original mode by calling the cfmakeraw() function. In the following example, the original mode is used for serial data communication.

cfmakeraw(&new_cfg);

3. Set the 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 to set the baud rate are: cfsetispeed() and cfsetospeed(). 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.

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

4. Set the character size

Unlike setting the baud rate, there is no ready-to-use function for setting the character size, and a bit mask is required. Generally, the bit mask in the data bit is removed first, and then set again as required.

new_cfg.c_cflag &= ~CSIZE; /* 用数据位掩码清空数据位设置 */
new_cfg.c_cflag |= CS8

5. Set the parity bit

Setting the parity bit requires two members in termios: c_cflag and c_iflag. First, activate the parity enable flag PARENB in ​​c_cflag and whether to perform even check, and at the same time activate the parity check enable (INPCK) for input data in c_iflag.

使能奇校验:
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;

使能偶校验:
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除偶校验标志,则配置为奇校验*/
new_cfg.c_iflag |= INPCK;

6. Set the stop bit

Setting the stop bit is realized by activating CSTOPB in c_cflag. If there is one stop bit, CSTOPB is cleared, and if there are two stop bits, CSTOPB is activated.

new_cfg.c_cflag &= ~CSTOPB; /* 将停止位设置为一个比特 */
new_cfg.c_cflag |= CSTOPB; /* 将停止位设置为两个比特 */

7. Set the minimum characters and wait for the event

If there are no special requirements for the received characters and waiting time, it can be set to 0, and the read() function returns immediately in any case.

new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;

8. Clear the serial port buffer

       After the serial port is reset, the current serial device needs to be properly processed, then the tcdrain(), tcflow(), tcflush() and other functions declared in <termios.h> can be called to process the current serial buffer Data in.

int tcdrain(int fd); /* 使程序阻塞,直到输出缓冲区的数据全部发送完毕*/
int tcflow(int fd, int action) ; /* 用于暂停或重新开始输出 */
int tcflush(int fd, int queue_selector); /* 用于清空输入/输出缓冲区*/

        The tcflush() function for the data that has not been transmitted in the buffer, or the data that has been received but has not been read, its processing method depends on the value of queue_selector, and 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: Including the first two functions, that is, clearing the unprocessed input and output data.
If the first method is used: tcflush(fd, TCIFLUSH);

9. Activate the configuration

After completing all serial port configuration, activate the configuration just now and make the configuration take effect. The function used here is tcsetattr(), and its function prototype is:

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

The possible values ​​of the optional_actions parameter are as follows:
 TCSANOW: configuration modification takes effect immediately.
 TCSADRAIN: configuration modification will take effect after all the output written to fd has been transmitted.
 TCSAFLUSH: All accepted but not read input will be discarded before the modification takes effect.
This function returns 0 if the call succeeds, and returns 1 if it fails. The code is as follows:

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

Three: configuration template

int set_com_config(int fd,int baud_rate, int data_bits, char parity, int stop_bits)
{
    struct termios new_cfg,old_cfg;
    int speed;
    /*保存并测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
    if (tcgetattr(fd, &old_cfg) != 0)
    {
        perror("tcgetattr");
        return -1;
    }
    /* 设置字符大小*/
    new_cfg = old_cfg;
    cfmakeraw(&new_cfg); /* 配置为原始模式 */
    new_cfg.c_cflag &= ~CSIZE;
    /*设置波特率*/
    switch (baud_rate)
    {
        case 2400:
        {
            speed = B2400;
        }
        break;
        case 4800:
        {
            speed = B4800;
        }
        break;
        case 9600:
        {
            speed = B9600;
        }
        break;
        case 19200:
        {
            speed = B19200;
        }
        break;
        case 38400:
        {
            speed = B38400;
        }
        break;
        default:
        case 115200:
        {
            speed = B115200;
        }
        break;
    }
    cfsetispeed(&new_cfg, speed);
    cfsetospeed(&new_cfg, speed);
    /*设置数据位*/
    switch (data_bits)
    {
        case 7:
        {
            new_cfg.c_cflag |= CS7;
        }
        break;
        default:
        case 8:
        {
            new_cfg.c_cflag |= CS8;
        }
        break;
    }
    /*设置奇偶校验位*/
    switch (parity)
    {
        default:
        case 'n':
        case 'N':
        {
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_iflag &= ~INPCK;
        }
        break;
        case 'o':
        case 'O':
        {
            new_cfg.c_cflag |= (PARODD | PARENB);
            new_cfg.c_iflag |= INPCK;
        }
        break;
        case 'e':
        case 'E':
        {
            new_cfg.c_cflag |= PARENB;
            new_cfg.c_cflag &= ~PARODD;
            new_cfg.c_iflag |= INPCK;
        }
        break;
        case 's': /*as no parity*/
        case 'S':
        {
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_cflag &= ~CSTOPB;
        }
        break;
    }
    /*设置停止位*/
    switch (stop_bits)
    {
        default:
        case 1:
        {
            new_cfg.c_cflag &= ~CSTOPB;
        }
        break;
        case 2:
        {
            new_cfg.c_cflag |= CSTOPB;
        }
    }
    /*设置等待时间和最小接收字符*/
    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[VMIN] = 1;
    /*处理未接收字符*/
    tcflush(fd, TCIFLUSH);
    /*激活新配置*/
    if ((tcsetattr(fd, TCSANOW, &new_cfg)) != 0)
    {
        perror("tcsetattr");
        return -1;
    }
    return 0;
}

Four: Serial port example

         After configuring the relevant properties of the serial port, you can open and read and write the serial port. The functions it uses are the same as the read and write functions of ordinary files, which are open(), write() and read(). The difference between them is that the serial port is a terminal device, so there will be some differences when selecting the specific parameters of the function. In addition, some additional functions will be used here to test the connection of terminal devices.

1. Open the serial port

Opening the serial port is the same as opening ordinary files, using the open() function, as shown below:

fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);

As you can see, in addition to the ordinary read and write parameters, there are two parameters O_NOCTTY and O_NDELAY.
 The O_NOCTTY flag is used to inform the Linux system that this parameter will not make the opened file the control terminal of this process. If this flag is not specified, then any input (such as the keyboard abort signal, etc.) will affect the user's process.
 The O_NDELAY flag informs the Linux system that this program does not care about the status of the DCD signal line (whether the other end of the port is activated or stopped). If the user specifies this flag, the process will stay in sleep state until the DCD signal line is activated.
Next, the state of the serial port can be restored to the blocked state, which is used to wait for the serial port data to be read in. It can be implemented with the fcntl() function, as shown below:

fcntl(fd, F_SETFL, 0);

Then you can test whether the open file descriptor is connected to a terminal device, to further confirm whether the serial port is opened correctly, the function call returns 0 if it succeeds, and -1 if it fails. As follows:

isatty(STDIN_FILENO);
/*打开串口函数*/
int open_port(int com_port)
{
    int fd;
#if (COM_TYPE == GNR_COM) /* 使用普通串口 */
    char *dev[] = {"/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2"};
#else /* 使用 USB 转串口 */
    char *dev[] = {"/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2"};
#endif
    if ((com_port < 0) || (com_port > MAX_COM_NUM))
    {
        return -1;
    }
    /* 打开串口 */
    fd = open(dev[com_port - 1], O_RDWR|O_NOCTTY|O_NDELAY);
    if (fd < 0)
    {
        perror("open serial port");
        return(-1);
    }
    /*恢复串口为阻塞状态*/
    if (fcntl(fd, F_SETFL, 0) < 0)
    {
        perror("fcntl F_SETFL\n");
    }
    /*测试是否为终端设备*/
    if (isatty(STDIN_FILENO) == 0)
    {
        perror("standard input is not a terminal device");
    }
    return fd;
}

2. Read and write serial port

Read and write serial port operations are the same as reading and writing ordinary files, just use the read() and write() functions, as shown below:

write(fd, buff, strlen(buff));
read(fd, buff, BUFFER_SIZE);

The following two examples show two procedures for serial port reading and writing, which use the open_port() and set_com_config() functions described above. The program for writing the serial port will run on the host computer, and the program for reading the serial port will run on the target board.
The procedure for writing serial port is as follows:

/* com_writer.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "uart_api.h"

int main(void)
{
    int fd;
    char buff[BUFFER_SIZE];
    if((fd = open_port(HOST_COM_PORT)) < 0) /* 打开串口 */
    {
        perror("open_port");
        return 1;
    }
    if(set_com_config(fd, 115200, 8, 'N', 1) < 0) /* 配置串口 */
    {
        perror("set_com_config");
        return 1;
    }
    do
    {
        printf("Input some words(enter 'quit' to exit):");
        memset(buff, 0, BUFFER_SIZE);
        if (fgets(buff, BUFFER_SIZE, stdin) == NULL)
        {
            perror("fgets");
            break;
        }
        write(fd, buff, strlen(buff));
    } while(strncmp(buff, "quit", 4));
    close(fd);
    return 0;
}

The procedure for reading the serial port is as follows:

/* com_reader.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "uart_api.h"

int main(void)
{
    int fd;
    char buff[BUFFER_SIZE];
    if((fd = open_port(TARGET_COM_PORT)) < 0) /* 打开串口 */
    {
        perror("open_port");
        return 1;
    }
    if(set_com_config(fd, 115200, 8, 'N', 1) < 0) /* 配置串口 */
    {
        perror("set_com_config");
        return 1;
    }
    do
    {
        memset(buff, 0, BUFFER_SIZE);
        if (read(fd, buff, BUFFER_SIZE) > 0)
        {
            printf("The received words are : %s", buff);
        }
    } while(strncmp(buff, "quit", 4));
    close(fd);
    return 0;
}

Run the program to write the serial port on the host computer, and run the program to read the serial port on the target board, the results are shown below.

/* 宿主机 ,写串口*/
$ ./com_writer
Input some words(enter 'quit' to exit):hello, Reader!
Input some words(enter 'quit' to exit):I'm Writer!
Input some words(enter 'quit' to exit):This is a serial port testing program.
Input some words(enter 'quit' to exit):quit
/* 目标板 ,读串口*/
$ ./com_reader
The received words are : hello, Reader!
The received words are : I'm Writer!
The received words are : This is a serial port testing program.
The received words are : quit

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/111287260