Linux 串口编程

串口编程     

       串口在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,除非设了IGNCR INLCR 将输入的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-NL OCRNL  将输出的CR转换为NL
ONLRET  不输出CR回车 ONOCR  在0列(行首)不输出CR
OFDEL  填充符为DEL,否则为 NULL OLCUC  将输出的小写字符转换为大写字符 
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  数据位屏蔽  CS7 7位数据 
CS5  5位数据 CS8 8位数据 
CS6 6位数据  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  启用扩充的输入字符处理
XCASE   ECHOCTL  如果设置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信号被回显为^X字符形式,
ECHOPRT   ECHO  回送输入关闭
ECHOE

如果设置了 ICANON,字符 ERASE

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

ECHONL  如果设置了 ICANON,回送NL
ECHOK  如果设置了 ICANON,字符KILL删除当前行。 ECHOKE  如果设置了 ICANON,回显 KILL 时将删除一行中的每个字符,如同指定了 ECHOE 和 ECHOPRT 一样
PENDIN   TOSTOP  对于后台输出发送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 }
View Code

参考:https://blog.csdn.net/flfihpv259/article/details/53786604

posted @ 2019-12-29 18:16  Little_Village  阅读(2356)  评论(0编辑  收藏  举报