目录
概述
1 Linux环境下UART设备
2 轮询方式操作UART功能实现
2.1 打开串口函数:usr_serial_open
2.2 关闭串口函数: usr_serial_close
2.3 发送数据函数: usr_serial_sendbytes
2.4 接收数据函数: thread_uart_readbytes
3 完整代码
3.1 usr_serial.c 文件内容
3.1 usr_serial.h 文件内容
4 编写应用代码
4.1 使用接口
4.2 编写应用程序Makefile
5 测试使用信号量实现串口数据的发送和接收功能
源代码下载地址: Linux环境下使用线程方式操作UART读写功能资源-CSDN文库
概述
本文介绍Linux环境下使用线程方式操作UART的方法,实现了串口打开,关闭,发送数据,接收数据功能,还编写测试代码,验证该功能。
1 Linux环境下UART设备
在linux环境下,UART作为一个终端设备存在,可使用命令, 系统会罗列出该目录下所有的device,其中以tty开头的设备为终端设备。串口也是这些设备之一。
ls /dev/ -l
执行该命令后,可以看见许多以tty开头的设备:

user根据板卡的信息,找到对应的端口,然后才能使用这些串口,笔者使用是基于iMX6ull芯片的板卡,板卡上COM1被用于调试终端,COM3可作为用户终端。

2 轮询方式操作UART功能实现
2.1 打开串口函数:usr_serial_open
函数参数
| 参数 | 描述 | 
|---|---|
| port | 终端设备: /dev/tty0 | 
| baudrate | 波特率: 1200/2400/4800 ... /115200 | 
| databit | 数据bit位: /5/6/7/8 | 
| stopbit | 停止位:"1" / "1.5" / "2" | 
| parity | 奇偶位使能: 'N' / 'E' / 'O' | 
函数实现方法:
代码 43行: 打开端口
代码 49行: 保存termios数据结构中,旧的参数
代码 51行:设置当前用户参数

源代码:
int usr_serial_open( char *port, unsigned int baudrate, 
                     unsigned int databit, const char *stopbit, char parity)
{
    int err;
    
    fd = open (port, O_RDWR | O_NOCTTY | O_NDELAY);
    if (-1 == fd) {
        fprintf(stderr, "cannot open port %s\n", port);
        return (-1);
    }
    
    tcgetattr (fd, &termios_old);       /* save the form termios value */
      
    err = set_portattr (baudrate, databit, stopbit, parity);
    if ( err ) {
        fprintf ( stderr, "\nport %s cannot set baudrate at %d\n",
                  port, baudrate);
    }
    usr_baudrate = baudrate;
    
    return fd;
} 
2.2 关闭串口函数: usr_serial_close
函数实现方法:
代码 64行: 恢复termios default参数
代码 65行:关闭fd端口

void usr_serial_close( void )
{
    /* flush output data before close and restore old attribute */
    tcsetattr(fd, TCSADRAIN, &termios_old);
    close(fd);
} 
 
2.3 发送数据函数: usr_serial_sendbytes
函数参数
| 参数 | 描述 | 
|---|---|
| *data | 存贮数据的数组 | 
| datalength | 发送的数据长度 | 
函数实现方法:
代码 72行: 使用write函数发送数据

源代码:
unsigned int usr_serial_sendbytes (void * data, unsigned int datalength)
{
    unsigned int total_len = 0;
    
    total_len = write(fd, data, datalength);
    return (total_len);
} 
 
2.4 接收数据函数: thread_uart_readbytes
函数参数
| 参数 | 描述 | 
| *arg | 线程函数传入的参数 | 
函数实现方法:
代码 39行: 使用read函数写数据

源代码
void *thread_uart_readbytes(void *arg) 
{
    int fd = *(int *)arg;
    char buf[128];
    int n;
 
    while (1) {
        // 读取串口数据
        n = read(fd, buf, sizeof(buf));
        if (n > 0) {
            printf("Received data: %.*s\n", n, buf);
        }
    }
}
 
