• Linux·串口编程


    目录

    串口编程     

    1.1 c_iflag 输入设置

    1.2 c_oflag 输出设置

    1.3 c_cflag 控制选项

    1.3 c_lflag 控制选项

    1.4 c_cc[NCCS] 控制字符


    串口编程     

           串口在Linux中也是一个设备文件(一切皆文件),这一部分从裸机开发转变过来还需要一定时间适应,不过可以去看看野火的Linux教程,中关于使用shell操作串口的示例有一个宏观的的体验和认知。回到通过程序使用串口收发数据,其也就如同读写一个普通文件一般,一般步骤是

    1、打开串口设备(字符设备)(open系统调用)。

    2、配置串口。

    3、然后就是如同读写文件一样使用,read和write函数进程数据的收发了。

    4、最后就是使用完后的关闭close操作。

    第一步打开字符设备通过调用open,这个过程和打开一个普通文件可能的唯一区别可能就是需要加上O_NOCTTY选项实际测试不加上好像也不会因为接收到0x03而导致程序退出(Ctrl+c == ascii(0x03))。

    然后就是配置串口的约定参数如struct termios

      {
        tcflag_t c_iflag;        /* input mode flags */
        tcflag_t c_oflag;        /* output mode flags */
        tcflag_t c_cflag;        /* control mode flags */
        tcflag_t c_lflag;        /* local mode flags */
        cc_t c_line;            /* line discipline */
        cc_t c_cc[NCCS];        /* control characters */
        speed_t c_ispeed;        /* input speed 
        speed_t c_ospeed;        /* output speed */
      };

    1.1 c_iflag 输入设置

    负责控制串口输入数据的处理配置。

    标志 作用标志作用
    IGNPAR 忽略桢错误和奇偶校验IGNBRK忽略 BREAK 条件
    INPCK 打开输入奇偶校验PARMRK标记奇偶错,只有设置INPCK并且没有设置 IGNPAR 才有效
    ISTRIP 去掉字符第8位IGNCR忽略输入中的回车CR
    ICRNL将输入的CR转换为 NL,除非设了IGNCRINLCR将输入的NL(换行)转换为CR
    IXON启用输出的 XON/XOFF 流控制IXOFF启用输入的 XON/XOFF 流控制
    IXANY尝试任何字符可做重启输出信号,默认只能START字符恢复输出IUCLC (no posix)将输入中的大写字母映射为小写字母
    BRKINIT如果IGNBRK没有设置当这是一个控制终端时将发送SIGINT信号IUTF8(no posix)输入是utf8字符允许在cooked 模式下正确进行字符擦除
    IMAXBEL 

    (no posix)输入队列满时提示(ring bell)

    使用软件流控制是启用 IXON、IXOFF 和 IXANY 选项:
    opt.c_iflag |= (IXON | IXOFF | IXANY);
    相反,要禁用软件流控制是禁止上面的选项:
    opt.c_iflag &= ~(IXON | IXOFF | IXANY);

    除此之外还有一个更粗暴的方式就是

    opt.c_iflag = 0;

    opt.c_iflag |= IXOFF;

    1.2 c_oflag 输出设置

    负责控制串口输出数据的处理配置。

    标志 作用标志作用
    OPOST 执行输出处理OFILL 对于延迟使用填充符
    ONLCR 将NL转换为CR-NLOCRNL 将输出的CR转换为NL
    ONLRET 不输出CR回车ONOCR 在0列(行首)不输出CR
    OFDEL 填充符为DEL,否则为 NULLOLCUC 将输出的小写字符转换为大写字符 
    XTABS 将制表符扩充为空格BSDLY 退格延迟屏蔽
    CRDLY CR 延迟屏蔽CMSPAR标志或空奇偶性
    FFDLY 换页延迟屏蔽NLDLY 新行延迟屏蔽

    启用输出处理
    启用输出处理需要在 c_oflag 成员中启用 OPOST 选项,其操作方法如下:

    options.c_oflag |= OPOST;

    使用原始输出
    就是禁用输出处理,使数据能不经过处理、过滤地完整地输出到串口。
    当 OPOST 被禁止,c_oflag 其它选项也被忽略,其操作方法如下:

    options.c_oflag &= ~OPOST;

    1.3 c_cflag 控制选项

    可设置串口的波特率、数据位、奇偶校验、停止位以及硬件流控制。

    标志作用标志作用
    CSIZE 数据位屏蔽 CS77位数据 
    CS5 5位数据CS88位数据 
    CS66位数据 CLOCAL忽略modem控制线 
    PARENB校验使能 PARODD寄校验使能,否则是偶校验 
    CSTOPB两位停止位,否则为1位 CREAD打开接收 
    CRTSCTS 硬件流控( no posix)

    比如关闭硬件流控,115200波特率,8位数据位,1位停止,偶校验配置如下:

        //设置串口输出波特率
        cfsetospeed(&opt, B115200);
        //设置串口输入波特率
        cfsetispeed(&opt, B115200);
        //设置数据位数
        opt.c_cflag &= ~CSIZE;
        opt.c_cflag |= CS8;
        //偶校验位
        opt.c_cflag |= PARENB;
        opt.c_cflag &= ~PARODD;
        //设置停止位 1bit
        opt.c_cflag &= ~CSTOPB;
        //激活本地连接和接受使能选项
        opt.c_cflag |= CLOCAL | CREAD;

    1.3 c_lflag 控制选项

    控制串口驱动怎样控制输入字符。

    标志作用标志作用
    ISIG使能特殊字符的信号发送 NOFLSH 在中断或退出键后禁用刷清
    ICANON 启用规范输入,默认开启IEXTEN 启用扩充的输入字符处理
    XCASEECHOCTL 如果设置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信号被回显为^X字符形式,
    ECHOPRTECHO 回送输入关闭
    ECHOE

    如果设置了 ICANON,字符 ERASE

    擦除前一个输入字符,WERASE 擦除前一个词

    ECHONL 如果设置了 ICANON,回送NL
    ECHOK 如果设置了 ICANON,字符KILL删除当前行。ECHOKE 如果设置了 ICANON,回显 KILL 时将删除一行中的每个字符,如同指定了 ECHOE 和 ECHOPRT 一样
    PENDINTOSTOP 对于后台输出发送SIGTTOU信号

    经典输入
    经典输入是以面向行设计的。输入字符会被放入一个缓冲之中,这样可以与用户以交互的方式编辑缓冲的内容,直到收到CR(carriage return)或者LF(line feed)字符。

    options.c_lflag |= (ICANON | ECHO | ECHOE);

    原始输入
    输入字符只是被原封不动的接收。一般情况中,如果要使用原始输入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG选项:

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    1.4 c_cc[NCCS] 控制字符

    主要用于控制串口在收到指定字符设备时的处理方式,如果设置对应的控制字符为1,则特殊字符的特殊功能将启用,比如

    //当ISIG设置时收到Ctrl+c(或0x0177)是认为这是一个中断,将发送SIGINT信号
    options.c_cc[VINTR] = 1;

    其中VMIN和VTIME是比较特殊的两个控制字符

    当 MIN > 0,TIME > 0 时

    计时器在收到第一个字节后启动, 在计时器超时之前 (TIME 的时间到) , 若已收到 MIN个字节,则 read 返回 MIN 个字节,否则,在计时器超时后返回实际接收到的字节。

    注意:因为只有在接收到第一个字节时才开始计时,所以至少可以返回 1 个字节。这种情形中,在接到第一个字节之前,调用者阻塞。如果在调用 read 时数据已经可用,则如同在 read 后数据立即被接到一样。

    当 MIN > 0,TIME = 0 时

    MIN 个字节完整接收后,read 才返回,这可能会造成 read 无限期地阻塞。

    当 MIN = 0, TIME > 0 时

    TIME 为允许等待的最大时间,计时器在调用 read 时立即启动,在串口接到 1 字节数据或者计时器超时后即返回,如果是计时器超时,则返回 0。

    当 MIN = 0,TIME = 0 时

    如果有数据可用,则 read 最多返回所要求的字节数,如果无数据可用,则 read 立即返回 0。

         如果只使用四线串口,需要关闭流控,否则可能会出现无法接收数据的问题。其次是几个标志位的名字很像,千万不要不把另一个的标志位的宏赋值到另一个标志位上这样牛头不对马嘴的还不容易发现问题。

    参考代码:

      1 /**
      2   * @brief  初始化串口
      3   * @param  device 设备
      4   * @retval -1 失败
      5   *           0 ok
      6   */
      7 int init_serial(char *device)
      8 {
      9     struct termios opt;
     10     int uart_fd;
     11     uart_fd = open(device,O_RDWR|O_NOCTTY);
     12     if(uart_fd<0){
     13         printf("The %s device open failed.\n",device);
     14         return -1;    
     15     }
     16     #if 1
     17     //fcntl(uart_fd, F_SETFL, FNDELAY);
     18     //清空串口接收缓冲区
     19     tcflush(uart_fd, TCIOFLUSH);
     20     // 获取串口参数 opt
     21     tcgetattr(uart_fd, &opt); 
     22     //设置串口输出波特率
     23     cfsetospeed(&opt, B115200);
     24     //设置串口输入波特率
     25     cfsetispeed(&opt, B115200);
     26     //设置数据位数
     27     opt.c_cflag &= ~CSIZE;
     28     opt.c_cflag |= CS8;
     29     //偶校验位
     30     opt.c_cflag |= PARENB;
     31     opt.c_cflag &= ~PARODD;
     32     //设置停止位 1bit
     33     opt.c_cflag &= ~CSTOPB;
     34     //激活本地连接和接受使能选项
     35     opt.c_cflag |= CLOCAL | CREAD;
     36     //关闭流控 接收回车符
     37     //opt.c_cflag &= ~IGNCR;
     38     //opt.c_cflag |= IXOFF;
     39     //原始数据输出
     40     opt.c_oflag &= (~ONLCR);
     41     opt.c_oflag &= (~OCRNL);
     42     //关闭echo
     43     opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
     44     //输入关闭流控 接收回车符
     45     opt.c_iflag |= IXOFF;
     46     opt.c_iflag &= (0|IXOFF);
     47     #endif
     48     //更新配置
     49     while(1)
     50     {
     51         errno = 0;
     52         if(tcsetattr(uart_fd, TCSANOW, &opt) != 0)
     53         {
     54             if(errno == EINTR)
     55             {
     56                 continue;
     57             }
     58             #ifdef PRINTF_MSG
     59             else if(errno == EINVAL)
     60             {
     61                 printf("config para elligal\n");
     62             }
     63             else if((errno == ENOTTY )||(errno == EBADF))  
     64             {
     65                 printf("fd error\n");
     66             }
     67             else
     68             {
     69                 printf("Device %s is set Fail%d\n");
     70             }
     71             #endif
     72             return -1;    
     73         }
     74         break;
     75     }
     76     #ifdef PRINTF_MSG
     77     printf("Device %s is set to 115200bps,8bits data , 1bit stop\n",device);
     78     #endif
     79     return uart_fd;
     80 }
     81 /**
     82   * @brief  读取串口数据
     83   * @param  buff 数据帧
     84   * @retval  数据长度
     85   *           
     86   */
     87 int recv_data_from_serial(int uart_fd,char *buf)
     88 {
     89     int ret,len=0,data_len;
     90     struct  pollfd fds[1];
     91     fds[0].fd = uart_fd;
     92     fds[0].events = POLLIN;
     93     while(1)
     94     {
     95         ret = poll(fds,1,0);
     96         #ifdef PRINTF_MSG
     97         //perror("uart poll");
     98         #endif /*PRINTF_MSG*/
     99         if(ret < 0)
    100         {
    101             /*调用出错*/
    102             if(errno == EINTR)
    103             {
    104                 /* 系统调用出错,可重试 */
    105                 continue;
    106             }
    107             else
    108             {
    109                 /* 系统调用出错,致命错误 */
    110                 #ifdef PRINTF_MSG
    111                 printf("uart read fail exit\n");
    112                 #endif /*PRINTF_MSG*/
    113                 exit(-1);
    114             }
    115             
    116         }
    117         else if(ret >= 1)
    118         {
    119             /*有数据可读*/
    120             if(fds[0].revents & (POLLIN|POLLPRI))
    121             {
    122                 if(ioctl(uart_fd,FIONREAD,&data_len)<0)
    123                 {
    124                     /* retry */
    125                     continue;    
    126                 }
    127                 ret = read(uart_fd,buf,data_len);
    128                 if(ret <= 0)
    129                 {
    130                     /*读失败*/
    131                 }
    132                 else if(ret)
    133                 {
    134                     len += ret;
    135                     buf += ret;
    136                 }
    137             }
    138         }
    139         else
    140         {
    141             /* no data to read */
    142             break;            
    143         }
    144         
    145     }
    146     return len;
    147 }
    148 /**
    149   * @brief  通过串口发送数据
    150   * @param  uart_fd 串口描述符
    151   * @param  data 数据帧
    152   * @param  lenth 数据长度
    153   * @retval 1 数据长度非法
    154   *           0 ok
    155   */
    156 int send_data_from_serial(int uart_fd,char *data,int lenth)
    157 {
    158     int ret,len=0;
    159     struct  pollfd fds;
    160     fds.fd = uart_fd;
    161     fds.events = POLLOUT;
    162     while(1)
    163     {
    164         ret = poll(&fds,1,0);
    165         if(ret < 0)
    166         {
    167             /*调用出错*/
    168             if(errno == EINTR)
    169             {
    170                 /* 系统调用出错,可重试 */
    171                 continue;
    172             }
    173             else
    174             {
    175                 /* 系统调用出错,致命错误 */
    176                 #ifdef PRINTF_MSG
    177                 printf("uart write fail exit\n");
    178                 #endif /*PRINTF_MSG*/
    179                 exit(-1);
    180             }    
    181         }
    182         else if(ret == 1)
    183         {
    184             /*可写*/
    185             if(fds.revents & POLLOUT)
    186             {
    187                 ret = write(uart_fd,data+len,lenth);
    188                 if(ret < 0)
    189                 {
    190                     /*写失败*/
    191                 }
    192                 else if(ret > 0)
    193                 {
    194                     len+=ret;
    195                     /*写完了*/
    196                     if(len == lenth)
    197                     {
    198 
    199                         break;
    200                     }
    201                 }
    202             }
    203         }
    204         else
    205         {
    206             /* 退出 */
    207             break;            
    208         }
    209     }
    210     return len;
    211 }
    212 
    213 int deinit_serial(int uart_fd)
    214 {
    215     close(uart_fd);
    216     return 0;
    217 }
  • 相关阅读:
    Python实现的直线段生成算法和圆弧生成算法
    The Company Structure
    目标检测 YOLOv5 - 模型推理预处理 letterbox
    C++ 关于引用的思考
    什么是组织孤岛?它会带来哪些影响?可以这样去对付它
    技术互联 创新交流 | 广汽研究院举办技术交流会圆满落幕
    pygame制作游戏全套的
    dataset和dataloader
    【kafka】mac环境安装kafka
    MEMM最大熵模型
  • 原文地址:https://blog.csdn.net/m0_64560763/article/details/126519902