你好,游客 登录 注册 搜索
背景:
阅读新闻

Linux设备驱动编程总结

Linux下串口通讯

[日期:2012-08-23] 来源:Linux社区  作者:memoryjs [字体: ]

用过开发板的朋友们都知道,UART在一个芯片中是很珍贵的资源,它可以被配置为485功能,也可以配置232功能,而在工业通讯中,485,232都是常用的通讯方式。

这里多说一句题外话:一直没有找到linux比较好的编辑工具,vim虽然功能强大,但是没有鼠标,总感觉不爽,gedit虽然有鼠标,但是缩进功能实在是不行,这里推荐一款本人一直用的,感觉用的不错,就是大家熟悉的eclipse for c++,这个有Linux版本,用起来虽然没有windows下那么爽,但是比大多数(至少笔者用过的)都好用。

首先说,Linux的串口通讯和我们逻辑编程的编程方法是不同的:

裸机下需要配置寄存器,然后如果中断方式,设置中断函数,如果查询方式,就查询状态位,判断是否有数据过来,如果要发送数据则就是往发送寄存器发送数据,然后就自动发出去了。

Linux下可不同,那些所谓的驱动都已经写好了,我们需要调用的是Linux统一封装的数据结构和函数来操作串口,对于不同的芯片,只要是Linux操作系统,都可以用这一类型的东西操作串口。

在Linux中所有串口配置数据都可以用struct termios来描述,里面有许多的成员函数,像我们配置波特率,数据位长度和奇偶校验,具体配置方法可以参考这篇程:http://www.linuxidc.com/Linux/2007-12/9815p3.htm