3 完整代码
代码文件命名为usr_serial, 包含两个文件
usr_serial.c usr_serial.h
3.1 usr_serial.c 文件内容
/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     : 01_usr_serial.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : linux 串口应用程序接口
其他       : 无
日志       : 初版V1.0 2024/03/01
***************************************************************/
#include "usr_serial.h"
/* Private define ------------------------------------------------------------*/
#define TIMEOUT_SEC(buflen,baud)    (buflen*20/baud+2)
#define TIMEOUT_USEC                 0
#define CH_TO_WAIT 5
#define CH_BITS 11
/* Private variables ---------------------------------------------------------*/
static unsigned int fd;  
static struct timeval tv_timeout;
static struct termios termios_old;
static struct termios termios_new;
static fd_set fs_read;
static unsigned int usr_baudrate;
/* Private function prototypes -----------------------------------------------*/
static speed_t baudrate_to_Bxx (unsigned int baudrate);
static void set_data_bit (unsigned int databit);
static unsigned int set_portattr ( unsigned int baudrate,unsigned int databit, const char *stopbit,char parity);
int usr_serial_open( char *port, unsigned int baudrate, 
                     unsigned int databit, const char *stopbit, char parity)
{
    int err;
    
    fd = open (port, O_RDWR | O_NOCTTY | O_NDELAY);
    if (-1 == fd) {
        fprintf(stderr, "cannot open port %s\n", port);
        return (-1);
    }
    
    tcgetattr (fd, &termios_old);       /* save the form termios value */
      
    err = set_portattr (baudrate, databit, stopbit, parity);
    if ( err ) {
        fprintf ( stderr, "\nport %s cannot set baudrate at %d\n",
                  port, baudrate);
    }
    usr_baudrate = baudrate;
    
    return fd;
}
void usr_serial_close( void )
{
    /* flush output data before close and restore old attribute */
    tcsetattr(fd, TCSADRAIN, &termios_old);
    close(fd);
}
unsigned int usr_serial_sendbytes (void * data, unsigned int datalength)
{
    unsigned int total_len = 0;
    
    total_len = write(fd, data, datalength);
    return (total_len);
}
int usr_serial_readbytes (void *data, unsigned int datalength)
{
    unsigned int total_len = 0;
    total_len = read(fd, data, datalength);
    if (total_len > 0) {
        printf("Receive %d bytes: %.*s\n", total_len, (char*)data);
    }
    return (total_len);
}
unsigned int usr_serial_readinterrupt (void *data, unsigned int datalength)
{
    int total_len = 0;
    /**
     * caculate the time of 5 characters and get the maxim
     * with 3ms and 5 ch's time
    */
    tv_timeout.tv_sec = 0;
    tv_timeout.tv_usec = ( (CH_TO_WAIT * CH_BITS) * (1000000/usr_baudrate));
    while(1){
        FD_ZERO (&fs_read);
        FD_SET (fd, &fs_read);
        select (fd + 1, &fs_read, NULL, NULL, &tv_timeout);
        
        total_len = read(fd, data, datalength);
        if (total_len > 0) {
            printf("Receive %d bytes: %.*s\n", total_len, (char*)data);
            return total_len;
        }
    }
    return total_len;
}
static void set_data_bit (unsigned int databit)
{
    termios_new.c_cflag &= ~CSIZE;
    switch (databit) {
    default:
    case 8:
        termios_new.c_cflag |= CS8;
        break;
    case 7:
        termios_new.c_cflag |= CS7;
        break;
    case 6:
        termios_new.c_cflag |= CS6;
        break;
    case 5:
        termios_new.c_cflag |= CS5;
        break;
    }
}
static void set_stopbit (const char *stopbit)
{
    if (0 == strcmp (stopbit, "1")) {
        termios_new.c_cflag &= ~CSTOPB;    /* 1 stop bit */
    }
    else if (0 == strcmp (stopbit, "1.5")) {
        termios_new.c_cflag &= ~CSTOPB;     /* 1.5 stop bits */
    }
    else if (0 == strcmp (stopbit, "2")) {
        termios_new.c_cflag |= CSTOPB;       /* 2 stop bits */
    }
    else {
        termios_new.c_cflag &= ~CSTOPB;     /* 1 stop bit */
    }
}
static void set_parity (char parity)
{
    switch (parity) {
    case 'N':                  /* no parity check */
        termios_new.c_cflag &= ~PARENB;
        break;
    case 'E':                  /* even */
        termios_new.c_cflag |= PARENB;
        termios_new.c_cflag &= ~PARODD;
        break;
    case 'O':                  /* odd */
        termios_new.c_cflag |= PARENB;
        termios_new.c_cflag |= ~PARODD;
        break;
    default:                   /* no parity check */
        termios_new.c_cflag &= ~PARENB;
        break;
    }
}
static speed_t baudrate_to_Bxx (unsigned int baudrate)
{
    switch (baudrate) {
    case 0:
        return (B0);
    case 50:
        return (B50);
    case 75:
        return (B75);
    case 110:
        return (B110);
    case 134:
        return (B134);
    case 150:
        return (B150);
    case 200:
        return (B200);
    case 300:
        return (B300);
    case 600:
        return (B600);
    case 1200:
        return (B1200);
    case 2400:
        return (B2400);
    case 9600:
        return (B9600);
    case 19200:
        return (B19200);
    case 38400:
        return (B38400);
    case 57600:
        return (B57600);
    case 115200:
        return (B115200);
    default:
        return (B9600);
    }
}
static void set_baudrate (unsigned int baudrate)
{
    speed_t speed;
    
    speed = baudrate_to_Bxx (baudrate);  /* set baudrate */
    cfsetispeed(&termios_new, speed);    // set input speed
    cfsetospeed(&termios_new, speed);    // set output speed
}
static unsigned int set_portattr ( unsigned int baudrate,  // 1200 2400 4800 9600 .. 115200
                                   unsigned int databit,   // 5, 6, 7, 8
                                   const char *stopbit,    //  "1", "1.5", "2"
                                   char parity)            // N(o), O(dd), E(ven)
{
    bzero(&termios_new, sizeof (termios_new));
    cfmakeraw (&termios_new);
    set_baudrate (baudrate);
    
    termios_new.c_cflag |= CLOCAL | CREAD;   /* | CRTSCTS */
    
    set_data_bit (databit);
    set_parity (parity);
    set_stopbit (stopbit);
 
    termios_new.c_cc[VTIME] = 1;            /* unit: 1/10 second. */
    termios_new.c_cc[VMIN]  = 255;          /* minimal characters for reading */
    
    return (tcsetattr (fd, TCSANOW, &termios_new));
}
/* End of this file */
 
