LINUX串口编程

1.在Linux系统中/dev 目录通常用于存放设备文件(device files)。设备文件是用于访问系统硬件设备或与内核模块进行交互的文件。每个硬件设备或内核模块都可以在 /dev 目录下有一个相应的设备文件。
ttyn:终端设备 包括虚拟终端和物理终端
ttySn:原生物理串口
ttyUSBn:USB转TTL串口

2.现在电脑上基本没有原生的物理串口(RS232)了,下面的实例代码使用USB转ttl进行回环测试

3.在使用串口时termios结构体非常重要。termios 是一个在 Unix-like 操作系统中用于控制终端设备参数的结构体。它定义了与终端设备交互的各种设置。下面是 termios 结构体的一些主要成员及其含义:

#include<termios.h>
struct termios 
{
    tcflag_t c_iflag;    /* 输入模式标志 */
    tcflag_t c_oflag;    /* 输出模式标志 */
    tcflag_t c_cflag;    /* 控制模式标志 */
    tcflag_t c_lflag;    /* 本地模式标志 */
    cc_t c_cc[NCCS];     /* 控制字符数组 */
};

4.工作模式(Work Modes)指的是终端设备在接收和发送数据时的操作模式。在Unix-like系统中,终端模式通常分为三种:规范模式(Canonical Mode)、非规范模式(Non-canonical Mode)和原始模式(Raw Mode)。

1. 规范模式(Canonical Mode):
   - 在规范模式下,输入被缓冲,并且只有在输入达到一行的终点时才会被传递给程序。这就是为什么在规范模式下按下回车键(Enter)才会导致输入的实际传递给程序的原因。
   - 输入数据会按行缓冲,而且可以使用退格(Backspace)和删除(Delete)键进行编辑。
   - 适用于需要按行输入和处理的情况,例如命令行交互。

2. 非规范模式(Non-canonical Mode):
   - 在非规范模式下,输入不会被缓冲,每个字符立即传递给程序。这种模式下,输入的处理更为及时。
   - 适用于需要实时处理每个输入字符的情况,如实时图形界面、游戏等。

3. 原始模式(Raw Mode):----我们常用
   - 原始模式是非规范模式的一个特例,它进一步禁用了特定的输入和输出处理。在原始模式下,所有的输入直接传递给程序,没有经过终端设备的特殊处理。
   - 适用于需要最小化输入延迟的情况,例如对输入响应时间要求非常高的应用程序。

我们实际应用过程中更多的是原始模式!


5.需要注意的是:

.c_cc[VMIN] 的设置表示最小接收字符数。这个值影响 read() 函数的行为。具体地说,VMIN 表示的是在 read() 函数中的最小可接收字符数的阈值。

设置.c_cc[VMIN]为10的意思是,在调用 read() 函数时,如果缓冲区中至少有10个字符可用read()就会返回,将这些字符读取到用户提供的缓冲区中。如果缓冲区中的字符数不足10个,则read()将阻塞(或者根据非阻塞设置返回适当的值)等待足够的字符到达。

串口通信通常涉及到传输的数据帧,为了保证处理的数据是完整的,希望在read()返回之前至少接收到某个固定数量的字符。这种设置适用于那些需要处理固定大小帧的应用,确保你的程序只会处理完整的帧数据而不是部分数据。

VMIN=0, VTIME=0: 非阻塞模式,read会立即返回,不管是否有字符。如果有字符,就读取;如果没有字符,返回0。

VMIN=0, VTIME>0: 非阻塞模式,但是会等待一段时间,直到超时或者有字符。如果有字符,就读取;如果没有字符,返回0。

VMIN>0, VTIME=0: 阻塞模式,read会一直等待直到收到至少readcount个字符---常用

VMIN>0, VTIME>0: 阻塞模式,read会等待,直到收到至少readcount个字符或者超时

6.常用的串口设置函数:

7.实例代码,主线程发送数据,注册线程读取数据

#include<errno.h>
#include<stdio.h>
#include<termios.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<pthread.h>
#include<time.h>
#include<sys/time.h>

int open_ttyUSB(int com_port) 
{
    int fd;  // 串口的文件描述符
    char *tty_USBn[] = {"/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2", "/dev/ttyUSB3"};

    if ((com_port < 0) || (com_port > 3)) 
    {
        printf("com_port is wrong!\n");
        return -1;
    }

    // 打开串口
    fd = open(tty_USBn[com_port], O_RDWR | O_NOCTTY | O_NDELAY);
    // O_RDWR: 读写方式打开 O_NOCTTY: 不将该终端作为控制终端 O_NDELAY: 不关心串口的另一端是否激活
    if (fd < 0) 
    {
        perror("open ttyUSBn failed");
        return -1;
    } 
    else 
    {
        printf("ttyUSBn successfully open!\n");
    }

    // 串口设置为阻塞状态,等待数据输入
    // 如果串口在非阻塞模式下打开,读取操作会立即返回,不管是否有数据可读,这可能导致不稳定的行为
    if (fcntl(fd, F_SETFL, 0) < 0) 
    {
        perror("fcntl failed");
        return -1;
    }

    return fd;
}