下面是笔者写的程序,大家可以拿来当做模板来使用,主要实现对UART0的操作:

  1. #include <unistd.h>   
  2. #include <stdio.h>   
  3. #include <termios.h>   
  4. #include <fcntl.h>   
  5. #include <string.h>   
  6. #include <time.h>   
  7.   
  8. //为了保证用户输入的波特率是个正确的值,所以需要这两个数组验证,对于设置波特率时候,前面要加个B   
  9. int speed_arr[] = { B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300,  
  10.     B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, };  
  11.   
  12. int name_arr[] = {115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300,  
  13.     115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, };  
  14.   
  15. /*----------------------------------------------------------------------------- 
  16.   函数名:      set_speed 
  17.   参数:        int fd ,int speed 
  18.   返回值:      void 
  19.   描述:        设置fd表述符的串口波特率 
  20.  *-----------------------------------------------------------------------------*/  
  21. void set_speed(int fd ,int speed)  
  22. {  
  23.     struct termios opt;  
  24.     int i;  
  25.     int status;  
  26.   
  27.     tcgetattr(fd,&opt);  
  28.     for(i = 0;i < sizeof(speed_arr)/sizeof(int);i++)  
  29.     {  
  30.         if(speed == name_arr[i])                        //找到标准的波特率与用户一致   
  31.         {  
  32.             tcflush(fd,TCIOFLUSH);                      //清除IO输入和输出缓存   
  33.             cfsetispeed(&opt,speed_arr[i]);         //设置串口输入波特率   
  34.             cfsetospeed(&opt,speed_arr[i]);         //设置串口输出波特率   
  35.   
  36.             status = tcsetattr(fd,TCSANOW,&opt);    //将属性设置到opt的数据结构中,并且立即生效   
  37.             if(status != 0)  
  38.                 perror("tcsetattr fd:");                //设置失败   
  39.             return ;  
  40.         }  
  41.         tcflush(fd,TCIOFLUSH);                          //每次清除IO缓存   
  42.     }  
  43. }  
  44. /*----------------------------------------------------------------------------- 
  45.   函数名:      set_parity 
  46.   参数:        int fd 
  47.   返回值:      int 
  48.   描述:        设置fd表述符的奇偶校验 
  49.  *-----------------------------------------------------------------------------*/  
  50. int set_parity(int fd)  
  51. {  
  52.     struct termios opt;  
  53.   
  54.     if(tcgetattr(fd,&opt) != 0)                 //或许原先的配置信息   
  55.     {  
  56.         perror("Get opt in parity error:");  
  57.         return -1;  
  58.     }  
  59.   
  60.     /*通过设置opt数据结构,来配置相关功能,以下为八个数据位,不使能奇偶校验*/  
  61.     opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP  
  62.                 | INLCR | IGNCR | ICRNL | IXON);  
  63.     opt.c_oflag &= ~OPOST;  
  64.     opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);  
  65.     opt.c_cflag &= ~(CSIZE | PARENB);  
  66.     opt.c_cflag |= CS8;  
  67.   
  68.     tcflush(fd,TCIFLUSH);                           //清空输入缓存   
  69.   
  70.     if(tcsetattr(fd,TCSANOW,&opt) != 0)  
  71.     {  
  72.         perror("set attr parity error:");  
  73.         return -1;  
  74.     }  
  75.   
  76.     return 0;  
  77. }  
  78. /*----------------------------------------------------------------------------- 
  79.   函数名:      serial_init 
  80.   参数:        char *dev_path,int speed,int is_block 
  81.   返回值:      初始化成功返回打开的文件描述符 
  82.   描述:        串口初始化,根据串口文件路径名,串口的速度,和串口是否阻塞,block为1表示阻塞 
  83.  *-----------------------------------------------------------------------------*/  
  84. int serial_init(char *dev_path,int speed,int is_block)  
  85. {  
  86.     int fd;  
  87.     int flag;  
  88.   
  89.     flag = 0;  
  90.     flag |= O_RDWR;                     //设置为可读写的串口属性文件   
  91.     if(is_block == 0)  
  92.         flag |=O_NONBLOCK;              //若为0则表示以非阻塞方式打开   
  93.   
  94.     fd = open(dev_path,flag);               //打开设备文件   
  95.     if(fd < 0)  
  96.     {  
  97.         perror("Open device file err:");  
  98.         close(fd);  
  99.         return -1;  
  100.     }  
  101.   
  102.     /*打开设备文件后,下面开始设置波特率*/  
  103.     set_speed(fd,speed);                //考虑到波特率可能被单独设置,所以独立成函数   
  104.   
  105.     /*设置奇偶校验*/  
  106.     if(set_parity(fd) != 0)  
  107.     {  
  108.         perror("set parity error:");  
  109.         close(fd);                      //一定要关闭文件,否则文件一直为打开状态   
  110.         return -1;  
  111.     }  
  112.   
  113.     return fd;  
  114. }  
  115. /*----------------------------------------------------------------------------- 
  116.   函数名:      serial_send 
  117.   参数:        int fd,char *str,unsigned int len 
  118.   返回值:      发送成功返回发送长度,否则返回小于0的值 
  119.   描述:        向fd描述符的串口发送数据,长度为len,内容为str 
  120.  *-----------------------------------------------------------------------------*/  
  121. int serial_send(int fd,char *str,unsigned int len)  
  122. {  
  123.     int ret;  
  124.   
  125.     if(len > strlen(str))                    //判断长度是否超过str的最大长度   
  126.         len = strlen(str);  
  127.   
  128.     ret = write(fd,str,len);  
  129.     if(ret < 0)  
  130.     {  
  131.         perror("serial send err:");  
  132.         return -1;  
  133.     }  
  134.   
  135.     return ret;  
  136. }  
  137.   
  138. /*----------------------------------------------------------------------------- 
  139.   函数名:      serial_read 
  140.   参数:        int fd,char *str,unsigned int len,unsigned int timeout 
  141.   返回值:      在规定的时间内读取数据,超时则退出,超时时间为ms级别 
  142.   描述:        向fd描述符的串口接收数据,长度为len,存入str,timeout 为超时时间 
  143.  *-----------------------------------------------------------------------------*/  
  144. int serial_read(int fd, char *str, unsigned int len, unsigned int timeout)  
  145. {  
  146.     fd_set rfds;  
  147.     struct timeval tv;  
  148.     int ret;                                //每次读的结果   
  149.     int sret;                               //select监控结果   
  150.     int readlen = 0;                        //实际读到的字节数   
  151.     char * ptr;  
  152.   
  153.     ptr = str;                          //读指针,每次移动,因为实际读出的长度和传入参数可能存在差异   
  154.   
  155.     FD_ZERO(&rfds);                     //清除文件描述符集合   
  156.     FD_SET(fd,&rfds);                   //将fd加入fds文件描述符,以待下面用select方法监听   
  157.   
  158.     /*传入的timeout是ms级别的单位,这里需要转换为struct timeval 结构的*/  
  159.     tv.tv_sec  = timeout / 1000;  
  160.     tv.tv_usec = (timeout%1000)*1000;  
  161.   
  162.     /*防止读数据长度超过缓冲区*/  
  163.     //if(sizeof(&str) < len)   
  164.     //  len = sizeof(str);   
  165.   
  166.   
  167.     /*开始读*/  
  168.     while(readlen < len)  
  169.     {  
  170.         sret = select(fd+1,&rfds,NULL,NULL,&tv);        //检测串口是否可读   
  171.   
  172.         if(sret == -1)                              //检测失败   
  173.         {  
  174.             perror("select:");  
  175.             break;  
  176.         }  
  177.         else if(sret > 0)                        <SPAN style="WHITE-SPACE: pre"> </SPAN>//检测成功可读   
  178.         {  
  179.             ret = read(fd,ptr,1);  
  180.             if(ret < 0)  
  181.             {  
  182.                 perror("read err:");  
  183.                 break;  
  184.             }  
  185.             else if(ret == 0)  
  186.                 break;  
  187.   
  188.             readlen += ret;                             //更新读的长度   
  189.             ptr     += ret;                             //更新读的位置   
  190.         }  
  191.         else                                                    //超时   
  192.         {  
  193.             printf("timeout!\n");  
  194.             break;  
  195.         }  
  196.     }  
  197.   
  198.     return readlen;  
  199. }  
  200. int main()  
  201. {  
  202.     int fd;  
  203.     int ret;  
  204.     char str[]="hello linux serial!";  
  205.     char buf[100];  
  206.   
  207.   
  208.     fd =  serial_init("/dev/ttyS1",115200,1);  
  209.     if(fd < 0)  
  210.     {  
  211.         perror("serial init err:");  
  212.         return -1;  
  213.     }  
  214.   
  215.     ret = serial_send(fd,str,22);  
  216.     printf("send %d bytes!\n",ret);  
  217.     serial_read(fd,buf,100,5000);  
  218.     printf("the buf is :%s\n",buf);  
  219.     close(fd);  
  220.     return 0;  
  221. }  

做几点简要的说明:

1.高级的一点芯片,如ARM9一般有个UART口专门用于打印调试信息,其对应的设备文件是ttyS0,然后UART0对应    ttyS1,以此类推,笔者这里打开的是ttyS1那就是UART0

2.可以看到一点,我定义了一个struct termios opt,对UART的所有配置信息都是在这个结构体中完成。

3.还是那句,发送和接受数据就和读写文件一样简单,发送就是write操作,读就是read操作

4.在读的时候,笔者用的是循环查询的方式,在Linux中,一般用的方法可以创建一个线程,用这个线程来循环调用select方法,来反复查询UART对应的设备文件是否可读(对应于是否有数据从串口传入),这种阻塞方式不是盲目等待,后来有调度机制,可以代替裸机编程中的中断方式。

下面是运行结果:

linux
【内容导航】
第1页:模块编程 第2页:Linux让LED灯闪起来
第3页:Linux的特殊字符设备:混杂设备,依旧让LED闪烁起来 第4页:Linux下串口通讯
第5页:Linux下定时器操作
相关资讯       Linux设备驱动编程 
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款