3.1 usr_serial.h 文件内容
#ifndef __USR_SERIAL_H
#define __USR_SERIAL_H
#include <termios.h>            /* tcgetattr, tcsetattr */
#include <stdio.h>              /* perror, printf, puts, fprintf, fputs */
#include <unistd.h>             /* read, write, close */
#include <fcntl.h>              /* open */
#include <sys/signal.h>
#include <sys/types.h>
#include <string.h>             /* bzero, memcpy */
#include <limits.h>             /* CHAR_MAX */
#ifdef __cplusplus
extern "C" {
#endif
int usr_serial_open( char *port, unsigned int baudrate, unsigned int databit, const char *stopbit, char parity);
void usr_serial_close( void );
unsigned int usr_serial_sendbytes (void * data, unsigned int datalength);
int usr_serial_readbytes (void *data, unsigned int datalength);
unsigned int usr_serial_readinterrupt (void *data, unsigned int datalength);
#ifdef __cplusplus
}
#endif
#endif /* __USR_SERIAL_H */
 
4 编写应用代码
4.1 使用接口
代码实现功能介绍:
代码 56行:初始化串口设备,设置baud,数据位,停止位等参数
代码 63行:创建线程
代码 72行:向串口写数据

源代码如下:
/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     :  test_serial.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : 使用线程方式读取串口数据
其他       : 无
日志       : 初版V1.0 2024/03/04
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include "usr_serial.h"
#define SERILA_PORT   "/dev/ttymxc2"
void *thread_uart_readbytes(void *arg) 
{
    int fd = *(int *)arg;
    char buf[128];
    int n;
 
    while (1) {
        // 读取串口数据
        n = read(fd, buf, sizeof(buf));
        if (n > 0) {
            printf("Received data: %.*s\n", n, buf);
        }
    }
}
int main()
{
    pthread_t tid;
    char buf[255];
    int n;
    int fd,err;
    void *tret;
 
    // 打开串口设备
    fd = usr_serial_open(SERILA_PORT, 115200, 8, "1", 'N');
    if (fd == -1) {
        printf("open err\n");
        exit(1);
    }
    
   // 创建读数据线程
    if (pthread_create(&tid, NULL, thread_uart_readbytes, &fd) != 0) {
        perror("pthread_create error \n");
        return -1;
    }
    while (1)
    {
        // 向串口发送数据
        strcpy(buf, "I am from iMX.6ULL board, hello world! \r\n");
        n = usr_serial_sendbytes(buf, strlen(buf));
        if (n < 0) {
            perror("write failed\n");
        }
        sleep(1);
    }
 
    err = pthread_join(tid, &tret);
    if (err) {
        fprintf(stderr, "pthread_join error: %s\n", strerror(err));
        exit(-1);
    }
 
    // 关闭串口设备
    usr_serial_close();
    printf("close uart\n");
 
    return 0;
}
 
4.2 编写应用程序Makefile
代码实现功能介绍:
代码 2行:编译器地址
代码 3行:linux内核地址
代码 3行:链接的.o文件名
代码 6行:生成可执行型文件

源代码
CFLAGS= -Wall -lpthread -O2
CC=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
STRIP=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip
test_serial: test_serial.o usr_serial.o
	$(CC) $(CFLAGS) -o test_serial test_serial.o usr_serial.o
	$(STRIP) -s test_serial
clean:
	rm -f test_serial test_serial.o usr_serial.o 
 
5 测试使用信号量实现串口数据的发送和接收功能
使用Make命令编译代码,然后将生成的可执行性文件copy到NFS的共享目录下,然后在板卡中执行。
在代码中,定义要发送的数据如下:
strcpy(buf, "I am from iMX.6ULL board, hello world! \r\n");

PC端,使用串口调试助手接收数据,详细信息如下:



