int set_ttyUSB(int fd, int uart_baud, int stop_bits, int data_bits, char parity, int min_chars) 
{
    int speed;
    // 串口设置的结构体
    struct termios new_tty_set, old_tty_set;

    // 测试串口能否打开 并保存现有串口参数设置
    if (tcgetattr(fd, &old_tty_set) != 0) 
    {
        perror("tcgetattr failed!");
        return -1;
    }

    // 复制旧串口设置
    new_tty_set = old_tty_set;

    // 设置为原始模式 无缓冲
    cfmakeraw(&new_tty_set);

    // 清除数据位设置
    new_tty_set.c_cflag &= ~CSIZE;

    // 设置波特率
    switch (uart_baud) 
    {
        case 9600:
            speed = B9600;
            break;
        case 19200:
            speed = B19200;
            break;
        case 38400:
            speed = B38400;
            break;
        case 115200:
            speed = B115200;
            break;
        default:
            printf("uart_baud is wrong!\n");
            return -1;
    }
    cfsetispeed(&new_tty_set, speed);
    cfsetospeed(&new_tty_set, speed);

    // 设置停止位
    switch (stop_bits) 
    {
        case 1:
            new_tty_set.c_cflag &= ~CSTOPB; // 清除2位停止位设置
            break;
        case 2:
            new_tty_set.c_cflag |= CSTOPB; // 设置2位停止位
            break;
        default:
            printf("uart_stop_bits is wrong!\n");
            return -1;
    }

    // 设置数据位
    switch (data_bits) 
    {
        case 7:
            new_tty_set.c_cflag |= CS7;
            break;
        case 8:
            new_tty_set.c_cflag |= CS8;
            break;
        default:
            printf("uart_data_bits is wrong!\n");
            return -1;
    }

    // 设置校验
    switch (parity) 
    {
        case 'n':
        case 'N':
            new_tty_set.c_cflag &= ~PARENB; // 禁用奇偶校验
            new_tty_set.c_cflag &= ~INPCK;  // 禁用输入奇偶校验
            break;
        case 'o':
        case 'O':
            new_tty_set.c_cflag |= (PARENB | PARODD); // 启用奇偶校验 设置为奇校验
            new_tty_set.c_cflag |= INPCK;             // 启用输入奇偶校验
            break;
        case 'e':
        case 'E':
            new_tty_set.c_cflag |= PARENB;  // 启用奇偶校验
            new_tty_set.c_cflag &= ~PARODD; // 设置为偶校验
            new_tty_set.c_cflag |= INPCK;   // 启用输入奇偶校验
            break;
        default:
            printf("uart_parity is wrong!\n");
            return -1;
    }

    /* 设置等待时间和最小接收字符 *//*这里VMIN最好与接收数据帧长度相同*/
    new_tty_set.c_cc[VTIME] = 0;
    new_tty_set.c_cc[VMIN] = min_chars;

    /* 清除串口缓冲区 未接收字符 */
    tcflush(fd, TCIFLUSH);

    /* 激活新配置 */
    if ((tcsetattr(fd, TCSANOW, &new_tty_set)) != 0) 
    {
        perror("tcsetattr failed");
        return -1;
    }

    return 0;
}

#define SEND_SIZE 32
#define RECV_SIZE 32
#define PORT_COUNT 2

int tty_usb_fd;//串口文件描述符
pthread_t thread_ID;//线程描述符
char sendbuf[SEND_SIZE] = "This is a test!\n";//发送缓冲区
char recvbuf[RECV_SIZE];//接收缓冲区

//线程处理函数---接收数据
void* receive_f(void* arg)
{
    int temp;
    struct timeval time;
    struct tm* tm_ptr;
    char timebuf[64] = {0x00};
    while(1)
    {
        memset(recvbuf,0x00,sizeof recvbuf);
        gettimeofday(&time,NULL);//time.tv_sec是time_t类型,把它转换为tm 再格式化
        tm_ptr = localtime(&time.tv_sec);
        strftime(timebuf,64,"%H:%M:%S",tm_ptr);
        temp = read(tty_usb_fd,recvbuf,strlen(sendbuf));
        fprintf(stdout,"%s:%ld ",timebuf,time.tv_usec/1000);
        fprintf(stdout,"接收到%d字节数据:%s\n",temp,recvbuf);
    }
    pthread_exit((void*)0);
}


//主函数部分
int main(int argc,char* argv[])
{
    //1.尝试打开串口
    if((tty_usb_fd = open_ttyUSB(PORT_COUNT)) < 0)
    {
        return -1;
    }

    //2.进行串口参数配置
    if(set_ttyUSB(tty_usb_fd,115200,1,8,'n',strlen(sendbuf)) < 0)
    {
        return -1;
    }
    else{printf("ttyUSbn successfully set!\n");}

    //注册一个线程用来接收打印数据
    pthread_create(&thread_ID,NULL,receive_f,NULL);
    
    //4.发送数据
    while(1)
    {
        usleep(1000*200);//200ms间隔
        write(tty_usb_fd,sendbuf,strlen(sendbuf));
    }

    //4.等待线程推出
    pthread_join(thread_ID,NULL);

    //5.关闭串口
    close(tty_usb_fd);

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值